From 5d70399de01ff2cd1fcce2aeb6003c848f1f3c5b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 30 Nov 2017 12:17:40 -0200 Subject: [PATCH] Add button for Huawei's power manager to setup wizard --- briar-android/build.gradle | 6 +- .../briar/android/login/DozeFragment.java | 47 +++-- .../briar/android/login/DozeView.java | 60 +++++++ .../briar/android/login/HuaweiView.java | 72 ++++++++ .../briar/android/login/PasswordFragment.java | 4 +- .../briar/android/login/PowerView.java | 162 ++++++++++++++++++ .../briar/android/login/SetupController.java | 2 +- .../android/login/SetupControllerImpl.java | 8 +- .../res/drawable/ic_help_outline_white.xml | 9 + .../main/res/drawable/navigation_accept.xml | 10 -- .../res/layout/fragment_setup_author_name.xml | 2 +- .../main/res/layout/fragment_setup_doze.xml | 38 ++-- .../src/main/res/layout/power_view.xml | 56 ++++++ briar-android/src/main/res/values/color.xml | 1 + briar-android/src/main/res/values/strings.xml | 7 +- briar-android/src/main/res/values/themes.xml | 1 + .../android/login/PasswordFragmentTest.java | 2 +- 17 files changed, 437 insertions(+), 50 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/login/DozeView.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/login/HuaweiView.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/login/PowerView.java create mode 100644 briar-android/src/main/res/drawable/ic_help_outline_white.xml delete mode 100644 briar-android/src/main/res/drawable/navigation_accept.xml create mode 100644 briar-android/src/main/res/layout/power_view.xml diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 14c2b10a8..7b74739ee 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -20,7 +20,7 @@ dependencies { } implementation "com.android.support:cardview-v7:$supportVersion" implementation "com.android.support:support-annotations:$supportVersion" - implementation 'com.android.support.constraint:constraint-layout:1.0.2' + implementation 'com.android.support.constraint:constraint-layout:1.1.0-beta3' implementation('ch.acra:acra:4.8.5') { exclude module: 'support-v4' @@ -61,8 +61,8 @@ dependencyVerification { 'ch.acra:acra:4.8.5:acra-4.8.5.aar:afd5b28934d5166b55f261c85685ad59e8a4ebe9ca1960906afaa8c76d8dc9eb', 'classworlds:classworlds:1.1-alpha-2:classworlds-1.1-alpha-2.jar:2bf4e59f3acd106fea6145a9a88fe8956509f8b9c0fdd11eb96fee757269e3f3', 'com.almworks.sqlite4java:sqlite4java:0.282:sqlite4java-0.282.jar:9e1d8dd83ca6003f841e3af878ce2dc7c22497493a7bb6d1b62ec1b0d0a83c05', - 'com.android.support.constraint:constraint-layout-solver:1.0.2:constraint-layout-solver-1.0.2.jar:8c62525a9bc5cff5633a96cb9b32fffeccaf41b8841aa87fc22607070dea9b8d', - 'com.android.support.constraint:constraint-layout:1.0.2:constraint-layout-1.0.2.aar:b0c688cc2b7172608f8153a689d746da40f71e52d7e2fe2bfd9df2f92db77085', + 'com.android.support.constraint:constraint-layout-solver:1.1.0-beta3:constraint-layout-solver-1.1.0-beta3.jar:c9084108415046c423983bdff8cf04c8e9a5bed41b8d5329f3764c08312ee3dd', + 'com.android.support.constraint:constraint-layout:1.1.0-beta3:constraint-layout-1.1.0-beta3.aar:1754a6bd135feae485aa2ebf9e170f0f3d3282b392f8aa3067d0ed668839db79', 'com.android.support:animated-vector-drawable:27.0.1:animated-vector-drawable-27.0.1.aar:365050110411c86c7eec86101b49ab53557ffe6667f60b19055f1d35c38a577b', 'com.android.support:appcompat-v7:27.0.1:appcompat-v7-27.0.1.aar:1402c29a49db30346c21a7d40634461765b3ab826f5dd95bc4dcc76787b21851', 'com.android.support:cardview-v7:27.0.1:cardview-v7-27.0.1.aar:43fccd44086c51eaa9d78be2fcf0dfea1556c8876a6fd325ea8d24e860054202', diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/DozeFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/login/DozeFragment.java index cb77e3d9f..f6362a2d8 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/DozeFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/DozeFragment.java @@ -1,17 +1,19 @@ package org.briarproject.briar.android.login; import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.content.Intent; import android.os.Bundle; +import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ProgressBar; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; +import org.briarproject.briar.android.login.PowerView.OnCheckedChangedListener; import org.briarproject.briar.android.util.UiUtils; import static android.view.View.INVISIBLE; @@ -19,12 +21,15 @@ import static android.view.View.VISIBLE; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING; import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog; -@TargetApi(23) -public class DozeFragment extends SetupFragment { +@NotNullByDefault +public class DozeFragment extends SetupFragment + implements OnCheckedChangedListener { private final static String TAG = DozeFragment.class.getName(); - private Button dozeButton; + private DozeView dozeView; + private HuaweiView huaweiView; + private Button next; private ProgressBar progressBar; private boolean secondAttempt = false; @@ -33,15 +38,22 @@ public class DozeFragment extends SetupFragment { } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { getActivity().setTitle(getString(R.string.setup_doze_title)); + setHasOptionsMenu(false); View v = inflater.inflate(R.layout.fragment_setup_doze, container, false); - dozeButton = v.findViewById(R.id.dozeButton); + dozeView = v.findViewById(R.id.dozeView); + dozeView.setOnCheckedChangedListener(this); + huaweiView = v.findViewById(R.id.huaweiView); + huaweiView.setOnCheckedChangedListener(this); + next = v.findViewById(R.id.next); progressBar = v.findViewById(R.id.progress); - dozeButton.setOnClickListener(view -> askForDozeWhitelisting()); + dozeView.setOnButtonClickListener(this::askForDozeWhitelisting); + next.setOnClickListener(this); return v; } @@ -65,25 +77,34 @@ public class DozeFragment extends SetupFragment { public void onActivityResult(int request, int result, Intent data) { super.onActivityResult(request, result, data); if (request == REQUEST_DOZE_WHITELISTING) { - if (!setupController.needsDozeWhitelisting() || secondAttempt) { - dozeButton.setEnabled(false); - onClick(dozeButton); - } else { + if (!dozeView.needsToBeShown() || secondAttempt) { + dozeView.setChecked(true); + } else if (getContext() != null) { secondAttempt = true; showOnboardingDialog(getContext(), getHelpText()); } } } + @Override + public void onCheckedChanged() { + if (dozeView.isChecked() && huaweiView.isChecked()) { + next.setEnabled(true); + } else { + next.setEnabled(false); + } + } + @SuppressLint("BatteryLife") private void askForDozeWhitelisting() { + if (getContext() == null) return; Intent i = UiUtils.getDozeWhitelistingIntent(getContext()); startActivityForResult(i, REQUEST_DOZE_WHITELISTING); } @Override public void onClick(View view) { - dozeButton.setVisibility(INVISIBLE); + next.setVisibility(INVISIBLE); progressBar.setVisibility(VISIBLE); setupController.createAccount(); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/DozeView.java b/briar-android/src/main/java/org/briarproject/briar/android/login/DozeView.java new file mode 100644 index 000000000..a2b1c5186 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/DozeView.java @@ -0,0 +1,60 @@ +package org.briarproject.briar.android.login; + + +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.util.AttributeSet; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.R; + +import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting; + +@UiThread +@NotNullByDefault +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; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/HuaweiView.java b/briar-android/src/main/java/org/briarproject/briar/android/login/HuaweiView.java new file mode 100644 index 000000000..dec5c913b --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/HuaweiView.java @@ -0,0 +1,72 @@ +package org.briarproject.briar.android.login; + + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.support.annotation.StringRes; +import android.support.annotation.UiThread; +import android.util.AttributeSet; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.R; + +import java.util.List; + +import javax.annotation.Nullable; + +@UiThread +@NotNullByDefault +class HuaweiView extends PowerView { + + private final static String PACKAGE_NAME = "com.huawei.systemmanager"; + private final static String CLASS_NAME = + PACKAGE_NAME + ".optimize.process.ProtectActivity"; + + public HuaweiView(Context context) { + this(context, null); + } + + public HuaweiView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public HuaweiView(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) { + PackageManager pm = context.getPackageManager(); + List resolveInfos = pm.queryIntentActivities(getIntent(), + PackageManager.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; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java index 17f7e06eb..80b8e04e4 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordFragment.java @@ -66,7 +66,7 @@ public class PasswordFragment extends SetupFragment { component.inject(this); // the controller is not yet available in onCreateView() - if (!setupController.needsDozeWhitelisting()) { + if (!setupController.needToShowDozeFragment()) { nextButton.setText(R.string.create_account_button); } } @@ -102,7 +102,7 @@ public class PasswordFragment extends SetupFragment { @Override public void onClick(View view) { - if (!setupController.needsDozeWhitelisting()) { + if (!setupController.needToShowDozeFragment()) { nextButton.setVisibility(INVISIBLE); progressBar.setVisibility(VISIBLE); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/PowerView.java b/briar-android/src/main/java/org/briarproject/briar/android/login/PowerView.java new file mode 100644 index 000000000..a09ff23fc --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/PowerView.java @@ -0,0 +1,162 @@ +package org.briarproject.briar.android.login; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.annotation.UiThread; +import android.support.constraint.ConstraintLayout; +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 org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.R; + +import static android.content.Context.LAYOUT_INFLATER_SERVICE; +import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog; + +@UiThread +@NotNullByDefault +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); + } + + @SuppressWarnings("ConstantConditions") + 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 + } + + public abstract boolean needsToBeShown(); + + public void setChecked(boolean checked) { + this.checked = checked; + if (checked) { + checkImage.setImageResource(R.drawable.ic_check_white); + } else { + checkImage.setImageResource(R.drawable.contact_disconnected); + } + 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 CREATOR + = new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + interface OnCheckedChangedListener { + void onCheckedChanged(); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupController.java b/briar-android/src/main/java/org/briarproject/briar/android/login/SetupController.java index cf75077fe..870b64df0 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/SetupController.java @@ -8,7 +8,7 @@ public interface SetupController { void setSetupActivity(SetupActivity setupActivity); - boolean needsDozeWhitelisting(); + boolean needToShowDozeFragment(); void setAuthorName(String authorName); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/login/SetupControllerImpl.java index 4d1f7ee73..00f2b012b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/SetupControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/SetupControllerImpl.java @@ -11,7 +11,6 @@ import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.android.controller.handler.ResultHandler; import org.briarproject.briar.android.controller.handler.UiResultHandler; -import org.briarproject.briar.android.util.UiUtils; import java.util.concurrent.Executor; @@ -41,9 +40,10 @@ public class SetupControllerImpl extends PasswordControllerImpl } @Override - public boolean needsDozeWhitelisting() { + public boolean needToShowDozeFragment() { if (setupActivity == null) throw new IllegalStateException(); - return UiUtils.needsDozeWhitelisting(setupActivity); + return DozeView.needsToBeShown(setupActivity) || + HuaweiView.needsToBeShown(setupActivity); } @Override @@ -61,7 +61,7 @@ public class SetupControllerImpl extends PasswordControllerImpl @Override public void showDozeOrCreateAccount() { if (setupActivity == null) throw new IllegalStateException(); - if (needsDozeWhitelisting()) { + if (needToShowDozeFragment()) { setupActivity.showDozeFragment(); } else { createAccount(); diff --git a/briar-android/src/main/res/drawable/ic_help_outline_white.xml b/briar-android/src/main/res/drawable/ic_help_outline_white.xml new file mode 100644 index 000000000..b39381493 --- /dev/null +++ b/briar-android/src/main/res/drawable/ic_help_outline_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/briar-android/src/main/res/drawable/navigation_accept.xml b/briar-android/src/main/res/drawable/navigation_accept.xml deleted file mode 100644 index 8dcd06dd6..000000000 --- a/briar-android/src/main/res/drawable/navigation_accept.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/briar-android/src/main/res/layout/fragment_setup_author_name.xml b/briar-android/src/main/res/layout/fragment_setup_author_name.xml index 8f22bc7ed..eae7ece48 100644 --- a/briar-android/src/main/res/layout/fragment_setup_author_name.xml +++ b/briar-android/src/main/res/layout/fragment_setup_author_name.xml @@ -26,7 +26,7 @@ app:hintEnabled="false" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" - app:layout_constraintTop_toBottomOf="parent"> + app:layout_constraintTop_toTopOf="parent"> @@ -16,38 +17,47 @@ android:paddingStart="@dimen/margin_activity_horizontal" android:paddingTop="@dimen/margin_activity_vertical"> - + +