IT WORKS!
This commit is contained in:
parent
6cc406b56d
commit
89e7d0989f
@ -58,7 +58,6 @@ task deleteLibs(type: Delete) {
|
||||
preBuild.dependsOn downloadx86*/
|
||||
|
||||
dependencies {
|
||||
implementation libs.commons.codec
|
||||
implementation libs.appcompat
|
||||
implementation libs.material
|
||||
implementation libs.activity
|
||||
@ -69,7 +68,7 @@ dependencies {
|
||||
implementation 'io.reactivex.rxjava3:rxjava:3.1.5'
|
||||
compileOnly 'org.projectlombok:lombok:1.18.34'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.34'
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
|
||||
implementation libs.androidx.coordinatorlayout
|
||||
annotationProcessor libs.room.compiler
|
||||
testImplementation libs.junit
|
||||
androidTestImplementation libs.ext.junit
|
||||
|
@ -8,7 +8,7 @@ public class FridaLib {
|
||||
System.loadLibrary("frida");
|
||||
}
|
||||
|
||||
public native int start(String config_b32, int tun_fd, boolean close_fd_on_drop);
|
||||
public native int start(String config_hex, int tun_fd, boolean close_fd_on_drop);
|
||||
|
||||
public native int stop();
|
||||
|
||||
|
@ -33,8 +33,6 @@ import com.alterdekim.fridaapp.room.Config;
|
||||
import com.alterdekim.fridaapp.service.FridaService;
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial;
|
||||
|
||||
import org.apache.commons.codec.binary.Base32;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Iterator;
|
||||
@ -57,8 +55,8 @@ public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuI
|
||||
try {
|
||||
String raw_data = Util.readTextFromUri(this, data.getData());
|
||||
String name = Util.getFilenameFromUri(this, data.getData());
|
||||
String b32 = Base32.builder().get().encodeToString(raw_data.getBytes(StandardCharsets.UTF_8));
|
||||
this.controller.insertNewConfig(name, b32);
|
||||
String hex = Util.bytesToHex(raw_data.getBytes());
|
||||
this.controller.insertNewConfig(name, hex);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
}
|
||||
@ -68,7 +66,7 @@ public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuI
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() != RESULT_OK) return;
|
||||
startService(new Intent(this, FridaService.class));
|
||||
startVpnService();
|
||||
}
|
||||
);
|
||||
|
||||
@ -95,6 +93,8 @@ public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuI
|
||||
popup.getMenuInflater().inflate(R.menu.mm, popup.getMenu());
|
||||
popup.show();
|
||||
});
|
||||
|
||||
startVpn();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -125,6 +125,10 @@ public class MainActivity extends AppCompatActivity implements PopupMenu.OnMenuI
|
||||
launcher.launch(intent);
|
||||
return;
|
||||
}
|
||||
startVpnService();
|
||||
}
|
||||
|
||||
private void startVpnService() {
|
||||
startService(new Intent(this, FridaService.class));
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package com.alterdekim.fridaapp.controller;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -13,11 +14,12 @@ import com.alterdekim.fridaapp.R;
|
||||
import com.alterdekim.fridaapp.activity.MainActivity;
|
||||
import com.alterdekim.fridaapp.room.AppDatabase;
|
||||
import com.alterdekim.fridaapp.room.Config;
|
||||
import com.alterdekim.fridaapp.service.FridaService;
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.functions.Action;
|
||||
import io.reactivex.rxjava3.functions.Consumer;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class MainActivityController implements IController {
|
||||
@ -41,30 +43,48 @@ public class MainActivityController implements IController {
|
||||
}
|
||||
|
||||
private void initConfigListGUI() {
|
||||
Toast.makeText(this.mainActivity, R.string.config_adding_success, Toast.LENGTH_LONG).show();
|
||||
LayoutInflater inflater = this.mainActivity.getLayoutInflater();
|
||||
this.mainActivity.getCfg_list().removeAllViews();
|
||||
this.db.userDao().getAll()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext(config -> {
|
||||
View cfg_instance = inflater.inflate(R.layout.single_config, this.mainActivity.getCfg_list(), false);
|
||||
this.mainActivity.getCfg_list().addView(cfg_instance);
|
||||
TextView view_name = (TextView) cfg_instance.findViewById(R.id.config_name);
|
||||
SwitchMaterial view_switch = (SwitchMaterial) cfg_instance.findViewById(R.id.config_switch);
|
||||
view_switch.setUseMaterialThemeColors(true);
|
||||
view_switch.setOnCheckedChangeListener((compoundButton, b) -> Log.i(TAG, "onCheckedChanged " + b));
|
||||
view_name.setText(config.title);
|
||||
.doOnNext(cl -> {
|
||||
this.mainActivity.getCfg_list().removeAllViews();
|
||||
if( cl.isEmpty() ) {
|
||||
this.mainActivity.getCfg_list().addView(inflater.inflate(R.layout.content_nocfg, this.mainActivity.getCfg_list(), false));
|
||||
return;
|
||||
}
|
||||
Iterator<Config> iter = cl.iterator();
|
||||
while( iter.hasNext() ) {
|
||||
Config config = iter.next();
|
||||
View cfg_instance = inflater.inflate(R.layout.single_config, this.mainActivity.getCfg_list(), false);
|
||||
this.mainActivity.getCfg_list().addView(cfg_instance);
|
||||
TextView view_name = (TextView) cfg_instance.findViewById(R.id.config_name);
|
||||
SwitchMaterial view_switch = (SwitchMaterial) cfg_instance.findViewById(R.id.config_switch);
|
||||
view_switch.setUseMaterialThemeColors(true);
|
||||
view_switch.setOnCheckedChangeListener((compoundButton, b) -> toggleVpn(view_switch, config, b));
|
||||
view_name.setText(config.getTitle());
|
||||
if( iter.hasNext() ) this.mainActivity.getCfg_list().addView(inflater.inflate(R.layout.single_divider, this.mainActivity.getCfg_list(), false));
|
||||
}
|
||||
})
|
||||
.doAfterNext(config -> inflater.inflate(R.layout.single_divider, this.mainActivity.getCfg_list(), false))
|
||||
.doOnError(throwable -> this.mainActivity.getCfg_list().addView(inflater.inflate(R.layout.content_error, this.mainActivity.getCfg_list(), false)))
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
public void insertNewConfig(String name, String b32) {
|
||||
db.userDao().insertAll(new Config(name, b32))
|
||||
public void insertNewConfig(String name, String hex) {
|
||||
db.userDao().insertAll(new Config(name, hex))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnError(throwable -> Toast.makeText(MainActivityController.this.mainActivity, R.string.config_adding_error, Toast.LENGTH_LONG).show())
|
||||
.doOnComplete(this::initConfigListGUI)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private void toggleVpn(SwitchMaterial view, Config config, boolean val) {
|
||||
Intent intent = new Intent(this.mainActivity, FridaService.class);
|
||||
intent.putExtra("vpn_hex", config.getData_hex());
|
||||
intent.putExtra("vpn_uid", config.getUid());
|
||||
intent.putExtra("vpn_state", val);
|
||||
this.mainActivity.startService(intent);
|
||||
}
|
||||
}
|
||||
|
@ -7,18 +7,20 @@ import androidx.room.PrimaryKey;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
@Entity
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class Config {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public int uid;
|
||||
private int uid;
|
||||
|
||||
@ColumnInfo(name = "title")
|
||||
public final String title;
|
||||
private final String title;
|
||||
|
||||
@ColumnInfo(name = "data_b32")
|
||||
public final String data_b32;
|
||||
@ColumnInfo(name = "data_hex")
|
||||
private final String data_hex;
|
||||
}
|
||||
|
@ -3,24 +3,28 @@ package com.alterdekim.fridaapp.room;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.core.Completable;
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
|
||||
@Dao
|
||||
public interface ConfigDAO {
|
||||
@Query("SELECT * FROM config")
|
||||
Observable<Config> getAll();
|
||||
Flowable<List<Config>> getAll();
|
||||
|
||||
@Query("SELECT * FROM config WHERE uid IN (:cfgIds)")
|
||||
Observable<Config> loadAllByIds(int[] cfgIds);
|
||||
Flowable<List<Config>> loadAllByIds(int[] cfgIds);
|
||||
|
||||
@Query("SELECT * FROM config WHERE uid = :cfgId")
|
||||
Single<Config> loadById(int cfgId);
|
||||
|
||||
@Insert
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
Completable insertAll(Config... users);
|
||||
|
||||
@Delete
|
||||
|
@ -6,24 +6,28 @@ import android.net.VpnService;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.functions.Consumer;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject;
|
||||
|
||||
public class FridaService extends VpnService {
|
||||
private static final String TAG = FridaService.class.getSimpleName();
|
||||
private static final String VPN_ADDRESS = "10.66.66.6"; // Only IPv4 support for now
|
||||
private static final String VPN_ROUTE = "0.0.0.0"; // Intercept everything
|
||||
|
||||
private ParcelFileDescriptor vpnInterface = null;
|
||||
private PendingIntent pendingIntent;
|
||||
|
||||
private Disposable vpnProcess;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Log.i(TAG, "Created");
|
||||
setupVPN();
|
||||
Log.i(TAG, "Started");
|
||||
try {
|
||||
Thread t = new Thread(new NativeBinaryConnection(vpnInterface.dup().detachFd(), getApplicationContext().getApplicationInfo().nativeLibraryDir));
|
||||
t.start();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void setupVPN() {
|
||||
@ -41,8 +45,33 @@ public class FridaService extends VpnService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
turnOff();
|
||||
}
|
||||
|
||||
private void turnOff() {
|
||||
if( this.vpnProcess != null && !this.vpnProcess.isDisposed() ) this.vpnProcess.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if( intent.getExtras() == null ) return START_STICKY;
|
||||
String hex = intent.getExtras().getString("vpn_hex");
|
||||
int uid = intent.getExtras().getInt("vpn_uid");
|
||||
boolean state = intent.getExtras().getBoolean("vpn_state");
|
||||
turnOff();
|
||||
if(!state) return START_STICKY;
|
||||
// TODO: different configs
|
||||
/*this.vpnProcess = Flowable.fromRunnable(new NativeBinaryConnection(vpnInterface.detachFd(), hex))
|
||||
.subscribeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe();*/
|
||||
try {
|
||||
Thread t = new Thread(new NativeBinaryConnection(vpnInterface.dup().detachFd(), hex));
|
||||
t.start();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
}
|
||||
return START_STICKY;
|
||||
}
|
||||
}
|
||||
|
@ -4,16 +4,14 @@ import android.util.Log;
|
||||
|
||||
import com.alterdekim.frida.FridaLib;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class NativeBinaryConnection implements Runnable {
|
||||
private static final String TAG = NativeBinaryConnection.class.getSimpleName();
|
||||
|
||||
private int fd = 0;
|
||||
private String baseDir;
|
||||
|
||||
public NativeBinaryConnection(int fd, String baseDir) {
|
||||
this.fd = fd;
|
||||
this.baseDir = baseDir;
|
||||
}
|
||||
private final int fd;
|
||||
private final String hex;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
@ -21,7 +19,8 @@ public class NativeBinaryConnection implements Runnable {
|
||||
Log.i(TAG, "FD: " + this.fd);
|
||||
FridaLib lib = new FridaLib();
|
||||
Log.i(TAG, "Starting Frida client");
|
||||
int r = lib.start("<data>", this.fd, false);
|
||||
Log.i(TAG, "Hex: " + this.hex);
|
||||
int r = lib.start(this.hex.toLowerCase(), this.fd, false);
|
||||
Log.i(TAG, "Exit code: " + r);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
|
@ -6,6 +6,7 @@ import android.net.Uri;
|
||||
import android.provider.OpenableColumns;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@ -13,17 +14,15 @@ import java.util.Objects;
|
||||
|
||||
public class Util {
|
||||
public static String readTextFromUri(Context context, Uri uri) throws IOException {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
try (InputStream inputStream =
|
||||
context.getContentResolver().openInputStream(uri);
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(Objects.requireNonNull(inputStream)))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
stringBuilder.append(line);
|
||||
context.getContentResolver().openInputStream(uri)) {
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[1024];
|
||||
for (int length; (length = inputStream.read(buffer)) != -1; ) {
|
||||
result.write(buffer, 0, length);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
public static String getFilenameFromUri(Context context, Uri uri) {
|
||||
@ -34,4 +33,15 @@ public class Util {
|
||||
}
|
||||
return "default";
|
||||
}
|
||||
|
||||
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
}
|
||||
|
5
src/main/res/drawable/baseline_error_24.xml
Normal file
5
src/main/res/drawable/baseline_error_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="48dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="48dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
|
||||
|
||||
</vector>
|
@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="48dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="48dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M17.5,3C15.57,3 14,4.57 14,6.5V8h-4V6.5C10,4.57 8.43,3 6.5,3S3,4.57 3,6.5S4.57,10 6.5,10H8v4H6.5C4.57,14 3,15.57 3,17.5S4.57,21 6.5,21s3.5,-1.57 3.5,-3.5V16h4v1.5c0,1.93 1.57,3.5 3.5,3.5s3.5,-1.57 3.5,-3.5S19.43,14 17.5,14H16v-4h1.5c1.93,0 3.5,-1.57 3.5,-3.5S19.43,3 17.5,3L17.5,3zM16,8V6.5C16,5.67 16.67,5 17.5,5S19,5.67 19,6.5S18.33,8 17.5,8H16L16,8zM6.5,8C5.67,8 5,7.33 5,6.5S5.67,5 6.5,5S8,5.67 8,6.5V8H6.5L6.5,8zM10,14v-4h4v4H10L10,14zM17.5,19c-0.83,0 -1.5,-0.67 -1.5,-1.5V16h1.5c0.83,0 1.5,0.67 1.5,1.5S18.33,19 17.5,19L17.5,19zM6.5,19C5.67,19 5,18.33 5,17.5S5.67,16 6.5,16H8v1.5C8,18.33 7.33,19 6.5,19L6.5,19z"/>
|
||||
|
||||
</vector>
|
@ -1,52 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:id="@+id/main"
|
||||
tools:context=".activity.MainActivity">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/Widget.Material3.AppBarLayout">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/addConfig"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:clickable="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:srcCompat="@drawable/baseline_add_24"
|
||||
android:focusable="true" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:background="@color/colorPrimary">
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:popupTheme="@style/Widget.Material3.AppBarLayout" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="11111" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/config_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
<include layout="@layout/content_main" />
|
||||
|
||||
</LinearLayout>
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/addConfig"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:clickable="true"
|
||||
app:srcCompat="@drawable/baseline_add_24"
|
||||
android:focusable="true" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
24
src/main/res/layout/content_error.xml
Normal file
24
src/main/res/layout/content_error.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:gravity="center"
|
||||
android:layout_height="match_parent">
|
||||
<ImageView
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:src="@drawable/baseline_error_24"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="22sp"
|
||||
android:textAlignment="center"
|
||||
android:text="Oops!"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textAlignment="center"
|
||||
android:text="Unable to load configurations list.\n Try to reload or reinstall app."
|
||||
android:layout_marginBottom="240dp"/>
|
||||
</LinearLayout>
|
8
src/main/res/layout/content_main.xml
Normal file
8
src/main/res/layout/content_main.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/config_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
24
src/main/res/layout/content_nocfg.xml
Normal file
24
src/main/res/layout/content_nocfg.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:gravity="center"
|
||||
android:layout_height="match_parent">
|
||||
<ImageView
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:src="@drawable/baseline_keyboard_command_key_24"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="22sp"
|
||||
android:textAlignment="center"
|
||||
android:text="There's void."/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textAlignment="center"
|
||||
android:text="Change that, press button in the bottom right corner."
|
||||
android:layout_marginBottom="240dp"/>
|
||||
</LinearLayout>
|
@ -3,4 +3,5 @@
|
||||
<string name="create_config">Create from scratch</string>
|
||||
<string name="import_config">Import from file</string>
|
||||
<string name="config_adding_error">Cannot add new config</string>
|
||||
<string name="config_adding_success">SUCCESS!</string>
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user