Factor out power management UI code into library

This commit is contained in:
Torsten Grote
2021-10-26 15:58:32 -03:00
parent 4acc5f4d8c
commit a1c5bf17ca
42 changed files with 477 additions and 285 deletions

1
dont-kill-me-lib/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,32 @@
plugins {
id 'com.android.library'
}
android {
compileSdkVersion 31
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
vectorDrawables.useSupportLibrary = true
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation "androidx.fragment:fragment:$androidx_fragment_version"
implementation "androidx.constraintlayout:constraintlayout:$androidx_constraintlayout_version"
implementation "com.google.android.material:material:$google_material_version"
}

View File

21
dont-kill-me-lib/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.briarproject.android.dontkillmelib">
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<queries>
<package android:name="com.huawei.systemmanager" />
<package android:name="com.huawei.powergenie" />
<package android:name="com.evenwell.PowerMonitor" />
</queries>
</manifest>

View File

@@ -0,0 +1,115 @@
package org.briarproject.android.dontkillmelib;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import org.briarproject.android.dontkillmelib.PowerView.OnCheckedChangedListener;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static org.briarproject.android.dontkillmelib.PowerUtils.getDozeWhitelistingIntent;
import static org.briarproject.android.dontkillmelib.PowerUtils.showOnboardingDialog;
public abstract class AbstractDoNotKillMeFragment extends Fragment
implements OnCheckedChangedListener,
ActivityResultCallback<ActivityResult> {
public final static String TAG =
AbstractDoNotKillMeFragment.class.getName();
private DozeView dozeView;
private HuaweiProtectedAppsView huaweiProtectedAppsView;
private HuaweiAppLaunchView huaweiAppLaunchView;
private XiaomiView xiaomiView;
private Button next;
private boolean secondAttempt = false;
private boolean buttonWasClicked = false;
private final ActivityResultLauncher<Intent> dozeLauncher =
registerForActivityResult(new StartActivityForResult(), this);
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireActivity().setTitle(getString(R.string.setup_doze_title));
setHasOptionsMenu(false);
View v = inflater.inflate(R.layout.fragment_dont_kill_me, container,
false);
dozeView = v.findViewById(R.id.dozeView);
dozeView.setOnCheckedChangedListener(this);
huaweiProtectedAppsView = v.findViewById(R.id.huaweiProtectedAppsView);
huaweiProtectedAppsView.setOnCheckedChangedListener(this);
huaweiAppLaunchView = v.findViewById(R.id.huaweiAppLaunchView);
huaweiAppLaunchView.setOnCheckedChangedListener(this);
xiaomiView = v.findViewById(R.id.xiaomiView);
xiaomiView.setOnCheckedChangedListener(this);
next = v.findViewById(R.id.next);
ProgressBar progressBar = v.findViewById(R.id.progress);
dozeView.setOnButtonClickListener(this::askForDozeWhitelisting);
next.setOnClickListener(view -> {
buttonWasClicked = true;
next.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
onButtonClicked();
});
// restore UI state if button was clicked already
buttonWasClicked = savedInstanceState != null &&
savedInstanceState.getBoolean("buttonWasClicked", false);
if (buttonWasClicked) {
next.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
}
return v;
}
protected abstract void onButtonClicked();
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("buttonWasClicked", buttonWasClicked);
}
@Override
public void onActivityResult(ActivityResult result) {
if (!dozeView.needsToBeShown() || secondAttempt) {
dozeView.setChecked(true);
} else if (getContext() != null) {
secondAttempt = true;
String s = getString(R.string.setup_doze_explanation);
showOnboardingDialog(getContext(), s);
}
}
@Override
public void onCheckedChanged() {
next.setEnabled(dozeView.isChecked() &&
huaweiProtectedAppsView.isChecked() &&
huaweiAppLaunchView.isChecked() &&
xiaomiView.isChecked());
}
@SuppressLint("BatteryLife")
private void askForDozeWhitelisting() {
if (getContext() == null) return;
dozeLauncher.launch(getDozeWhitelistingIntent(getContext()));
}
}

View File

@@ -0,0 +1,49 @@
package org.briarproject.android.dontkillmelib;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.PowerManager;
import java.util.concurrent.atomic.AtomicBoolean;
import static android.content.Context.POWER_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
public abstract class AbstractDozeWatchdogImpl {
private final Context appContext;
private final AtomicBoolean dozed = new AtomicBoolean(false);
private final BroadcastReceiver receiver = new DozeBroadcastReceiver();
public AbstractDozeWatchdogImpl(Context appContext) {
this.appContext = appContext;
}
public boolean getAndResetDozeFlag() {
return dozed.getAndSet(false);
}
public void startService() {
if (SDK_INT < 23) return;
IntentFilter filter = new IntentFilter(ACTION_DEVICE_IDLE_MODE_CHANGED);
appContext.registerReceiver(receiver, filter);
}
public void stopService() {
if (SDK_INT < 23) return;
appContext.unregisterReceiver(receiver);
}
private class DozeBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (SDK_INT < 23) return;
PowerManager pm =
(PowerManager) appContext.getSystemService(POWER_SERVICE);
if (pm.isDeviceIdleMode()) dozed.set(true);
}
}
}

View File

@@ -0,0 +1,7 @@
package org.briarproject.android.dontkillmelib;
import android.content.Context;
public interface DozeHelper {
boolean needToShowDoNotKillMeFragment(Context context);
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.android.dontkillmelib;
import android.content.Context;
import static org.briarproject.android.dontkillmelib.PowerUtils.needsDozeWhitelisting;
public class DozeHelperImpl implements DozeHelper {
@Override
public boolean needToShowDoNotKillMeFragment(Context context) {
Context appContext = context.getApplicationContext();
return needsDozeWhitelisting(appContext) ||
HuaweiProtectedAppsView.needsToBeShown(appContext) ||
HuaweiAppLaunchView.needsToBeShown(appContext) ||
XiaomiView.isXiaomiOrRedmiDevice();
}
}

View File

@@ -0,0 +1,57 @@
package org.briarproject.android.dontkillmelib;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import static org.briarproject.android.dontkillmelib.PowerUtils.needsDozeWhitelisting;
@UiThread
class DozeView extends PowerView {
@Nullable
private Runnable onButtonClickListener;
public DozeView(Context context) {
this(context, null);
}
public DozeView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DozeView(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
setText(R.string.setup_doze_intro);
setButtonText(R.string.setup_doze_button);
}
@Override
public boolean needsToBeShown() {
return needsToBeShown(getContext());
}
public static boolean needsToBeShown(Context context) {
return needsDozeWhitelisting(context);
}
@Override
protected int getHelpText() {
return R.string.setup_doze_explanation;
}
@Override
protected void onButtonClick() {
if (onButtonClickListener == null) throw new IllegalStateException();
onButtonClickListener.run();
}
public void setOnButtonClickListener(Runnable runnable) {
onButtonClickListener = runnable;
}
}

View File

@@ -0,0 +1,73 @@
package org.briarproject.android.dontkillmelib;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.util.AttributeSet;
import java.util.List;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.os.Build.VERSION.SDK_INT;
@UiThread
class HuaweiAppLaunchView extends PowerView {
private final static String PACKAGE_NAME = "com.huawei.systemmanager";
private final static String CLASS_NAME =
PACKAGE_NAME + ".power.ui.HwPowerManagerActivity";
public HuaweiAppLaunchView(Context context) {
this(context, null);
}
public HuaweiAppLaunchView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public HuaweiAppLaunchView(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
setText(R.string.setup_huawei_app_launch_text);
setButtonText(R.string.setup_huawei_app_launch_button);
}
@Override
public boolean needsToBeShown() {
return needsToBeShown(getContext());
}
public static boolean needsToBeShown(Context context) {
// "App launch" was introduced in EMUI 8 (Android 8.0)
if (SDK_INT < 26) return false;
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(getIntent(),
MATCH_DEFAULT_ONLY);
return !resolveInfos.isEmpty();
}
@Override
@StringRes
protected int getHelpText() {
return R.string.setup_huawei_app_launch_help;
}
@Override
protected void onButtonClick() {
getContext().startActivity(getIntent());
setChecked(true);
}
private static Intent getIntent() {
Intent intent = new Intent();
intent.setClassName(PACKAGE_NAME, CLASS_NAME);
return intent;
}
}

View File

@@ -0,0 +1,75 @@
package org.briarproject.android.dontkillmelib;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.util.AttributeSet;
import java.util.List;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.os.Build.VERSION.SDK_INT;
@UiThread
class HuaweiProtectedAppsView extends PowerView {
private final static String PACKAGE_NAME = "com.huawei.systemmanager";
private final static String CLASS_NAME =
PACKAGE_NAME + ".optimize.process.ProtectActivity";
public HuaweiProtectedAppsView(Context context) {
this(context, null);
}
public HuaweiProtectedAppsView(Context context,
@Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public HuaweiProtectedAppsView(Context context,
@Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
setText(R.string.setup_huawei_text);
setButtonText(R.string.setup_huawei_button);
}
@Override
public boolean needsToBeShown() {
return needsToBeShown(getContext());
}
public static boolean needsToBeShown(Context context) {
// "Protected apps" no longer exists on Huawei EMUI 5.0 (Android 7.0)
if (SDK_INT >= 24) return false;
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(getIntent(),
MATCH_DEFAULT_ONLY);
return !resolveInfos.isEmpty();
}
@Override
@StringRes
protected int getHelpText() {
return R.string.setup_huawei_help;
}
@Override
protected void onButtonClick() {
getContext().startActivity(getIntent());
setChecked(true);
}
private static Intent getIntent() {
Intent intent = new Intent();
intent.setClassName(PACKAGE_NAME, CLASS_NAME);
return intent;
}
}

View File

@@ -0,0 +1,60 @@
package org.briarproject.android.dontkillmelib;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.PowerManager;
import java.io.IOException;
import java.util.Scanner;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import static android.content.Context.POWER_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS;
import static java.lang.Runtime.getRuntime;
public class PowerUtils {
public static boolean needsDozeWhitelisting(Context ctx) {
if (SDK_INT < 23) return false;
PowerManager pm = (PowerManager) ctx.getSystemService(POWER_SERVICE);
String packageName = ctx.getPackageName();
if (pm == null) throw new AssertionError();
return !pm.isIgnoringBatteryOptimizations(packageName);
}
@TargetApi(23)
@SuppressLint("BatteryLife")
public static Intent getDozeWhitelistingIntent(Context ctx) {
Intent i = new Intent();
i.setAction(ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
i.setData(Uri.parse("package:" + ctx.getPackageName()));
return i;
}
static void showOnboardingDialog(Context ctx, String text) {
new AlertDialog.Builder(ctx, R.style.OnboardingDialogTheme)
.setMessage(text)
.setNeutralButton(R.string.got_it,
(dialog, which) -> dialog.cancel())
.show();
}
@Nullable
static String getSystemProperty(String propName) {
try {
Process p = getRuntime().exec("getprop " + propName);
Scanner s = new Scanner(p.getInputStream());
String line = s.nextLine();
s.close();
return line;
} catch (SecurityException | IOException e) {
return null;
}
}
}

View File

@@ -0,0 +1,161 @@
package org.briarproject.android.dontkillmelib;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
import androidx.constraintlayout.widget.ConstraintLayout;
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
import static org.briarproject.android.dontkillmelib.PowerUtils.showOnboardingDialog;
@UiThread
abstract class PowerView extends ConstraintLayout {
private final TextView textView;
private final ImageView checkImage;
private final Button button;
private boolean checked = false;
@Nullable
private OnCheckedChangedListener onCheckedChangedListener;
public PowerView(Context context) {
this(context, null);
}
public PowerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PowerView(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.power_view, this, true);
textView = v.findViewById(R.id.textView);
checkImage = v.findViewById(R.id.checkImage);
button = v.findViewById(R.id.button);
button.setOnClickListener(view -> onButtonClick());
ImageButton helpButton = v.findViewById(R.id.helpButton);
helpButton.setOnClickListener(view -> onHelpButtonClick());
// we need to manage the checkImage state ourselves, because automatic
// state saving is done based on the view's ID and there can be
// multiple ImageViews with the same ID in the view hierarchy
setSaveFromParentEnabled(true);
if (!isInEditMode() && !needsToBeShown()) {
setVisibility(GONE);
}
}
@Nullable
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.value = new boolean[] {checked};
return ss;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setChecked(ss.value[0]); // also calls listener
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public abstract boolean needsToBeShown();
public void setChecked(boolean checked) {
this.checked = checked;
if (checked) {
checkImage.setVisibility(VISIBLE);
} else {
checkImage.setVisibility(INVISIBLE);
}
if (onCheckedChangedListener != null) {
onCheckedChangedListener.onCheckedChanged();
}
}
public boolean isChecked() {
return getVisibility() == GONE || checked;
}
public void setOnCheckedChangedListener(
OnCheckedChangedListener onCheckedChangedListener) {
this.onCheckedChangedListener = onCheckedChangedListener;
}
@StringRes
protected abstract int getHelpText();
protected void setText(@StringRes int res) {
textView.setText(res);
}
protected void setButtonText(@StringRes int res) {
button.setText(res);
}
protected abstract void onButtonClick();
private void onHelpButtonClick() {
showOnboardingDialog(getContext(),
getContext().getString(getHelpText()));
}
private static class SavedState extends BaseSavedState {
private boolean[] value = {false};
private SavedState(@Nullable Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
in.readBooleanArray(value);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeBooleanArray(value);
}
static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
interface OnCheckedChangedListener {
void onCheckedChanged();
}
}

View File

@@ -0,0 +1,68 @@
package org.briarproject.android.dontkillmelib;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
import static android.os.Build.BRAND;
import static org.briarproject.android.dontkillmelib.PowerUtils.getSystemProperty;
import static org.briarproject.android.dontkillmelib.PowerUtils.showOnboardingDialog;
@UiThread
class XiaomiView extends PowerView {
public XiaomiView(Context context) {
this(context, null);
}
public XiaomiView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public XiaomiView(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
setText(R.string.setup_xiaomi_text);
setButtonText(R.string.setup_xiaomi_button);
}
@Override
public boolean needsToBeShown() {
return isXiaomiOrRedmiDevice();
}
public static boolean isXiaomiOrRedmiDevice() {
return "Xiaomi".equalsIgnoreCase(BRAND) ||
"Redmi".equalsIgnoreCase(BRAND);
}
@Override
@StringRes
protected int getHelpText() {
return R.string.setup_xiaomi_help;
}
@Override
protected void onButtonClick() {
int bodyRes = isMiuiTenOrLater()
? R.string.setup_xiaomi_dialog_body_new
: R.string.setup_xiaomi_dialog_body_old;
showOnboardingDialog(getContext(), getContext().getString(bodyRes));
setChecked(true);
}
private boolean isMiuiTenOrLater() {
String version = getSystemProperty("ro.miui.ui.version.name");
if (version == null || version.equals("")) return false;
version = version.replaceAll("[^\\d]", "");
try {
return Integer.parseInt(version) >= 10;
} catch (NumberFormatException e) {
return false;
}
}
}

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M11,18h2v-2h-2v2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12,6c-2.21,0 -4,1.79 -4,4h2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,2 -3,1.75 -3,5h2c0,-2.25 3,-2.5 3,-5 0,-2.21 -1.79,-4 -4,-4z" />
</vector>

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView 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:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<org.briarproject.android.dontkillmelib.DozeView
android:id="@+id/dozeView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<org.briarproject.android.dontkillmelib.HuaweiProtectedAppsView
android:id="@+id/huaweiProtectedAppsView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/dozeView" />
<org.briarproject.android.dontkillmelib.HuaweiAppLaunchView
android:id="@+id/huaweiAppLaunchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/huaweiProtectedAppsView" />
<org.briarproject.android.dontkillmelib.XiaomiView
android:id="@+id/xiaomiView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/huaweiAppLaunchView" />
<Button
android:id="@+id/next"
style="@style/DoNotKillMeButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
android:text="@string/create_account_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/xiaomiView"
app:layout_constraintVertical_bias="1.0"
tools:enabled="true" />
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/next"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/next" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<merge 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:layout_width="match_parent"
android:layout_height="wrap_content"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/setup_huawei_text" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/checkImage"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_margin="8dp"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/button"
app:srcCompat="@drawable/ic_check_white"
app:tint="?attr/colorControlNormal"
tools:ignore="ContentDescription" />
<Button
android:id="@+id/button"
style="@style/DoNotKillMeButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:layout_constraintEnd_toStartOf="@+id/helpButton"
app:layout_constraintStart_toEndOf="@+id/checkImage"
app:layout_constraintTop_toBottomOf="@+id/textView"
tools:text="@string/setup_huawei_button" />
<ImageButton
android:id="@+id/helpButton"
style="@style/HelpButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/help"
app:layout_constraintBottom_toBottomOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/button"
app:srcCompat="@drawable/ic_help_outline_white" />
</merge>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<!--
WARNING: These strings typically get overwritten by library consumers.
If adding/removing strings here, consumers need to adapt.
-->
<string name="setup_doze_title">Background Connections</string>
<string name="setup_doze_intro">To work properly, this app needs to run in the background.</string>
<string name="setup_doze_explanation">Please disable battery optimizations so this app can run in the background.</string>
<string name="setup_doze_button">Allow Connections</string>
<string name="create_account_button">Continue</string>
<string name="setup_huawei_text">Please tap the button below and make sure this app is protected in the \"Protected Apps\" screen.</string>
<string name="setup_huawei_button">Protect this app</string>
<string name="setup_huawei_help">If this app is not added to the protected apps list, it will be unable to run in the background.</string>
<string name="setup_huawei_app_launch_text">Please tap the button below, open the \"App launch\" screen and make sure this app is set to \"Manage manually\".</string>
<string name="setup_huawei_app_launch_button">Open Battery Settings</string>
<string name="setup_huawei_app_launch_help">If this app is not set to \"Manage manually\" in the \"App launch\" screen, it will not be able to run in the background.</string>
<string name="setup_xiaomi_text">To run in the background, this app needs to be locked to the recent apps list.</string>
<string name="setup_xiaomi_button">Protect this app</string>
<string name="setup_xiaomi_help">If this app is not locked to the recent apps list, it will be unable to run in the background.</string>
<string name="setup_xiaomi_dialog_body_old">1. Open the recent apps list (also called the app switcher)\n\n2. Swipe down on the image of this app to show the padlock icon\n\n3. If the padlock is not locked, tap to lock it</string>
<string name="setup_xiaomi_dialog_body_new">1. Open the recent apps list (also called the app switcher)\n\n2. Press and hold the image of this app until the padlock button appears\n\n3. If the padlock is not locked, tap to lock it</string>
<string name="got_it">Got it</string>
<string name="help">Help</string>
</resources>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="OnboardingDialogTheme" parent="Theme.AppCompat.DayNight.Dialog.MinWidth" />
<style name="DoNotKillMeButton" parent="Widget.AppCompat.Button.Colored" />
<style name="HelpButton" parent="Widget.AppCompat.Button.Borderless">
<item name="android:tint">#418cd8</item>
</style>
</resources>