serviceClass) {
+ return getActivity().getSystemService(serviceClass);
+ }
+
/**
* Returns the PackageManager from the owning Activity.
*/
diff --git a/src/com/android/settings/SetupWizardUtils.java b/src/com/android/settings/SetupWizardUtils.java
index bce6f3f2919aa2fb95201f569cc59e094695186b..e0292ef5b9cbcb0e84ad1c39d29ba0e0b3154594 100644
--- a/src/com/android/settings/SetupWizardUtils.java
+++ b/src/com/android/settings/SetupWizardUtils.java
@@ -19,6 +19,7 @@ package com.android.settings;
import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_FIRST_RUN;
import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_SETUP_FLOW;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.sysprop.SetupWizardProperties;
@@ -39,25 +40,39 @@ public class SetupWizardUtils {
return theme;
}
- public static int getTheme(Intent intent) {
+ public static int getTheme(Context context, Intent intent) {
String theme = getThemeString(intent);
// TODO(yukl): Move to ThemeResolver and add any additional required attributes in
// onApplyThemeResource using Theme overlays
if (theme != null) {
if (WizardManagerHelper.isAnySetupWizard(intent)) {
- switch (theme) {
- case ThemeHelper.THEME_GLIF_V3_LIGHT:
- return R.style.GlifV3Theme_Light;
- case ThemeHelper.THEME_GLIF_V3:
- return R.style.GlifV3Theme;
- case ThemeHelper.THEME_GLIF_V2_LIGHT:
- return R.style.GlifV2Theme_Light;
- case ThemeHelper.THEME_GLIF_V2:
- return R.style.GlifV2Theme;
- case ThemeHelper.THEME_GLIF_LIGHT:
- return R.style.GlifTheme_Light;
- case ThemeHelper.THEME_GLIF:
- return R.style.GlifTheme;
+ if (ThemeHelper.isSetupWizardDayNightEnabled(context)) {
+ switch (theme) {
+ case ThemeHelper.THEME_GLIF_V3_LIGHT:
+ case ThemeHelper.THEME_GLIF_V3:
+ return R.style.GlifV3Theme_DayNight;
+ case ThemeHelper.THEME_GLIF_V2_LIGHT:
+ case ThemeHelper.THEME_GLIF_V2:
+ return R.style.GlifV2Theme_DayNight;
+ case ThemeHelper.THEME_GLIF_LIGHT:
+ case ThemeHelper.THEME_GLIF:
+ return R.style.GlifTheme_DayNight;
+ }
+ } else {
+ switch (theme) {
+ case ThemeHelper.THEME_GLIF_V3_LIGHT:
+ return R.style.GlifV3Theme_Light;
+ case ThemeHelper.THEME_GLIF_V3:
+ return R.style.GlifV3Theme;
+ case ThemeHelper.THEME_GLIF_V2_LIGHT:
+ return R.style.GlifV2Theme_Light;
+ case ThemeHelper.THEME_GLIF_V2:
+ return R.style.GlifV2Theme;
+ case ThemeHelper.THEME_GLIF_LIGHT:
+ return R.style.GlifTheme_Light;
+ case ThemeHelper.THEME_GLIF:
+ return R.style.GlifTheme;
+ }
}
} else {
switch (theme) {
@@ -76,17 +91,30 @@ public class SetupWizardUtils {
return R.style.GlifTheme;
}
- public static int getTransparentTheme(Intent intent) {
- final int suwTheme = getTheme(intent);
- int transparentTheme = R.style.GlifV2Theme_Light_Transparent;
- if (suwTheme == R.style.GlifV3Theme) {
- transparentTheme = R.style.GlifV3Theme_Transparent;
+ public static int getTransparentTheme(Context context, Intent intent) {
+ int transparentTheme;
+ final int suwTheme = getTheme(context, intent);
+ if (ThemeHelper.isSetupWizardDayNightEnabled(context)) {
+ transparentTheme = R.style.GlifV2Theme_DayNight_Transparent;
+ } else {
+ transparentTheme = R.style.GlifV2Theme_Light_Transparent;
+ }
+ if (suwTheme == R.style.GlifV3Theme_DayNight) {
+ transparentTheme = R.style.GlifV3Theme_DayNight_Transparent;
} else if (suwTheme == R.style.GlifV3Theme_Light) {
transparentTheme = R.style.GlifV3Theme_Light_Transparent;
- } else if (suwTheme == R.style.GlifV2Theme) {
- transparentTheme = R.style.GlifV2Theme_Transparent;
+ } else if (suwTheme == R.style.GlifV2Theme_DayNight) {
+ transparentTheme = R.style.GlifV2Theme_DayNight_Transparent;
+ } else if (suwTheme == R.style.GlifV2Theme_Light) {
+ transparentTheme = R.style.GlifV2Theme_Light_Transparent;
+ } else if (suwTheme == R.style.GlifTheme_DayNight) {
+ transparentTheme = R.style.SetupWizardTheme_DayNight_Transparent;
} else if (suwTheme == R.style.GlifTheme_Light) {
transparentTheme = R.style.SetupWizardTheme_Light_Transparent;
+ } else if (suwTheme == R.style.GlifV3Theme) {
+ transparentTheme = R.style.GlifV3Theme_Transparent;
+ } else if (suwTheme == R.style.GlifV2Theme) {
+ transparentTheme = R.style.GlifV2Theme_Transparent;
} else if (suwTheme == R.style.GlifTheme) {
transparentTheme = R.style.SetupWizardTheme_Transparent;
}
diff --git a/src/com/android/settings/SidecarFragment.java b/src/com/android/settings/SidecarFragment.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a69c03f48dbdbbf2be87a009aa1d6eec35adba4
--- /dev/null
+++ b/src/com/android/settings/SidecarFragment.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.IntDef;
+
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * A headless fragment encapsulating a long-running action such as a network RPC surviving rotation.
+ *
+ * Subclasses should implement their own state machine, updating the state on each state change
+ * via {@link #setState(int, int)}. They can define their own states, however, it is suggested that
+ * the pre-defined {@link @State} constants are used and customizations are implemented via
+ * substates. Custom states must be outside the range of pre-defined states.
+ *
+ *
It is safe to update the state at any time, but state updates must originate from the main
+ * thread.
+ *
+ *
A listener can be attached that receives state updates while it's registered. Note that state
+ * change events can occur at any point in time and hence a registered listener should unregister if
+ * it cannot act upon the state change (typically a non-resumed fragment).
+ *
+ *
Listeners can receive state changes for the same state/substate combination, so listeners
+ * should make sure to be idempotent during state change events.
+ *
+ *
If a SidecarFragment is only relevant during the lifetime of another fragment (for example, a
+ * sidecar performing a details request for a DetailsFragment), that fragment needs to become the
+ * managing fragment of the sidecar.
+ *
+ *
Managing fragment responsibilities
+ *
+ *
+ * - Instantiates the sidecar fragment when necessary, preferably in {@link #onStart}.
+ *
- Removes the sidecar fragment when it's no longer used or when itself is removed. Removal of
+ * the managing fragment can be detected by checking {@link #isRemoving} in {@link #onStop}.
+ *
+ * - Registers as a listener in {@link #onResume()}, unregisters in {@link #onPause()}.
+ *
- Starts the long-running operation by calling into the sidecar.
+ *
- Receives state updates via {@link Listener#onStateChange(SidecarFragment)} and updates the
+ * UI accordingly.
+ *
+ *
+ * Managing fragment example
+ *
+ *
+ * public class MainFragment implements SidecarFragment.Listener {
+ * private static final String TAG_SOME_SIDECAR = ...;
+ * private static final String KEY_SOME_SIDECAR_STATE = ...;
+ *
+ * private SomeSidecarFragment mSidecar;
+ *
+ * @Override
+ * public void onStart() {
+ * super.onStart();
+ * Bundle args = ...; // optional args
+ * mSidecar = SidecarFragment.get(getFragmentManager(), TAG_SOME_SIDECAR,
+ * SidecarFragment.class, args);
+ * }
+ *
+ * @Override
+ * public void onResume() {
+ * mSomeSidecar.addListener(this);
+ * }
+ *
+ * @Override
+ * public void onPause() {
+ * mSomeSidecar.removeListener(this):
+ * }
+ * }
+ *
+ */
+public class SidecarFragment extends Fragment {
+
+ private static final String TAG = "SidecarFragment";
+
+ /**
+ * Get an instance of this sidecar.
+ *
+ * Will return the existing instance if one is already present. Note that the args will not
+ * be used in this situation, so args must be constant for any particular fragment manager and
+ * tag.
+ */
+ @SuppressWarnings("unchecked")
+ protected static T get(
+ FragmentManager fm, String tag, Class clazz, Bundle args) {
+ T fragment = (T) fm.findFragmentByTag(tag);
+ if (fragment == null) {
+ try {
+ fragment = clazz.newInstance();
+ } catch (java.lang.InstantiationException e) {
+ throw new InstantiationException("Unable to create fragment", e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException("Unable to create fragment", e);
+ }
+ if (args != null) {
+ fragment.setArguments(args);
+ }
+ fm.beginTransaction().add(fragment, tag).commit();
+ // No real harm in doing this here - get() should generally only be called from onCreate
+ // which is on the main thread - and it allows us to start running the sidecar on this
+ // instance immediately rather than having to wait until the transaction commits.
+ fm.executePendingTransactions();
+ }
+
+ return fragment;
+ }
+
+ /** State definitions. @see {@link #getState} */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({State.INIT, State.RUNNING, State.SUCCESS, State.ERROR})
+ public @interface State {
+ /** Initial idling state. */
+ int INIT = 0;
+
+ /** The long-running operation is in progress. */
+ int RUNNING = 1;
+
+ /** The long-running operation has succeeded. */
+ int SUCCESS = 2;
+
+ /** The long-running operation has failed. */
+ int ERROR = 3;
+ }
+
+ /** Substate definitions. @see {@link #getSubstate} */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ Substate.UNUSED,
+ Substate.RUNNING_BIND_SERVICE,
+ Substate.RUNNING_GET_ACTIVATION_CODE,
+ })
+ public @interface Substate {
+ // Unknown/unused substate.
+ int UNUSED = 0;
+ int RUNNING_BIND_SERVICE = 1;
+ int RUNNING_GET_ACTIVATION_CODE = 2;
+
+ // Future tags: 3+
+ }
+
+ /** **************************************** */
+ private Set mListeners = new CopyOnWriteArraySet<>();
+
+ // Used to track whether onCreate has been called yet.
+ private boolean mCreated;
+
+ @State private int mState;
+ @Substate private int mSubstate;
+
+ /** A listener receiving state change events. */
+ public interface Listener {
+
+ /**
+ * Called upon any state or substate change.
+ *
+ * The new state can be queried through {@link #getState} and {@link #getSubstate}.
+ *
+ *
Called from the main thread.
+ *
+ * @param fragment the SidecarFragment that changed its state
+ */
+ void onStateChange(SidecarFragment fragment);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setRetainInstance(true);
+ mCreated = true;
+ setState(State.INIT, Substate.UNUSED);
+ }
+
+ @Override
+ public void onDestroy() {
+ mCreated = false;
+ super.onDestroy();
+ }
+
+ /**
+ * Registers a listener that will receive subsequent state changes.
+ *
+ *
A {@link Listener#onStateChange(SidecarFragment)} event is fired as part of this call
+ * unless {@link #onCreate} has not yet been called (which means that it's unsafe to access this
+ * fragment as it has not been setup or restored completely). In that case, the future call to
+ * onCreate will trigger onStateChange on registered listener.
+ *
+ *
Must be called from the main thread.
+ *
+ * @param listener a listener, or null for unregistering the current listener
+ */
+ public void addListener(Listener listener) {
+ ThreadUtils.ensureMainThread();
+ mListeners.add(listener);
+ if (mCreated) {
+ notifyListener(listener);
+ }
+ }
+
+ /**
+ * Removes a previously registered listener.
+ *
+ * @return {@code true} if the listener was removed, {@code false} if there was no such listener
+ * registered.
+ */
+ public boolean removeListener(Listener listener) {
+ ThreadUtils.ensureMainThread();
+ return mListeners.remove(listener);
+ }
+
+ /** Returns the current state. */
+ @State
+ public int getState() {
+ return mState;
+ }
+
+ /** Returns the current substate. */
+ @Substate
+ public int getSubstate() {
+ return mSubstate;
+ }
+
+ /**
+ * Resets the sidecar to its initial state.
+ *
+ *
Implementers can override this method to perform additional reset tasks, but must call the
+ * super method.
+ */
+ @CallSuper
+ public void reset() {
+ setState(State.INIT, Substate.UNUSED);
+ }
+
+ /**
+ * Updates the state and substate and notifies the registered listener.
+ *
+ *
Must be called from the main thread.
+ *
+ * @param state the state to transition to
+ * @param substate the substate to transition to
+ */
+ protected void setState(@State int state, @Substate int substate) {
+ ThreadUtils.ensureMainThread();
+
+ mState = state;
+ mSubstate = substate;
+ notifyAllListeners();
+ printState();
+ }
+
+ private void notifyAllListeners() {
+ for (Listener listener : mListeners) {
+ notifyListener(listener);
+ }
+ }
+
+ private void notifyListener(Listener listener) {
+ listener.onStateChange(this);
+ }
+
+ /** Prints the state of the sidecar. */
+ public void printState() {
+ StringBuilder sb =
+ new StringBuilder("SidecarFragment.setState(): Sidecar Class: ")
+ .append(getClass().getCanonicalName());
+ sb.append(", State: ");
+ switch (mState) {
+ case SidecarFragment.State.INIT:
+ sb.append("State.INIT");
+ break;
+ case SidecarFragment.State.RUNNING:
+ sb.append("State.RUNNING");
+ break;
+ case SidecarFragment.State.SUCCESS:
+ sb.append("State.SUCCESS");
+ break;
+ case SidecarFragment.State.ERROR:
+ sb.append("State.ERROR");
+ break;
+ default:
+ sb.append(mState);
+ break;
+ }
+ switch (mSubstate) {
+ case SidecarFragment.Substate.UNUSED:
+ sb.append(", Substate.UNUSED");
+ break;
+ default:
+ sb.append(", ").append(mSubstate);
+ break;
+ }
+
+ Log.v(TAG, sb.toString());
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ Locale.US,
+ "SidecarFragment[mState=%d, mSubstate=%d]: %s",
+ mState,
+ mSubstate,
+ super.toString());
+ }
+
+ /** The State of the sidecar status. */
+ public static final class States {
+ public static final States SUCCESS = States.create(State.SUCCESS, Substate.UNUSED);
+ public static final States ERROR = States.create(State.ERROR, Substate.UNUSED);
+
+ @State public final int state;
+ @Substate public final int substate;
+
+ /** Creates a new sidecar state. */
+ public static States create(@State int state, @Substate int substate) {
+ return new States(state, substate);
+ }
+
+ public States(@State int state, @Substate int substate) {
+ this.state = state;
+ this.substate = substate;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof States)) {
+ return false;
+ }
+ States other = (States) o;
+ return this.state == other.state && this.substate == other.substate;
+ }
+
+ @Override
+ public int hashCode() {
+ return state * 31 + substate;
+ }
+ }
+}
diff --git a/src/com/android/settings/TestingSettingsBroadcastReceiver.java b/src/com/android/settings/TestingSettingsBroadcastReceiver.java
index 0e1296ba5781cb7d45ea489a5cd51e76dd8e5ad6..30a0d796c1cffcd74e390563a0786d9fcdfd5654 100644
--- a/src/com/android/settings/TestingSettingsBroadcastReceiver.java
+++ b/src/com/android/settings/TestingSettingsBroadcastReceiver.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.settings;
import android.content.BroadcastReceiver;
@@ -15,7 +31,8 @@ public class TestingSettingsBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(TelephonyManager.ACTION_SECRET_CODE)) {
+ if (intent != null && intent.getAction() != null
+ && intent.getAction().equals(TelephonyManager.ACTION_SECRET_CODE)) {
Intent i = new Intent(Intent.ACTION_MAIN);
i.setClass(context, TestingSettingsActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/src/com/android/settings/TetherSettings.java b/src/com/android/settings/TetherSettings.java
index 008100b14afb0adc79783dd2a120541ee243b31c..ab1c437271f2aa4b0a4019578b3b5c8284684894 100644
--- a/src/com/android/settings/TetherSettings.java
+++ b/src/com/android/settings/TetherSettings.java
@@ -20,6 +20,8 @@ import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
import static android.net.ConnectivityManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static com.android.settingslib.RestrictedLockUtilsInternal.checkIfUsbDataSignalingIsDisabled;
+
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
@@ -38,10 +40,12 @@ import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.UserHandle;
import android.os.UserManager;
import android.provider.SearchIndexableResource;
import android.text.TextUtils;
import android.util.FeatureFlagUtils;
+import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -51,6 +55,7 @@ import com.android.settings.core.FeatureFlags;
import com.android.settings.datausage.DataSaverBackend;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.wifi.tether.WifiTetherPreferenceController;
+import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.TetherUtil;
import com.android.settingslib.search.SearchIndexable;
@@ -78,15 +83,12 @@ public class TetherSettings extends RestrictedSettingsFragment
private static final String KEY_ENABLE_ETHERNET_TETHERING = "enable_ethernet_tethering";
private static final String KEY_DATA_SAVER_FOOTER = "disabled_on_data_saver";
@VisibleForTesting
- static final String KEY_TETHER_PREFS_FOOTER = "tether_prefs_footer";
-
- @VisibleForTesting
- static final String BLUETOOTH_TETHERING_STATE_CHANGED =
- "android.bluetooth.pan.profile.action.TETHERING_STATE_CHANGED";
+ static final String KEY_TETHER_PREFS_TOP_INTRO = "tether_prefs_top_intro";
private static final String TAG = "TetheringSettings";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private SwitchPreference mUsbTether;
+ private RestrictedSwitchPreference mUsbTether;
private SwitchPreference mBluetoothTether;
@@ -94,7 +96,6 @@ public class TetherSettings extends RestrictedSettingsFragment
private BroadcastReceiver mTetherChangeReceiver;
- private String[] mUsbRegexs;
private String[] mBluetoothRegexs;
private String mEthernetRegex;
private AtomicReference mBluetoothPan = new AtomicReference<>();
@@ -103,7 +104,6 @@ public class TetherSettings extends RestrictedSettingsFragment
private OnStartTetheringCallback mStartTetheringCallback;
private ConnectivityManager mCm;
private EthernetManager mEm;
- private TetheringManager mTm;
private TetheringEventCallback mTetheringEventCallback;
private EthernetListener mEthernetListener;
@@ -119,6 +119,13 @@ public class TetherSettings extends RestrictedSettingsFragment
private boolean mDataSaverEnabled;
private Preference mDataSaverFooter;
+ @VisibleForTesting
+ String[] mUsbRegexs;
+ @VisibleForTesting
+ Context mContext;
+ @VisibleForTesting
+ TetheringManager mTm;
+
@Override
public int getMetricsCategory() {
return SettingsEnums.TETHER;
@@ -140,7 +147,8 @@ public class TetherSettings extends RestrictedSettingsFragment
super.onCreate(icicle);
addPreferencesFromResource(R.xml.tether_prefs);
- mDataSaverBackend = new DataSaverBackend(getContext());
+ mContext = getContext();
+ mDataSaverBackend = new DataSaverBackend(mContext);
mDataSaverEnabled = mDataSaverBackend.isDataSaverEnabled();
mDataSaverFooter = findPreference(KEY_DATA_SAVER_FOOTER);
@@ -159,7 +167,7 @@ public class TetherSettings extends RestrictedSettingsFragment
}
setupTetherPreference();
- setFooterPreferenceTitle();
+ setTopIntroPreferenceTitle();
mDataSaverBackend.addListener(this);
@@ -169,7 +177,7 @@ public class TetherSettings extends RestrictedSettingsFragment
mUsbRegexs = mTm.getTetherableUsbRegexs();
mBluetoothRegexs = mTm.getTetherableBluetoothRegexs();
- mEthernetRegex = getContext().getResources().getString(
+ mEthernetRegex = mContext.getResources().getString(
com.android.internal.R.string.config_ethernet_iface_regex);
final boolean usbAvailable = mUsbRegexs.length != 0;
@@ -212,7 +220,7 @@ public class TetherSettings extends RestrictedSettingsFragment
@VisibleForTesting
void setupTetherPreference() {
- mUsbTether = (SwitchPreference) findPreference(KEY_USB_TETHER_SETTINGS);
+ mUsbTether = (RestrictedSwitchPreference) findPreference(KEY_USB_TETHER_SETTINGS);
mBluetoothTether = (SwitchPreference) findPreference(KEY_ENABLE_BLUETOOTH_TETHERING);
mEthernetTether = (SwitchPreference) findPreference(KEY_ENABLE_ETHERNET_TETHERING);
}
@@ -227,22 +235,21 @@ public class TetherSettings extends RestrictedSettingsFragment
}
@Override
- public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) {
+ public void onAllowlistStatusChanged(int uid, boolean isAllowlisted) {
}
@Override
- public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) {
+ public void onDenylistStatusChanged(int uid, boolean isDenylisted) {
}
@VisibleForTesting
- void setFooterPreferenceTitle() {
- final Preference footerPreference = findPreference(KEY_TETHER_PREFS_FOOTER);
- final WifiManager wifiManager =
- (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
+ void setTopIntroPreferenceTitle() {
+ final Preference topIntroPreference = findPreference(KEY_TETHER_PREFS_TOP_INTRO);
+ final WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
if (wifiManager.isStaApConcurrencySupported()) {
- footerPreference.setTitle(R.string.tethering_footer_info_sta_ap_concurrency);
+ topIntroPreference.setTitle(R.string.tethering_footer_info_sta_ap_concurrency);
} else {
- footerPreference.setTitle(R.string.tethering_footer_info);
+ topIntroPreference.setTitle(R.string.tethering_footer_info);
}
}
@@ -250,27 +257,32 @@ public class TetherSettings extends RestrictedSettingsFragment
@Override
public void onReceive(Context content, Intent intent) {
String action = intent.getAction();
- // TODO: stop using ACTION_TETHER_STATE_CHANGED and use mTetheringEventCallback instead.
+ if (DEBUG) {
+ Log.d(TAG, "onReceive() action : " + action);
+ }
+ // TODO(b/194961339): Stop using ACTION_TETHER_STATE_CHANGED and use
+ // mTetheringEventCallback instead.
if (action.equals(TetheringManager.ACTION_TETHER_STATE_CHANGED)) {
// TODO - this should understand the interface types
ArrayList available = intent.getStringArrayListExtra(
TetheringManager.EXTRA_AVAILABLE_TETHER);
ArrayList active = intent.getStringArrayListExtra(
TetheringManager.EXTRA_ACTIVE_TETHER);
- ArrayList errored = intent.getStringArrayListExtra(
- TetheringManager.EXTRA_ERRORED_TETHER);
- updateState(available.toArray(new String[available.size()]),
- active.toArray(new String[active.size()]),
- errored.toArray(new String[errored.size()]));
+ updateBluetoothState();
+ updateEthernetState(available.toArray(new String[available.size()]),
+ active.toArray(new String[active.size()]));
} else if (action.equals(Intent.ACTION_MEDIA_SHARED)) {
mMassStorageActive = true;
- updateState();
+ updateBluetoothAndEthernetState();
+ updateUsbPreference();
} else if (action.equals(Intent.ACTION_MEDIA_UNSHARED)) {
mMassStorageActive = false;
- updateState();
+ updateBluetoothAndEthernetState();
+ updateUsbPreference();
} else if (action.equals(UsbManager.ACTION_USB_STATE)) {
mUsbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
- updateState();
+ updateBluetoothAndEthernetState();
+ updateUsbPreference();
} else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
if (mBluetoothEnableForTether) {
switch (intent
@@ -289,9 +301,9 @@ public class TetherSettings extends RestrictedSettingsFragment
// ignore transition states
}
}
- updateState();
- } else if (action.equals(BLUETOOTH_TETHERING_STATE_CHANGED)) {
- updateState();
+ updateBluetoothAndEthernetState();
+ } else if (action.equals(BluetoothPan.ACTION_TETHERING_STATE_CHANGED)) {
+ updateBluetoothAndEthernetState();
}
}
}
@@ -320,7 +332,8 @@ public class TetherSettings extends RestrictedSettingsFragment
if (mEm != null)
mEm.addListener(mEthernetListener);
- updateState();
+ updateUsbState();
+ updateBluetoothAndEthernetState();
}
@Override
@@ -360,64 +373,66 @@ public class TetherSettings extends RestrictedSettingsFragment
filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- filter.addAction(BLUETOOTH_TETHERING_STATE_CHANGED);
+ filter.addAction(BluetoothPan.ACTION_TETHERING_STATE_CHANGED);
activity.registerReceiver(mTetherChangeReceiver, filter);
if (intent != null) mTetherChangeReceiver.onReceive(activity, intent);
}
- private void updateState() {
- final TetheringManager tm = getContext().getSystemService(TetheringManager.class);
- final String[] available = tm.getTetherableIfaces();
- final String[] tethered = tm.getTetheredIfaces();
- final String[] errored = tm.getTetheringErroredIfaces();
- updateState(available, tethered, errored);
+ // TODO(b/194961339): Separate the updateBluetoothAndEthernetState() to two methods,
+ // updateBluetoothAndEthernetState() and updateBluetoothAndEthernetPreference().
+ // Because we should update the state when only receiving tethering
+ // state changes and update preference when usb or media share changed.
+ private void updateBluetoothAndEthernetState() {
+ String[] tethered = mTm.getTetheredIfaces();
+ updateBluetoothAndEthernetState(tethered);
}
- private void updateState(String[] available, String[] tethered,
- String[] errored) {
- updateUsbState(available, tethered, errored);
+ private void updateBluetoothAndEthernetState(String[] tethered) {
+ String[] available = mTm.getTetherableIfaces();
updateBluetoothState();
updateEthernetState(available, tethered);
}
+ private void updateUsbState() {
+ String[] tethered = mTm.getTetheredIfaces();
+ updateUsbState(tethered);
+ }
+
@VisibleForTesting
- void updateUsbState(String[] available, String[] tethered,
- String[] errored) {
- boolean usbAvailable = mUsbConnected && !mMassStorageActive;
- int usbError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
- for (String s : available) {
- for (String regex : mUsbRegexs) {
- if (s.matches(regex)) {
- if (usbError == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
- usbError = mTm.getLastTetherError(s);
- }
- }
- }
- }
+ void updateUsbState(String[] tethered) {
boolean usbTethered = false;
for (String s : tethered) {
for (String regex : mUsbRegexs) {
if (s.matches(regex)) usbTethered = true;
}
}
- boolean usbErrored = false;
- for (String s: errored) {
- for (String regex : mUsbRegexs) {
- if (s.matches(regex)) usbErrored = true;
- }
+ if (DEBUG) {
+ Log.d(TAG, "updateUsbState() mUsbConnected : " + mUsbConnected
+ + ", mMassStorageActive : " + mMassStorageActive
+ + ", usbTethered : " + usbTethered);
}
-
if (usbTethered) {
mUsbTether.setEnabled(!mDataSaverEnabled);
mUsbTether.setChecked(true);
- } else if (usbAvailable) {
- mUsbTether.setEnabled(!mDataSaverEnabled);
+ mUsbTether.setDisabledByAdmin(
+ checkIfUsbDataSignalingIsDisabled(mContext, UserHandle.myUserId()));
+ } else {
mUsbTether.setChecked(false);
+ updateUsbPreference();
+ }
+ }
+
+ private void updateUsbPreference() {
+ boolean usbAvailable = mUsbConnected && !mMassStorageActive;
+
+ if (usbAvailable) {
+ mUsbTether.setEnabled(!mDataSaverEnabled);
} else {
mUsbTether.setEnabled(false);
- mUsbTether.setChecked(false);
}
+ mUsbTether.setDisabledByAdmin(
+ checkIfUsbDataSignalingIsDisabled(mContext, UserHandle.myUserId()));
}
@VisibleForTesting
@@ -437,7 +452,11 @@ public class TetherSettings extends RestrictedSettingsFragment
private void updateBluetoothState() {
final int btState = getBluetoothState();
+ if (DEBUG) {
+ Log.d(TAG, "updateBluetoothState() btState : " + btState);
+ }
if (btState == BluetoothAdapter.ERROR) {
+ Log.w(TAG, "updateBluetoothState() Bluetooth state is error!");
return;
}
@@ -458,7 +477,6 @@ public class TetherSettings extends RestrictedSettingsFragment
@VisibleForTesting
void updateEthernetState(String[] available, String[] tethered) {
-
boolean isAvailable = false;
boolean isTethered = false;
@@ -470,6 +488,11 @@ public class TetherSettings extends RestrictedSettingsFragment
if (s.matches(mEthernetRegex)) isTethered = true;
}
+ if (DEBUG) {
+ Log.d(TAG, "updateEthernetState() isAvailable : " + isAvailable
+ + ", isTethered : " + isTethered);
+ }
+
if (isTethered) {
mEthernetTether.setEnabled(!mDataSaverEnabled);
mEthernetTether.setChecked(true);
@@ -606,7 +629,7 @@ public class TetherSettings extends RestrictedSettingsFragment
private void update() {
TetherSettings settings = mTetherSettings.get();
if (settings != null) {
- settings.updateState();
+ settings.updateBluetoothAndEthernetState();
}
}
}
@@ -614,13 +637,16 @@ public class TetherSettings extends RestrictedSettingsFragment
private final class TetheringEventCallback implements TetheringManager.TetheringEventCallback {
@Override
public void onTetheredInterfacesChanged(List interfaces) {
- updateState();
+ Log.d(TAG, "onTetheredInterfacesChanged() interfaces : " + interfaces.toString());
+ String[] tethered = interfaces.toArray(new String[interfaces.size()]);
+ updateUsbState(tethered);
+ updateBluetoothAndEthernetState(tethered);
}
}
private final class EthernetListener implements EthernetManager.Listener {
public void onAvailabilityChanged(String iface, boolean isAvailable) {
- mHandler.post(TetherSettings.this::updateState);
+ mHandler.post(() -> updateBluetoothAndEthernetState());
}
}
}
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index 2099d85b8e3eedb46b8b6e53d4fd9464cb8b8637..e79852bf7ef140e03986ac1ad5c7a8e6c1c019c6 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -18,10 +18,6 @@ package com.android.settings;
import static android.content.Intent.EXTRA_USER;
import static android.content.Intent.EXTRA_USER_ID;
-import static android.media.MediaRoute2Info.TYPE_GROUP;
-import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
-import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
-import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
@@ -52,13 +48,12 @@ import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
-import android.media.MediaRoute2Info;
-import android.media.MediaRouter2Manager;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -90,6 +85,7 @@ import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.TtsSpan;
import android.util.ArraySet;
+import android.util.FeatureFlagUtils;
import android.util.IconDrawableFactory;
import android.util.Log;
import android.view.LayoutInflater;
@@ -99,6 +95,7 @@ import android.widget.EditText;
import android.widget.ListView;
import android.widget.TabWidget;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.core.graphics.drawable.IconCompat;
@@ -116,6 +113,7 @@ import com.android.settings.dashboard.profileselector.ProfileFragmentBridge;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.widget.ActionBarShadowController;
+import com.android.settingslib.widget.AdaptiveIcon;
import java.util.Iterator;
import java.util.List;
@@ -147,6 +145,24 @@ public final class Utils extends com.android.settingslib.Utils {
*/
public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
+ /**
+ * Whether to show location indicators.
+ */
+ public static final String PROPERTY_LOCATION_INDICATORS_ENABLED = "location_indicators_enabled";
+
+ /**
+ * Whether to show location indicator settings in developer options.
+ */
+ public static final String PROPERTY_LOCATION_INDICATOR_SETTINGS_ENABLED =
+ "location_indicator_settings_enabled";
+
+ /** Whether or not app hibernation is enabled on the device **/
+ public static final String PROPERTY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled";
+
+ /** Whether or not app hibernation targets apps that target a pre-S SDK **/
+ public static final String PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS =
+ "app_hibernation_targets_pre_s_apps";
+
/**
* Finds a matching activity for a preference's intent. If a matching
* activity is not found, it will remove the preference.
@@ -452,6 +468,19 @@ public final class Utils extends com.android.settingslib.Utils {
return UserHandle.USER_NULL;
}
+ /** Returns user ID of current user, throws IllegalStateException if it's not available. */
+ public static int getCurrentUserId(UserManager userManager, boolean isWorkProfile)
+ throws IllegalStateException {
+ if (isWorkProfile) {
+ final UserHandle managedUserHandle = getManagedProfile(userManager);
+ if (managedUserHandle == null) {
+ throw new IllegalStateException("Work profile user ID is not available.");
+ }
+ return managedUserHandle.getIdentifier();
+ }
+ return UserHandle.myUserId();
+ }
+
/**
* Returns the target user for a Settings activity.
*
@@ -540,7 +569,7 @@ public final class Utils extends com.android.settingslib.Utils {
* @return UserInfo of the user or null for non-existent user.
*/
public static UserInfo getExistingUser(UserManager userManager, UserHandle checkUser) {
- final List users = userManager.getUsers(true /* excludeDying */);
+ final List users = userManager.getAliveUsers();
final int checkUserId = checkUser.getIdentifier();
for (UserInfo user : users) {
if (user.id == checkUserId) {
@@ -649,7 +678,7 @@ public final class Utils extends com.android.settingslib.Utils {
*
* @param isInternal indicating if the caller is "internal" to the system,
* meaning we're willing to trust extras like
- * {@link ChooseLockSettingsHelper#EXTRA_ALLOW_ANY_USER}.
+ * {@link ChooseLockSettingsHelper#EXTRA_KEY_ALLOW_ANY_USER}.
* @throws SecurityException if the given userId does not belong to the
* current user group.
*/
@@ -658,7 +687,7 @@ public final class Utils extends com.android.settingslib.Utils {
return getCredentialOwnerUserId(context);
}
final boolean allowAnyUser = isInternal
- && bundle.getBoolean(ChooseLockSettingsHelper.EXTRA_ALLOW_ANY_USER, false);
+ && bundle.getBoolean(ChooseLockSettingsHelper.EXTRA_KEY_ALLOW_ANY_USER, false);
final int userId = bundle.getInt(Intent.EXTRA_USER_ID, UserHandle.myUserId());
if (userId == LockPatternUtils.USER_FRP) {
return allowAnyUser ? userId : enforceSystemUser(context, userId);
@@ -832,6 +861,13 @@ public final class Utils extends com.android.settingslib.Utils {
return faceManager != null && faceManager.isHardwareDetected();
}
+ /**
+ * Return true if the device supports multiple biometrics authentications.
+ */
+ public static boolean isMultipleBiometricsSupported(Context context) {
+ return hasFingerprintHardware(context) && hasFaceHardware(context);
+ }
+
/**
* Launches an intent which may optionally have a user id defined.
* @param fragment Fragment to use to launch the activity.
@@ -951,15 +987,36 @@ public final class Utils extends com.android.settingslib.Utils {
}
/**
- * Sets the preference icon with a drawable that is scaled down to to avoid crashing Settings if
- * it's too big.
+ * Gets the adaptive icon with a drawable that wrapped with an adaptive background using {@code
+ * backgroundColor} if it is not a {@link AdaptiveIconDrawable}
+ *
+ * If the given {@code icon} is too big, it will be auto scaled down to to avoid crashing
+ * Settings.
+ */
+ public static Drawable getAdaptiveIcon(Context context, Drawable icon,
+ @ColorInt int backgroundColor) {
+ Drawable adaptiveIcon = getSafeIcon(icon);
+
+ if (!(adaptiveIcon instanceof AdaptiveIconDrawable)) {
+ adaptiveIcon = new AdaptiveIcon(context, adaptiveIcon);
+ ((AdaptiveIcon) adaptiveIcon).setBackgroundColor(backgroundColor);
+ }
+
+ return adaptiveIcon;
+ }
+
+ /**
+ * Gets the icon with a drawable that is scaled down to to avoid crashing Settings if it's too
+ * big and not a {@link VectorDrawable}.
*/
- public static void setSafeIcon(Preference pref, Drawable icon) {
+ public static Drawable getSafeIcon(Drawable icon) {
Drawable safeIcon = icon;
+
if ((icon != null) && !(icon instanceof VectorDrawable)) {
safeIcon = getSafeDrawable(icon, 500, 500);
}
- pref.setIcon(safeIcon);
+
+ return safeIcon;
}
/**
@@ -969,7 +1026,7 @@ public final class Utils extends com.android.settingslib.Utils {
* @param maxWidth maximum width, in pixels.
* @param maxHeight maximum height, in pixels.
*/
- public static Drawable getSafeDrawable(Drawable original, int maxWidth, int maxHeight) {
+ private static Drawable getSafeDrawable(Drawable original, int maxWidth, int maxHeight) {
final int actualWidth = original.getMinimumWidth();
final int actualHeight = original.getMinimumHeight();
@@ -1101,13 +1158,17 @@ public final class Utils extends com.android.settingslib.Utils {
== ProfileSelectFragment.ProfileType.PERSONAL : false;
final boolean isWork = args != null ? args.getInt(ProfileSelectFragment.EXTRA_PROFILE)
== ProfileSelectFragment.ProfileType.WORK : false;
- if (activity.getSystemService(UserManager.class).getUserProfiles().size() > 1
- && ProfileFragmentBridge.FRAGMENT_MAP.get(fragmentName) != null
- && !isWork && !isPersonal) {
- f = Fragment.instantiate(activity, ProfileFragmentBridge.FRAGMENT_MAP.get(fragmentName),
- args);
- } else {
- f = Fragment.instantiate(activity, fragmentName, args);
+ try {
+ if (activity.getSystemService(UserManager.class).getUserProfiles().size() > 1
+ && ProfileFragmentBridge.FRAGMENT_MAP.get(fragmentName) != null
+ && !isWork && !isPersonal) {
+ f = Fragment.instantiate(activity,
+ ProfileFragmentBridge.FRAGMENT_MAP.get(fragmentName), args);
+ } else {
+ f = Fragment.instantiate(activity, fragmentName, args);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to get target fragment", e);
}
return f;
}
@@ -1154,29 +1215,14 @@ public final class Utils extends com.android.settingslib.Utils {
}
/**
- * Returns {@code true} if needed to disable media output, otherwise returns {@code false}.
+ * Returns the color of homepage preference icons.
*/
- public static boolean isMediaOutputDisabled(
- MediaRouter2Manager router2Manager, String packageName) {
- boolean isMediaOutputDisabled = false;
- if (!TextUtils.isEmpty(packageName)) {
- final List infos = router2Manager.getAvailableRoutes(packageName);
- if (infos.size() == 1) {
- final MediaRoute2Info info = infos.get(0);
- final int deviceType = info.getType();
- switch (deviceType) {
- case TYPE_UNKNOWN:
- case TYPE_REMOTE_TV:
- case TYPE_REMOTE_SPEAKER:
- case TYPE_GROUP:
- isMediaOutputDisabled = true;
- break;
- default:
- isMediaOutputDisabled = false;
- break;
- }
- }
- }
- return isMediaOutputDisabled;
+ @ColorInt
+ public static int getHomepageIconColor(Context context) {
+ return getColorAttrDefaultColor(context, android.R.attr.textColorSecondary);
+ }
+
+ public static boolean isProviderModelEnabled(Context context) {
+ return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
}
}
diff --git a/src/com/android/settings/accessibility/AccessibilityButtonFooterPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityButtonFooterPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..de90374c393ef590dba0d98bb30ede9798177dcc
--- /dev/null
+++ b/src/com/android/settings/accessibility/AccessibilityButtonFooterPreferenceController.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+
+/**
+ * Preference controller for accessibility button footer.
+ */
+public class AccessibilityButtonFooterPreferenceController extends
+ AccessibilityFooterPreferenceController {
+
+ public AccessibilityButtonFooterPreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ protected String getLabelName() {
+ return mContext.getString(R.string.accessibility_button_title);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ // Need to update footerPreference's data before super.displayPreference(), then it will use
+ // data to update related property of footerPreference.
+ if (AccessibilityUtil.isGestureNavigateEnabled(mContext)) {
+ final AccessibilityFooterPreference footerPreference =
+ screen.findPreference(getPreferenceKey());
+ footerPreference.setTitle(
+ mContext.getString(R.string.accessibility_button_gesture_description));
+ }
+
+ super.displayPreference(screen);
+ }
+}
diff --git a/src/com/android/settings/accessibility/AccessibilityButtonFragment.java b/src/com/android/settings/accessibility/AccessibilityButtonFragment.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e067d8e293ce98b4cf5485404919510c4c367fc
--- /dev/null
+++ b/src/com/android/settings/accessibility/AccessibilityButtonFragment.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.app.settings.SettingsEnums;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+/** Settings fragment containing accessibility button properties. */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class AccessibilityButtonFragment extends DashboardFragment {
+
+ private static final String TAG = "AccessibilityButtonFragment";
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.accessibility_button_settings;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.ACCESSIBILITY_BUTTON_SETTINGS;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.accessibility_button_settings);
+}
diff --git a/src/com/android/settings/accessibility/AccessibilityButtonLocationPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityButtonLocationPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..ed7cb27bff6712aa34a76337c94c51af8f179b03
--- /dev/null
+++ b/src/com/android/settings/accessibility/AccessibilityButtonLocationPreferenceController.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.util.ArrayMap;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+import com.google.common.primitives.Ints;
+
+/** Preference controller that controls the preferred location in accessibility button page. */
+public class AccessibilityButtonLocationPreferenceController extends BasePreferenceController
+ implements Preference.OnPreferenceChangeListener {
+
+ private final ArrayMap mValueTitleMap = new ArrayMap<>();
+ private int mDefaultLocation;
+
+ public AccessibilityButtonLocationPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ initValueTitleMap();
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AccessibilityUtil.isGestureNavigateEnabled(mContext)
+ ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final ListPreference listPreference = (ListPreference) preference;
+ final Integer value = Ints.tryParse((String) newValue);
+ if (value != null) {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, value);
+ updateState(listPreference);
+ }
+ return true;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ final ListPreference listPreference = (ListPreference) preference;
+
+ listPreference.setValue(getCurrentAccessibilityButtonMode());
+ }
+
+ private String getCurrentAccessibilityButtonMode() {
+ final int mode = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, mDefaultLocation);
+ return String.valueOf(mode);
+ }
+
+ private void initValueTitleMap() {
+ if (mValueTitleMap.size() == 0) {
+ final String[] values = mContext.getResources().getStringArray(
+ R.array.accessibility_button_location_selector_values);
+ final String[] titles = mContext.getResources().getStringArray(
+ R.array.accessibility_button_location_selector_titles);
+ final int mapSize = values.length;
+
+ mDefaultLocation = Integer.parseInt(values[0]);
+ for (int i = 0; i < mapSize; i++) {
+ mValueTitleMap.put(values[i], titles[i]);
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..69a7a46f0c3944b3f0211513b2622cd10d4eb495
--- /dev/null
+++ b/src/com/android/settings/accessibility/AccessibilityButtonPreviewPreferenceController.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.widget.ImageView;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+import com.android.settingslib.widget.LayoutPreference;
+
+/** Preference controller that controls the preview effect in accessibility button page. */
+public class AccessibilityButtonPreviewPreferenceController extends BasePreferenceController
+ implements LifecycleObserver, OnResume, OnPause {
+
+ private static final int SMALL_SIZE = 0;
+ private static final float DEFAULT_OPACITY = 0.55f;
+ private static final int DEFAULT_SIZE = 0;
+
+ private final ContentResolver mContentResolver;
+ @VisibleForTesting
+ final ContentObserver mContentObserver;
+ private FloatingMenuLayerDrawable mFloatingMenuPreviewDrawable;
+
+ @VisibleForTesting
+ ImageView mPreview;
+
+ public AccessibilityButtonPreviewPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mContentResolver = context.getContentResolver();
+ mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updatePreviewPreference();
+ }
+ };
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ final LayoutPreference preference = screen.findPreference(getPreferenceKey());
+ mPreview = preference.findViewById(R.id.preview_image);
+
+ updatePreviewPreference();
+ }
+
+ @Override
+ public void onResume() {
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_MODE),
+ /* notifyForDescendants= */ false, mContentObserver);
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
+ /* notifyForDescendants= */ false, mContentObserver);
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY),
+ /* notifyForDescendants= */ false, mContentObserver);
+ }
+
+ @Override
+ public void onPause() {
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ }
+
+ private void updatePreviewPreference() {
+ if (AccessibilityUtil.isFloatingMenuEnabled(mContext)) {
+ final int size = Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, DEFAULT_SIZE);
+ final int opacity = (int) (Settings.Secure.getFloat(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, DEFAULT_OPACITY) * 100);
+ final int floatingMenuIconId = (size == SMALL_SIZE)
+ ? R.drawable.accessibility_button_preview_small_floating_menu
+ : R.drawable.accessibility_button_preview_large_floating_menu;
+
+ mPreview.setImageDrawable(getFloatingMenuPreviewDrawable(floatingMenuIconId, opacity));
+ // Only change opacity(alpha) would not invoke redraw view, need to invalidate manually.
+ mPreview.invalidate();
+ } else {
+ mPreview.setImageDrawable(
+ mContext.getDrawable(R.drawable.accessibility_button_navigation));
+ }
+ }
+
+ private Drawable getFloatingMenuPreviewDrawable(int resId, int opacity) {
+ if (mFloatingMenuPreviewDrawable == null) {
+ mFloatingMenuPreviewDrawable = FloatingMenuLayerDrawable.createLayerDrawable(
+ mContext, resId, opacity);
+ } else {
+ mFloatingMenuPreviewDrawable.updateLayerDrawable(mContext, resId, opacity);
+ }
+
+ return mFloatingMenuPreviewDrawable;
+ }
+}
diff --git a/src/com/android/settings/accessibility/AccessibilityControlTimeoutFooterPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityControlTimeoutFooterPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e65ac5b82782349f76f2b219897828a382a713d
--- /dev/null
+++ b/src/com/android/settings/accessibility/AccessibilityControlTimeoutFooterPreferenceController.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+
+import com.android.settings.R;
+
+/**
+ * Preference controller for accessibility control timeout footer.
+ */
+public class AccessibilityControlTimeoutFooterPreferenceController extends
+ AccessibilityFooterPreferenceController {
+
+ public AccessibilityControlTimeoutFooterPreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ protected String getLabelName() {
+ return mContext.getString(R.string.accessibility_setting_item_control_timeout_title);
+ }
+
+ @Override
+ protected int getHelpResource() {
+ return R.string.help_url_timeout;
+ }
+}
diff --git a/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java b/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java
index e91f2a2ff9d89f891ac1a56eaffcac62c8077dfd..7ff3dba7fcd69482468c65d26be6f90cd29717e7 100644
--- a/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java
+++ b/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java
@@ -16,6 +16,9 @@
package com.android.settings.accessibility;
+import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_BUTTON_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
@@ -30,6 +33,9 @@ import android.text.TextUtils;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
@@ -37,6 +43,7 @@ import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.accessibility.AccessibilityUtils;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment {
@@ -61,44 +68,83 @@ public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment {
return;
}
- // In case the A11yServiceInfo doesn't exist, go to ally services list.
final ComponentName componentName = ComponentName.unflattenFromString(extraComponentName);
+ if (openSystemAccessibilitySettingsAndFinish(componentName)) {
+ return;
+ }
+
+ if (openAccessibilityDetailsSettingsAndFinish(componentName)) {
+ return;
+ }
+ // Fall back to open accessibility services list.
+ openAccessibilitySettingsAndFinish();
+ }
+
+ private boolean openSystemAccessibilitySettingsAndFinish(
+ @Nullable ComponentName componentName) {
+ final LaunchFragmentArguments launchArguments =
+ getSystemAccessibilitySettingsLaunchArguments(componentName);
+ if (launchArguments == null) {
+ return false;
+ }
+ openSubSettings(launchArguments.mDestination, launchArguments.mArguments);
+ finish();
+ return true;
+ }
+
+ @Nullable
+ private LaunchFragmentArguments getSystemAccessibilitySettingsLaunchArguments(
+ @Nullable ComponentName componentName) {
+ if (MAGNIFICATION_COMPONENT_NAME.equals(componentName)) {
+ final String destination = ToggleScreenMagnificationPreferenceFragment.class.getName();
+ final Bundle arguments = new Bundle();
+ MagnificationGesturesPreferenceController.populateMagnificationGesturesPreferenceExtras(
+ arguments, getContext());
+ return new LaunchFragmentArguments(destination, arguments);
+ }
+
+ if (ACCESSIBILITY_BUTTON_COMPONENT_NAME.equals(componentName)) {
+ final String destination = AccessibilityButtonFragment.class.getName();
+ return new LaunchFragmentArguments(destination, /* arguments= */ null);
+ }
+
+ return null;
+ }
+
+
+ private void openAccessibilitySettingsAndFinish() {
+ openSubSettings(AccessibilitySettings.class.getName(), /* arguments= */ null);
+ finish();
+ }
+
+ private boolean openAccessibilityDetailsSettingsAndFinish(
+ @Nullable ComponentName componentName) {
+ // In case the A11yServiceInfo doesn't exist, go to ally services list.
final AccessibilityServiceInfo info = getAccessibilityServiceInfo(componentName);
if (info == null) {
- Log.w(TAG, "Open accessibility services list due to invalid component name.");
- openAccessibilitySettingsAndFinish();
- return;
+ Log.w(TAG, "openAccessibilityDetailsSettingsAndFinish : invalid component name.");
+ return false;
}
// In case this accessibility service isn't permitted, go to a11y services list.
if (!isServiceAllowed(componentName.getPackageName())) {
Log.w(TAG,
- "Open accessibility services list due to target accessibility service is "
+ "openAccessibilityDetailsSettingsAndFinish: target accessibility service is"
+ "prohibited by Device Admin.");
- openAccessibilitySettingsAndFinish();
- return;
+ return false;
}
-
- openAccessibilityDetailsSettingsAndFinish(buildArguments(info));
- }
-
- @VisibleForTesting
- void openAccessibilitySettingsAndFinish() {
- new SubSettingLauncher(getActivity())
- .setDestination(AccessibilitySettings.class.getName())
- .setSourceMetricsCategory(getMetricsCategory())
- .launch();
+ openSubSettings(ToggleAccessibilityServicePreferenceFragment.class.getName(),
+ buildArguments(info));
finish();
+ return true;
}
- @VisibleForTesting
- void openAccessibilityDetailsSettingsAndFinish(Bundle arguments) {
+ private void openSubSettings(@NonNull String destination, @Nullable Bundle arguments) {
new SubSettingLauncher(getActivity())
- .setDestination(ToggleAccessibilityServicePreferenceFragment.class.getName())
+ .setDestination(destination)
.setSourceMetricsCategory(getMetricsCategory())
.setArguments(arguments)
.launch();
- finish();
}
@VisibleForTesting
@@ -175,4 +221,13 @@ public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment {
}
activity.finish();
}
+
+ private static class LaunchFragmentArguments {
+ final String mDestination;
+ final Bundle mArguments;
+ LaunchFragmentArguments(@NonNull String destination, @Nullable Bundle arguments) {
+ mDestination = Objects.requireNonNull(destination);
+ mArguments = arguments;
+ }
+ }
}
diff --git a/src/com/android/settings/accessibility/AccessibilityDialogUtils.java b/src/com/android/settings/accessibility/AccessibilityDialogUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..ffe5d6db59ba315c4668f6fdd878c6d7a77d9463
--- /dev/null
+++ b/src/com/android/settings/accessibility/AccessibilityDialogUtils.java
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import static com.android.settings.accessibility.ItemInfoArrayAdapter.ItemInfo;
+
+import android.app.Dialog;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.icu.text.MessageFormat;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ImageSpan;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.core.content.ContextCompat;
+
+import com.android.settings.R;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.utils.AnnotationSpan;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+
+/**
+ * Utility class for creating the edit dialog.
+ */
+public class AccessibilityDialogUtils {
+
+ /** Denotes the dialog emuns for show dialog. */
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DialogEnums {
+
+ /** OPEN: Settings > Accessibility > Any toggle service > Shortcut > Settings. */
+ int EDIT_SHORTCUT = 1;
+
+ /** OPEN: Settings > Accessibility > Magnification > Shortcut > Settings. */
+ int MAGNIFICATION_EDIT_SHORTCUT = 1001;
+
+ /**
+ * OPEN: Settings > Accessibility > Downloaded toggle service > Toggle use service to
+ * enable service.
+ */
+ int ENABLE_WARNING_FROM_TOGGLE = 1002;
+
+ /** OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox. */
+ int ENABLE_WARNING_FROM_SHORTCUT = 1003;
+
+ /**
+ * OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox
+ * toggle.
+ */
+ int ENABLE_WARNING_FROM_SHORTCUT_TOGGLE = 1004;
+
+ /**
+ * OPEN: Settings > Accessibility > Downloaded toggle service > Toggle use service to
+ * disable service.
+ */
+ int DISABLE_WARNING_FROM_TOGGLE = 1005;
+
+ /**
+ * OPEN: Settings > Accessibility > Magnification > Toggle user service in button
+ * navigation.
+ */
+ int ACCESSIBILITY_BUTTON_TUTORIAL = 1006;
+
+ /**
+ * OPEN: Settings > Accessibility > Magnification > Toggle user service in gesture
+ * navigation.
+ */
+ int GESTURE_NAVIGATION_TUTORIAL = 1007;
+
+ /**
+ * OPEN: Settings > Accessibility > Downloaded toggle service > Toggle user service > Show
+ * launch tutorial.
+ */
+ int LAUNCH_ACCESSIBILITY_TUTORIAL = 1008;
+ }
+
+ /**
+ * IntDef enum for dialog type that indicates different dialog for user to choose the shortcut
+ * type.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DialogType.EDIT_SHORTCUT_GENERIC,
+ DialogType.EDIT_SHORTCUT_GENERIC_SUW,
+ DialogType.EDIT_SHORTCUT_MAGNIFICATION,
+ DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW,
+ DialogType.EDIT_MAGNIFICATION_SWITCH_SHORTCUT,
+ })
+
+ public @interface DialogType {
+ int EDIT_SHORTCUT_GENERIC = 0;
+ int EDIT_SHORTCUT_GENERIC_SUW = 1;
+ int EDIT_SHORTCUT_MAGNIFICATION = 2;
+ int EDIT_SHORTCUT_MAGNIFICATION_SUW = 3;
+ int EDIT_MAGNIFICATION_SWITCH_SHORTCUT = 4;
+ }
+
+ /**
+ * Method to show the edit shortcut dialog.
+ *
+ * @param context A valid context
+ * @param dialogType The type of edit shortcut dialog
+ * @param dialogTitle The title of edit shortcut dialog
+ * @param listener The listener to determine the action of edit shortcut dialog
+ * @return A edit shortcut dialog for showing
+ */
+ public static AlertDialog showEditShortcutDialog(Context context, int dialogType,
+ CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
+ final AlertDialog alertDialog = createDialog(context, dialogType, dialogTitle, listener);
+ alertDialog.show();
+ setScrollIndicators(alertDialog);
+ return alertDialog;
+ }
+
+ /**
+ * Method to show the magnification edit shortcut dialog in Magnification.
+ *
+ * @param context A valid context
+ * @param positiveBtnListener The positive button listener
+ * @return A magnification edit shortcut dialog in Magnification
+ */
+ public static Dialog createMagnificationSwitchShortcutDialog(Context context,
+ CustomButtonsClickListener positiveBtnListener) {
+ final View contentView = createSwitchShortcutDialogContentView(context);
+ final AlertDialog alertDialog = new AlertDialog.Builder(context)
+ .setView(contentView)
+ .setTitle(context.getString(
+ R.string.accessibility_magnification_switch_shortcut_title))
+ .create();
+ setCustomButtonsClickListener(alertDialog, contentView,
+ positiveBtnListener, /* negativeBtnListener= */ null);
+ setScrollIndicators(contentView);
+ return alertDialog;
+ }
+
+ private static AlertDialog createDialog(Context context, int dialogType,
+ CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
+
+ final AlertDialog alertDialog = new AlertDialog.Builder(context)
+ .setView(createEditDialogContentView(context, dialogType))
+ .setTitle(dialogTitle)
+ .setPositiveButton(R.string.save, listener)
+ .setNegativeButton(R.string.cancel,
+ (DialogInterface dialog, int which) -> dialog.dismiss())
+ .create();
+
+ return alertDialog;
+ }
+
+ /**
+ * Sets the scroll indicators for dialog view. The indicators appears while content view is
+ * out of vision for vertical scrolling.
+ */
+ private static void setScrollIndicators(AlertDialog dialog) {
+ final ScrollView scrollView = dialog.findViewById(R.id.container_layout);
+ setScrollIndicators(scrollView);
+ }
+
+ /**
+ * Sets the scroll indicators for dialog view. The indicators appear while content view is
+ * out of vision for vertical scrolling.
+ *
+ * @param view The view contains customized dialog content. Usually it is {@link ScrollView} or
+ * {@link AbsListView}
+ */
+ private static void setScrollIndicators(@NonNull View view) {
+ view.setScrollIndicators(
+ View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM,
+ View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
+ }
+
+
+ interface CustomButtonsClickListener {
+ void onClick(@CustomButton int which);
+ }
+
+ /**
+ * Annotation for customized dialog button type.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ CustomButton.POSITIVE,
+ CustomButton.NEGATIVE,
+ })
+
+ public @interface CustomButton {
+ int POSITIVE = 1;
+ int NEGATIVE = 2;
+ }
+
+ private static void setCustomButtonsClickListener(Dialog dialog, View contentView,
+ CustomButtonsClickListener positiveBtnListener,
+ CustomButtonsClickListener negativeBtnListener) {
+ final Button positiveButton = contentView.findViewById(
+ R.id.custom_positive_button);
+ final Button negativeButton = contentView.findViewById(
+ R.id.custom_negative_button);
+
+ if (positiveButton != null) {
+ positiveButton.setOnClickListener(v -> {
+ if (positiveBtnListener != null) {
+ positiveBtnListener.onClick(CustomButton.POSITIVE);
+ }
+ dialog.dismiss();
+ });
+ }
+
+ if (negativeButton != null) {
+ negativeButton.setOnClickListener(v -> {
+ if (negativeBtnListener != null) {
+ negativeBtnListener.onClick(CustomButton.NEGATIVE);
+ }
+ dialog.dismiss();
+ });
+ }
+ }
+
+ private static View createSwitchShortcutDialogContentView(Context context) {
+ return createEditDialogContentView(context, DialogType.EDIT_MAGNIFICATION_SWITCH_SHORTCUT);
+ }
+
+ /**
+ * Get a content View for the edit shortcut dialog.
+ *
+ * @param context A valid context
+ * @param dialogType The type of edit shortcut dialog
+ * @return A content view suitable for viewing
+ */
+ private static View createEditDialogContentView(Context context, int dialogType) {
+ final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ View contentView = null;
+
+ switch (dialogType) {
+ case DialogType.EDIT_SHORTCUT_GENERIC:
+ contentView = inflater.inflate(
+ R.layout.accessibility_edit_shortcut, null);
+ initSoftwareShortcut(context, contentView);
+ initHardwareShortcut(context, contentView);
+ break;
+ case DialogType.EDIT_SHORTCUT_GENERIC_SUW:
+ contentView = inflater.inflate(
+ R.layout.accessibility_edit_shortcut, null);
+ initSoftwareShortcutForSUW(context, contentView);
+ initHardwareShortcut(context, contentView);
+ break;
+ case DialogType.EDIT_SHORTCUT_MAGNIFICATION:
+ contentView = inflater.inflate(
+ R.layout.accessibility_edit_shortcut_magnification, null);
+ initSoftwareShortcut(context, contentView);
+ initHardwareShortcut(context, contentView);
+ initMagnifyShortcut(context, contentView);
+ initAdvancedWidget(contentView);
+ break;
+ case DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW:
+ contentView = inflater.inflate(
+ R.layout.accessibility_edit_shortcut_magnification, null);
+ initSoftwareShortcutForSUW(context, contentView);
+ initHardwareShortcut(context, contentView);
+ initMagnifyShortcut(context, contentView);
+ initAdvancedWidget(contentView);
+ break;
+ case DialogType.EDIT_MAGNIFICATION_SWITCH_SHORTCUT:
+ contentView = inflater.inflate(
+ R.layout.accessibility_edit_magnification_shortcut, null);
+ final ImageView image = contentView.findViewById(R.id.image);
+ image.setImageResource(retrieveSoftwareShortcutImageResId(context));
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+
+ return contentView;
+ }
+
+ private static void setupShortcutWidget(View view, CharSequence titleText,
+ CharSequence summaryText, int imageResId) {
+ final CheckBox checkBox = view.findViewById(R.id.checkbox);
+ checkBox.setText(titleText);
+ final TextView summary = view.findViewById(R.id.summary);
+ if (TextUtils.isEmpty(summaryText)) {
+ summary.setVisibility(View.GONE);
+ } else {
+ summary.setText(summaryText);
+ summary.setMovementMethod(LinkMovementMethod.getInstance());
+ summary.setFocusable(false);
+ }
+ final ImageView image = view.findViewById(R.id.image);
+ image.setImageResource(imageResId);
+ }
+
+ private static void initSoftwareShortcutForSUW(Context context, View view) {
+ final View dialogView = view.findViewById(R.id.software_shortcut);
+ final CharSequence title = context.getText(
+ R.string.accessibility_shortcut_edit_dialog_title_software);
+ final TextView summary = dialogView.findViewById(R.id.summary);
+ final int lineHeight = summary.getLineHeight();
+
+ setupShortcutWidget(dialogView, title,
+ retrieveSoftwareShortcutSummaryForSUW(context, lineHeight),
+ retrieveSoftwareShortcutImageResId(context));
+ }
+
+ private static void initSoftwareShortcut(Context context, View view) {
+ final View dialogView = view.findViewById(R.id.software_shortcut);
+ final CharSequence title = context.getText(
+ R.string.accessibility_shortcut_edit_dialog_title_software);
+ final TextView summary = dialogView.findViewById(R.id.summary);
+ final int lineHeight = summary.getLineHeight();
+
+ setupShortcutWidget(dialogView, title,
+ retrieveSoftwareShortcutSummary(context, lineHeight),
+ retrieveSoftwareShortcutImageResId(context));
+ }
+
+ private static void initHardwareShortcut(Context context, View view) {
+ final View dialogView = view.findViewById(R.id.hardware_shortcut);
+ final CharSequence title = context.getText(
+ R.string.accessibility_shortcut_edit_dialog_title_hardware);
+ final CharSequence summary = context.getText(
+ R.string.accessibility_shortcut_edit_dialog_summary_hardware);
+ setupShortcutWidget(dialogView, title, summary,
+ R.drawable.accessibility_shortcut_type_hardware);
+ // TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted.
+ }
+
+ private static void initMagnifyShortcut(Context context, View view) {
+ final View dialogView = view.findViewById(R.id.triple_tap_shortcut);
+ final CharSequence title = context.getText(
+ R.string.accessibility_shortcut_edit_dialog_title_triple_tap);
+ String summary = context.getString(
+ R.string.accessibility_shortcut_edit_dialog_summary_triple_tap);
+ // Format the number '3' in the summary.
+ final Object[] arguments = {3};
+ summary = MessageFormat.format(summary, arguments);
+
+ setupShortcutWidget(dialogView, title, summary,
+ R.drawable.accessibility_shortcut_type_triple_tap);
+ // TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted.
+ }
+
+ private static void initAdvancedWidget(View view) {
+ final LinearLayout advanced = view.findViewById(R.id.advanced_shortcut);
+ final View tripleTap = view.findViewById(R.id.triple_tap_shortcut);
+ advanced.setOnClickListener((View v) -> {
+ advanced.setVisibility(View.GONE);
+ tripleTap.setVisibility(View.VISIBLE);
+ });
+ }
+
+ private static CharSequence retrieveSoftwareShortcutSummaryForSUW(Context context,
+ int lineHeight) {
+ final SpannableStringBuilder sb = new SpannableStringBuilder();
+ if (!AccessibilityUtil.isFloatingMenuEnabled(context)) {
+ sb.append(getSummaryStringWithIcon(context, lineHeight));
+ }
+ return sb;
+ }
+
+ private static CharSequence retrieveSoftwareShortcutSummary(Context context, int lineHeight) {
+ final SpannableStringBuilder sb = new SpannableStringBuilder();
+ if (!AccessibilityUtil.isFloatingMenuEnabled(context)) {
+ sb.append(getSummaryStringWithIcon(context, lineHeight));
+ sb.append("\n\n");
+ }
+ sb.append(getCustomizeAccessibilityButtonLink(context));
+ return sb;
+ }
+
+ private static int retrieveSoftwareShortcutImageResId(Context context) {
+ return AccessibilityUtil.isFloatingMenuEnabled(context)
+ ? R.drawable.accessibility_shortcut_type_software_floating
+ : R.drawable.accessibility_shortcut_type_software;
+ }
+
+ private static CharSequence getCustomizeAccessibilityButtonLink(Context context) {
+ final View.OnClickListener linkListener = v -> new SubSettingLauncher(context)
+ .setDestination(AccessibilityButtonFragment.class.getName())
+ .setSourceMetricsCategory(
+ SettingsEnums.SWITCH_SHORTCUT_DIALOG_ACCESSIBILITY_BUTTON_SETTINGS)
+ .launch();
+ final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo(
+ AnnotationSpan.LinkInfo.DEFAULT_ANNOTATION, linkListener);
+
+ return AnnotationSpan.linkify(context.getText(
+ R.string.accessibility_shortcut_edit_dialog_summary_software_floating), linkInfo);
+ }
+
+ private static SpannableString getSummaryStringWithIcon(Context context, int lineHeight) {
+ final String summary = context
+ .getString(R.string.accessibility_shortcut_edit_dialog_summary_software);
+ final SpannableString spannableMessage = SpannableString.valueOf(summary);
+
+ // Icon
+ final int indexIconStart = summary.indexOf("%s");
+ final int indexIconEnd = indexIconStart + 2;
+ final Drawable icon = context.getDrawable(R.drawable.ic_accessibility_new);
+ final ImageSpan imageSpan = new ImageSpan(icon);
+ imageSpan.setContentDescription("");
+ icon.setBounds(0, 0, lineHeight, lineHeight);
+ spannableMessage.setSpan(
+ imageSpan, indexIconStart, indexIconEnd,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return spannableMessage;
+ }
+
+ /**
+ * Returns the color associated with the specified attribute in the context's theme.
+ */
+ @ColorInt
+ private static int getThemeAttrColor(final Context context, final int attributeColor) {
+ final int colorResId = getAttrResourceId(context, attributeColor);
+ return ContextCompat.getColor(context, colorResId);
+ }
+
+ /**
+ * Returns the identifier of the resolved resource assigned to the given attribute.
+ */
+ private static int getAttrResourceId(final Context context, final int attributeColor) {
+ final int[] attrs = {attributeColor};
+ final TypedArray typedArray = context.obtainStyledAttributes(attrs);
+ final int colorResId = typedArray.getResourceId(0, 0);
+ typedArray.recycle();
+ return colorResId;
+ }
+
+ /**
+ * Creates a dialog with the given view.
+ *
+ * @param context A valid context
+ * @param dialogTitle The title of the dialog
+ * @param customView The customized view
+ * @param listener This listener will be invoked when the positive button in the dialog is
+ * clicked
+ * @return the {@link Dialog} with the given view
+ */
+ public static Dialog createCustomDialog(Context context, CharSequence dialogTitle,
+ View customView, DialogInterface.OnClickListener listener) {
+ final AlertDialog alertDialog = new AlertDialog.Builder(context)
+ .setView(customView)
+ .setTitle(dialogTitle)
+ .setCancelable(true)
+ .setPositiveButton(R.string.save, listener)
+ .setNegativeButton(R.string.cancel, null)
+ .create();
+ if (customView instanceof ScrollView || customView instanceof AbsListView) {
+ setScrollIndicators(customView);
+ }
+ return alertDialog;
+ }
+
+ /**
+ * Creates a single choice {@link ListView} with given {@link ItemInfo} list.
+ *
+ * @param context A context.
+ * @param itemInfoList A {@link ItemInfo} list.
+ * @param itemListener The listener will be invoked when the item is clicked.
+ */
+ @NonNull
+ public static ListView createSingleChoiceListView(@NonNull Context context,
+ @NonNull List extends ItemInfo> itemInfoList,
+ @Nullable AdapterView.OnItemClickListener itemListener) {
+ final ListView list = new ListView(context);
+ // Set an id to save its state.
+ list.setId(android.R.id.list);
+ list.setDivider(/* divider= */ null);
+ list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ final ItemInfoArrayAdapter
+ adapter = new ItemInfoArrayAdapter(context, itemInfoList);
+ list.setAdapter(adapter);
+ list.setOnItemClickListener(itemListener);
+ return list;
+ }
+}
diff --git a/src/com/android/settings/accessibility/AccessibilityEditDialogUtils.java b/src/com/android/settings/accessibility/AccessibilityEditDialogUtils.java
deleted file mode 100644
index 393058181b2bc811b8ee9b19f799b2b81c0d1b57..0000000000000000000000000000000000000000
--- a/src/com/android/settings/accessibility/AccessibilityEditDialogUtils.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.accessibility;
-
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.TextUtils;
-import android.text.style.ImageSpan;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.CheckBox;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
-import android.widget.TextView;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.IntDef;
-import androidx.appcompat.app.AlertDialog;
-import androidx.core.content.ContextCompat;
-
-import com.android.settings.R;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Utility class for creating the edit dialog.
- */
-public class AccessibilityEditDialogUtils {
-
- /**
- * IntDef enum for dialog type that indicates different dialog for user to choose the shortcut
- * type.
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({
- DialogType.EDIT_SHORTCUT_GENERIC,
- DialogType.EDIT_SHORTCUT_MAGNIFICATION,
- DialogType.EDIT_MAGNIFICATION_MODE,
- })
-
- private @interface DialogType {
- int EDIT_SHORTCUT_GENERIC = 0;
- int EDIT_SHORTCUT_MAGNIFICATION = 1;
- int EDIT_MAGNIFICATION_MODE = 2;
- }
-
- /**
- * Method to show the edit shortcut dialog.
- *
- * @param context A valid context
- * @param dialogTitle The title of edit shortcut dialog
- * @param listener The listener to determine the action of edit shortcut dialog
- * @return A edit shortcut dialog for showing
- */
- public static AlertDialog showEditShortcutDialog(Context context, CharSequence dialogTitle,
- DialogInterface.OnClickListener listener) {
- final AlertDialog alertDialog = createDialog(context, DialogType.EDIT_SHORTCUT_GENERIC,
- dialogTitle, listener);
- alertDialog.show();
- setScrollIndicators(alertDialog);
- return alertDialog;
- }
-
- /**
- * Method to show the edit shortcut dialog in Magnification.
- *
- * @param context A valid context
- * @param dialogTitle The title of edit shortcut dialog
- * @param listener The listener to determine the action of edit shortcut dialog
- * @return A edit shortcut dialog for showing in Magnification
- */
- public static AlertDialog showMagnificationEditShortcutDialog(Context context,
- CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
- final AlertDialog alertDialog = createDialog(context,
- DialogType.EDIT_SHORTCUT_MAGNIFICATION, dialogTitle, listener);
- alertDialog.show();
- setScrollIndicators(alertDialog);
- return alertDialog;
- }
-
- /**
- * Method to show the magnification mode dialog in Magnification.
- *
- * @param context A valid context
- * @param dialogTitle The title of magnify mode dialog
- * @param listener The listener to determine the action of magnify mode dialog
- * @return A magnification mode dialog in Magnification
- */
- public static AlertDialog showMagnificationModeDialog(Context context,
- CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
- final AlertDialog alertDialog = createDialog(context,
- DialogType.EDIT_MAGNIFICATION_MODE, dialogTitle, listener);
- alertDialog.show();
- setScrollIndicators(alertDialog);
- return alertDialog;
- }
-
- private static AlertDialog createDialog(Context context, int dialogType,
- CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
-
- final AlertDialog alertDialog = new AlertDialog.Builder(context)
- .setView(createEditDialogContentView(context, dialogType))
- .setTitle(dialogTitle)
- .setPositiveButton(R.string.save, listener)
- .setNegativeButton(R.string.cancel,
- (DialogInterface dialog, int which) -> dialog.dismiss())
- .create();
-
- return alertDialog;
- }
-
- /**
- * Sets the scroll indicators for dialog view. The indicators appears while content view is
- * out of vision for vertical scrolling.
- */
- private static void setScrollIndicators(AlertDialog dialog) {
- final ScrollView scrollView = dialog.findViewById(R.id.container_layout);
- scrollView.setScrollIndicators(
- View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM,
- View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
- }
-
- /**
- * Get a content View for the edit shortcut dialog.
- *
- * @param context A valid context
- * @param dialogType The type of edit shortcut dialog
- * @return A content view suitable for viewing
- */
- private static View createEditDialogContentView(Context context, int dialogType) {
- final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
-
- View contentView = null;
-
- switch (dialogType) {
- case DialogType.EDIT_SHORTCUT_GENERIC:
- contentView = inflater.inflate(
- R.layout.accessibility_edit_shortcut, null);
- initSoftwareShortcut(context, contentView);
- initHardwareShortcut(context, contentView);
- break;
- case DialogType.EDIT_SHORTCUT_MAGNIFICATION:
- contentView = inflater.inflate(
- R.layout.accessibility_edit_shortcut_magnification, null);
- initSoftwareShortcut(context, contentView);
- initHardwareShortcut(context, contentView);
- initMagnifyShortcut(context, contentView);
- initAdvancedWidget(contentView);
- break;
- case DialogType.EDIT_MAGNIFICATION_MODE:
- contentView = inflater.inflate(
- R.layout.accessibility_edit_magnification_mode, null);
- initMagnifyFullScreen(context, contentView);
- initMagnifyWindowScreen(context, contentView);
- break;
- default:
- throw new IllegalArgumentException();
- }
-
- return contentView;
- }
-
- private static void initMagnifyFullScreen(Context context, View view) {
- final View dialogView = view.findViewById(R.id.magnify_full_screen);
- final CharSequence title = context.getText(
- R.string.accessibility_magnification_area_settings_full_screen);
- // TODO(b/146019459): Use vector drawable instead of temporal png file to avoid distorted.
- setupShortcutWidget(dialogView, title, R.drawable.accessibility_magnification_full_screen);
- }
-
- private static void initMagnifyWindowScreen(Context context, View view) {
- final View dialogView = view.findViewById(R.id.magnify_window_screen);
- final CharSequence title = context.getText(
- R.string.accessibility_magnification_area_settings_window_screen);
- // TODO(b/146019459): Use vector drawable instead of temporal png file to avoid distorted.
- setupShortcutWidget(dialogView, title,
- R.drawable.accessibility_magnification_window_screen);
- }
-
- private static void setupShortcutWidget(View view, CharSequence titleText, int imageResId) {
- setupShortcutWidget(view, titleText, null, imageResId);
- }
-
- private static void setupShortcutWidget(View view, CharSequence titleText,
- CharSequence summaryText, int imageResId) {
- final CheckBox checkBox = view.findViewById(R.id.checkbox);
- checkBox.setText(titleText);
- final TextView summary = view.findViewById(R.id.summary);
- if (TextUtils.isEmpty(summaryText)) {
- summary.setVisibility(View.GONE);
- } else {
- summary.setText(summaryText);
- }
- final ImageView image = view.findViewById(R.id.image);
- image.setImageResource(imageResId);
- }
-
- private static void initSoftwareShortcut(Context context, View view) {
- final View dialogView = view.findViewById(R.id.software_shortcut);
- final TextView summary = dialogView.findViewById(R.id.summary);
- final int lineHeight = summary.getLineHeight();
- setupShortcutWidget(dialogView, retrieveTitle(context),
- retrieveSummary(context, lineHeight), retrieveImageResId(context));
- }
-
- private static void initHardwareShortcut(Context context, View view) {
- final View dialogView = view.findViewById(R.id.hardware_shortcut);
- final CharSequence title = context.getText(
- R.string.accessibility_shortcut_edit_dialog_title_hardware);
- final CharSequence summary = context.getText(
- R.string.accessibility_shortcut_edit_dialog_summary_hardware);
- setupShortcutWidget(dialogView, title, summary,
- R.drawable.accessibility_shortcut_type_hardware);
- // TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted.
- }
-
- private static void initMagnifyShortcut(Context context, View view) {
- final View dialogView = view.findViewById(R.id.triple_tap_shortcut);
- final CharSequence title = context.getText(
- R.string.accessibility_shortcut_edit_dialog_title_triple_tap);
- final CharSequence summary = context.getText(
- R.string.accessibility_shortcut_edit_dialog_summary_triple_tap);
- setupShortcutWidget(dialogView, title, summary,
- R.drawable.accessibility_shortcut_type_triple_tap);
- // TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted.
- }
-
- private static void initAdvancedWidget(View view) {
- final LinearLayout advanced = view.findViewById(R.id.advanced_shortcut);
- final View tripleTap = view.findViewById(R.id.triple_tap_shortcut);
- advanced.setOnClickListener((View v) -> {
- advanced.setVisibility(View.GONE);
- tripleTap.setVisibility(View.VISIBLE);
- });
- }
-
- private static CharSequence retrieveTitle(Context context) {
- int resId = R.string.accessibility_shortcut_edit_dialog_title_software;
- if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
- resId = AccessibilityUtil.isTouchExploreEnabled(context)
- ? R.string.accessibility_shortcut_edit_dialog_title_software_gesture_talkback
- : R.string.accessibility_shortcut_edit_dialog_title_software_gesture;
- }
- return context.getText(resId);
- }
-
- private static CharSequence retrieveSummary(Context context, int lineHeight) {
- if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
- final int resId = AccessibilityUtil.isTouchExploreEnabled(context)
- ? R.string.accessibility_shortcut_edit_dialog_summary_software_gesture_talkback
- : R.string.accessibility_shortcut_edit_dialog_summary_software_gesture;
- return context.getText(resId);
- }
- return getSummaryStringWithIcon(context, lineHeight);
- }
-
- private static int retrieveImageResId(Context context) {
- // TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted.
- int resId = R.drawable.accessibility_shortcut_type_software;
- if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
- resId = AccessibilityUtil.isTouchExploreEnabled(context)
- ? R.drawable.accessibility_shortcut_type_software_gesture_talkback
- : R.drawable.accessibility_shortcut_type_software_gesture;
- }
- return resId;
- }
-
- private static SpannableString getSummaryStringWithIcon(Context context, int lineHeight) {
- final String summary = context
- .getString(R.string.accessibility_shortcut_edit_dialog_summary_software);
- final SpannableString spannableMessage = SpannableString.valueOf(summary);
-
- // Icon
- final int indexIconStart = summary.indexOf("%s");
- final int indexIconEnd = indexIconStart + 2;
- final Drawable icon = context.getDrawable(R.drawable.ic_accessibility_new);
- final ImageSpan imageSpan = new ImageSpan(icon);
- imageSpan.setContentDescription("");
- icon.setBounds(0, 0, lineHeight, lineHeight);
- spannableMessage.setSpan(
- imageSpan, indexIconStart, indexIconEnd,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-
- return spannableMessage;
- }
-
- /**
- * Returns the color associated with the specified attribute in the context's theme.
- */
- @ColorInt
- private static int getThemeAttrColor(final Context context, final int attributeColor) {
- final int colorResId = getAttrResourceId(context, attributeColor);
- return ContextCompat.getColor(context, colorResId);
- }
-
- /**
- * Returns the identifier of the resolved resource assigned to the given attribute.
- */
- private static int getAttrResourceId(final Context context, final int attributeColor) {
- final int[] attrs = {attributeColor};
- final TypedArray typedArray = context.obtainStyledAttributes(attrs);
- final int colorResId = typedArray.getResourceId(0, 0);
- typedArray.recycle();
- return colorResId;
- }
-}
diff --git a/src/com/android/settings/accessibility/AccessibilityFooterPreference.java b/src/com/android/settings/accessibility/AccessibilityFooterPreference.java
new file mode 100644
index 0000000000000000000000000000000000000000..67b78273e3ba264b2ec73a8e601bfb51ba308f99
--- /dev/null
+++ b/src/com/android/settings/accessibility/AccessibilityFooterPreference.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+import android.text.method.LinkMovementMethod;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settingslib.widget.FooterPreference;
+
+/**
+ * A custom preference acting as footer of a page. Disables the movement method by default.
+ */
+public final class AccessibilityFooterPreference extends FooterPreference {
+
+ private boolean mLinkEnabled;
+
+ public AccessibilityFooterPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AccessibilityFooterPreference(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ final TextView title = holder.itemView.findViewById(android.R.id.title);
+ if (mLinkEnabled) {
+ // When a TextView has a movement method, it will set the view to clickable. This makes
+ // View.onTouchEvent always return true and consumes the touch event, essentially
+ // nullifying any return values of MovementMethod.onTouchEvent.
+ // To still allow propagating touch events to the parent when this view doesn't have
+ // links, we only set the movement method here if the text contains links.
+ title.setMovementMethod(LinkMovementMethod.getInstance());
+ } else {
+ title.setMovementMethod(/* movement= */ null);
+ }
+ }
+
+ /**
+ * Sets the title field supports movement method.
+ */
+ public void setLinkEnabled(boolean enabled) {
+ if (mLinkEnabled != enabled) {
+ mLinkEnabled = enabled;
+ notifyChanged();
+ }
+ }
+
+ /**
+ * Returns true if the title field supports movement method.
+ */
+ public boolean isLinkEnabled() {
+ return mLinkEnabled;
+ }
+}
diff --git a/src/com/android/settings/accessibility/AccessibilityFooterPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityFooterPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3422e43fbd5d82c720b2c4bdef0be3c134408c0
--- /dev/null
+++ b/src/com/android/settings/accessibility/AccessibilityFooterPreferenceController.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.HelpUtils;
+
+/**
+ * Base class for accessibility preference footer.
+ */
+public abstract class AccessibilityFooterPreferenceController extends BasePreferenceController {
+
+ public AccessibilityFooterPreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ final AccessibilityFooterPreference footerPreference =
+ screen.findPreference(getPreferenceKey());
+ updateFooterPreferences(footerPreference);
+ }
+
+ /**
+ * Override this if showing a help item in the footer bar, by returning the resource id.
+ *
+ * @return the resource id for the help url
+ */
+ protected int getHelpResource() {
+ return 0;
+ }
+
+ /** Returns the accessibility feature name. */
+ protected abstract String getLabelName();
+
+ private void updateFooterPreferences(AccessibilityFooterPreference footerPreference) {
+ final StringBuffer sb = new StringBuffer();
+ sb.append(mContext.getString(
+ R.string.accessibility_introduction_title, getLabelName()))
+ .append("\n\n")
+ .append(footerPreference.getTitle());
+ footerPreference.setContentDescription(sb);
+
+ if (getHelpResource() != 0) {
+ footerPreference.setLearnMoreAction(view -> {
+ final Intent helpIntent = HelpUtils.getHelpIntent(
+ mContext, mContext.getString(getHelpResource()),
+ mContext.getClass().getName());
+ view.startActivityForResult(helpIntent, 0);
+ });
+
+ final String learnMoreContentDescription = mContext.getString(
+ R.string.footer_learn_more_content_description, getLabelName());
+ footerPreference.setLearnMoreContentDescription(learnMoreContentDescription);
+ }
+ }
+}
diff --git a/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java b/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java
index 482822e4f66d2e0c36e09974b46e61485e22ab23..f8cdcb3388a21ff5fcc0f3d75c6f686f436ec0e1 100644
--- a/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java
+++ b/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java
@@ -24,12 +24,10 @@ import static com.android.settings.accessibility.AccessibilityUtil.UserShortcutT
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
-import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ImageSpan;
-import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.TextureView;
@@ -49,11 +47,11 @@ import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.util.Preconditions;
+import androidx.core.widget.TextViewCompat;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.android.settings.R;
-import com.android.settings.Utils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -310,30 +308,23 @@ public final class AccessibilityGestureNavigationTutorial {
}
private static View makeTitleView(Context context) {
- final String familyName =
- context.getString(
- com.android.internal.R.string.config_headlineFontFamilyMedium);
final TextView textView = new TextView(context);
-
- textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, /* size= */ 20);
- textView.setTextColor(Utils.getColorAttr(context, android.R.attr.textColorPrimary));
+ // Sets the text color, size, style, hint color, and highlight color from the specified
+ // TextAppearance resource.
+ TextViewCompat.setTextAppearance(textView, R.style.AccessibilityDialogTitle);
textView.setGravity(Gravity.CENTER);
- textView.setTypeface(Typeface.create(familyName, Typeface.NORMAL));
-
return textView;
}
private static View makeInstructionView(Context context) {
final TextView textView = new TextView(context);
- textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, /* size= */ 16);
- textView.setTextColor(Utils.getColorAttr(context, android.R.attr.textColorPrimary));
- textView.setTypeface(
- Typeface.create(/* familyName= */ "sans-serif", Typeface.NORMAL));
+ TextViewCompat.setTextAppearance(textView, R.style.AccessibilityDialogDescription);
return textView;
}
private static TutorialPage createSoftwareTutorialPage(@NonNull Context context) {
- final CharSequence title = getSoftwareTitle(context);
+ final CharSequence title = context.getText(
+ R.string.accessibility_tutorial_dialog_title_button);
final ImageView image = createSoftwareImage(context);
final CharSequence instruction = getSoftwareInstruction(context);
final ImageView indicatorIcon =
@@ -390,44 +381,19 @@ public final class AccessibilityGestureNavigationTutorial {
return tutorialPages;
}
- private static CharSequence getSoftwareTitle(Context context) {
- final boolean isGestureNavigationEnabled =
- AccessibilityUtil.isGestureNavigateEnabled(context);
- final int resId = isGestureNavigationEnabled
- ? R.string.accessibility_tutorial_dialog_title_gesture
- : R.string.accessibility_tutorial_dialog_title_button;
-
- return context.getText(resId);
- }
-
private static ImageView createSoftwareImage(Context context) {
- int resId = R.drawable.accessibility_shortcut_type_software;
- if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
- resId = AccessibilityUtil.isTouchExploreEnabled(context)
- ? R.drawable.accessibility_shortcut_type_software_gesture_talkback
- : R.drawable.accessibility_shortcut_type_software_gesture;
- }
+ final int resId = AccessibilityUtil.isFloatingMenuEnabled(context)
+ ? R.drawable.accessibility_shortcut_type_software_floating
+ : R.drawable.accessibility_shortcut_type_software;
return createImageView(context, resId);
}
private static CharSequence getSoftwareInstruction(Context context) {
- final boolean isGestureNavigateEnabled =
- AccessibilityUtil.isGestureNavigateEnabled(context);
- final boolean isTouchExploreEnabled = AccessibilityUtil.isTouchExploreEnabled(context);
- int resId = R.string.accessibility_tutorial_dialog_message_button;
- if (isGestureNavigateEnabled) {
- resId = isTouchExploreEnabled
- ? R.string.accessibility_tutorial_dialog_message_gesture_talkback
- : R.string.accessibility_tutorial_dialog_message_gesture;
- }
-
- CharSequence text = context.getText(resId);
- if (resId == R.string.accessibility_tutorial_dialog_message_button) {
- text = getSoftwareInstructionWithIcon(context, text);
- }
-
- return text;
+ return AccessibilityUtil.isFloatingMenuEnabled(context)
+ ? context.getText(R.string.accessibility_tutorial_dialog_message_floating_button)
+ : getSoftwareInstructionWithIcon(context,
+ context.getText(R.string.accessibility_tutorial_dialog_message_button));
}
private static CharSequence getSoftwareInstructionWithIcon(Context context, CharSequence text) {
diff --git a/src/com/android/settings/accessibility/AccessibilitySearchFeatureProvider.java b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..6aa8c841ed1f1ea5552ffbae12d78565e9929b84
--- /dev/null
+++ b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+
+import com.android.settingslib.search.SearchIndexableRaw;
+
+import java.util.List;
+
+/**
+ * Provider for Accessibility Search related features.
+ */
+public interface AccessibilitySearchFeatureProvider {
+
+ /**
+ * Returns a list of raw data for indexing. See {@link SearchIndexableRaw}
+ *
+ * @param context a valid context {@link Context} instance
+ * @return a list of {@link SearchIndexableRaw} references. Can be null.
+ */
+ List getSearchIndexableRawData(Context context);
+}
diff --git a/src/com/android/settings/accessibility/AccessibilitySearchFeatureProviderImpl.java b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProviderImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..c358af11d062c5cbdd6445d1bd6c88ad04538172
--- /dev/null
+++ b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProviderImpl.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+
+import com.android.settingslib.search.SearchIndexableRaw;
+
+import java.util.List;
+
+/**
+ * Provider implementation for Accessibility Search related features.
+ */
+public class AccessibilitySearchFeatureProviderImpl implements AccessibilitySearchFeatureProvider {
+
+ @Override
+ public List getSearchIndexableRawData(Context context) {
+ return null;
+ }
+}
diff --git a/src/com/android/settings/accessibility/AccessibilityServiceWarning.java b/src/com/android/settings/accessibility/AccessibilityServiceWarning.java
index 2206e81e650af99589443cbaf9820c279a39e199..dcf78977c7468188c0b54d84828fe4d6f552f283 100644
--- a/src/com/android/settings/accessibility/AccessibilityServiceWarning.java
+++ b/src/com/android/settings/accessibility/AccessibilityServiceWarning.java
@@ -35,6 +35,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
@@ -60,11 +61,19 @@ public class AccessibilityServiceWarning {
return false;
};
+ /**
+ * The interface to execute the uninstallation action.
+ */
+ interface UninstallActionPerformer {
+ void uninstallPackage();
+ }
+
/** Returns a {@link Dialog} to be shown to confirm that they want to enable a service. */
- public static Dialog createCapabilitiesDialog(Context context,
- AccessibilityServiceInfo info, View.OnClickListener listener) {
+ public static Dialog createCapabilitiesDialog(@NonNull Context context,
+ @NonNull AccessibilityServiceInfo info, @NonNull View.OnClickListener listener,
+ @NonNull UninstallActionPerformer performer) {
final AlertDialog ad = new AlertDialog.Builder(context)
- .setView(createEnableDialogContentView(context, info, listener))
+ .setView(createEnableDialogContentView(context, info, listener, performer))
.create();
Window window = ad.getWindow();
@@ -88,7 +97,8 @@ public class AccessibilityServiceWarning {
}
private static View createEnableDialogContentView(Context context,
- AccessibilityServiceInfo info, View.OnClickListener listener) {
+ @NonNull AccessibilityServiceInfo info, View.OnClickListener listener,
+ UninstallActionPerformer performer) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
@@ -129,6 +139,14 @@ public class AccessibilityServiceWarning {
permissionAllowButton.setOnTouchListener(filterTouchListener);
permissionDenyButton.setOnClickListener(listener);
+ final Button uninstallButton = content.findViewById(
+ R.id.permission_enable_uninstall_button);
+ // Shows an uninstall button to help users quickly remove the non-system App due to the
+ // required permissions.
+ if (!AccessibilityUtil.isSystemApp(info)) {
+ uninstallButton.setVisibility(View.VISIBLE);
+ uninstallButton.setOnClickListener(v -> performer.uninstallPackage());
+ }
return content;
}
diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java
index f918046bc37bc481f6379144561802660416b8ff..78bea0f00a9dfefaa96e613b92217150241594f5 100644
--- a/src/com/android/settings/accessibility/AccessibilitySettings.java
+++ b/src/com/android/settings/accessibility/AccessibilitySettings.java
@@ -16,7 +16,7 @@
package com.android.settings.accessibility;
-import static com.android.settingslib.TwoTargetPreference.ICON_SIZE_MEDIUM;
+import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityShortcutInfo;
@@ -28,8 +28,8 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.graphics.Color;
import android.graphics.drawable.Drawable;
-import android.hardware.display.ColorDisplayManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -43,7 +43,6 @@ import androidx.annotation.VisibleForTesting;
import androidx.core.content.ContextCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
-import androidx.preference.SwitchPreference;
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.content.PackageMonitor;
@@ -51,13 +50,14 @@ import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.display.DarkUIPreferenceController;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.accessibility.AccessibilityUtils;
import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.search.SearchIndexableRaw;
import java.util.ArrayList;
import java.util.Collection;
@@ -76,28 +76,17 @@ public class AccessibilitySettings extends DashboardFragment {
// Preference categories
private static final String CATEGORY_SCREEN_READER = "screen_reader_category";
- private static final String CATEGORY_AUDIO_AND_CAPTIONS = "audio_and_captions_category";
+ private static final String CATEGORY_CAPTIONS = "captions_category";
+ private static final String CATEGORY_AUDIO = "audio_category";
private static final String CATEGORY_DISPLAY = "display_category";
private static final String CATEGORY_INTERACTION_CONTROL = "interaction_control_category";
- private static final String CATEGORY_EXPERIMENTAL = "experimental_category";
private static final String CATEGORY_DOWNLOADED_SERVICES = "user_installed_services_category";
- private static final String[] CATEGORIES = new String[] {
- CATEGORY_SCREEN_READER, CATEGORY_AUDIO_AND_CAPTIONS, CATEGORY_DISPLAY,
- CATEGORY_INTERACTION_CONTROL, CATEGORY_EXPERIMENTAL, CATEGORY_DOWNLOADED_SERVICES
+ private static final String[] CATEGORIES = new String[]{
+ CATEGORY_SCREEN_READER, CATEGORY_CAPTIONS, CATEGORY_AUDIO, CATEGORY_DISPLAY,
+ CATEGORY_INTERACTION_CONTROL, CATEGORY_DOWNLOADED_SERVICES
};
- // Preferences
- private static final String TOGGLE_INVERSION_PREFERENCE =
- "toggle_inversion_preference";
- private static final String TOGGLE_LARGE_POINTER_ICON =
- "toggle_large_pointer_icon";
- private static final String TOGGLE_DISABLE_ANIMATIONS = "toggle_disable_animations";
- private static final String DISPLAY_MAGNIFICATION_PREFERENCE_SCREEN =
- "magnification_preference_screen";
- private static final String DISPLAY_DALTONIZER_PREFERENCE_SCREEN =
- "daltonizer_preference";
-
// Extras passed to sub-fragments.
static final String EXTRA_PREFERENCE_KEY = "preference_key";
static final String EXTRA_CHECKED = "checked";
@@ -125,7 +114,7 @@ public class AccessibilitySettings extends DashboardFragment {
@Override
public void run() {
if (getActivity() != null) {
- updateServicePreferences();
+ onContentChanged();
}
}
};
@@ -156,7 +145,8 @@ public class AccessibilitySettings extends DashboardFragment {
}
};
- private final SettingsContentObserver mSettingsContentObserver;
+ @VisibleForTesting
+ final SettingsContentObserver mSettingsContentObserver;
private final Map mCategoryToPrefCategoryMap =
new ArrayMap<>();
@@ -165,22 +155,8 @@ public class AccessibilitySettings extends DashboardFragment {
private final Map mPreBundledServiceComponentToCategoryMap =
new ArrayMap<>();
- private SwitchPreference mToggleLargePointerIconPreference;
- private SwitchPreference mToggleDisableAnimationsPreference;
- private Preference mDisplayMagnificationPreferenceScreen;
- private Preference mDisplayDaltonizerPreferenceScreen;
- private Preference mToggleInversionPreference;
-
- /**
- * Check if the color transforms are color accelerated. Some transforms are experimental only
- * on non-accelerated platforms due to the performance implications.
- *
- * @param context The current context
- */
- public static boolean isColorTransformAccelerated(Context context) {
- return context.getResources()
- .getBoolean(com.android.internal.R.bool.config_setColorTransformAccelerated);
- }
+ private boolean mNeedPreferencesUpdate = false;
+ private boolean mIsForeground = true;
public AccessibilitySettings() {
// Observe changes to anything that the shortcut can toggle, so we can reflect updates
@@ -197,7 +173,7 @@ public class AccessibilitySettings extends DashboardFragment {
mSettingsContentObserver = new SettingsContentObserver(mHandler, shortcutFeatureKeys) {
@Override
public void onChange(boolean selfChange, Uri uri) {
- updateAllPreferences();
+ onContentChanged();
}
};
}
@@ -212,36 +188,43 @@ public class AccessibilitySettings extends DashboardFragment {
return R.string.help_uri_accessibility;
}
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- initializeAllPreferences();
- }
-
@Override
public void onAttach(Context context) {
super.onAttach(context);
- use(DarkUIPreferenceController.class).setParentFragment(this);
use(AccessibilityHearingAidPreferenceController.class)
.setFragmentManager(getFragmentManager());
}
@Override
- public void onStart() {
- super.onStart();
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ initializeAllPreferences();
updateAllPreferences();
+ registerContentMonitors();
+ }
- mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
- mSettingsContentObserver.register(getContentResolver());
+ @Override
+ public void onStart() {
+ if (mNeedPreferencesUpdate) {
+ updateAllPreferences();
+ mNeedPreferencesUpdate = false;
+ }
+ mIsForeground = true;
+ super.onStart();
}
@Override
public void onStop() {
- mSettingsPackageMonitor.unregister();
- mSettingsContentObserver.unregister(getContentResolver());
+ mIsForeground = false;
super.onStop();
}
+ @Override
+ public void onDestroy() {
+ unregisterContentMonitors();
+ super.onDestroy();
+ }
+
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_settings;
@@ -255,8 +238,8 @@ public class AccessibilitySettings extends DashboardFragment {
/**
* Returns the summary for the current state of this accessibilityService.
*
- * @param context A valid context
- * @param info The accessibilityService's info
+ * @param context A valid context
+ * @param info The accessibilityService's info
* @param serviceEnabled Whether the accessibility service is enabled.
* @return The service summary
*/
@@ -295,8 +278,8 @@ public class AccessibilitySettings extends DashboardFragment {
/**
* Returns the description for the current state of this accessibilityService.
*
- * @param context A valid context
- * @param info The accessibilityService's info
+ * @param context A valid context
+ * @param info The accessibilityService's info
* @param serviceEnabled Whether the accessibility service is enabled.
* @return The service description
*/
@@ -315,31 +298,41 @@ public class AccessibilitySettings extends DashboardFragment {
context.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, 0) == 1;
}
+ @VisibleForTesting
+ void onContentChanged() {
+ // If the fragment is visible then update preferences immediately, else set the flag then
+ // wait for the fragment to show up to update preferences.
+ if (mIsForeground) {
+ updateAllPreferences();
+ } else {
+ mNeedPreferencesUpdate = true;
+ }
+ }
+
private void initializeAllPreferences() {
for (int i = 0; i < CATEGORIES.length; i++) {
PreferenceCategory prefCategory = findPreference(CATEGORIES[i]);
mCategoryToPrefCategoryMap.put(CATEGORIES[i], prefCategory);
}
+ }
- // Display inversion.
- mToggleInversionPreference = findPreference(TOGGLE_INVERSION_PREFERENCE);
-
- // Large pointer icon.
- mToggleLargePointerIconPreference = findPreference(TOGGLE_LARGE_POINTER_ICON);
-
- mToggleDisableAnimationsPreference = findPreference(TOGGLE_DISABLE_ANIMATIONS);
+ @VisibleForTesting
+ void updateAllPreferences() {
+ updateSystemPreferences();
+ updateServicePreferences();
+ }
- // Display magnification.
- mDisplayMagnificationPreferenceScreen = findPreference(
- DISPLAY_MAGNIFICATION_PREFERENCE_SCREEN);
+ private void registerContentMonitors() {
+ final Context context = getActivity();
- // Display color adjustments.
- mDisplayDaltonizerPreferenceScreen = findPreference(DISPLAY_DALTONIZER_PREFERENCE_SCREEN);
+ mSettingsPackageMonitor.register(context, context.getMainLooper(), /* externalStorage= */
+ false);
+ mSettingsContentObserver.register(getContentResolver());
}
- private void updateAllPreferences() {
- updateSystemPreferences();
- updateServicePreferences();
+ private void unregisterContentMonitors() {
+ mSettingsPackageMonitor.unregister();
+ mSettingsContentObserver.unregister(getContentResolver());
}
protected void updateServicePreferences() {
@@ -356,8 +349,10 @@ public class AccessibilitySettings extends DashboardFragment {
initializePreBundledServicesMapFromArray(CATEGORY_SCREEN_READER,
R.array.config_preinstalled_screen_reader_services);
- initializePreBundledServicesMapFromArray(CATEGORY_AUDIO_AND_CAPTIONS,
- R.array.config_preinstalled_audio_and_caption_services);
+ initializePreBundledServicesMapFromArray(CATEGORY_CAPTIONS,
+ R.array.config_preinstalled_captions_services);
+ initializePreBundledServicesMapFromArray(CATEGORY_AUDIO,
+ R.array.config_preinstalled_audio_services);
initializePreBundledServicesMapFromArray(CATEGORY_DISPLAY,
R.array.config_preinstalled_display_services);
initializePreBundledServicesMapFromArray(CATEGORY_INTERACTION_CONTROL,
@@ -384,13 +379,15 @@ public class AccessibilitySettings extends DashboardFragment {
// Update the order of all the category according to the order defined in xml file.
updateCategoryOrderFromArray(CATEGORY_SCREEN_READER,
- R.array.config_order_screen_reader_services);
- updateCategoryOrderFromArray(CATEGORY_AUDIO_AND_CAPTIONS,
- R.array.config_order_audio_and_caption_services);
+ R.array.config_order_screen_reader_services);
+ updateCategoryOrderFromArray(CATEGORY_CAPTIONS,
+ R.array.config_order_captions_services);
+ updateCategoryOrderFromArray(CATEGORY_AUDIO,
+ R.array.config_order_audio_services);
updateCategoryOrderFromArray(CATEGORY_INTERACTION_CONTROL,
- R.array.config_order_interaction_control_services);
+ R.array.config_order_interaction_control_services);
updateCategoryOrderFromArray(CATEGORY_DISPLAY,
- R.array.config_order_display_services);
+ R.array.config_order_display_services);
// Need to check each time when updateServicePreferences() called.
if (downloadedServicesCategory.getPreferenceCount() == 0) {
@@ -398,6 +395,9 @@ public class AccessibilitySettings extends DashboardFragment {
} else {
getPreferenceScreen().addPreference(downloadedServicesCategory);
}
+
+ // Hide screen reader category if it is empty.
+ updatePreferenceCategoryVisibility(CATEGORY_SCREEN_READER);
}
private List getInstalledAccessibilityList(Context context) {
@@ -460,7 +460,7 @@ public class AccessibilitySettings extends DashboardFragment {
* key with the string array of preference order which is defined in the xml.
*
* @param categoryKey The key of the category need to update the order
- * @param key The key of the string array which defines the order of category
+ * @param key The key of the string array which defines the order of category
*/
private void updateCategoryOrderFromArray(String categoryKey, int key) {
String[] services = getResources().getStringArray(key);
@@ -478,37 +478,33 @@ public class AccessibilitySettings extends DashboardFragment {
}
}
+ /**
+ * Updates the visibility of a category according to its child preference count.
+ *
+ * @param categoryKey The key of the category which needs to check
+ */
+ private void updatePreferenceCategoryVisibility(String categoryKey) {
+ final PreferenceCategory category = mCategoryToPrefCategoryMap.get(categoryKey);
+ category.setVisible(category.getPreferenceCount() != 0);
+ }
+
+ /**
+ * Updates preferences related to system configurations.
+ */
protected void updateSystemPreferences() {
- // Move color inversion and color correction preferences to Display category if device
- // supports HWC hardware-accelerated color transform.
- if (ColorDisplayManager.isColorTransformAccelerated(getContext())) {
- PreferenceCategory experimentalCategory =
- mCategoryToPrefCategoryMap.get(CATEGORY_EXPERIMENTAL);
- PreferenceCategory displayCategory =
- mCategoryToPrefCategoryMap.get(CATEGORY_DISPLAY);
- experimentalCategory.removePreference(mToggleInversionPreference);
- experimentalCategory.removePreference(mDisplayDaltonizerPreferenceScreen);
- mDisplayMagnificationPreferenceScreen.setSummary(
- ToggleScreenMagnificationPreferenceFragment.getServiceSummary(getContext()));
- mDisplayDaltonizerPreferenceScreen.setOrder(
- mDisplayMagnificationPreferenceScreen.getOrder() + 1);
- mDisplayDaltonizerPreferenceScreen.setSummary(AccessibilityUtil.getSummary(
- getContext(), Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED));
- mToggleInversionPreference.setOrder(
- mDisplayDaltonizerPreferenceScreen.getOrder() + 1);
- mToggleLargePointerIconPreference.setOrder(
- mToggleInversionPreference.getOrder() + 1);
- mToggleDisableAnimationsPreference.setOrder(
- mToggleLargePointerIconPreference.getOrder() + 1);
- mToggleInversionPreference.setSummary(AccessibilityUtil.getSummary(
- getContext(), Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED));
- displayCategory.addPreference(mToggleInversionPreference);
- displayCategory.addPreference(mDisplayDaltonizerPreferenceScreen);
- }
+ // Do nothing.
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
- new BaseSearchIndexProvider(R.xml.accessibility_settings);
+ new BaseSearchIndexProvider(R.xml.accessibility_settings) {
+ @Override
+ public List getRawDataToIndex(Context context,
+ boolean enabled) {
+ return FeatureFactory.getFactory(context)
+ .getAccessibilitySearchFeatureProvider().getSearchIndexableRawData(
+ context);
+ }
+ };
/**
* This class helps setup RestrictedPreference.
@@ -532,6 +528,7 @@ public class AccessibilitySettings extends DashboardFragment {
* installed accessibility services
* @return The list of {@link RestrictedPreference}
*/
+ @VisibleForTesting
List createAccessibilityServicePreferenceList(
List installedServices) {
@@ -572,7 +569,6 @@ public class AccessibilitySettings extends DashboardFragment {
setRestrictedPreferenceEnabled(preference, packageName, serviceAllowed,
serviceEnabled);
-
final String prefKey = preference.getKey();
final int imageRes = info.getAnimatedImageRes();
final CharSequence description = getServiceDescription(mContext, info,
@@ -597,6 +593,7 @@ public class AccessibilitySettings extends DashboardFragment {
* installed accessibility shortcuts
* @return The list of {@link RestrictedPreference}
*/
+ @VisibleForTesting
List createAccessibilityActivityPreferenceList(
List installedShortcuts) {
final Set enabledServices =
@@ -676,7 +673,7 @@ public class AccessibilitySettings extends DashboardFragment {
preference.setKey(key);
preference.setTitle(title);
preference.setSummary(summary);
- Utils.setSafeIcon(preference, icon);
+ preference.setIcon(Utils.getAdaptiveIcon(mContext, icon, Color.WHITE));
preference.setFragment(fragment);
preference.setIconSize(ICON_SIZE_MEDIUM);
preference.setPersistent(false); // Disable SharedPreferences.
diff --git a/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizard.java b/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizard.java
index ffc8335c88960847d6a2833b8bf6212f96a10205..4e8be420a3726daeee1300a1001e10c70b2baf30 100644
--- a/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizard.java
+++ b/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizard.java
@@ -16,26 +16,33 @@
package com.android.settings.accessibility;
+import static com.android.settings.Utils.getAdaptiveIcon;
import static com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE;
+import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ServiceInfo;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
+import android.widget.LinearLayout;
import androidx.preference.Preference;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
+import com.android.settingslib.RestrictedPreference;
import com.google.android.setupdesign.GlifPreferenceLayout;
+import com.google.android.setupdesign.util.ThemeHelper;
import java.util.List;
@@ -61,8 +68,8 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm
// Preference controls.
private Preference mDisplayMagnificationPreference;
- private Preference mScreenReaderPreference;
- private Preference mSelectToSpeakPreference;
+ private RestrictedPreference mScreenReaderPreference;
+ private RestrictedPreference mSelectToSpeakPreference;
@Override
public int getMetricsCategory() {
@@ -73,16 +80,23 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- GlifPreferenceLayout layout = (GlifPreferenceLayout) view;
+ final GlifPreferenceLayout layout = (GlifPreferenceLayout) view;
layout.setDividerInsets(Integer.MAX_VALUE, 0);
-
+ layout.setDescriptionText(R.string.vision_settings_description);
layout.setHeaderText(R.string.vision_settings_title);
+ layout.setIcon(getPrefContext().getDrawable(R.drawable.ic_accessibility_visibility));
+
+ if (ThemeHelper.shouldApplyExtendedPartnerConfig(getActivity())) {
+ final LinearLayout headerLayout = layout.findManagedViewById(R.id.sud_layout_header);
+ headerLayout.setPadding(0, headerLayout.getPaddingTop(), 0,
+ headerLayout.getPaddingBottom());
+ }
}
@Override
public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
- GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
+ final GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
return layout.onCreateRecyclerView(inflater, parent, savedInstanceState);
}
@@ -145,7 +159,7 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm
return null;
}
- private void updateAccessibilityServicePreference(Preference preference,
+ private void updateAccessibilityServicePreference(RestrictedPreference preference,
String packageName, String serviceName, String targetFragment) {
final AccessibilityServiceInfo info = findService(packageName, serviceName);
if (info == null) {
@@ -153,24 +167,28 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm
return;
}
- ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
- String title = info.getResolveInfo().loadLabel(getPackageManager()).toString();
+ final ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
+ final Drawable icon = info.getResolveInfo().loadIcon(getPackageManager());
+ preference.setIcon(getAdaptiveIcon(getContext(), icon, Color.WHITE));
+ preference.setIconSize(ICON_SIZE_MEDIUM);
+ final String title = info.getResolveInfo().loadLabel(getPackageManager()).toString();
preference.setTitle(title);
- ComponentName componentName = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ final ComponentName componentName =
+ new ComponentName(serviceInfo.packageName, serviceInfo.name);
preference.setKey(componentName.flattenToString());
if (AccessibilityUtil.getAccessibilityServiceFragmentType(info) == VOLUME_SHORTCUT_TOGGLE) {
preference.setFragment(targetFragment);
}
// Update the extras.
- Bundle extras = preference.getExtras();
+ final Bundle extras = preference.getExtras();
extras.putParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME, componentName);
extras.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY,
preference.getKey());
extras.putString(AccessibilitySettings.EXTRA_TITLE, title);
- String description = info.loadDescription(getPackageManager());
+ final String description = info.loadDescription(getPackageManager());
extras.putString(AccessibilitySettings.EXTRA_SUMMARY, description);
extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, info.getAnimatedImageRes());
diff --git a/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardActivity.java b/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardActivity.java
index 1a6c8e80f50b8262c3c812bc30b58ca4a10fbd6f..b65b349707c154d86f597fcb13f3d96d43f86e9f 100644
--- a/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardActivity.java
+++ b/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardActivity.java
@@ -34,8 +34,10 @@ import com.android.settings.display.FontSizePreferenceFragmentForSetupWizard;
import com.android.settings.search.actionbar.SearchMenuController;
import com.android.settings.support.actionbar.HelpResourceProvider;
import com.android.settingslib.core.instrumentation.Instrumentable;
+import com.android.settingslib.transition.SettingsTransitionHelper;
import com.google.android.setupcompat.util.WizardManagerHelper;
+import com.google.android.setupdesign.util.ThemeHelper;
public class AccessibilitySettingsForSetupWizardActivity extends SettingsActivity {
@@ -89,6 +91,9 @@ public class AccessibilitySettingsForSetupWizardActivity extends SettingsActivit
.setSourceMetricsCategory(caller instanceof Instrumentable
? ((Instrumentable) caller).getMetricsCategory()
: Instrumentable.METRICS_CATEGORY_UNKNOWN)
+ .setExtras(SetupWizardUtils.copyLifecycleExtra(getIntent().getExtras(),
+ new Bundle()))
+ .setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_FADE)
.launch();
return true;
}
@@ -96,11 +101,22 @@ public class AccessibilitySettingsForSetupWizardActivity extends SettingsActivit
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
-
+ applyTheme();
tryLaunchFontSizeSettings();
findViewById(R.id.content_parent).setFitsSystemWindows(false);
}
+ private void applyTheme() {
+ if (ThemeHelper.trySetDynamicColor(this)) {
+ final int appliedTheme = ThemeHelper.isSetupWizardDayNightEnabled(this)
+ ? R.style.SudDynamicColorThemeSettings_SetupWizard_DayNight
+ : R.style.SudDynamicColorThemeSettings_SetupWizard;
+ setTheme(appliedTheme);
+ } else {
+ setTheme(SetupWizardUtils.getTheme(this, getIntent()));
+ }
+ }
+
@VisibleForTesting
void tryLaunchFontSizeSettings() {
if (WizardManagerHelper.isAnySetupWizard(getIntent())
@@ -115,7 +131,8 @@ public class AccessibilitySettingsForSetupWizardActivity extends SettingsActivit
.setArguments(args)
.setSourceMetricsCategory(Instrumentable.METRICS_CATEGORY_UNKNOWN)
.setExtras(SetupWizardUtils.copyLifecycleExtra(getIntent().getExtras(),
- new Bundle()));
+ new Bundle()))
+ .setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_FADE);
Log.d(LOG_TAG, "Launch font size settings");
subSettingLauncher.launch();
diff --git a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java
new file mode 100644
index 0000000000000000000000000000000000000000..127c7c68a3950972bb1d9f3b79d3810e8786efbc
--- /dev/null
+++ b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
+import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_GENERAL_CATEGORY;
+
+import android.app.Dialog;
+import android.app.settings.SettingsEnums;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.icu.text.CaseMap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.CheckBox;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.utils.LocaleUtils;
+
+import com.google.android.setupcompat.util.WizardManagerHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Base class for accessibility fragments shortcut functions and dialog management.
+ */
+public abstract class AccessibilityShortcutPreferenceFragment extends DashboardFragment
+ implements ShortcutPreference.OnClickCallback {
+ private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
+ protected static final String KEY_SAVED_USER_SHORTCUT_TYPE = "shortcut_type";
+ protected static final int NOT_SET = -1;
+ // Save user's shortcutType value when savedInstance has value (e.g. device rotated).
+ protected int mSavedCheckBoxValue = NOT_SET;
+
+ protected ShortcutPreference mShortcutPreference;
+ private AccessibilityManager.TouchExplorationStateChangeListener
+ mTouchExplorationStateChangeListener;
+ private SettingsContentObserver mSettingsContentObserver;
+ private CheckBox mSoftwareTypeCheckBox;
+ private CheckBox mHardwareTypeCheckBox;
+
+ /** Returns the accessibility component name. */
+ protected abstract ComponentName getComponentName();
+
+ /** Returns the accessibility feature name. */
+ protected abstract CharSequence getLabelName();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Restore the user shortcut type.
+ if (savedInstanceState != null && savedInstanceState.containsKey(
+ KEY_SAVED_USER_SHORTCUT_TYPE)) {
+ mSavedCheckBoxValue = savedInstanceState.getInt(KEY_SAVED_USER_SHORTCUT_TYPE, NOT_SET);
+ }
+
+ final int resId = getPreferenceScreenResId();
+ if (resId <= 0) {
+ final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
+ getPrefContext());
+ setPreferenceScreen(preferenceScreen);
+ }
+
+ if (showGeneralCategory()) {
+ initGeneralCategory();
+ }
+
+ final List shortcutFeatureKeys = new ArrayList<>();
+ shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
+ shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
+ mSettingsContentObserver = new SettingsContentObserver(new Handler(), shortcutFeatureKeys) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateShortcutPreferenceData();
+ updateShortcutPreference();
+ }
+ };
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ mShortcutPreference = new ShortcutPreference(getPrefContext(), /* attrs= */ null);
+ mShortcutPreference.setPersistent(false);
+ mShortcutPreference.setKey(getShortcutPreferenceKey());
+ mShortcutPreference.setOnClickCallback(this);
+
+ final CharSequence title = getString(R.string.accessibility_shortcut_title, getLabelName());
+ mShortcutPreference.setTitle(title);
+ getPreferenceScreen().addPreference(mShortcutPreference);
+
+ mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
+ removeDialog(DialogEnums.EDIT_SHORTCUT);
+ mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
+ };
+
+ return super.onCreateView(inflater, container, savedInstanceState);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ final AccessibilityManager am = getPrefContext().getSystemService(
+ AccessibilityManager.class);
+ am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
+ mSettingsContentObserver.register(getContentResolver());
+ updateShortcutPreferenceData();
+ updateShortcutPreference();
+ }
+
+ @Override
+ public void onPause() {
+ final AccessibilityManager am = getPrefContext().getSystemService(
+ AccessibilityManager.class);
+ am.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
+ mSettingsContentObserver.unregister(getContentResolver());
+ super.onPause();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ final int value = getShortcutTypeCheckBoxValue();
+ if (value != NOT_SET) {
+ outState.putInt(KEY_SAVED_USER_SHORTCUT_TYPE, value);
+ }
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ public Dialog onCreateDialog(int dialogId) {
+ final Dialog dialog;
+ switch (dialogId) {
+ case DialogEnums.EDIT_SHORTCUT:
+ final CharSequence dialogTitle = getPrefContext().getString(
+ R.string.accessibility_shortcut_title, getLabelName());
+ final int dialogType = WizardManagerHelper.isAnySetupWizard(getIntent())
+ ? AccessibilityDialogUtils.DialogType.EDIT_SHORTCUT_GENERIC_SUW :
+ AccessibilityDialogUtils.DialogType.EDIT_SHORTCUT_GENERIC;
+ dialog = AccessibilityDialogUtils.showEditShortcutDialog(
+ getPrefContext(), dialogType, dialogTitle,
+ this::callOnAlertDialogCheckboxClicked);
+ setupEditShortcutDialog(dialog);
+ return dialog;
+ case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
+ dialog = AccessibilityGestureNavigationTutorial
+ .createAccessibilityTutorialDialog(getPrefContext(),
+ getUserShortcutTypes());
+ dialog.setCanceledOnTouchOutside(false);
+ return dialog;
+ default:
+ throw new IllegalArgumentException("Unsupported dialogId " + dialogId);
+ }
+ }
+
+ @Override
+ public int getDialogMetricsCategory(int dialogId) {
+ switch (dialogId) {
+ case DialogEnums.EDIT_SHORTCUT:
+ return SettingsEnums.DIALOG_ACCESSIBILITY_SERVICE_EDIT_SHORTCUT;
+ case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
+ return SettingsEnums.DIALOG_ACCESSIBILITY_TUTORIAL;
+ default:
+ return SettingsEnums.ACTION_UNKNOWN;
+ }
+ }
+
+ @Override
+ public void onSettingsClicked(ShortcutPreference preference) {
+ showDialog(DialogEnums.EDIT_SHORTCUT);
+ }
+
+ @Override
+ public void onToggleClicked(ShortcutPreference preference) {
+ if (getComponentName() == null) {
+ return;
+ }
+
+ final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(),
+ getComponentName().flattenToString(), AccessibilityUtil.UserShortcutType.SOFTWARE);
+ if (preference.isChecked()) {
+ AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), shortcutTypes,
+ getComponentName());
+ showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
+ } else {
+ AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), shortcutTypes,
+ getComponentName());
+ }
+ mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
+ }
+
+ /**
+ * Overrides to return specific shortcut preference key
+ *
+ * @return String The specific shortcut preference key
+ */
+ protected String getShortcutPreferenceKey() {
+ return KEY_SHORTCUT_PREFERENCE;
+ }
+
+ @VisibleForTesting
+ void setupEditShortcutDialog(Dialog dialog) {
+ final View dialogSoftwareView = dialog.findViewById(R.id.software_shortcut);
+ mSoftwareTypeCheckBox = dialogSoftwareView.findViewById(R.id.checkbox);
+ setDialogTextAreaClickListener(dialogSoftwareView, mSoftwareTypeCheckBox);
+
+ final View dialogHardwareView = dialog.findViewById(R.id.hardware_shortcut);
+ mHardwareTypeCheckBox = dialogHardwareView.findViewById(R.id.checkbox);
+ setDialogTextAreaClickListener(dialogHardwareView, mHardwareTypeCheckBox);
+
+ updateEditShortcutDialogCheckBox();
+ }
+
+ /**
+ * Returns accumulated {@link AccessibilityUtil.UserShortcutType} checkbox value or
+ * {@code NOT_SET} if checkboxes did not exist.
+ */
+ protected int getShortcutTypeCheckBoxValue() {
+ if (mSoftwareTypeCheckBox == null || mHardwareTypeCheckBox == null) {
+ return NOT_SET;
+ }
+
+ int value = AccessibilityUtil.UserShortcutType.EMPTY;
+ if (mSoftwareTypeCheckBox.isChecked()) {
+ value |= AccessibilityUtil.UserShortcutType.SOFTWARE;
+ }
+ if (mHardwareTypeCheckBox.isChecked()) {
+ value |= AccessibilityUtil.UserShortcutType.HARDWARE;
+ }
+ return value;
+ }
+
+ /**
+ * Returns the shortcut type list which has been checked by user.
+ */
+ protected int getUserShortcutTypes() {
+ return AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(),
+ getComponentName());
+ };
+
+ /**
+ * This method will be invoked when a button in the edit shortcut dialog is clicked.
+ *
+ * @param dialog The dialog that received the click
+ * @param which The button that was clicked
+ */
+ protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
+ if (getComponentName() == null) {
+ return;
+ }
+
+ final int value = getShortcutTypeCheckBoxValue();
+
+ saveNonEmptyUserShortcutType(value);
+ AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), value, getComponentName());
+ AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), ~value, getComponentName());
+ mShortcutPreference.setChecked(value != AccessibilityUtil.UserShortcutType.EMPTY);
+ mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
+ }
+
+ @VisibleForTesting
+ void initGeneralCategory() {
+ final PreferenceCategory generalCategory = new PreferenceCategory(getPrefContext());
+ generalCategory.setKey(KEY_GENERAL_CATEGORY);
+ generalCategory.setTitle(getGeneralCategoryDescription(null));
+
+ getPreferenceScreen().addPreference(generalCategory);
+ }
+
+ @VisibleForTesting
+ void saveNonEmptyUserShortcutType(int type) {
+ if (type == AccessibilityUtil.UserShortcutType.EMPTY) {
+ return;
+ }
+
+ final PreferredShortcut shortcut = new PreferredShortcut(
+ getComponentName().flattenToString(), type);
+ PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
+ }
+
+ /**
+ * Overrides to return customized description for general category above shortcut
+ *
+ * @return CharSequence The customized description for general category
+ */
+ protected CharSequence getGeneralCategoryDescription(@Nullable CharSequence title) {
+ if (title == null || title.toString().isEmpty()) {
+ // Return default 'Options' string for category
+ return getContext().getString(R.string.accessibility_screen_option);
+ }
+ return title;
+ }
+
+ /**
+ * Overrides to determinate if showing additional category description above shortcut
+ *
+ * @return boolean true to show category, false otherwise.
+ */
+ protected boolean showGeneralCategory() {
+ return false;
+ }
+
+ private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) {
+ final View dialogTextArea = dialogView.findViewById(R.id.container);
+ dialogTextArea.setOnClickListener(v -> checkBox.toggle());
+ }
+
+ protected CharSequence getShortcutTypeSummary(Context context) {
+ if (!mShortcutPreference.isSettingsEditable()) {
+ return context.getText(R.string.accessibility_shortcut_edit_dialog_title_hardware);
+ }
+
+ if (!mShortcutPreference.isChecked()) {
+ return context.getText(R.string.switch_off_text);
+ }
+
+ final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(context,
+ getComponentName().flattenToString(), AccessibilityUtil.UserShortcutType.SOFTWARE);
+
+ final List list = new ArrayList<>();
+ final CharSequence softwareTitle = context.getText(
+ R.string.accessibility_shortcut_edit_summary_software);
+
+ if (hasShortcutType(shortcutTypes, AccessibilityUtil.UserShortcutType.SOFTWARE)) {
+ list.add(softwareTitle);
+ }
+ if (hasShortcutType(shortcutTypes, AccessibilityUtil.UserShortcutType.HARDWARE)) {
+ final CharSequence hardwareTitle = context.getText(
+ R.string.accessibility_shortcut_hardware_keyword);
+ list.add(hardwareTitle);
+ }
+
+ // Show software shortcut if first time to use.
+ if (list.isEmpty()) {
+ list.add(softwareTitle);
+ }
+
+ return CaseMap.toTitle().wholeString().noLowercase().apply(Locale.getDefault(), /* iter= */
+ null, LocaleUtils.getConcatenatedString(list));
+ }
+
+ private void updateEditShortcutDialogCheckBox() {
+ // If it is during onConfigChanged process then restore the value, or get the saved value
+ // when shortcutPreference is checked.
+ int value = restoreOnConfigChangedValue();
+ if (value == NOT_SET) {
+ final int lastNonEmptyUserShortcutType = PreferredShortcuts.retrieveUserShortcutType(
+ getPrefContext(), getComponentName().flattenToString(),
+ AccessibilityUtil.UserShortcutType.SOFTWARE);
+ value = mShortcutPreference.isChecked() ? lastNonEmptyUserShortcutType
+ : AccessibilityUtil.UserShortcutType.EMPTY;
+ }
+
+ mSoftwareTypeCheckBox.setChecked(
+ hasShortcutType(value, AccessibilityUtil.UserShortcutType.SOFTWARE));
+ mHardwareTypeCheckBox.setChecked(
+ hasShortcutType(value, AccessibilityUtil.UserShortcutType.HARDWARE));
+ }
+
+ private int restoreOnConfigChangedValue() {
+ final int savedValue = mSavedCheckBoxValue;
+ mSavedCheckBoxValue = NOT_SET;
+ return savedValue;
+ }
+
+ private boolean hasShortcutType(int value, @AccessibilityUtil.UserShortcutType int type) {
+ return (value & type) == type;
+ }
+
+ protected void updateShortcutPreferenceData() {
+ if (getComponentName() == null) {
+ return;
+ }
+
+ final int shortcutTypes = AccessibilityUtil.getUserShortcutTypesFromSettings(
+ getPrefContext(), getComponentName());
+ if (shortcutTypes != AccessibilityUtil.UserShortcutType.EMPTY) {
+ final PreferredShortcut shortcut = new PreferredShortcut(
+ getComponentName().flattenToString(), shortcutTypes);
+ PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
+ }
+ }
+
+ protected void updateShortcutPreference() {
+ if (getComponentName() == null) {
+ return;
+ }
+
+ final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(),
+ getComponentName().flattenToString(), AccessibilityUtil.UserShortcutType.SOFTWARE);
+ mShortcutPreference.setChecked(
+ AccessibilityUtil.hasValuesInSettings(getPrefContext(), shortcutTypes,
+ getComponentName()));
+ mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
+ }
+}
diff --git a/src/com/android/settings/accessibility/AccessibilityUtil.java b/src/com/android/settings/accessibility/AccessibilityUtil.java
index eb202b5b044258628a7d4f3f73538dcebbfb11a2..5c316a40acddbbf946f67048eddb6b8d73ea4693 100644
--- a/src/com/android/settings/accessibility/AccessibilityUtil.java
+++ b/src/com/android/settings/accessibility/AccessibilityUtil.java
@@ -16,6 +16,7 @@
package com.android.settings.accessibility;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -143,6 +144,13 @@ final class AccessibilityUtil {
== NAV_BAR_MODE_GESTURAL;
}
+ /** Determines if a accessibility floating menu is being used. */
+ public static boolean isFloatingMenuEnabled(Context context) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, /* def= */ -1)
+ == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+ }
+
/** Determines if a touch explore is being used. */
public static boolean isTouchExploreEnabled(Context context) {
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
@@ -381,4 +389,13 @@ final class AccessibilityUtil {
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenHeightDp,
resources.getDisplayMetrics()));
}
+
+ /**
+ * Indicates if the accessibility service belongs to a system App.
+ * @param info AccessibilityServiceInfo
+ * @return {@code true} if the App is a system App.
+ */
+ public static boolean isSystemApp(@NonNull AccessibilityServiceInfo info) {
+ return info.getResolveInfo().serviceInfo.applicationInfo.isSystemApp();
+ }
}
diff --git a/src/com/android/settings/accessibility/AnimatedImagePreference.java b/src/com/android/settings/accessibility/AnimatedImagePreference.java
deleted file mode 100644
index 2ca13f33fe2eadb7e21f6dde6f4df848004e2c66..0000000000000000000000000000000000000000
--- a/src/com/android/settings/accessibility/AnimatedImagePreference.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.accessibility;
-
-import android.content.Context;
-import android.graphics.drawable.AnimatedImageDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.widget.ImageView;
-
-import androidx.preference.Preference;
-import androidx.preference.PreferenceViewHolder;
-
-import com.android.settings.R;
-
-/**
- * A custom {@link ImageView} preference for showing animated or static image, such as animated webp and static png.
- */
-public class AnimatedImagePreference extends Preference {
-
- private Uri mImageUri;
- private int mMaxHeight = -1;
-
- AnimatedImagePreference(Context context) {
- super(context);
- setLayoutResource(R.layout.preference_animated_image);
- }
-
- @Override
- public void onBindViewHolder(PreferenceViewHolder holder) {
- super.onBindViewHolder(holder);
-
- final ImageView imageView = holder.itemView.findViewById(R.id.animated_img);
- if (imageView == null) {
- return;
- }
-
- if (mImageUri != null) {
- imageView.setImageURI(mImageUri);
-
- final Drawable drawable = imageView.getDrawable();
- if (drawable instanceof AnimatedImageDrawable) {
- ((AnimatedImageDrawable) drawable).start();
- }
- }
-
- if (mMaxHeight > -1) {
- imageView.setMaxHeight(mMaxHeight);
- }
- }
-
- /**
- * Sets image uri to display image in {@link ImageView}
- *
- * @param imageUri the Uri of an image
- */
- public void setImageUri(Uri imageUri) {
- if (imageUri != null && !imageUri.equals(mImageUri)) {
- mImageUri = imageUri;
- notifyChanged();
- }
- }
-
- /**
- * Sets the maximum height of the view.
- *
- * @param maxHeight the maximum height of ImageView in terms of pixels.
- */
- public void setMaxHeight(int maxHeight) {
- if (maxHeight != mMaxHeight) {
- mMaxHeight = maxHeight;
- notifyChanged();
- }
- }
-}
diff --git a/src/com/android/settings/accessibility/AudioAdjustmentFragment.java b/src/com/android/settings/accessibility/AudioAdjustmentFragment.java
new file mode 100644
index 0000000000000000000000000000000000000000..c80216b18c82a292277aac208d5635e7250b6eaf
--- /dev/null
+++ b/src/com/android/settings/accessibility/AudioAdjustmentFragment.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.app.settings.SettingsEnums;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+/** Accessibility settings for audio adjustment. */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class AudioAdjustmentFragment extends DashboardFragment {
+
+ private static final String TAG = "AudioAdjustmentFragment";
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.ACCESSIBILITY_AUDIO_ADJUSTMENT;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.accessibility_audio_adjustment;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.accessibility_audio_adjustment);
+
+}
diff --git a/src/com/android/settings/accessibility/BalanceSeekBar.java b/src/com/android/settings/accessibility/BalanceSeekBar.java
index ecc226bd67d0ee027e43a0eb7ead09295d7d4960..8a88d6c8376fa0e7a8e251c2c3085be5a1b9ea3f 100644
--- a/src/com/android/settings/accessibility/BalanceSeekBar.java
+++ b/src/com/android/settings/accessibility/BalanceSeekBar.java
@@ -16,6 +16,7 @@
package com.android.settings.accessibility;
+import static android.view.HapticFeedbackConstants.CLOCK_TICK;
import static com.android.settings.Utils.isNightMode;
import android.content.Context;
@@ -44,6 +45,7 @@ public class BalanceSeekBar extends SeekBar {
private final Context mContext;
private final Object mListenerLock = new Object();
private OnSeekBarChangeListener mOnSeekBarChangeListener;
+ private int mLastProgress = -1;
private final OnSeekBarChangeListener mProxySeekBarListener = new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
@@ -73,6 +75,12 @@ public class BalanceSeekBar extends SeekBar {
progress = mCenter;
seekBar.setProgress(progress); // direct update (fromUser becomes false)
}
+ if (progress != mLastProgress) {
+ if (progress == mCenter || progress == getMin() || progress == getMax()) {
+ seekBar.performHapticFeedback(CLOCK_TICK);
+ }
+ mLastProgress = progress;
+ }
final float balance = (progress - mCenter) * 0.01f;
Settings.System.putFloatForUser(mContext.getContentResolver(),
Settings.System.MASTER_BALANCE, balance, UserHandle.USER_CURRENT);
@@ -115,6 +123,7 @@ public class BalanceSeekBar extends SeekBar {
res.getDimensionPixelSize(R.dimen.balance_seekbar_center_marker_width),
res.getDimensionPixelSize(R.dimen.balance_seekbar_center_marker_height));
mCenterMarkerPaint = new Paint();
+ // TODO use a more suitable colour?
mCenterMarkerPaint.setColor(isNightMode(context) ? Color.WHITE : Color.BLACK);
mCenterMarkerPaint.setStyle(Paint.Style.FILL);
// Remove the progress colour
@@ -151,10 +160,5 @@ public class BalanceSeekBar extends SeekBar {
canvas.restore();
super.onDraw(canvas);
}
-
- @VisibleForTesting
- OnSeekBarChangeListener getProxySeekBarListener() {
- return mProxySeekBarListener;
- }
}
diff --git a/src/com/android/settings/accessibility/CaptionAppearanceFragment.java b/src/com/android/settings/accessibility/CaptionAppearanceFragment.java
index d780ac5d87fd7ddb838e4ac199b85246917e960b..7eceb7d5b28cd8ae2a6ecfcac9fdf8e0622c1bd7 100644
--- a/src/com/android/settings/accessibility/CaptionAppearanceFragment.java
+++ b/src/com/android/settings/accessibility/CaptionAppearanceFragment.java
@@ -33,8 +33,8 @@ import androidx.preference.PreferenceCategory;
import com.android.internal.widget.SubtitleView;
import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.accessibility.ListDialogPreference.OnValueChangedListener;
+import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.accessibility.AccessibilityUtils;
import com.android.settingslib.search.SearchIndexable;
@@ -46,8 +46,10 @@ import java.util.Locale;
/** Settings fragment containing font style of captioning properties. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
-public class CaptionAppearanceFragment extends SettingsPreferenceFragment
+public class CaptionAppearanceFragment extends DashboardFragment
implements OnPreferenceChangeListener, OnValueChangedListener {
+
+ private static final String TAG = "CaptionAppearanceFragment";
private static final String PREF_CAPTION_PREVIEW = "caption_preview";
private static final String PREF_BACKGROUND_COLOR = "captioning_background_color";
private static final String PREF_BACKGROUND_OPACITY = "captioning_background_opacity";
@@ -107,12 +109,11 @@ public class CaptionAppearanceFragment extends SettingsPreferenceFragment
}
@Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ super.onCreatePreferences(savedInstanceState, rootKey);
mCaptioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
- addPreferencesFromResource(R.xml.captioning_appearance);
initializeAllPreferences();
updateAllPreferences();
refreshShowingCustom();
@@ -120,6 +121,16 @@ public class CaptionAppearanceFragment extends SettingsPreferenceFragment
refreshPreviewText();
}
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.captioning_appearance;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
private void refreshPreviewText() {
final Context context = getActivity();
if (context == null) {
diff --git a/src/com/android/settings/accessibility/CaptionFooterPreferenceController.java b/src/com/android/settings/accessibility/CaptionFooterPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..4e50b899d61e5bb7aaf24715148d709486638a2a
--- /dev/null
+++ b/src/com/android/settings/accessibility/CaptionFooterPreferenceController.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+
+import com.android.settings.R;
+
+/**
+ * Preference controller for caption footer.
+ */
+public class CaptionFooterPreferenceController extends AccessibilityFooterPreferenceController {
+
+ public CaptionFooterPreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ protected String getLabelName() {
+ return mContext.getString(R.string.accessibility_captioning_title);
+ }
+
+ @Override
+ protected int getHelpResource() {
+ return R.string.help_url_caption;
+ }
+}
diff --git a/src/com/android/settings/accessibility/CaptionMoreOptionsFragment.java b/src/com/android/settings/accessibility/CaptionMoreOptionsFragment.java
index 8ac82e5f2062f59b5887733d28ad016a1238cee6..6aa69fa4075f35564df086c39fb4bd71bbc79fd1 100644
--- a/src/com/android/settings/accessibility/CaptionMoreOptionsFragment.java
+++ b/src/com/android/settings/accessibility/CaptionMoreOptionsFragment.java
@@ -26,18 +26,19 @@ import android.view.accessibility.CaptioningManager;
import androidx.preference.Preference;
import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
/** Settings fragment containing more options of captioning properties. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
-public class CaptionMoreOptionsFragment extends SettingsPreferenceFragment
+public class CaptionMoreOptionsFragment extends DashboardFragment
implements Preference.OnPreferenceChangeListener {
+
+ private static final String TAG = "CaptionMoreOptionsFragment";
private static final String PREF_LOCALE = "captioning_locale";
private CaptioningManager mCaptioningManager;
-
private LocalePreference mLocale;
@Override
@@ -46,17 +47,26 @@ public class CaptionMoreOptionsFragment extends SettingsPreferenceFragment
}
@Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ super.onCreatePreferences(savedInstanceState, rootKey);
mCaptioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
- addPreferencesFromResource(R.xml.captioning_more_options);
initializeAllPreferences();
updateAllPreferences();
installUpdateListeners();
}
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.captioning_more_options;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
private void initializeAllPreferences() {
mLocale = (LocalePreference) findPreference(PREF_LOCALE);
}
diff --git a/src/com/android/settings/accessibility/CaptionPropertiesFragment.java b/src/com/android/settings/accessibility/CaptionPropertiesFragment.java
index 786585ad72e977959666488c73d35d19935ab57a..bcdba11e7d39ebde1d197e29f023733bd05b88ff 100644
--- a/src/com/android/settings/accessibility/CaptionPropertiesFragment.java
+++ b/src/com/android/settings/accessibility/CaptionPropertiesFragment.java
@@ -22,15 +22,17 @@ import android.content.Context;
import android.os.Bundle;
import android.provider.Settings;
import android.view.accessibility.CaptioningManager;
+import android.widget.Switch;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
-import androidx.preference.SwitchPreference;
import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.widget.SettingsMainSwitchPreference;
import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.widget.OnMainSwitchChangeListener;
import com.google.common.primitives.Floats;
@@ -39,15 +41,17 @@ import java.util.List;
/** Settings fragment containing captioning properties. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
-public class CaptionPropertiesFragment extends SettingsPreferenceFragment
- implements OnPreferenceChangeListener {
+public class CaptionPropertiesFragment extends DashboardFragment
+ implements OnPreferenceChangeListener, OnMainSwitchChangeListener {
+
+ private static final String TAG = "CaptionPropertiesFragment";
private static final String PREF_SWITCH = "captioning_preference_switch";
private static final String PREF_TEXT = "captioning_caption_appearance";
private static final String PREF_MORE = "captioning_more_options";
private CaptioningManager mCaptioningManager;
- private SwitchPreference mSwitch;
+ private SettingsMainSwitchPreference mSwitch;
private Preference mTextAppearance;
private Preference mMoreOptions;
@@ -60,12 +64,11 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
}
@Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ super.onCreatePreferences(savedInstanceState, rootKey);
mCaptioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
- addPreferencesFromResource(R.xml.captioning_settings);
initializeAllPreferences();
installUpdateListeners();
initFontSizeValuesArray();
@@ -77,8 +80,18 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
updateAllPreferences();
}
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.captioning_settings;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
private void initializeAllPreferences() {
- mSwitch = (SwitchPreference) findPreference(PREF_SWITCH);
+ mSwitch = (SettingsMainSwitchPreference) findPreference(PREF_SWITCH);
mTextAppearance = (Preference) findPreference(PREF_TEXT);
mMoreOptions = (Preference) findPreference(PREF_MORE);
@@ -88,6 +101,8 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
private void installUpdateListeners() {
mSwitch.setOnPreferenceChangeListener(this);
+ mSwitch.addOnSwitchChangeListener(this);
+
}
private void initFontSizeValuesArray() {
@@ -133,4 +148,11 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.captioning_settings);
+
+ @Override
+ public void onSwitchChanged(Switch switchView, boolean isChecked) {
+ final ContentResolver cr = getActivity().getContentResolver();
+ Settings.Secure.putInt(
+ cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, isChecked ? 1 : 0);
+ }
}
diff --git a/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java b/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd419d0a6e51daf69d0a60d3d65de89ce2705902
--- /dev/null
+++ b/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+
+/** Preference controller that controls the fade switch button in accessibility button page. */
+public class FloatingMenuFadePreferenceController extends BasePreferenceController implements
+ Preference.OnPreferenceChangeListener, LifecycleObserver, OnResume, OnPause {
+
+ private static final int OFF = 0;
+ private static final int ON = 1;
+
+ private final ContentResolver mContentResolver;
+ @VisibleForTesting
+ final ContentObserver mContentObserver;
+
+ @VisibleForTesting
+ SwitchPreference mPreference;
+
+ public FloatingMenuFadePreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mContentResolver = context.getContentResolver();
+ mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateAvailabilityStatus();
+ }
+ };
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AccessibilityUtil.isFloatingMenuEnabled(mContext)
+ ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ mPreference = screen.findPreference(getPreferenceKey());
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean isEnabled = (boolean) newValue;
+ putFloatingMenuFadeValue(isEnabled);
+ return true;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ final SwitchPreference switchPreference = (SwitchPreference) preference;
+
+ switchPreference.setChecked(getFloatingMenuFadeValue() == ON);
+ }
+
+ @Override
+ public void onResume() {
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE),
+ /* notifyForDescendants= */ false, mContentObserver);
+ }
+
+ @Override
+ public void onPause() {
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ }
+
+ private void updateAvailabilityStatus() {
+ mPreference.setEnabled(AccessibilityUtil.isFloatingMenuEnabled(mContext));
+ }
+
+ private int getFloatingMenuFadeValue() {
+ return Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, ON);
+ }
+
+ private void putFloatingMenuFadeValue(boolean isEnabled) {
+ Settings.Secure.putInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
+ isEnabled ? ON : OFF);
+ }
+}
diff --git a/src/com/android/settings/accessibility/FloatingMenuLayerDrawable.java b/src/com/android/settings/accessibility/FloatingMenuLayerDrawable.java
new file mode 100644
index 0000000000000000000000000000000000000000..bfce114ae139e0cd60d7a33bdb5e9b601f2b9632
--- /dev/null
+++ b/src/com/android/settings/accessibility/FloatingMenuLayerDrawable.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.R;
+
+import java.util.Objects;
+
+/** LayerDrawable that contains device icon as background and floating menu icon as foreground. */
+public class FloatingMenuLayerDrawable extends LayerDrawable {
+
+ private FloatingMenuLayerDrawableState mState;
+
+ /**
+ * Creates a new layer drawable with the list of specified layers.
+ *
+ * @param layers a list of drawables to use as layers in this new drawable,
+ * must be non-null
+ */
+ private FloatingMenuLayerDrawable(@NonNull Drawable[] layers) {
+ super(layers);
+ }
+
+ /**
+ * Create the {@link LayerDrawable} that contains device icon as background and floating menu
+ * icon with given {@code opacity} value as foreground.
+ *
+ * @param context the valid context used to get the icon
+ * @param resId the resource ID of the floating menu icon
+ * @param opacity the opacity to apply to the given icon
+ * @return the drawable that combines the device icon and the floating menu icon
+ */
+ public static FloatingMenuLayerDrawable createLayerDrawable(Context context, int resId,
+ int opacity) {
+ final Drawable bg = context.getDrawable(R.drawable.accessibility_button_preview_base);
+ final FloatingMenuLayerDrawable basicDrawable = new FloatingMenuLayerDrawable(
+ new Drawable[]{bg, null});
+
+ basicDrawable.updateLayerDrawable(context, resId, opacity);
+ return basicDrawable;
+ }
+
+ /**
+ * Update the drawable with given {@code resId} drawable and {@code opacity}(alpha)
+ * value at index 1 layer.
+ *
+ * @param context the valid context used to get the icon
+ * @param resId the resource ID of the floating menu icon
+ * @param opacity the opacity to apply to the given icon
+ */
+ public void updateLayerDrawable(Context context, int resId, int opacity) {
+ final Drawable icon = context.getDrawable(resId);
+ icon.setAlpha(opacity);
+ this.setDrawable(/* index= */ 1, icon);
+ this.setConstantState(context, resId, opacity);
+ }
+
+ @Override
+ public ConstantState getConstantState() {
+ return mState;
+ }
+
+ /** Stores the constant state and data to the given drawable. */
+ private void setConstantState(Context context, int resId, int opacity) {
+ mState = new FloatingMenuLayerDrawableState(context, resId, opacity);
+ }
+
+ /** {@link ConstantState} to store the data of {@link FloatingMenuLayerDrawable}. */
+ @VisibleForTesting
+ static class FloatingMenuLayerDrawableState extends ConstantState {
+
+ private final Context mContext;
+ private final int mResId;
+ private final int mOpacity;
+
+ FloatingMenuLayerDrawableState(Context context, int resId, int opacity) {
+ mContext = context;
+ mResId = resId;
+ mOpacity = opacity;
+ }
+
+ @NonNull
+ @Override
+ public Drawable newDrawable() {
+ return createLayerDrawable(mContext, mResId, mOpacity);
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final FloatingMenuLayerDrawableState that = (FloatingMenuLayerDrawableState) o;
+ return mResId == that.mResId
+ && mOpacity == that.mOpacity
+ && Objects.equals(mContext, that.mContext);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mContext, mResId, mOpacity);
+ }
+ }
+}
diff --git a/src/com/android/settings/accessibility/FloatingMenuSizePreferenceController.java b/src/com/android/settings/accessibility/FloatingMenuSizePreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f0f833772e5e0816ec42d7e546e46b09c0b68a7
--- /dev/null
+++ b/src/com/android/settings/accessibility/FloatingMenuSizePreferenceController.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.ArrayMap;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+
+import com.google.common.primitives.Ints;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Preference controller that controls the preferred size in accessibility button page. */
+public class FloatingMenuSizePreferenceController extends BasePreferenceController
+ implements Preference.OnPreferenceChangeListener, LifecycleObserver, OnResume, OnPause {
+
+ private final ContentResolver mContentResolver;
+ @VisibleForTesting
+ final ContentObserver mContentObserver;
+
+ @VisibleForTesting
+ ListPreference mPreference;
+
+ private final ArrayMap mValueTitleMap = new ArrayMap<>();
+ private int mDefaultSize;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ Size.SMALL,
+ Size.LARGE,
+ })
+ @VisibleForTesting
+ @interface Size {
+ int SMALL = 0;
+ int LARGE = 1;
+ }
+
+ public FloatingMenuSizePreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mContentResolver = context.getContentResolver();
+ mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateAvailabilityStatus();
+ }
+ };
+
+ initValueTitleMap();
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AccessibilityUtil.isFloatingMenuEnabled(mContext)
+ ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ mPreference = screen.findPreference(getPreferenceKey());
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final ListPreference listPreference = (ListPreference) preference;
+ final Integer value = Ints.tryParse((String) newValue);
+ if (value != null) {
+ putAccessibilityFloatingMenuSize(value);
+ updateState(listPreference);
+ }
+ return true;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ final ListPreference listPreference = (ListPreference) preference;
+
+ listPreference.setValue(String.valueOf(getAccessibilityFloatingMenuSize(mDefaultSize)));
+ }
+
+ @Override
+ public void onResume() {
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE), /* notifyForDescendants= */
+ false, mContentObserver);
+
+ }
+
+ @Override
+ public void onPause() {
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ }
+
+ private void updateAvailabilityStatus() {
+ mPreference.setEnabled(AccessibilityUtil.isFloatingMenuEnabled(mContext));
+ }
+
+ private void initValueTitleMap() {
+ if (mValueTitleMap.size() == 0) {
+ final String[] values = mContext.getResources().getStringArray(
+ R.array.accessibility_button_size_selector_values);
+ final String[] titles = mContext.getResources().getStringArray(
+ R.array.accessibility_button_size_selector_titles);
+ final int mapSize = values.length;
+
+ mDefaultSize = Integer.parseInt(values[0]);
+ for (int i = 0; i < mapSize; i++) {
+ mValueTitleMap.put(values[i], titles[i]);
+ }
+ }
+ }
+
+ @Size
+ private int getAccessibilityFloatingMenuSize(@Size int defaultValue) {
+ return Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, defaultValue);
+ }
+
+ private void putAccessibilityFloatingMenuSize(@Size int value) {
+ Settings.Secure.putInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, value);
+ }
+}
diff --git a/src/com/android/settings/accessibility/FloatingMenuTransparencyPreferenceController.java b/src/com/android/settings/accessibility/FloatingMenuTransparencyPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..894d3aea2d4e89ae8f0cf4b47e9b06378181f56f
--- /dev/null
+++ b/src/com/android/settings/accessibility/FloatingMenuTransparencyPreferenceController.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import androidx.annotation.FloatRange;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.SliderPreferenceController;
+import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+
+/** Preference controller that controls the transparency seekbar in accessibility button page. */
+public class FloatingMenuTransparencyPreferenceController extends SliderPreferenceController
+ implements LifecycleObserver, OnResume, OnPause {
+
+ @VisibleForTesting
+ @FloatRange(from = 0.0, to = 1.0)
+ static final float DEFAULT_TRANSPARENCY = 0.45f;
+ @VisibleForTesting
+ static final float MAXIMUM_TRANSPARENCY = 1.0f;
+ private static final int FADE_ENABLED = 1;
+ private static final float MIN_PROGRESS = 0f;
+ private static final float MAX_PROGRESS = 90f;
+ @VisibleForTesting
+ static final float PRECISION = 100f;
+
+ private final ContentResolver mContentResolver;
+ @VisibleForTesting
+ final ContentObserver mContentObserver;
+
+ @VisibleForTesting
+ SeekBarPreference mPreference;
+
+ public FloatingMenuTransparencyPreferenceController(Context context,
+ String preferenceKey) {
+ super(context, preferenceKey);
+ mContentResolver = context.getContentResolver();
+ mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateAvailabilityStatus();
+ }
+ };
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AccessibilityUtil.isFloatingMenuEnabled(mContext)
+ ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ mPreference = screen.findPreference(getPreferenceKey());
+ mPreference.setContinuousUpdates(true);
+ mPreference.setMax(getMax());
+ mPreference.setMin(getMin());
+ mPreference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS);
+
+ updateState(mPreference);
+ }
+
+ @Override
+ public void onResume() {
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE), /* notifyForDescendants= */
+ false, mContentObserver);
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED),
+ /* notifyForDescendants= */ false, mContentObserver);
+ }
+
+ @Override
+ public void onPause() {
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ }
+
+ @Override
+ public int getSliderPosition() {
+ return convertTransparencyFloatToInt(getTransparency());
+ }
+
+ @Override
+ public boolean setSliderPosition(int position) {
+ final float opacityValue = MAXIMUM_TRANSPARENCY - convertTransparencyIntToFloat(position);
+ return Settings.Secure.putFloat(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, opacityValue);
+ }
+
+ @Override
+ public int getMax() {
+ return (int) MAX_PROGRESS;
+ }
+
+ @Override
+ public int getMin() {
+ return (int) MIN_PROGRESS;
+ }
+
+ private void updateAvailabilityStatus() {
+ final boolean fadeEnabled = Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, FADE_ENABLED)
+ == FADE_ENABLED;
+
+ mPreference.setEnabled(AccessibilityUtil.isFloatingMenuEnabled(mContext) && fadeEnabled);
+ }
+
+ private int convertTransparencyFloatToInt(float value) {
+ return Math.round(value * PRECISION);
+ }
+
+ private float convertTransparencyIntToFloat(int value) {
+ return (float) value / PRECISION;
+ }
+
+ private float getTransparency() {
+ float transparencyValue = MAXIMUM_TRANSPARENCY - (Settings.Secure.getFloat(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, DEFAULT_TRANSPARENCY));
+ final float minValue = MIN_PROGRESS / PRECISION;
+ final float maxValue = MAX_PROGRESS / PRECISION;
+
+ return (transparencyValue < minValue || transparencyValue > maxValue)
+ ? DEFAULT_TRANSPARENCY : transparencyValue;
+ }
+}
diff --git a/src/com/android/settings/security/LockdownButtonPreferenceController.java b/src/com/android/settings/accessibility/FontWeightAdjustmentPreferenceController.java
similarity index 52%
rename from src/com/android/settings/security/LockdownButtonPreferenceController.java
rename to src/com/android/settings/accessibility/FontWeightAdjustmentPreferenceController.java
index 5b29868f06d61536c9f5084e7b277b73e2d94dff..97f96a477f6246bdfacb688f653c134180b4a443 100644
--- a/src/com/android/settings/security/LockdownButtonPreferenceController.java
+++ b/src/com/android/settings/accessibility/FontWeightAdjustmentPreferenceController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,43 +14,37 @@
* limitations under the License.
*/
-package com.android.settings.security;
+package com.android.settings.accessibility;
import android.content.Context;
-import android.os.UserHandle;
+import android.graphics.fonts.FontStyle;
import android.provider.Settings;
-import com.android.internal.widget.LockPatternUtils;
import com.android.settings.core.TogglePreferenceController;
-public class LockdownButtonPreferenceController extends TogglePreferenceController {
+/** PreferenceController for displaying all text in bold. */
+public class FontWeightAdjustmentPreferenceController extends TogglePreferenceController {
+ static final int BOLD_TEXT_ADJUSTMENT =
+ FontStyle.FONT_WEIGHT_BOLD - FontStyle.FONT_WEIGHT_NORMAL;
- private final LockPatternUtils mLockPatternUtils;
-
- public LockdownButtonPreferenceController(Context context, String key) {
- super(context, key);
- mLockPatternUtils = new LockPatternUtils(context);
+ public FontWeightAdjustmentPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
- if (mLockPatternUtils.isSecure(UserHandle.myUserId())) {
- return AVAILABLE;
- } else {
- return DISABLED_FOR_USER;
- }
+ return AVAILABLE;
}
@Override
public boolean isChecked() {
return Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0) != 0;
+ Settings.Secure.FONT_WEIGHT_ADJUSTMENT, 0) == BOLD_TEXT_ADJUSTMENT;
}
@Override
public boolean setChecked(boolean isChecked) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.LOCKDOWN_IN_POWER_MENU, isChecked ? 1 : 0);
- return true;
+ return Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.FONT_WEIGHT_ADJUSTMENT, (isChecked ? BOLD_TEXT_ADJUSTMENT : 0));
}
}
diff --git a/src/com/android/settings/accessibility/InvisibleToggleAccessibilityServicePreferenceFragment.java b/src/com/android/settings/accessibility/InvisibleToggleAccessibilityServicePreferenceFragment.java
index 6b44a0a0f8ecd9e4300b8463a02dc76481d87663..0c1876f95d02be467faaeda13dfaabda52d9ca29 100644
--- a/src/com/android/settings/accessibility/InvisibleToggleAccessibilityServicePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/InvisibleToggleAccessibilityServicePreferenceFragment.java
@@ -38,7 +38,7 @@ public class InvisibleToggleAccessibilityServicePreferenceFragment extends
@Override
protected void onInstallSwitchPreferenceToggleSwitch() {
super.onInstallSwitchPreferenceToggleSwitch();
- mToggleServiceDividerSwitchPreference.setVisible(false);
+ mToggleServiceSwitchPreference.setVisible(false);
}
/**
diff --git a/src/com/android/settings/accessibility/ItemInfoArrayAdapter.java b/src/com/android/settings/accessibility/ItemInfoArrayAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7c41771ceab11d51fed277b067e9ec41dfda611
--- /dev/null
+++ b/src/com/android/settings/accessibility/ItemInfoArrayAdapter.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settings.R;
+
+import java.util.List;
+
+/**
+ * An {@link ArrayAdapter} to fill the information of {@link ItemInfo} in the item view. The item
+ * view must have textview to set the title.
+ *
+ * @param the type of elements in the array, inherited from {@link ItemInfo}.
+ */
+public class ItemInfoArrayAdapter extends ArrayAdapter {
+
+ public ItemInfoArrayAdapter(@NonNull Context context, @NonNull List items) {
+ super(context, R.layout.dialog_single_radio_choice_list_item, R.id.title, items);
+ }
+
+ @NonNull
+ @Override
+ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ final View root = super.getView(position, convertView, parent);
+
+ final ItemInfo item = getItem(position);
+ final TextView title = root.findViewById(R.id.title);
+ title.setText(item.mTitle);
+ final TextView summary = root.findViewById(R.id.summary);
+ if (!TextUtils.isEmpty(item.mSummary)) {
+ summary.setVisibility(View.VISIBLE);
+ summary.setText(item.mSummary);
+ } else {
+ summary.setVisibility(View.GONE);
+ }
+ final ImageView image = root.findViewById(R.id.image);
+ image.setImageResource(item.mDrawableId);
+ if (getContext().getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_LTR) {
+ image.setScaleType(ImageView.ScaleType.FIT_START);
+ } else {
+ image.setScaleType(ImageView.ScaleType.FIT_END);
+ }
+ return root;
+ }
+
+ /**
+ * Presents a data structure shown in the item view.
+ */
+ public static class ItemInfo {
+ @NonNull
+ public final CharSequence mTitle;
+ @Nullable
+ public final CharSequence mSummary;
+ @DrawableRes
+ public final int mDrawableId;
+
+ public ItemInfo(@NonNull CharSequence title, @Nullable CharSequence summary,
+ @DrawableRes int drawableId) {
+ mTitle = title;
+ mSummary = summary;
+ mDrawableId = drawableId;
+ }
+ }
+}
diff --git a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java
index 23c0c57bbf1e476ba9486ae75e022be03661e0ab..98090ac1dba77e49ae92255a2852f04143feb5e0 100644
--- a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java
@@ -30,45 +30,41 @@ import android.os.Bundle;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.Nullable;
-import androidx.preference.SwitchPreference;
+import androidx.preference.Preference;
import com.android.settings.R;
+import java.util.ArrayList;
import java.util.List;
/** Fragment for providing open activity button. */
-public class LaunchAccessibilityActivityPreferenceFragment extends
- ToggleFeaturePreferenceFragment {
+public class LaunchAccessibilityActivityPreferenceFragment extends ToggleFeaturePreferenceFragment {
private static final String TAG = "LaunchA11yActivity";
private static final String EMPTY_STRING = "";
+ protected static final String KEY_LAUNCH_PREFERENCE = "launch_preference";
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ final View view = super.onCreateView(inflater, container, savedInstanceState);
- mToggleServiceDividerSwitchPreference.setSwitchVisibility(View.GONE);
- }
+ // Init new preference to replace the switch preference instead.
+ initLaunchPreference();
+ removePreference(KEY_USE_SERVICE_PREFERENCE);
+ return view;
+ };
@Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
- logAccessibilityServiceEnabled(mComponentName, enabled);
- launchShortcutTargetActivity(getPrefContext().getDisplayId(), mComponentName);
- }
-
- @Override
- protected void onInstallSwitchPreferenceToggleSwitch() {
- super.onInstallSwitchPreferenceToggleSwitch();
- mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener((preference) -> {
- final boolean checked = ((DividerSwitchPreference) preference).isChecked();
- onPreferenceToggled(mPreferenceKey, checked);
- return false;
- });
+ // Do nothing.
}
@Override
@@ -82,10 +78,12 @@ public class LaunchAccessibilityActivityPreferenceFragment extends
// Settings animated image.
final int animatedImageRes = arguments.getInt(
AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES);
- mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
- .authority(mComponentName.getPackageName())
- .appendPath(String.valueOf(animatedImageRes))
- .build();
+ if (animatedImageRes > 0) {
+ mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority(mComponentName.getPackageName())
+ .appendPath(String.valueOf(animatedImageRes))
+ .build();
+ }
// Settings html description.
mHtmlDescription = arguments.getCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION);
@@ -97,12 +95,6 @@ public class LaunchAccessibilityActivityPreferenceFragment extends
mSettingsTitle = (mSettingsIntent == null) ? null : settingsTitle;
}
- @Override
- public void onSettingsClicked(ShortcutPreference preference) {
- super.onSettingsClicked(preference);
- showDialog(DialogEnums.EDIT_SHORTCUT);
- }
-
@Override
int getUserShortcutTypes() {
return AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(),
@@ -116,16 +108,6 @@ public class LaunchAccessibilityActivityPreferenceFragment extends
// accessibility service from this page.
}
- @Override
- protected void updateToggleServiceTitle(SwitchPreference switchPreference) {
- final AccessibilityShortcutInfo info = getAccessibilityShortcutInfo();
- final String switchBarText = (info == null) ? EMPTY_STRING : getString(
- R.string.accessibility_service_master_open_title,
- info.getActivityInfo().loadLabel(getPackageManager()));
-
- switchPreference.setTitle(switchBarText);
- }
-
// IMPORTANT: Refresh the info since there are dynamically changing capabilities.
private AccessibilityShortcutInfo getAccessibilityShortcutInfo() {
final List infos = AccessibilityManager.getInstance(
@@ -143,6 +125,34 @@ public class LaunchAccessibilityActivityPreferenceFragment extends
return null;
}
+ /** Customizes the order by preference key. */
+ protected List getPreferenceOrderList() {
+ final List lists = new ArrayList<>();
+ lists.add(KEY_ANIMATED_IMAGE);
+ lists.add(KEY_LAUNCH_PREFERENCE);
+ lists.add(KEY_GENERAL_CATEGORY);
+ lists.add(KEY_HTML_DESCRIPTION_PREFERENCE);
+ return lists;
+ }
+
+ private void initLaunchPreference() {
+ final Preference launchPreference = new Preference(getPrefContext());
+ launchPreference.setKey(KEY_LAUNCH_PREFERENCE);
+
+ final AccessibilityShortcutInfo info = getAccessibilityShortcutInfo();
+ final String switchBarText = (info == null) ? EMPTY_STRING : getString(
+ R.string.accessibility_service_primary_open_title,
+ info.getActivityInfo().loadLabel(getPackageManager()));
+ launchPreference.setTitle(switchBarText);
+
+ launchPreference.setOnPreferenceClickListener(preference -> {
+ logAccessibilityServiceEnabled(mComponentName, /* enabled= */ true);
+ launchShortcutTargetActivity(getPrefContext().getDisplayId(), mComponentName);
+ return true;
+ });
+ getPreferenceScreen().addPreference(launchPreference);
+ }
+
private void launchShortcutTargetActivity(int displayId, ComponentName name) {
final Intent intent = new Intent();
final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
diff --git a/src/com/android/settings/accessibility/MagnificationCapabilities.java b/src/com/android/settings/accessibility/MagnificationCapabilities.java
new file mode 100644
index 0000000000000000000000000000000000000000..04a2992f011885608b67e28fc888b4828ad13636
--- /dev/null
+++ b/src/com/android/settings/accessibility/MagnificationCapabilities.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.annotation.IntDef;
+
+import com.android.settings.R;
+
+import com.google.common.primitives.Ints;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Class to provide magnification capabilities. */
+public final class MagnificationCapabilities {
+
+ private static final String KEY_CAPABILITY =
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY;
+
+ /**
+ * Annotation for supported magnification mode.
+ *
+ * @see Settings.Secure#ACCESSIBILITY_MAGNIFICATION_CAPABILITY
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ MagnificationMode.NONE,
+ MagnificationMode.FULLSCREEN,
+ MagnificationMode.WINDOW,
+ MagnificationMode.ALL,
+ })
+
+ public @interface MagnificationMode {
+ int NONE = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
+ int FULLSCREEN = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+ int WINDOW = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+ int ALL = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
+ }
+
+ /**
+ * Gets the summary for the given {@code capabilities}.
+ *
+ * @param context A {@link Context}.
+ * @param capabilities Magnification capabilities {@link MagnificationMode}
+ * @return The summary text represents the given capabilities
+ */
+ public static String getSummary(Context context, @MagnificationMode int capabilities) {
+ final String[] summaries = context.getResources().getStringArray(
+ R.array.magnification_mode_summaries);
+ final int[] values = context.getResources().getIntArray(
+ R.array.magnification_mode_values);
+
+ final int idx = Ints.indexOf(values, capabilities);
+ return summaries[idx == /* no index exist */ -1 ? 0 : idx];
+ }
+
+ /**
+ * Sets the magnification capabilities {@link MagnificationMode} to settings key. This
+ * overwrites any existing capabilities.
+ *
+ * @param context A {@link Context}.
+ * @param capabilities Magnification capabilities {@link MagnificationMode}
+ */
+ public static void setCapabilities(Context context, @MagnificationMode int capabilities) {
+ final ContentResolver contentResolver = context.getContentResolver();
+
+ Settings.Secure.putIntForUser(contentResolver, KEY_CAPABILITY, capabilities,
+ contentResolver.getUserId());
+ }
+
+ /**
+ * Returns the magnification capabilities {@link MagnificationMode} from setting's key. May be
+ * default value {@link MagnificationMode#FULLSCREEN} if not set.
+ *
+ * @param context A {@link Context}.
+ * @return The magnification capabilities {@link MagnificationMode}
+ */
+ @MagnificationMode
+ public static int getCapabilities(Context context) {
+ final ContentResolver contentResolver = context.getContentResolver();
+
+ return Settings.Secure.getIntForUser(contentResolver, KEY_CAPABILITY,
+ MagnificationMode.FULLSCREEN, contentResolver.getUserId());
+ }
+
+ private MagnificationCapabilities() {}
+}
diff --git a/src/com/android/settings/accessibility/MagnificationEnablePreferenceController.java b/src/com/android/settings/accessibility/MagnificationEnablePreferenceController.java
deleted file mode 100644
index 57a29621b394505878d11e5a41f555349b20f5e4..0000000000000000000000000000000000000000
--- a/src/com/android/settings/accessibility/MagnificationEnablePreferenceController.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.accessibility;
-
-import android.content.Context;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import com.android.settings.core.TogglePreferenceController;
-
-/** Controller that shows the magnification enable mode summary. */
-public class MagnificationEnablePreferenceController extends TogglePreferenceController {
-
- private static final String KEY_ENABLE = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE;
-
- public MagnificationEnablePreferenceController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- }
-
- @Override
- public boolean isChecked() {
- final int enableMode = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- KEY_ENABLE,
- Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
- UserHandle.USER_CURRENT);
- return enableMode == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
- }
-
- @Override
- public boolean setChecked(boolean isChecked) {
- final int value = isChecked ? Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
- : Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
- return Settings.Secure.putIntForUser(mContext.getContentResolver(), KEY_ENABLE, value,
- UserHandle.USER_CURRENT);
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AVAILABLE;
- }
-}
diff --git a/src/com/android/settings/accessibility/MagnificationGesturesPreferenceController.java b/src/com/android/settings/accessibility/MagnificationGesturesPreferenceController.java
index 68836449463235460666ed5ed4e958a0474e1edc..900e2800a0990fc6fa693c043f9f0aa68b36447e 100644
--- a/src/com/android/settings/accessibility/MagnificationGesturesPreferenceController.java
+++ b/src/com/android/settings/accessibility/MagnificationGesturesPreferenceController.java
@@ -14,6 +14,7 @@
package com.android.settings.accessibility;
import android.content.Context;
+import android.icu.text.MessageFormat;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -93,8 +94,12 @@ public class MagnificationGesturesPreferenceController extends TogglePreferenceC
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
extras.putInt(AccessibilitySettings.EXTRA_TITLE_RES,
R.string.accessibility_screen_magnification_gestures_title);
- extras.putCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION,
- context.getText(R.string.accessibility_screen_magnification_summary));
+
+ String summary = context.getString(R.string.accessibility_screen_magnification_summary);
+ final Object[] numberArguments = {1, 2, 3, 4, 5};
+ summary = MessageFormat.format(summary, numberArguments);
+ extras.putCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, summary);
+
extras.putInt(AccessibilitySettings.EXTRA_VIDEO_RAW_RESOURCE_ID,
R.raw.accessibility_screen_magnification);
}
diff --git a/src/com/android/settings/accessibility/MagnificationModePreferenceController.java b/src/com/android/settings/accessibility/MagnificationModePreferenceController.java
index b45ad88a52035a5fb63dfbc8fa3ad0561982bfb0..711065602bee18c5a85b04c12b6a561d335aea63 100644
--- a/src/com/android/settings/accessibility/MagnificationModePreferenceController.java
+++ b/src/com/android/settings/accessibility/MagnificationModePreferenceController.java
@@ -16,15 +16,87 @@
package com.android.settings.accessibility;
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.settings.accessibility.AccessibilityDialogUtils.CustomButton;
+import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
+import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
+
+import android.app.Dialog;
+import android.app.settings.SettingsEnums;
import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import com.android.settings.DialogCreatable;
+import com.android.settings.R;
+import com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode;
import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnCreate;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+/** Controller that shows the magnification area mode summary and the preference click behavior. */
+public class MagnificationModePreferenceController extends BasePreferenceController implements
+ DialogCreatable, LifecycleObserver, OnCreate, OnResume, OnSaveInstanceState {
+
+ static final String PREF_KEY = "screen_magnification_mode";
+ private static final int DIALOG_ID_BASE = 10;
+ @VisibleForTesting
+ static final int DIALOG_MAGNIFICATION_MODE = DIALOG_ID_BASE + 1;
+ @VisibleForTesting
+ static final int DIALOG_MAGNIFICATION_SWITCH_SHORTCUT = DIALOG_ID_BASE + 2;
+ @VisibleForTesting
+ static final String EXTRA_MODE = "mode";
+
+ private static final String TAG = "MagnificationModePreferenceController";
+ private static final char COMPONENT_NAME_SEPARATOR = ':';
+
+ private DialogHelper mDialogHelper;
+ // The magnification mode in the dialog.
+ private int mMode = MagnificationMode.NONE;
+ private Preference mModePreference;
+
+ @VisibleForTesting
+ ListView mMagnificationModesListView;
-/** Controller that shows the magnification area mode summary. */
-public class MagnificationModePreferenceController extends BasePreferenceController {
+ private final List mModeInfos = new ArrayList<>();
public MagnificationModePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
+ initModeInfos();
+ }
+
+ private void initModeInfos() {
+ mModeInfos.add(new MagnificationModeInfo(mContext.getText(
+ R.string.accessibility_magnification_mode_dialog_option_full_screen), null,
+ R.drawable.ic_illustration_fullscreen, MagnificationMode.FULLSCREEN));
+ mModeInfos.add(new MagnificationModeInfo(
+ mContext.getText(R.string.accessibility_magnification_mode_dialog_option_window),
+ null, R.drawable.ic_illustration_window, MagnificationMode.WINDOW));
+ mModeInfos.add(new MagnificationModeInfo(
+ mContext.getText(R.string.accessibility_magnification_mode_dialog_option_switch),
+ mContext.getText(
+ R.string.accessibility_magnification_area_settings_mode_switch_summary),
+ R.drawable.ic_illustration_switch, MagnificationMode.ALL));
}
@Override
@@ -32,10 +104,197 @@ public class MagnificationModePreferenceController extends BasePreferenceControl
return AVAILABLE;
}
-
@Override
public CharSequence getSummary() {
- return MagnificationSettingsFragment.getMagnificationCapabilitiesSummary(
- mContext);
+ final int capabilities = MagnificationCapabilities.getCapabilities(mContext);
+ return MagnificationCapabilities.getSummary(mContext, capabilities);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ mMode = savedInstanceState.getInt(EXTRA_MODE, MagnificationMode.NONE);
+ }
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mModePreference = screen.findPreference(getPreferenceKey());
+ mModePreference.setOnPreferenceClickListener(preference -> {
+ mMode = MagnificationCapabilities.getCapabilities(mContext);
+ mDialogHelper.showDialog(DIALOG_MAGNIFICATION_MODE);
+ return true;
+ });
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putInt(EXTRA_MODE, mMode);
+ }
+
+ /**
+ * Sets {@link DialogHelper} used to show the dialog.
+ */
+ public void setDialogHelper(DialogHelper dialogHelper) {
+ mDialogHelper = dialogHelper;
+ mDialogHelper.setDialogDelegate(this);
+ }
+
+ @Override
+ public Dialog onCreateDialog(int dialogId) {
+ switch (dialogId) {
+ case DIALOG_MAGNIFICATION_MODE:
+ return createMagnificationModeDialog();
+
+ case DIALOG_MAGNIFICATION_SWITCH_SHORTCUT:
+ return createMagnificationShortCutConfirmDialog();
+ }
+ return null;
+ }
+
+ @Override
+ public int getDialogMetricsCategory(int dialogId) {
+ switch (dialogId) {
+ case DIALOG_MAGNIFICATION_MODE:
+ return SettingsEnums.DIALOG_MAGNIFICATION_CAPABILITY;
+ case DIALOG_MAGNIFICATION_SWITCH_SHORTCUT:
+ return SettingsEnums.DIALOG_MAGNIFICATION_SWITCH_SHORTCUT;
+ default:
+ return 0;
+ }
+ }
+
+ private Dialog createMagnificationModeDialog() {
+ mMagnificationModesListView = AccessibilityDialogUtils.createSingleChoiceListView(
+ mContext, mModeInfos, this::onMagnificationModeSelected);
+
+ final View headerView = LayoutInflater.from(mContext).inflate(
+ R.layout.accessibility_magnification_mode_header, mMagnificationModesListView,
+ false);
+ mMagnificationModesListView.addHeaderView(headerView, /* data= */ null, /* isSelectable= */
+ false);
+
+ mMagnificationModesListView.setItemChecked(computeSelectionIndex(), true);
+ final CharSequence title = mContext.getString(
+ R.string.accessibility_magnification_mode_dialog_title);
+
+ return AccessibilityDialogUtils.createCustomDialog(mContext, title,
+ mMagnificationModesListView, this::onMagnificationModeDialogPositiveButtonClicked);
+ }
+
+ private void onMagnificationModeDialogPositiveButtonClicked(DialogInterface dialogInterface,
+ int which) {
+ final int selectedIndex = mMagnificationModesListView.getCheckedItemPosition();
+ if (selectedIndex != AdapterView.INVALID_POSITION) {
+ final MagnificationModeInfo modeInfo =
+ (MagnificationModeInfo) mMagnificationModesListView.getItemAtPosition(
+ selectedIndex);
+ setMode(modeInfo.mMagnificationMode);
+ } else {
+ Log.w(TAG, "invalid index");
+ }
+ }
+
+ private void setMode(int mode) {
+ mMode = mode;
+ MagnificationCapabilities.setCapabilities(mContext, mMode);
+ mModePreference.setSummary(
+ MagnificationCapabilities.getSummary(mContext, mMode));
+ }
+
+ private void onMagnificationModeSelected(AdapterView> parent, View view, int position,
+ long id) {
+ final MagnificationModeInfo modeInfo =
+ (MagnificationModeInfo) mMagnificationModesListView.getItemAtPosition(
+ position);
+ if (modeInfo.mMagnificationMode == mMode) {
+ return;
+ }
+ mMode = modeInfo.mMagnificationMode;
+ if (isTripleTapEnabled(mContext) && mMode != MagnificationMode.FULLSCREEN) {
+ mDialogHelper.showDialog(DIALOG_MAGNIFICATION_SWITCH_SHORTCUT);
+ }
+ }
+
+ private int computeSelectionIndex() {
+ final int modesSize = mModeInfos.size();
+ for (int i = 0; i < modesSize; i++) {
+ if (mModeInfos.get(i).mMagnificationMode == mMode) {
+ return i + mMagnificationModesListView.getHeaderViewsCount();
+ }
+ }
+ Log.w(TAG, "computeSelectionIndex failed");
+ return 0;
+ }
+
+ @VisibleForTesting
+ static boolean isTripleTapEnabled(Context context) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF) == ON;
+ }
+
+ private Dialog createMagnificationShortCutConfirmDialog() {
+ return AccessibilityDialogUtils.createMagnificationSwitchShortcutDialog(mContext,
+ this::onSwitchShortcutDialogButtonClicked);
+ }
+
+ @VisibleForTesting
+ void onSwitchShortcutDialogButtonClicked(@CustomButton int which) {
+ optOutMagnificationFromTripleTap();
+ //TODO(b/147990389): Merge this function into AccessibilityUtils after the format of
+ // magnification target is changed to ComponentName.
+ optInMagnificationToAccessibilityButton();
+ }
+
+ private void optOutMagnificationFromTripleTap() {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF);
+ }
+
+ private void optInMagnificationToAccessibilityButton() {
+ final String targetKey = Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
+ final String targetString = Settings.Secure.getString(mContext.getContentResolver(),
+ targetKey);
+ if (targetString != null && targetString.contains(MAGNIFICATION_CONTROLLER_NAME)) {
+ return;
+ }
+
+ final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR));
+
+ if (!TextUtils.isEmpty(targetString)) {
+ joiner.add(targetString);
+ }
+ joiner.add(MAGNIFICATION_CONTROLLER_NAME);
+
+ Settings.Secure.putString(mContext.getContentResolver(), targetKey,
+ joiner.toString());
+ }
+
+ // TODO(b/186731461): Remove it when this controller is used in DashBoardFragment only.
+ @Override
+ public void onResume() {
+ updateState(mModePreference);
+ }
+
+
+ /**
+ * An interface to help the delegate to show the dialog. It will be injected to the delegate.
+ */
+ interface DialogHelper extends DialogCreatable {
+ void showDialog(int dialogId);
+ void setDialogDelegate(DialogCreatable delegate);
+ }
+
+ @VisibleForTesting
+ static class MagnificationModeInfo extends ItemInfoArrayAdapter.ItemInfo {
+ @MagnificationMode
+ public final int mMagnificationMode;
+
+ MagnificationModeInfo(@NonNull CharSequence title, @Nullable CharSequence summary,
+ @DrawableRes int drawableId, @MagnificationMode int magnificationMode) {
+ super(title, summary, drawableId);
+ mMagnificationMode = magnificationMode;
+ }
}
}
diff --git a/src/com/android/settings/accessibility/MagnificationSettingsFragment.java b/src/com/android/settings/accessibility/MagnificationSettingsFragment.java
index 04e8036256e5a385c79a3ab704117e9789a10192..a898076033c657ca56fe05c456f1df5ac85d778e 100644
--- a/src/com/android/settings/accessibility/MagnificationSettingsFragment.java
+++ b/src/com/android/settings/accessibility/MagnificationSettingsFragment.java
@@ -19,62 +19,22 @@ package com.android.settings.accessibility;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.view.View;
-import android.widget.CheckBox;
-
-import androidx.annotation.IntDef;
-import androidx.appcompat.app.AlertDialog;
-import androidx.preference.Preference;
+import com.android.settings.DialogCreatable;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
-import com.google.common.primitives.Ints;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
/** Settings page for magnification. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
-public class MagnificationSettingsFragment extends DashboardFragment {
+public class MagnificationSettingsFragment extends DashboardFragment implements
+ MagnificationModePreferenceController.DialogHelper {
private static final String TAG = "MagnificationSettingsFragment";
- private static final String PREF_KEY_MODE = "magnification_mode";
- // TODO(b/146019459): Use magnification_capability.
- private static final String KEY_CAPABILITY = Settings.System.MASTER_MONO;
- private static final int DIALOG_MAGNIFICATION_CAPABILITY = 1;
- private static final String EXTRA_CAPABILITY = "capability";
- private Preference mModePreference;
- private int mCapabilities = MagnifyMode.NONE;
- private CheckBox mMagnifyFullScreenCheckBox;
- private CheckBox mMagnifyWindowCheckBox;
-
- static String getMagnificationCapabilitiesSummary(Context context) {
- final String[] magnificationModeSummaries = context.getResources().getStringArray(
- R.array.magnification_mode_summaries);
- final int[] magnificationModeValues = context.getResources().getIntArray(
- R.array.magnification_mode_values);
- final int capabilities = MagnificationSettingsFragment.getMagnificationCapabilities(
- context);
-
- final int idx = Ints.indexOf(magnificationModeValues, capabilities);
- return magnificationModeSummaries[idx == -1 ? 0 : idx];
- }
- private static int getMagnificationCapabilities(Context context) {
- return getSecureIntValue(context, KEY_CAPABILITY, MagnifyMode.FULLSCREEN);
- }
+ private DialogCreatable mDialogDelegate;
- private static int getSecureIntValue(Context context, String key, int defaultValue) {
- return Settings.Secure.getIntForUser(
- context.getContentResolver(),
- key, defaultValue, context.getContentResolver().getUserId());
- }
@Override
public int getMetricsCategory() {
@@ -82,30 +42,27 @@ public class MagnificationSettingsFragment extends DashboardFragment {
}
@Override
- public void onSaveInstanceState(Bundle outState) {
- outState.putInt(EXTRA_CAPABILITY, mCapabilities);
- super.onSaveInstanceState(outState);
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ use(MagnificationModePreferenceController.class).setDialogHelper(this);
}
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- if (savedInstanceState != null) {
- mCapabilities = savedInstanceState.getInt(EXTRA_CAPABILITY, MagnifyMode.NONE);
- }
- if (mCapabilities == MagnifyMode.NONE) {
- mCapabilities = getMagnificationCapabilities(getPrefContext());
- }
+ public void showDialog(int dialogId) {
+ super.showDialog(dialogId);
+ }
+
+ @Override
+ public void setDialogDelegate(DialogCreatable delegate) {
+ mDialogDelegate = delegate;
}
@Override
public int getDialogMetricsCategory(int dialogId) {
- switch (dialogId) {
- case DIALOG_MAGNIFICATION_CAPABILITY:
- return SettingsEnums.DIALOG_MAGNIFICATION_CAPABILITY;
- default:
- return 0;
+ if (mDialogDelegate != null) {
+ return mDialogDelegate.getDialogMetricsCategory(dialogId);
}
+ return 0;
}
@Override
@@ -113,17 +70,6 @@ public class MagnificationSettingsFragment extends DashboardFragment {
return TAG;
}
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- mModePreference = findPreference(PREF_KEY_MODE);
- mModePreference.setOnPreferenceClickListener(preference -> {
- mCapabilities = getMagnificationCapabilities(getPrefContext());
- showDialog(DIALOG_MAGNIFICATION_CAPABILITY);
- return true;
- });
- }
-
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_magnification_service_settings;
@@ -131,104 +77,15 @@ public class MagnificationSettingsFragment extends DashboardFragment {
@Override
public Dialog onCreateDialog(int dialogId) {
- if (dialogId == DIALOG_MAGNIFICATION_CAPABILITY) {
- final String title = getPrefContext().getString(
- R.string.accessibility_magnification_mode_title);
- AlertDialog alertDialog = AccessibilityEditDialogUtils
- .showMagnificationModeDialog(getPrefContext(), title,
- this::callOnAlertDialogCheckboxClicked);
- initializeDialogCheckBox(alertDialog);
- return alertDialog;
+ if (mDialogDelegate != null) {
+ final Dialog dialog = mDialogDelegate.onCreateDialog(dialogId);
+ if (dialog != null) {
+ return dialog;
+ }
}
throw new IllegalArgumentException("Unsupported dialogId " + dialogId);
}
- private void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
- updateCapabilities(true);
- mModePreference.setSummary(
- getMagnificationCapabilitiesSummary(getPrefContext()));
- }
-
- private void initializeDialogCheckBox(AlertDialog dialog) {
- final View dialogFullScreenView = dialog.findViewById(R.id.magnify_full_screen);
- mMagnifyFullScreenCheckBox = dialogFullScreenView.findViewById(R.id.checkbox);
-
- final View dialogWidowView = dialog.findViewById(R.id.magnify_window_screen);
- mMagnifyWindowCheckBox = dialogWidowView.findViewById(R.id.checkbox);
-
- updateAlertDialogCheckState();
- updateAlertDialogEnableState();
- }
-
- private void updateAlertDialogCheckState() {
- updateCheckStatus(mMagnifyWindowCheckBox, MagnifyMode.WINDOW);
- updateCheckStatus(mMagnifyFullScreenCheckBox, MagnifyMode.FULLSCREEN);
-
- }
-
- private void updateCheckStatus(CheckBox checkBox, int mode) {
- checkBox.setChecked((mode & mCapabilities) != 0);
- checkBox.setOnClickListener(v -> {
- updateCapabilities(false);
- updateAlertDialogEnableState();
- });
- }
-
- private void updateAlertDialogEnableState() {
- if (mCapabilities != MagnifyMode.ALL) {
- disableEnabledMagnificationModePreference();
- } else {
- enableAllPreference();
- }
- }
-
- private void enableAllPreference() {
- mMagnifyFullScreenCheckBox.setEnabled(true);
- mMagnifyWindowCheckBox.setEnabled(true);
- }
-
- private void disableEnabledMagnificationModePreference() {
- if (!mMagnifyFullScreenCheckBox.isChecked()) {
- mMagnifyWindowCheckBox.setEnabled(false);
- } else if (!mMagnifyWindowCheckBox.isChecked()) {
- mMagnifyFullScreenCheckBox.setEnabled(false);
- }
- }
-
- private void updateCapabilities(boolean saveToDB) {
- int capabilities = 0;
- capabilities |=
- mMagnifyFullScreenCheckBox.isChecked() ? MagnifyMode.FULLSCREEN : 0;
- capabilities |= mMagnifyWindowCheckBox.isChecked() ? MagnifyMode.WINDOW : 0;
- mCapabilities = capabilities;
- if (saveToDB) {
- setMagnificationCapabilities(capabilities);
- }
- }
-
- private void setSecureIntValue(String key, int value) {
- Settings.Secure.putIntForUser(getPrefContext().getContentResolver(),
- key, value, getPrefContext().getContentResolver().getUserId());
- }
-
- private void setMagnificationCapabilities(int capabilities) {
- setSecureIntValue(KEY_CAPABILITY, capabilities);
- }
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({
- MagnifyMode.NONE,
- MagnifyMode.FULLSCREEN,
- MagnifyMode.WINDOW,
- MagnifyMode.ALL,
- })
- private @interface MagnifyMode {
- int NONE = 0;
- int FULLSCREEN = 1;
- int WINDOW = 2;
- int ALL = 3;
- }
-
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_magnification_service_settings);
}
diff --git a/src/com/android/settings/accessibility/PaletteListPreference.java b/src/com/android/settings/accessibility/PaletteListPreference.java
index c5c42054a649ca2bdf48e6a72d041469eeab1025..ac552ebc0e01c1b42f932b2a006045e3eaaba9bc 100644
--- a/src/com/android/settings/accessibility/PaletteListPreference.java
+++ b/src/com/android/settings/accessibility/PaletteListPreference.java
@@ -16,23 +16,58 @@
package com.android.settings.accessibility;
+import static android.graphics.drawable.GradientDrawable.Orientation;
+
+import static com.android.settings.accessibility.AccessibilityUtil.getScreenHeightPixels;
+import static com.android.settings.accessibility.AccessibilityUtil.getScreenWidthPixels;
+
+import static com.google.common.primitives.Ints.max;
+
import android.content.Context;
+import android.graphics.Paint.FontMetrics;
+import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.View;
-import android.view.ViewTreeObserver;
-import android.widget.FrameLayout;
-import android.widget.ListView;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import androidx.annotation.ColorInt;
+import androidx.annotation.IntDef;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
+import com.google.common.primitives.Floats;
+import com.google.common.primitives.Ints;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
/** Preference that easier preview by matching name to color. */
-public class PaletteListPreference extends Preference {
+public final class PaletteListPreference extends Preference {
+
+ private final List mGradientColors = new ArrayList<>();
+ private final List mGradientOffsets = new ArrayList<>();
- private ListView mListView;
- private ViewTreeObserver.OnPreDrawListener mPreDrawListener;
+ @IntDef({
+ Position.START,
+ Position.CENTER,
+ Position.END,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Position {
+ int START = 0;
+ int CENTER = 1;
+ int END = 2;
+ }
/**
* Constructs a new PaletteListPreference with the given context's theme and the supplied
@@ -61,47 +96,94 @@ public class PaletteListPreference extends Preference {
public PaletteListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayoutResource(R.layout.daltonizer_preview);
- initPreDrawListener();
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- final View rootView = holder.itemView;
- mListView = rootView.findViewById(R.id.palette_listView);
- if (mPreDrawListener != null) {
- mListView.getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
+ final ViewGroup paletteView = holder.itemView.findViewById(R.id.palette_view);
+ initPaletteAttributes(getContext());
+ initPaletteView(getContext(), paletteView);
+ }
+
+ private void initPaletteAttributes(Context context) {
+ final int defaultColor = context.getColor(R.color.palette_list_gradient_background);
+ mGradientColors.add(Position.START, defaultColor);
+ mGradientColors.add(Position.CENTER, defaultColor);
+ mGradientColors.add(Position.END, defaultColor);
+
+ mGradientOffsets.add(Position.START, /* element= */ 0.0f);
+ mGradientOffsets.add(Position.CENTER, /* element= */ 0.5f);
+ mGradientOffsets.add(Position.END, /* element= */ 1.0f);
+ }
+
+ private void initPaletteView(Context context, ViewGroup rootView) {
+ if (rootView.getChildCount() > 0) {
+ rootView.removeAllViews();
+ }
+
+ final List paletteColors = getPaletteColors(context);
+ final List paletteData = getPaletteData(context);
+
+ final float textPadding =
+ context.getResources().getDimension(R.dimen.accessibility_layout_margin_start_end);
+ final String maxLengthData =
+ Collections.max(paletteData, Comparator.comparing(String::length));
+ final int textWidth = getTextWidth(context, maxLengthData);
+ final float textBound = (textWidth + textPadding) / getScreenWidthPixels(context);
+ mGradientOffsets.set(Position.CENTER, textBound);
+
+ final int screenHalfHeight = getScreenHeightPixels(context) / 2;
+ final int paletteItemHeight =
+ max(screenHalfHeight / paletteData.size(), getTextLineHeight(context));
+
+ for (int i = 0; i < paletteData.size(); ++i) {
+ final TextView textView = new TextView(context);
+ textView.setText(paletteData.get(i));
+ textView.setHeight(paletteItemHeight);
+ textView.setPaddingRelative(Math.round(textPadding), 0, 0, 0);
+ textView.setGravity(Gravity.CENTER_VERTICAL);
+ textView.setBackground(createGradientDrawable(rootView, paletteColors.get(i)));
+
+ rootView.addView(textView);
}
}
- private void initPreDrawListener() {
- mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- if (mListView == null) {
- return false;
- }
-
- final int listViewHeight = mListView.getMeasuredHeight();
- final int listViewWidth = mListView.getMeasuredWidth();
-
- // Removes the callback after get result of measure view.
- final ViewTreeObserver viewTreeObserver = mListView.getViewTreeObserver();
- if (viewTreeObserver.isAlive()) {
- viewTreeObserver.removeOnPreDrawListener(this);
- }
- mPreDrawListener = null;
-
- // Resets layout parameters to display whole items from listView.
- final FrameLayout.LayoutParams layoutParams =
- (FrameLayout.LayoutParams) mListView.getLayoutParams();
- layoutParams.height = listViewHeight * mListView.getAdapter().getCount();
- layoutParams.width = listViewWidth;
- mListView.setLayoutParams(layoutParams);
-
- return true;
- }
- };
+ private GradientDrawable createGradientDrawable(ViewGroup rootView, @ColorInt int color) {
+ mGradientColors.set(Position.END, color);
+
+ final GradientDrawable gradientDrawable = new GradientDrawable();
+ final Orientation orientation =
+ rootView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? Orientation.RIGHT_LEFT
+ : Orientation.LEFT_RIGHT;
+ gradientDrawable.setOrientation(orientation);
+ gradientDrawable.setColors(Ints.toArray(mGradientColors), Floats.toArray(mGradientOffsets));
+
+ return gradientDrawable;
+ }
+
+ private List getPaletteColors(Context context) {
+ final int[] paletteResources =
+ context.getResources().getIntArray(R.array.setting_palette_colors);
+ return Arrays.stream(paletteResources).boxed().collect(Collectors.toList());
+ }
+
+ private List getPaletteData(Context context) {
+ final String[] paletteResources =
+ context.getResources().getStringArray(R.array.setting_palette_data);
+ return Arrays.asList(paletteResources);
+ }
+
+ private int getTextWidth(Context context, String text) {
+ final TextView tempView = new TextView(context);
+ return Math.round(tempView.getPaint().measureText(text));
+ }
+
+ private int getTextLineHeight(Context context) {
+ final TextView tempView = new TextView(context);
+ final FontMetrics fontMetrics = tempView.getPaint().getFontMetrics();
+ return Math.round(fontMetrics.bottom - fontMetrics.top);
}
}
diff --git a/src/com/android/settings/accessibility/PaletteListView.java b/src/com/android/settings/accessibility/PaletteListView.java
deleted file mode 100644
index ef010e2d979f8c975435d8d04088371899f0aa99..0000000000000000000000000000000000000000
--- a/src/com/android/settings/accessibility/PaletteListView.java
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.accessibility;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.GradientDrawable.Orientation;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.BaseAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.settings.R;
-
-import com.google.common.collect.Iterables;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Custom ListView {@link ListView} which displays palette to deploy the color code preview.
- *
- * The preview shows gradient from color white to specific color code on each list view item, in
- * addition, text view adjusts the attribute of width for adapting the text length.
- *
- *
The text cannot fills the whole view for ensuring the gradient color preview can purely
- * display also the view background shows the color beside the text variable end point.
- */
-public class PaletteListView extends ListView {
- private final Context mContext;
- private final DisplayAdapter mDisplayAdapter;
- private final LayoutInflater mLayoutInflater;
- private final String mDefaultGradientColorCodeString;
- private final int mDefaultGradientColor;
- private float mTextBound;
- private static final float LANDSCAPE_MAX_WIDTH_PERCENTAGE = 100f;
-
- public PaletteListView(Context context) {
- this(context, null);
- }
-
- public PaletteListView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public PaletteListView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mContext = context;
- mDisplayAdapter = new DisplayAdapter();
- mLayoutInflater = LayoutInflater.from(context);
- mDefaultGradientColorCodeString =
- getResources().getString(R.color.palette_list_gradient_background);
- mDefaultGradientColor =
- getResources().getColor(R.color.palette_list_gradient_background, null);
- mTextBound = 0.0f;
- init();
- }
-
- private static int getScreenWidth(WindowManager windowManager) {
- final Display display = windowManager.getDefaultDisplay();
- final DisplayMetrics displayMetrics = new DisplayMetrics();
- display.getMetrics(displayMetrics);
- return displayMetrics.widthPixels;
- }
-
- private void init() {
- final TypedArray colorNameArray = getResources().obtainTypedArray(
- R.array.setting_palette_colors);
- final TypedArray colorCodeArray = getResources().obtainTypedArray(
- R.array.setting_palette_data);
- final int colorNameArrayLength = colorNameArray.length();
- final List colorList = new ArrayList<>();
- computeTextWidthBounds(colorNameArray);
-
- for (int index = 0; index < colorNameArrayLength; index++) {
- colorList.add(
- new ColorAttributes(
- /* colorName= */ colorNameArray.getString(index),
- /* colorCode= */ colorCodeArray.getColor(index, mDefaultGradientColor),
- /* textBound= */ mTextBound,
- /* gradientDrawable= */
- new GradientDrawable(Orientation.LEFT_RIGHT, null)));
- }
-
- mDisplayAdapter.setColorList(colorList);
- setAdapter(mDisplayAdapter);
- setDividerHeight(/* height= */ 0);
- }
-
- /**
- * Sets string array that required the color name and color code for deploy the new color
- * preview.
- *
- * The parameters not allow null define but two array length inconsistent are acceptable, in
- * addition, to prevent IndexOutOfBoundsException the algorithm will check array data, and base
- * on the array size to display data, or fills color code array if length less than other.
- *
- * @param colorNames a string array of color name
- * @param colorCodes a string array of color code
- * @return true if new array data apply successful
- */
- @VisibleForTesting
- boolean setPaletteListColors(@NonNull String[] colorNames, @NonNull String[] colorCodes) {
- if (colorNames == null || colorCodes == null) {
- return false;
- }
-
- final int colorNameArrayLength = colorNames.length;
- final int colorCodeArrayLength = colorCodes.length;
- final List colorList = new ArrayList<>();
- final String[] colorCodeArray = fillColorCodeArray(colorCodes, colorNameArrayLength,
- colorCodeArrayLength);
- computeTextWidthBounds(colorNames);
-
- for (int index = 0; index < colorNameArrayLength; index++) {
- colorList.add(
- new ColorAttributes(
- /* colorName= */ colorNames[index],
- /* colorCode= */ Color.parseColor(colorCodeArray[index]),
- /* textBound= */ mTextBound,
- /* gradientDrawable= */
- new GradientDrawable(Orientation.LEFT_RIGHT, null)));
- }
-
- mDisplayAdapter.setColorList(colorList);
- mDisplayAdapter.notifyDataSetChanged();
- return true;
- }
-
- private String[] fillColorCodeArray(String[] colorCodes, int colorNameArrayLength,
- int colorCodeArrayLength) {
- if (colorNameArrayLength == colorCodeArrayLength
- || colorNameArrayLength < colorCodeArrayLength) {
- return colorCodes;
- }
-
- final String[] colorCodeArray = new String[colorNameArrayLength];
- for (int index = 0; index < colorNameArrayLength; index++) {
- if (index < colorCodeArrayLength) {
- colorCodeArray[index] = colorCodes[index];
- } else {
- colorCodeArray[index] = mDefaultGradientColorCodeString;
- }
- }
- return colorCodeArray;
- }
-
- private void computeTextWidthBounds(TypedArray colorNameTypedArray) {
- final int colorNameArrayLength = colorNameTypedArray.length();
- final String[] colorNames = new String[colorNameArrayLength];
- for (int index = 0; index < colorNameArrayLength; index++) {
- colorNames[index] = colorNameTypedArray.getString(index);
- }
-
- measureBound(colorNames);
- }
-
- private void computeTextWidthBounds(String[] colorNameArray) {
- final int colorNameArrayLength = colorNameArray.length;
- final String[] colorNames = new String[colorNameArrayLength];
- for (int index = 0; index < colorNameArrayLength; index++) {
- colorNames[index] = colorNameArray[index];
- }
-
- measureBound(colorNames);
- }
-
- private void measureBound(String[] dataArray) {
- final WindowManager windowManager = (WindowManager) mContext.getSystemService(
- Context.WINDOW_SERVICE);
- final View view = mLayoutInflater.inflate(R.layout.palette_listview_item, null);
- final TextView textView = view.findViewById(R.id.item_textview);
- final List colorNameList = new ArrayList<>(Arrays.asList(dataArray));
- Collections.sort(colorNameList, Comparator.comparing(String::length));
- // Gets the last index of list which sort by text length.
- textView.setText(Iterables.getLast(colorNameList));
-
- final float textWidth = textView.getPaint().measureText(textView.getText().toString());
- // Computes rate of text width compare to screen width, and measures the round the double
- // to two decimal places manually.
- final float textBound = Math.round(
- textWidth / getScreenWidth(windowManager) * LANDSCAPE_MAX_WIDTH_PERCENTAGE)
- / LANDSCAPE_MAX_WIDTH_PERCENTAGE;
-
- // Left padding and right padding with color preview.
- final float paddingPixel = getResources().getDimension(
- R.dimen.accessibility_layout_margin_start_end);
- final float paddingWidth =
- Math.round(paddingPixel / getScreenWidth(windowManager)
- * LANDSCAPE_MAX_WIDTH_PERCENTAGE) / LANDSCAPE_MAX_WIDTH_PERCENTAGE;
- mTextBound = textBound + paddingWidth + paddingWidth;
- }
-
- private static class ViewHolder {
- public TextView textView;
- }
-
- /** An adapter that converts color text title and color code to text views. */
- private final class DisplayAdapter extends BaseAdapter {
-
- private List mColorList;
-
- @Override
- public int getCount() {
- return mColorList.size();
- }
-
- @Override
- public Object getItem(int position) {
- return mColorList.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final ViewHolder viewHolder;
- final ColorAttributes paletteAttribute = mColorList.get(position);
- final String colorName = paletteAttribute.getColorName();
- final GradientDrawable gradientDrawable = paletteAttribute.getGradientDrawable();
-
- if (convertView == null) {
- convertView = mLayoutInflater.inflate(R.layout.palette_listview_item, null);
- viewHolder = new ViewHolder();
- viewHolder.textView = convertView.findViewById(R.id.item_textview);
- convertView.setTag(viewHolder);
- } else {
- viewHolder = (ViewHolder) convertView.getTag();
- }
-
- viewHolder.textView.setText(colorName);
- viewHolder.textView.setBackground(gradientDrawable);
- return convertView;
- }
-
- protected void setColorList(List colorList) {
- mColorList = colorList;
- }
- }
-
- private final class ColorAttributes {
- private final int mColorIndex = 2; // index for inject color.
- private final int mColorOffsetIndex = 1; // index for offset effect.
- private final String mColorName;
- private final GradientDrawable mGradientDrawable;
- private final int[] mGradientColors =
- {/* startColor=*/ mDefaultGradientColor, /* centerColor=*/ mDefaultGradientColor,
- /* endCode= */ 0};
- private final float[] mGradientOffsets =
- {/* starOffset= */ 0.0f, /* centerOffset= */ 0.5f, /* endOffset= */ 1.0f};
-
- ColorAttributes(
- String colorName, int colorCode, float textBound,
- GradientDrawable gradientDrawable) {
- mGradientColors[mColorIndex] = colorCode;
- mGradientOffsets[mColorOffsetIndex] = textBound;
- gradientDrawable.setColors(mGradientColors, mGradientOffsets);
- mColorName = colorName;
- mGradientDrawable = gradientDrawable;
- }
-
- public String getColorName() {
- return mColorName;
- }
-
- public GradientDrawable getGradientDrawable() {
- return mGradientDrawable;
- }
- }
-}
diff --git a/src/com/android/settings/accessibility/PreferredShortcut.java b/src/com/android/settings/accessibility/PreferredShortcut.java
new file mode 100644
index 0000000000000000000000000000000000000000..1654992c8b38557348796c8e56967f1aca33a137
--- /dev/null
+++ b/src/com/android/settings/accessibility/PreferredShortcut.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.ComponentName;
+import android.text.TextUtils;
+
+import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
+
+import com.google.common.base.Objects;
+
+/**
+ * A data class for containing {@link ComponentName#flattenToString()} and
+ * {@link UserShortcutType}. Represents the preferred shortcuts of the service or activity.
+ */
+public class PreferredShortcut {
+
+ private static final char COMPONENT_NAME_SEPARATOR = ':';
+ private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
+ new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
+
+ /**
+ * Creates a {@link PreferredShortcut} from a encoded string described in {@link #toString()}.
+ *
+ * @param preferredShortcutString A string conform to the format described in {@link
+ * #toString()}
+ * @return A {@link PreferredShortcut} with the specified value
+ * @throws IllegalArgumentException If preferredShortcutString does not conform to the format
+ * described in {@link #toString()}
+ */
+ public static PreferredShortcut fromString(String preferredShortcutString) {
+ sStringColonSplitter.setString(preferredShortcutString);
+ if (sStringColonSplitter.hasNext()) {
+ final String componentName = sStringColonSplitter.next();
+ final int type = Integer.parseInt(sStringColonSplitter.next());
+ return new PreferredShortcut(componentName, type);
+ }
+
+ throw new IllegalArgumentException(
+ "Invalid PreferredShortcut string: " + preferredShortcutString);
+ }
+
+ /** The format of {@link ComponentName#flattenToString()} */
+ private String mComponentName;
+ /** The format of {@link UserShortcutType} */
+ private int mType;
+
+ public PreferredShortcut(String componentName, int type) {
+ mComponentName = componentName;
+ mType = type;
+ }
+
+ public String getComponentName() {
+ return mComponentName;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ @Override
+ public String toString() {
+ return mComponentName + COMPONENT_NAME_SEPARATOR + mType;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PreferredShortcut that = (PreferredShortcut) o;
+ return mType == that.mType && Objects.equal(mComponentName, that.mComponentName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mComponentName, mType);
+ }
+}
diff --git a/src/com/android/settings/accessibility/PreferredShortcuts.java b/src/com/android/settings/accessibility/PreferredShortcuts.java
new file mode 100644
index 0000000000000000000000000000000000000000..2c9840d09eca2fd8d03a5c579dfa0f01103d513e
--- /dev/null
+++ b/src/com/android/settings/accessibility/PreferredShortcuts.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/** Static utility methods relating to {@link PreferredShortcut} */
+public final class PreferredShortcuts {
+
+ private static final String ACCESSIBILITY_PERF = "accessibility_prefs";
+ private static final String USER_SHORTCUT_TYPE = "user_shortcut_type";
+
+ /**
+ * Retrieves {@link UserShortcutType} for the given {@code componentName} from
+ * SharedPreferences.
+ *
+ * @param context {@link Context} to access the {@link SharedPreferences}
+ * @param componentName Name of the service or activity, should be the format of {@link
+ * ComponentName#flattenToString()}.
+ * @param defaultType See {@link UserShortcutType}
+ * @return {@link UserShortcutType}
+ */
+ public static int retrieveUserShortcutType(Context context, String componentName,
+ int defaultType) {
+ if (componentName == null) {
+ return defaultType;
+ }
+
+ // Create a mutable set to modify
+ final Set info = new HashSet<>(getFromSharedPreferences(context));
+ info.removeIf(str -> !str.contains(componentName));
+
+ if (info.isEmpty()) {
+ return defaultType;
+ }
+
+ final String str = info.stream().findFirst().get();
+ final PreferredShortcut shortcut = PreferredShortcut.fromString(str);
+ return shortcut.getType();
+ }
+
+ /**
+ * Saves a {@link PreferredShortcut} which containing {@link ComponentName#flattenToString()}
+ * and {@link UserShortcutType} in SharedPreferences.
+ *
+ * @param context {@link Context} to access the {@link SharedPreferences}
+ * @param shortcut Contains {@link ComponentName#flattenToString()} and {@link UserShortcutType}
+ */
+ public static void saveUserShortcutType(Context context, PreferredShortcut shortcut) {
+ final String componentName = shortcut.getComponentName();
+ if (componentName == null) {
+ return;
+ }
+
+ // Create a mutable set to modify
+ final Set info = new HashSet<>(getFromSharedPreferences(context));
+ info.removeIf(str -> str.contains(componentName));
+ info.add(shortcut.toString());
+ saveToSharedPreferences(context, info);
+ }
+
+ /**
+ * Returns a immutable set of {@link PreferredShortcut#toString()} list from
+ * SharedPreferences.
+ */
+ private static Set getFromSharedPreferences(Context context) {
+ return getSharedPreferences(context).getStringSet(USER_SHORTCUT_TYPE, Set.of());
+ }
+
+ /** Sets a set of {@link PreferredShortcut#toString()} list into SharedPreferences. */
+ private static void saveToSharedPreferences(Context context, Set data) {
+ SharedPreferences.Editor editor = getSharedPreferences(context).edit();
+ editor.putStringSet(USER_SHORTCUT_TYPE, data).apply();
+ }
+
+ private static SharedPreferences getSharedPreferences(Context context) {
+ return context.getSharedPreferences(ACCESSIBILITY_PERF, Context.MODE_PRIVATE);
+ }
+
+ private PreferredShortcuts() {}
+}
diff --git a/src/com/android/settings/accessibility/MasterMonoPreferenceController.java b/src/com/android/settings/accessibility/PrimaryMonoPreferenceController.java
similarity index 86%
rename from src/com/android/settings/accessibility/MasterMonoPreferenceController.java
rename to src/com/android/settings/accessibility/PrimaryMonoPreferenceController.java
index 6082f14b7ae2adec58bbe2175c09a2428e450bf2..bf12bdebe4068bb99dda4f446d228cae4b853426 100644
--- a/src/com/android/settings/accessibility/MasterMonoPreferenceController.java
+++ b/src/com/android/settings/accessibility/PrimaryMonoPreferenceController.java
@@ -22,9 +22,12 @@ import android.provider.Settings;
import com.android.settings.core.TogglePreferenceController;
-public class MasterMonoPreferenceController extends TogglePreferenceController {
+/**
+ * A toggle preference controller for Primary Mono
+ */
+public class PrimaryMonoPreferenceController extends TogglePreferenceController {
- public MasterMonoPreferenceController(Context context, String preferenceKey) {
+ public PrimaryMonoPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
diff --git a/src/com/android/settings/accessibility/RTTSettingPreferenceController.java b/src/com/android/settings/accessibility/RTTSettingPreferenceController.java
index 3b5915d43d7ede017b1a2b7b239c8493d0ca85fb..3ad2a3bc9d4dfbf1df291ead9063510ce18e8467 100644
--- a/src/com/android/settings/accessibility/RTTSettingPreferenceController.java
+++ b/src/com/android/settings/accessibility/RTTSettingPreferenceController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,27 +20,34 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.PersistableBundle;
import android.provider.Settings;
-import android.telecom.TelecomManager;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
import android.text.TextUtils;
+import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
+import com.android.settings.accessibility.rtt.TelecomUtil;
import com.android.settings.core.BasePreferenceController;
import java.util.List;
-/** A controller to control the status for RTT setting in Accessibility screen.*/
+/** A controller to control the status for RTT setting in Accessibility screen. */
public class RTTSettingPreferenceController extends BasePreferenceController {
- private static final String DIALER_RTT_CONFIGURATION = "dialer_rtt_configuration";
+ private static final String TAG = "RTTSettingsCtr";
+ private static final String DIALER_RTT_CONFIGURATION = "dialer_rtt_configuration";
private final Context mContext;
private final PackageManager mPackageManager;
- private final TelecomManager mTelecomManager;
+ private final CarrierConfigManager mCarrierConfigManager;
private final CharSequence[] mModes;
private final String mDialerPackage;
@@ -52,17 +59,17 @@ public class RTTSettingPreferenceController extends BasePreferenceController {
mContext = context;
mModes = mContext.getResources().getTextArray(R.array.rtt_setting_mode);
mDialerPackage = mContext.getString(R.string.config_rtt_setting_package_name);
- mPackageManager = context.getPackageManager();
- mTelecomManager = context.getSystemService(TelecomManager.class);
+ mPackageManager = mContext.getPackageManager();
+ mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
mRTTIntent = new Intent(context.getString(R.string.config_rtt_setting_intent_action));
-
+ Log.d(TAG, "init controller");
}
@Override
public int getAvailabilityStatus() {
final List resolved =
mPackageManager.queryIntentActivities(mRTTIntent, 0 /* flags */);
- return resolved != null && !resolved.isEmpty() && isDialerSupportRTTSetting()
+ return resolved != null && !resolved.isEmpty() && isRttSettingSupported()
? AVAILABLE
: UNSUPPORTED_ON_DEVICE;
}
@@ -77,12 +84,82 @@ public class RTTSettingPreferenceController extends BasePreferenceController {
@Override
public CharSequence getSummary() {
final int option = Settings.Secure.getInt(mContext.getContentResolver(),
- DIALER_RTT_CONFIGURATION, 1 /* not visible */);
+ DIALER_RTT_CONFIGURATION, 0 /* Invalid value */);
+ Log.d(TAG, "DIALER_RTT_CONFIGURATION value = " + option);
return mModes[option];
}
@VisibleForTesting
- boolean isDialerSupportRTTSetting() {
- return TextUtils.equals(mTelecomManager.getDefaultDialerPackage(), mDialerPackage);
+ boolean isRttSettingSupported() {
+ Log.d(TAG, "isRttSettingSupported [start]");
+ if (!isDefaultDialerSupportedRTT(mContext)) {
+ Log.d(TAG, "Dialer doesn't support RTT.");
+ return false;
+ }
+ // At least one PhoneAccount must have both isRttSupported and
+ // ignore_rtt_mode_setting_bool being true
+ for (PhoneAccountHandle phoneAccountHandle :
+ TelecomUtil.getCallCapablePhoneAccounts(mContext)) {
+ final int subId =
+ TelecomUtil.getSubIdForPhoneAccountHandle(mContext, phoneAccountHandle);
+ Log.d(TAG, "subscription id for the device: " + subId);
+
+ final boolean isRttCallingSupported = isRttSupportedByTelecom(phoneAccountHandle);
+ Log.d(TAG, "rtt calling supported by telecom:: " + isRttCallingSupported);
+
+ if (isRttCallingSupported) {
+ PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(subId);
+ // If IGNORE_RTT_MODE_SETTING_BOOL=true, RTT visibility is not supported because
+ // this means we must use the legacy Telecom setting, which does not support RTT
+ // visibility.
+ if (carrierConfig != null
+ && getBooleanCarrierConfig(
+ CarrierConfigManager.KEY_IGNORE_RTT_MODE_SETTING_BOOL)) {
+ Log.d(TAG, "RTT visibility setting is supported.");
+ return true;
+ }
+ Log.d(TAG, "IGNORE_RTT_MODE_SETTING_BOOL is false.");
+ }
+ }
+ Log.d(TAG, "isRttSettingSupported [Not support]");
+ return false;
+ }
+
+ private boolean isRttSupportedByTelecom(PhoneAccountHandle phoneAccountHandle) {
+ PhoneAccount phoneAccount =
+ TelecomUtil.getTelecomManager(mContext).getPhoneAccount(phoneAccountHandle);
+ if (phoneAccount != null && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
+ Log.d(TAG, "Phone account has RTT capability.");
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Gets the boolean config from carrier config manager.
+ *
+ * @param key config key defined in CarrierConfigManager.
+ * @return boolean value of corresponding key.
+ */
+ private boolean getBooleanCarrierConfig(String key) {
+ if (mCarrierConfigManager == null) {
+ // Return static default defined in CarrierConfigManager.
+ return CarrierConfigManager.getDefaultConfig().getBoolean(key);
+ }
+
+ // If an invalid subId is used, this bundle will contain default values.
+ final int subId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+ final PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(subId);
+
+ return bundle != null
+ ? bundle.getBoolean(key)
+ : CarrierConfigManager.getDefaultConfig().getBoolean(key);
+ }
+
+ /** Returns whether is a correct default dialer which supports RTT. */
+ private static boolean isDefaultDialerSupportedRTT(Context context) {
+ return TextUtils.equals(
+ context.getString(R.string.config_rtt_setting_package_name),
+ TelecomUtil.getTelecomManager(context).getDefaultDialerPackage());
}
}
diff --git a/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceController.java b/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..156b9421765de8294557997fcf2654dffec731ca
--- /dev/null
+++ b/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceController.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+import android.hardware.display.ColorDisplayManager;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.SliderPreferenceController;
+import com.android.settings.widget.SeekBarPreference;
+
+/** PreferenceController for feature intensity. */
+public class ReduceBrightColorsIntensityPreferenceController extends SliderPreferenceController {
+
+ private static final int INVERSE_PERCENTAGE_BASE = 100;
+ private final ColorDisplayManager mColorDisplayManager;
+
+ public ReduceBrightColorsIntensityPreferenceController(Context context, String key) {
+ super(context, key);
+ mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (!ColorDisplayManager.isReduceBrightColorsAvailable(mContext)) {
+ return UNSUPPORTED_ON_DEVICE;
+ }
+ if (!mColorDisplayManager.isReduceBrightColorsActivated()) {
+ return DISABLED_DEPENDENT_SETTING;
+ }
+ return AVAILABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ final SeekBarPreference preference = screen.findPreference(getPreferenceKey());
+ preference.setContinuousUpdates(true);
+ preference.setMax(getMax());
+ preference.setMin(getMin());
+ preference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS);
+ updateState(preference);
+ }
+
+
+ @Override
+ public final void updateState(Preference preference) {
+ super.updateState(preference);
+ preference.setEnabled(mColorDisplayManager.isReduceBrightColorsActivated());
+ }
+
+ @Override
+ public int getSliderPosition() {
+ return INVERSE_PERCENTAGE_BASE - mColorDisplayManager.getReduceBrightColorsStrength();
+ }
+
+ @Override
+ public boolean setSliderPosition(int position) {
+ return mColorDisplayManager.setReduceBrightColorsStrength(
+ INVERSE_PERCENTAGE_BASE - position);
+ }
+
+ @Override
+ public int getMax() {
+ return INVERSE_PERCENTAGE_BASE
+ - ColorDisplayManager.getMinimumReduceBrightColorsStrength(mContext);
+ }
+
+ @Override
+ public int getMin() {
+ return INVERSE_PERCENTAGE_BASE
+ - ColorDisplayManager.getMaximumReduceBrightColorsStrength(mContext);
+ }
+}
diff --git a/src/com/android/settings/accessibility/ReduceBrightColorsPersistencePreferenceController.java b/src/com/android/settings/accessibility/ReduceBrightColorsPersistencePreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..62051c15390620bcd1b6f5afa3d69dd6bd59b569
--- /dev/null
+++ b/src/com/android/settings/accessibility/ReduceBrightColorsPersistencePreferenceController.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+import android.hardware.display.ColorDisplayManager;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+
+import com.android.settings.core.TogglePreferenceController;
+
+/** PreferenceController for persisting feature activation state after a restart. */
+public class ReduceBrightColorsPersistencePreferenceController extends TogglePreferenceController {
+ private final ColorDisplayManager mColorDisplayManager;
+
+ public ReduceBrightColorsPersistencePreferenceController(
+ Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (!ColorDisplayManager.isReduceBrightColorsAvailable(mContext)) {
+ return UNSUPPORTED_ON_DEVICE;
+ }
+ if (!mColorDisplayManager.isReduceBrightColorsActivated()) {
+ return DISABLED_DEPENDENT_SETTING;
+ }
+ return AVAILABLE;
+ }
+
+ @Override
+ public boolean isChecked() {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.REDUCE_BRIGHT_COLORS_PERSIST_ACROSS_REBOOTS, 0) == 1;
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ return Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.REDUCE_BRIGHT_COLORS_PERSIST_ACROSS_REBOOTS, (isChecked ? 1 : 0));
+ }
+
+ @Override
+ public final void updateState(Preference preference) {
+ super.updateState(preference);
+ preference.setEnabled(mColorDisplayManager.isReduceBrightColorsActivated());
+ }
+}
diff --git a/src/com/android/settings/accessibility/ReduceBrightColorsPreferenceController.java b/src/com/android/settings/accessibility/ReduceBrightColorsPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..d886a595d9c62306771fb348078d19f96910a797
--- /dev/null
+++ b/src/com/android/settings/accessibility/ReduceBrightColorsPreferenceController.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.hardware.display.ColorDisplayManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.widget.PrimarySwitchPreference;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+/** PreferenceController that shows the Reduce Bright Colors summary */
+public class ReduceBrightColorsPreferenceController extends TogglePreferenceController
+ implements LifecycleObserver, OnStart, OnStop {
+ private ContentObserver mSettingsContentObserver;
+ private PrimarySwitchPreference mPreference;
+ private final Context mContext;
+ private final ColorDisplayManager mColorDisplayManager;
+
+ public ReduceBrightColorsPreferenceController(Context context,
+ String preferenceKey) {
+ super(context, preferenceKey);
+ mContext = context;
+ mSettingsContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())){
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ final String path = uri == null ? null : uri.getLastPathSegment();
+ if (TextUtils.equals(path, Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED)) {
+ updateState(mPreference);
+ }
+ }
+ };
+ mColorDisplayManager = mContext.getSystemService(ColorDisplayManager.class);
+ }
+
+ @Override
+ public boolean isChecked() {
+ return mColorDisplayManager.isReduceBrightColorsActivated();
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ return mColorDisplayManager.setReduceBrightColorsActivated(isChecked);
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return mContext.getText(
+ R.string.reduce_bright_colors_preference_summary);
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ refreshSummary(preference);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return ColorDisplayManager.isReduceBrightColorsAvailable(mContext) ? AVAILABLE
+ : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ }
+
+ @Override
+ public void onStart() {
+ mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED),
+ false, mSettingsContentObserver, UserHandle.USER_CURRENT);
+ }
+ @Override
+ public void onStop() {
+ mContext.getContentResolver().unregisterContentObserver(mSettingsContentObserver);
+ }
+}
diff --git a/src/com/android/settings/accessibility/SharedPreferenceUtils.java b/src/com/android/settings/accessibility/SharedPreferenceUtils.java
deleted file mode 100644
index e99729180540a35d6676958be2da922617ddd4d2..0000000000000000000000000000000000000000
--- a/src/com/android/settings/accessibility/SharedPreferenceUtils.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.accessibility;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-
-import com.google.common.collect.ImmutableSet;
-
-import java.util.Set;
-
-/** Utility class for SharedPreferences. */
-public final class SharedPreferenceUtils {
-
- private static final String ACCESSIBILITY_PERF = "accessibility_prefs";
- private static final String USER_SHORTCUT_TYPE = "user_shortcut_type";
- private SharedPreferenceUtils() { }
-
- private static SharedPreferences getSharedPreferences(Context context, String fileName) {
- return context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
- }
-
- /** Returns a set of user shortcuts list to determine user preferred service shortcut. */
- public static Set getUserShortcutTypes(Context context) {
- return getSharedPreferences(context, ACCESSIBILITY_PERF)
- .getStringSet(USER_SHORTCUT_TYPE, ImmutableSet.of());
- }
-
- /** Sets a set of user shortcuts list to determine user preferred service shortcut. */
- public static void setUserShortcutType(Context context, Set data) {
- SharedPreferences.Editor editor = getSharedPreferences(context, ACCESSIBILITY_PERF).edit();
- editor.remove(USER_SHORTCUT_TYPE).apply();
- editor.putStringSet(USER_SHORTCUT_TYPE, data).apply();
- }
-}
diff --git a/src/com/android/settings/accessibility/ShortcutPreference.java b/src/com/android/settings/accessibility/ShortcutPreference.java
index 05c8a15adf9d762ea7d4acb30a639e3afca9ac45..cff91171abe64e3bd0ba9107fe69be5496fd3e83 100644
--- a/src/com/android/settings/accessibility/ShortcutPreference.java
+++ b/src/com/android/settings/accessibility/ShortcutPreference.java
@@ -62,8 +62,8 @@ public class ShortcutPreference extends Preference {
ShortcutPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setLayoutResource(R.layout.accessibility_shortcut_secondary_action);
- setWidgetLayoutResource(R.layout.preference_widget_master_switch);
- setIconSpaceReserved(true);
+ setWidgetLayoutResource(R.layout.preference_widget_primary_switch);
+ setIconSpaceReserved(false);
}
@Override
diff --git a/src/com/android/settings/accessibility/ShortcutsSettingsFragment.java b/src/com/android/settings/accessibility/ShortcutsSettingsFragment.java
new file mode 100644
index 0000000000000000000000000000000000000000..84268349b7c5b1d5d70a3cd643da3ab7e9c39494
--- /dev/null
+++ b/src/com/android/settings/accessibility/ShortcutsSettingsFragment.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.app.settings.SettingsEnums;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+/** Accessibility settings for accessibility shortcuts. */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class ShortcutsSettingsFragment extends DashboardFragment {
+
+ private static final String TAG = "ShortcutsSettingsFragment";
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.ACCESSIBILITY_SHORTCUTS_SETTINGS;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.accessibility_shortcuts_settings;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.accessibility_shortcuts_settings);
+
+}
diff --git a/src/com/android/settings/gestures/GlobalActionsPanelSettings.java b/src/com/android/settings/accessibility/SystemControlsFragment.java
similarity index 64%
rename from src/com/android/settings/gestures/GlobalActionsPanelSettings.java
rename to src/com/android/settings/accessibility/SystemControlsFragment.java
index 1ae2a2311732cbacd761660aebcd40bd22b75f60..237f2b4425b19732aa4c484bc14222ac67076a30 100644
--- a/src/com/android/settings/gestures/GlobalActionsPanelSettings.java
+++ b/src/com/android/settings/accessibility/SystemControlsFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,10 +11,10 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-package com.android.settings.gestures;
+package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
@@ -23,26 +23,28 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
-@SearchIndexable
-public class GlobalActionsPanelSettings extends DashboardFragment {
+/** Accessibility settings for system controls. */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class SystemControlsFragment extends DashboardFragment {
- private static final String TAG = "GlobalActionsPanelSettings";
+ private static final String TAG = "SystemControlsFragment";
@Override
public int getMetricsCategory() {
- return SettingsEnums.GLOBAL_ACTIONS_PANEL_SETTINGS;
+ return SettingsEnums.ACCESSIBILITY_SYSTEM_CONTROLS;
}
@Override
- protected String getLogTag() {
- return TAG;
+ protected int getPreferenceScreenResId() {
+ return R.xml.accessibility_system_controls;
}
@Override
- protected int getPreferenceScreenResId() {
- return R.xml.global_actions_panel_settings;
+ protected String getLogTag() {
+ return TAG;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
- new BaseSearchIndexProvider(R.xml.global_actions_panel_settings);
+ new BaseSearchIndexProvider(R.xml.accessibility_system_controls);
+
}
diff --git a/src/com/android/settings/gestures/DeviceControlsSettings.java b/src/com/android/settings/accessibility/TapAssistanceFragment.java
similarity index 67%
rename from src/com/android/settings/gestures/DeviceControlsSettings.java
rename to src/com/android/settings/accessibility/TapAssistanceFragment.java
index df36717cb0bf5789bc43efef9fe34cc48a31bddf..1a999ea39a2429b5c6d247de4376e0a3d96b3dfa 100644
--- a/src/com/android/settings/gestures/DeviceControlsSettings.java
+++ b/src/com/android/settings/accessibility/TapAssistanceFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settings.gestures;
+package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
@@ -23,26 +23,28 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
-@SearchIndexable
-public class DeviceControlsSettings extends DashboardFragment {
+/** Accessibility settings for tap assistance. */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class TapAssistanceFragment extends DashboardFragment {
- private static final String TAG = "QuickControlsSettings";
+ private static final String TAG = "TapAssistanceFragment";
@Override
public int getMetricsCategory() {
- return SettingsEnums.DEVICE_CONTROLS_SETTINGS;
+ return SettingsEnums.ACCESSIBILITY_TAP_ASSISTANCE;
}
@Override
- protected String getLogTag() {
- return TAG;
+ protected int getPreferenceScreenResId() {
+ return R.xml.accessibility_tap_assistance;
}
@Override
- protected int getPreferenceScreenResId() {
- return R.xml.device_controls_settings;
+ protected String getLogTag() {
+ return TAG;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
- new BaseSearchIndexProvider(R.xml.device_controls_settings);
+ new BaseSearchIndexProvider(R.xml.accessibility_tap_assistance);
+
}
diff --git a/src/com/android/settings/accessibility/TextAndDisplayFragment.java b/src/com/android/settings/accessibility/TextAndDisplayFragment.java
new file mode 100644
index 0000000000000000000000000000000000000000..e81dded9fbd05b2f7aeab3c8697cc3fa560edd68
--- /dev/null
+++ b/src/com/android/settings/accessibility/TextAndDisplayFragment.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.app.settings.SettingsEnums;
+import android.hardware.display.ColorDisplayManager;
+import android.os.Bundle;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.SwitchPreference;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+/** Accessibility settings for text and display. */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class TextAndDisplayFragment extends DashboardFragment {
+
+ private static final String TAG = "TextAndDisplayFragment";
+
+ private static final String CATEGORY_EXPERIMENTAL = "experimental_category";
+
+ // Preferences
+ private static final String DISPLAY_DALTONIZER_PREFERENCE_SCREEN = "daltonizer_preference";
+ private static final String TOGGLE_DISABLE_ANIMATIONS = "toggle_disable_animations";
+ private static final String TOGGLE_LARGE_POINTER_ICON = "toggle_large_pointer_icon";
+
+ private Preference mDisplayDaltonizerPreferenceScreen;
+ private SwitchPreference mToggleDisableAnimationsPreference;
+ private SwitchPreference mToggleLargePointerIconPreference;
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.ACCESSIBILITY_TEXT_AND_DISPLAY;
+ }
+
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ initializeAllPreferences();
+ updateSystemPreferences();
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.accessibility_text_and_display;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ private void initializeAllPreferences() {
+ // Display color adjustments.
+ mDisplayDaltonizerPreferenceScreen = findPreference(DISPLAY_DALTONIZER_PREFERENCE_SCREEN);
+
+ // Disable animation.
+ mToggleDisableAnimationsPreference = findPreference(TOGGLE_DISABLE_ANIMATIONS);
+
+ // Large pointer icon.
+ mToggleLargePointerIconPreference = findPreference(TOGGLE_LARGE_POINTER_ICON);
+ }
+
+ /**
+ * Updates preferences related to system configurations.
+ */
+ private void updateSystemPreferences() {
+ final PreferenceCategory experimentalCategory = getPreferenceScreen().findPreference(
+ CATEGORY_EXPERIMENTAL);
+ if (ColorDisplayManager.isColorTransformAccelerated(getContext())) {
+ mDisplayDaltonizerPreferenceScreen.setSummary(AccessibilityUtil.getSummary(
+ getContext(), Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED));
+ getPreferenceScreen().removePreference(experimentalCategory);
+ } else {
+ // Move following preferences to experimental category if device don't supports HWC
+ // hardware-accelerated color transform.
+ getPreferenceScreen().removePreference(mDisplayDaltonizerPreferenceScreen);
+ getPreferenceScreen().removePreference(mToggleDisableAnimationsPreference);
+ getPreferenceScreen().removePreference(mToggleLargePointerIconPreference);
+ experimentalCategory.addPreference(mDisplayDaltonizerPreferenceScreen);
+ experimentalCategory.addPreference(mToggleDisableAnimationsPreference);
+ experimentalCategory.addPreference(mToggleLargePointerIconPreference);
+ }
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.accessibility_text_and_display);
+}
diff --git a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
index 05147a8889d094118dcb2c155369b8f752e0a100..934907181a7d5b126f2e4c5ad505b6898af5e04c 100644
--- a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
@@ -16,17 +16,23 @@
package com.android.settings.accessibility;
+import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
import static com.android.settings.accessibility.AccessibilityStatsLogUtils.logAccessibilityServiceEnabled;
+import static com.android.settings.accessibility.PreferredShortcuts.retrieveUserShortcutType;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.net.Uri;
@@ -36,18 +42,20 @@ import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
+import android.widget.Switch;
-import androidx.preference.Preference;
-import androidx.preference.SwitchPreference;
+import androidx.annotation.Nullable;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
+import com.android.settings.widget.SettingsMainSwitchPreference;
import com.android.settingslib.accessibility.AccessibilityUtils;
import java.util.List;
@@ -57,7 +65,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class ToggleAccessibilityServicePreferenceFragment extends
ToggleFeaturePreferenceFragment {
- public static final int ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION = 1;
+ private static final String TAG = "ToggleAccessibilityServicePreferenceFragment";
+ private static final int ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION = 1;
private LockPatternUtils mLockPatternUtils;
private AtomicBoolean mIsDialogShown = new AtomicBoolean(/* initialValue= */ false);
@@ -72,6 +81,7 @@ public class ToggleAccessibilityServicePreferenceFragment extends
};
private Dialog mDialog;
+ private BroadcastReceiver mPackageRemovedReceiver;
@Override
public int getMetricsCategory() {
@@ -91,6 +101,17 @@ public class ToggleAccessibilityServicePreferenceFragment extends
mLockPatternUtils = new LockPatternUtils(getPrefContext());
}
+ @Override
+ public void onStart() {
+ super.onStart();
+ final AccessibilityServiceInfo serviceInfo = getAccessibilityServiceInfo();
+ if (serviceInfo == null) {
+ getActivity().finishAndRemoveTask();
+ } else if (!AccessibilityUtil.isSystemApp(serviceInfo)) {
+ registerPackageRemoveReceiver();
+ }
+ }
+
@Override
public void onResume() {
super.onResume();
@@ -109,6 +130,7 @@ public class ToggleAccessibilityServicePreferenceFragment extends
// capabilities. For
// example, before JellyBean MR2 the user was granting the explore by touch
// one.
+ @Nullable
AccessibilityServiceInfo getAccessibilityServiceInfo() {
final List infos = AccessibilityManager.getInstance(
getPrefContext()).getInstalledAccessibilityServiceList();
@@ -134,7 +156,8 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
mDialog = AccessibilityServiceWarning
.createCapabilitiesDialog(getPrefContext(), info,
- this::onDialogButtonFromEnableToggleClicked);
+ this::onDialogButtonFromEnableToggleClicked,
+ this::onDialogButtonFromUninstallClicked);
break;
}
case DialogEnums.ENABLE_WARNING_FROM_SHORTCUT_TOGGLE: {
@@ -144,7 +167,8 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
mDialog = AccessibilityServiceWarning
.createCapabilitiesDialog(getPrefContext(), info,
- this::onDialogButtonFromShortcutToggleClicked);
+ this::onDialogButtonFromShortcutToggleClicked,
+ this::onDialogButtonFromUninstallClicked);
break;
}
case DialogEnums.ENABLE_WARNING_FROM_SHORTCUT: {
@@ -154,7 +178,8 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
mDialog = AccessibilityServiceWarning
.createCapabilitiesDialog(getPrefContext(), info,
- this::onDialogButtonFromShortcutClicked);
+ this::onDialogButtonFromShortcutClicked,
+ this::onDialogButtonFromUninstallClicked);
break;
}
case DialogEnums.DISABLE_WARNING_FROM_TOGGLE: {
@@ -197,21 +222,26 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
@Override
- protected void updateToggleServiceTitle(SwitchPreference switchPreference) {
+ protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) {
final AccessibilityServiceInfo info = getAccessibilityServiceInfo();
final String switchBarText = (info == null) ? "" :
- getString(R.string.accessibility_service_master_switch_title,
+ getString(R.string.accessibility_service_primary_switch_title,
info.getResolveInfo().loadLabel(getPackageManager()));
switchPreference.setTitle(switchBarText);
}
- private void updateSwitchBarToggleSwitch() {
- final boolean checked = AccessibilityUtils.getEnabledServicesFromSettings(getPrefContext())
- .contains(mComponentName);
- if (mToggleServiceDividerSwitchPreference.isChecked() == checked) {
+ @Override
+ protected void updateSwitchBarToggleSwitch() {
+ final boolean checked = isAccessibilityServiceEnabled();
+ if (mToggleServiceSwitchPreference.isChecked() == checked) {
return;
}
- mToggleServiceDividerSwitchPreference.setChecked(checked);
+ mToggleServiceSwitchPreference.setChecked(checked);
+ }
+
+ private boolean isAccessibilityServiceEnabled() {
+ return AccessibilityUtils.getEnabledServicesFromSettings(getPrefContext())
+ .contains(mComponentName);
}
/**
@@ -243,6 +273,32 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
}
+ private void registerPackageRemoveReceiver() {
+ if (mPackageRemovedReceiver != null || getContext() == null) {
+ return;
+ }
+ mPackageRemovedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String packageName = intent.getData().getSchemeSpecificPart();
+ if (TextUtils.equals(mComponentName.getPackageName(), packageName)) {
+ getActivity().finishAndRemoveTask();
+ }
+ }
+ };
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ getContext().registerReceiver(mPackageRemovedReceiver, filter);
+ }
+
+ private void unregisterPackageRemoveReceiver() {
+ if (mPackageRemovedReceiver == null || getContext() == null) {
+ return;
+ }
+ getContext().unregisterReceiver(mPackageRemovedReceiver);
+ mPackageRemovedReceiver = null;
+ }
+
private boolean isServiceSupportAccessibilityButton() {
final AccessibilityManager ams = getPrefContext().getSystemService(
AccessibilityManager.class);
@@ -262,7 +318,6 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
private void handleConfirmServiceEnabled(boolean confirmed) {
- mToggleServiceDividerSwitchPreference.setChecked(confirmed);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, confirmed);
onPreferenceToggled(mPreferenceKey, confirmed);
}
@@ -285,16 +340,18 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
@Override
- protected void onInstallSwitchPreferenceToggleSwitch() {
- super.onInstallSwitchPreferenceToggleSwitch();
- mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener(this::onPreferenceClick);
+ public void onSwitchChanged(Switch switchView, boolean isChecked) {
+ if (isChecked != isAccessibilityServiceEnabled()) {
+ onPreferenceClick(isChecked);
+ }
}
@Override
public void onToggleClicked(ShortcutPreference preference) {
- final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
+ final int shortcutTypes = retrieveUserShortcutType(getPrefContext(),
+ mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
if (preference.isChecked()) {
- if (!mToggleServiceDividerSwitchPreference.isChecked()) {
+ if (!mToggleServiceSwitchPreference.isChecked()) {
preference.setChecked(false);
showPopupDialog(DialogEnums.ENABLE_WARNING_FROM_SHORTCUT_TOGGLE);
} else {
@@ -311,9 +368,8 @@ public class ToggleAccessibilityServicePreferenceFragment extends
@Override
public void onSettingsClicked(ShortcutPreference preference) {
- super.onSettingsClicked(preference);
final boolean isServiceOnOrShortcutAdded = mShortcutPreference.isChecked()
- || mToggleServiceDividerSwitchPreference.isChecked();
+ || mToggleServiceSwitchPreference.isChecked();
showPopupDialog(isServiceOnOrShortcutAdded ? DialogEnums.EDIT_SHORTCUT
: DialogEnums.ENABLE_WARNING_FROM_SHORTCUT);
}
@@ -340,10 +396,12 @@ public class ToggleAccessibilityServicePreferenceFragment extends
// Settings animated image.
final int animatedImageRes = arguments.getInt(
AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES);
- mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
- .authority(mComponentName.getPackageName())
- .appendPath(String.valueOf(animatedImageRes))
- .build();
+ if (animatedImageRes > 0) {
+ mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority(mComponentName.getPackageName())
+ .appendPath(String.valueOf(animatedImageRes))
+ .build();
+ }
// Get Accessibility service name.
mPackageName = getAccessibilityServiceInfo().getResolveInfo().loadLabel(
@@ -374,6 +432,35 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
}
+ private void onDialogButtonFromUninstallClicked() {
+ mDialog.dismiss();
+ final Intent uninstallIntent = createUninstallPackageActivityIntent();
+ if (uninstallIntent == null) {
+ return;
+ }
+ startActivity(uninstallIntent);
+ }
+
+ @Nullable
+ private Intent createUninstallPackageActivityIntent() {
+ final AccessibilityServiceInfo a11yServiceInfo = getAccessibilityServiceInfo();
+ if (a11yServiceInfo == null) {
+ Log.w(TAG, "createUnInstallIntent -- invalid a11yServiceInfo");
+ return null;
+ }
+ final ApplicationInfo appInfo =
+ a11yServiceInfo.getResolveInfo().serviceInfo.applicationInfo;
+ final Uri packageUri = Uri.parse("package:" + appInfo.packageName);
+ final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
+ return uninstallIntent;
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ unregisterPackageRemoveReceiver();
+ }
+
private void onAllowButtonFromEnableToggleClicked() {
if (isFullDiskEncrypted()) {
final String title = createConfirmCredentialReasonMessage();
@@ -411,7 +498,8 @@ public class ToggleAccessibilityServicePreferenceFragment extends
private void onAllowButtonFromShortcutToggleClicked() {
mShortcutPreference.setChecked(true);
- final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
+ final int shortcutTypes = retrieveUserShortcutType(getPrefContext(),
+ mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), shortcutTypes, mComponentName);
mIsDialogShown.set(false);
@@ -450,10 +538,9 @@ public class ToggleAccessibilityServicePreferenceFragment extends
mDialog.dismiss();
}
- private boolean onPreferenceClick(Preference preference) {
- boolean checked = ((DividerSwitchPreference) preference).isChecked();
- if (checked) {
- mToggleServiceDividerSwitchPreference.setChecked(false);
+ private boolean onPreferenceClick(boolean isChecked) {
+ if (isChecked) {
+ mToggleServiceSwitchPreference.setChecked(false);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED,
/* disableService */ false);
if (!mShortcutPreference.isChecked()) {
@@ -465,7 +552,7 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
}
} else {
- mToggleServiceDividerSwitchPreference.setChecked(true);
+ mToggleServiceSwitchPreference.setChecked(true);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED,
/* enableService */ true);
showDialog(DialogEnums.DISABLE_WARNING_FROM_TOGGLE);
diff --git a/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarController.java b/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarController.java
index 31ce9d8daf84299d320ec7cc2560064744d5433f..1a58b3968d27231c3cc3d1b5fa155aadc652bebe 100644
--- a/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarController.java
+++ b/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarController.java
@@ -30,6 +30,7 @@ import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
+import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
@@ -49,7 +50,9 @@ public class ToggleAutoclickCustomSeekbarController extends BasePreferenceContro
private static final String CONTROL_AUTOCLICK_DELAY_SECURE =
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY;
- private static final String KEY_CUSTOM_DELAY_VALUE = "custom_delay_value";
+
+ @VisibleForTesting
+ static final String KEY_CUSTOM_DELAY_VALUE = "custom_delay_value";
// Min allowed autoclick delay value.
static final int MIN_AUTOCLICK_DELAY_MS = 200;
@@ -59,7 +62,8 @@ public class ToggleAutoclickCustomSeekbarController extends BasePreferenceContro
// Allowed autoclick delay values are discrete.
// This is the difference between two allowed values.
- private static final int AUTOCLICK_DELAY_STEP = 100;
+ @VisibleForTesting
+ static final int AUTOCLICK_DELAY_STEP = 100;
private final SharedPreferences mSharedPreferences;
private final ContentResolver mContentResolver;
@@ -68,7 +72,8 @@ public class ToggleAutoclickCustomSeekbarController extends BasePreferenceContro
private SeekBar mSeekBar;
private TextView mDelayLabel;
- private final SeekBar.OnSeekBarChangeListener mSeekBarChangeListener =
+ @VisibleForTesting
+ final SeekBar.OnSeekBarChangeListener mSeekBarChangeListener =
new SeekBar.OnSeekBarChangeListener() {
@Override
diff --git a/src/com/android/settings/accessibility/ToggleAutoclickFooterPreferenceController.java b/src/com/android/settings/accessibility/ToggleAutoclickFooterPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..053fa9dc18e25a60e52931c959d47d7fc64ca3f4
--- /dev/null
+++ b/src/com/android/settings/accessibility/ToggleAutoclickFooterPreferenceController.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.content.Context;
+
+import com.android.settings.R;
+
+/**
+ * Preference controller for accessibility autoclick footer.
+ */
+public class ToggleAutoclickFooterPreferenceController extends
+ AccessibilityFooterPreferenceController {
+
+ public ToggleAutoclickFooterPreferenceController(Context context, String key) {
+ super(context, key);
+ }
+
+ @Override
+ protected String getLabelName() {
+ return mContext.getString(R.string.accessibility_autoclick_preference_title);
+ }
+
+ @Override
+ protected int getHelpResource() {
+ return R.string.help_url_autoclick;
+ }
+}
diff --git a/src/com/android/settings/accessibility/ToggleAutoclickPreferenceController.java b/src/com/android/settings/accessibility/ToggleAutoclickPreferenceController.java
index b9af7ce3ee80d1d71f6e8b5f56679cbfd1f3f593..78f31dfb779fd69a95b4e1bcedfd5783ee74cfe2 100644
--- a/src/com/android/settings/accessibility/ToggleAutoclickPreferenceController.java
+++ b/src/com/android/settings/accessibility/ToggleAutoclickPreferenceController.java
@@ -25,6 +25,7 @@ import android.content.res.Resources;
import android.provider.Settings;
import android.util.ArrayMap;
+import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LifecycleObserver;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -44,16 +45,23 @@ import java.util.Map;
public class ToggleAutoclickPreferenceController extends BasePreferenceController implements
LifecycleObserver, RadioButtonPreference.OnClickListener, PreferenceControllerMixin {
- private static final String CONTROL_AUTOCLICK_DELAY_SECURE =
+ @VisibleForTesting
+ static final String CONTROL_AUTOCLICK_DELAY_SECURE =
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY;
- private static final String KEY_AUTOCLICK_CUSTOM_SEEKBAR = "autoclick_custom_seekbar";
+
+ @VisibleForTesting
+ static final String KEY_AUTOCLICK_CUSTOM_SEEKBAR = "autoclick_custom_seekbar";
static final String KEY_DELAY_MODE = "delay_mode";
- private static final int AUTOCLICK_OFF_MODE = 0;
- private static final int AUTOCLICK_CUSTOM_MODE = 2000;
+ @VisibleForTesting
+ static final int AUTOCLICK_OFF_MODE = 0;
+
+ @VisibleForTesting
+ static final int AUTOCLICK_CUSTOM_MODE = 2000;
// Pair the preference key and autoclick mode value.
- private final Map mAccessibilityAutoclickKeyToValueMap = new ArrayMap<>();
+ @VisibleForTesting
+ Map mAccessibilityAutoclickKeyToValueMap = new ArrayMap<>();
private SharedPreferences mSharedPreferences;
private final ContentResolver mContentResolver;
@@ -70,13 +78,7 @@ public class ToggleAutoclickPreferenceController extends BasePreferenceControlle
private int mCurrentUiAutoClickMode;
public ToggleAutoclickPreferenceController(Context context, String preferenceKey) {
- super(context, preferenceKey);
-
- mSharedPreferences = context.getSharedPreferences(context.getPackageName(), MODE_PRIVATE);
- mContentResolver = context.getContentResolver();
- mResources = context.getResources();
-
- setAutoclickModeToKeyMap();
+ this(context, /* lifecycle= */ null, preferenceKey);
}
public ToggleAutoclickPreferenceController(Context context, Lifecycle lifecycle,
diff --git a/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java
index dfdcd9e9f5292985476a2920f470ba68b87a43e5..b41bafda5125528ec39c7fe9b9ecb9f7932cb000 100644
--- a/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java
@@ -31,15 +31,15 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import androidx.preference.SwitchPreference;
-
import com.android.settings.R;
+import com.android.settings.widget.SettingsMainSwitchPreference;
import java.util.ArrayList;
import java.util.List;
/** Settings page for color inversion. */
-public class ToggleColorInversionPreferenceFragment extends ToggleFeaturePreferenceFragment {
+public class ToggleColorInversionPreferenceFragment extends
+ ToggleFeaturePreferenceFragment {
private static final String ENABLED = Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED;
private final Handler mHandler = new Handler();
@@ -64,25 +64,14 @@ public class ToggleColorInversionPreferenceFragment extends ToggleFeaturePrefere
@Override
protected void onRemoveSwitchPreferenceToggleSwitch() {
super.onRemoveSwitchPreferenceToggleSwitch();
- mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener(null);
+ mToggleServiceSwitchPreference.setOnPreferenceClickListener(null);
}
@Override
- protected void updateToggleServiceTitle(SwitchPreference switchPreference) {
+ protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) {
switchPreference.setTitle(R.string.accessibility_display_inversion_switch_title);
}
- @Override
- protected void onInstallSwitchPreferenceToggleSwitch() {
- super.onInstallSwitchPreferenceToggleSwitch();
- updateSwitchBarToggleSwitch();
- mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener((preference) -> {
- boolean checked = ((SwitchPreference) preference).isChecked();
- onPreferenceToggled(mPreferenceKey, checked);
- return false;
- });
- }
-
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -91,7 +80,7 @@ public class ToggleColorInversionPreferenceFragment extends ToggleFeaturePrefere
mHtmlDescription = getText(R.string.accessibility_display_inversion_preference_subtitle);
mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(getPrefContext().getPackageName())
- .appendPath(String.valueOf(R.drawable.accessibility_color_inversion_banner))
+ .appendPath(String.valueOf(R.raw.accessibility_color_inversion_banner))
.build();
final List enableServiceFeatureKeys = new ArrayList<>(/* initialCapacity= */ 1);
enableServiceFeatureKeys.add(ENABLED);
@@ -122,23 +111,18 @@ public class ToggleColorInversionPreferenceFragment extends ToggleFeaturePrefere
return R.string.help_url_color_inversion;
}
- @Override
- public void onSettingsClicked(ShortcutPreference preference) {
- super.onSettingsClicked(preference);
- showDialog(DialogEnums.EDIT_SHORTCUT);
- }
-
@Override
int getUserShortcutTypes() {
return AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(),
mComponentName);
}
- private void updateSwitchBarToggleSwitch() {
+ @Override
+ protected void updateSwitchBarToggleSwitch() {
final boolean checked = Settings.Secure.getInt(getContentResolver(), ENABLED, OFF) == ON;
- if (mToggleServiceDividerSwitchPreference.isChecked() == checked) {
+ if (mToggleServiceSwitchPreference.isChecked() == checked) {
return;
}
- mToggleServiceDividerSwitchPreference.setChecked(checked);
+ mToggleServiceSwitchPreference.setChecked(checked);
}
}
diff --git a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java
index 8a4b7f8824b73e22bb622889dfcd7cc519e209fd..4bcf47811625a743660ca99e2cf9b4166f4f2704 100644
--- a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java
@@ -33,11 +33,10 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.widget.SettingsMainSwitchPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
@@ -99,34 +98,19 @@ public final class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePrefe
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- updatePreferenceOrder();
}
/** Customizes the order by preference key. */
- private List getPreferenceOrderList() {
- List lists = new ArrayList<>();
+ protected List getPreferenceOrderList() {
+ final List lists = new ArrayList<>();
lists.add(KEY_PREVIEW);
lists.add(KEY_USE_SERVICE_PREFERENCE);
lists.add(KEY_CATEGORY_MODE);
lists.add(KEY_GENERAL_CATEGORY);
- lists.add(KEY_INTRODUCTION_CATEGORY);
+ lists.add(KEY_HTML_DESCRIPTION_PREFERENCE);
return lists;
}
- private void updatePreferenceOrder() {
- List lists = getPreferenceOrderList();
- final PreferenceScreen preferenceScreen = getPreferenceScreen();
- preferenceScreen.setOrderingAsAdded(false);
-
- final int size = lists.size();
- for (int i = 0; i < size; i++) {
- final Preference preference = preferenceScreen.findPreference(lists.get(i));
- if (preference != null) {
- preference.setOrder(i);
- }
- }
- }
-
@Override
public void onResume() {
super.onResume();
@@ -175,29 +159,12 @@ public final class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePrefe
@Override
protected void onRemoveSwitchPreferenceToggleSwitch() {
super.onRemoveSwitchPreferenceToggleSwitch();
- mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener(null);
+ mToggleServiceSwitchPreference.setOnPreferenceClickListener(null);
}
@Override
- protected void updateToggleServiceTitle(SwitchPreference switchPreference) {
- switchPreference.setTitle(R.string.accessibility_daltonizer_master_switch_title);
- }
-
- @Override
- protected void onInstallSwitchPreferenceToggleSwitch() {
- super.onInstallSwitchPreferenceToggleSwitch();
- updateSwitchBarToggleSwitch();
- mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener((preference) -> {
- boolean checked = ((SwitchPreference) preference).isChecked();
- onPreferenceToggled(mPreferenceKey, checked);
- return false;
- });
- }
-
- @Override
- public void onSettingsClicked(ShortcutPreference preference) {
- super.onSettingsClicked(preference);
- showDialog(DialogEnums.EDIT_SHORTCUT);
+ protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) {
+ switchPreference.setTitle(R.string.accessibility_daltonizer_primary_switch_title);
}
@Override
@@ -206,12 +173,13 @@ public final class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePrefe
mComponentName);
}
- private void updateSwitchBarToggleSwitch() {
+ @Override
+ protected void updateSwitchBarToggleSwitch() {
final boolean checked = Settings.Secure.getInt(getContentResolver(), ENABLED, OFF) == ON;
- if (mToggleServiceDividerSwitchPreference.isChecked() == checked) {
+ if (mToggleServiceSwitchPreference.isChecked() == checked) {
return;
}
- mToggleServiceDividerSwitchPreference.setChecked(checked);
+ mToggleServiceSwitchPreference.setChecked(checked);
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
index d0b83b6b8e124124edb857b88d6a682f35eb8ef4..510f8d3484a298d57c85ee4069039d746a6715f8 100644
--- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
@@ -16,8 +16,7 @@
package com.android.settings.accessibility;
-import static com.android.settings.accessibility.AccessibilityUtil.getScreenHeightPixels;
-import static com.android.settings.accessibility.AccessibilityUtil.getScreenWidthPixels;
+import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
@@ -43,38 +42,40 @@ import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
import android.widget.CheckBox;
import android.widget.ImageView;
+import android.widget.Switch;
+import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
-import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.accessibility.AccessibilityDialogUtils.DialogType;
import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
-import com.android.settings.widget.SwitchBar;
+import com.android.settings.utils.LocaleUtils;
+import com.android.settings.widget.SettingsMainSwitchBar;
+import com.android.settings.widget.SettingsMainSwitchPreference;
+import com.android.settingslib.HelpUtils;
import com.android.settingslib.accessibility.AccessibilityUtils;
-import com.android.settingslib.widget.FooterPreference;
+import com.android.settingslib.widget.IllustrationPreference;
+import com.android.settingslib.widget.OnMainSwitchChangeListener;
+
+import com.google.android.setupcompat.util.WizardManagerHelper;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
import java.util.Locale;
-import java.util.Set;
-import java.util.StringJoiner;
-import java.util.stream.Collectors;
/**
* Base class for accessibility fragments with toggle, shortcut, some helper functions
* and dialog management.
*/
public abstract class ToggleFeaturePreferenceFragment extends SettingsPreferenceFragment
- implements ShortcutPreference.OnClickCallback {
+ implements ShortcutPreference.OnClickCallback, OnMainSwitchChangeListener {
- protected DividerSwitchPreference mToggleServiceDividerSwitchPreference;
+ protected SettingsMainSwitchPreference mToggleServiceSwitchPreference;
protected ShortcutPreference mShortcutPreference;
protected Preference mSettingsPreference;
protected String mPreferenceKey;
@@ -87,19 +88,24 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
protected Uri mImageUri;
private CharSequence mDescription;
protected CharSequence mHtmlDescription;
- // Used to restore the edit dialog status.
- protected int mUserShortcutTypesCache = UserShortcutType.EMPTY;
+
private static final String DRAWABLE_FOLDER = "drawable";
protected static final String KEY_USE_SERVICE_PREFERENCE = "use_service";
- protected static final String KEY_GENERAL_CATEGORY = "general_categories";
- protected static final String KEY_INTRODUCTION_CATEGORY = "introduction_categories";
+ public static final String KEY_GENERAL_CATEGORY = "general_categories";
+ protected static final String KEY_HTML_DESCRIPTION_PREFERENCE = "html_description";
private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
- private static final String EXTRA_SHORTCUT_TYPE = "shortcut_type";
+ protected static final String KEY_SAVED_USER_SHORTCUT_TYPE = "shortcut_type";
+ protected static final String KEY_ANIMATED_IMAGE = "animated_image";
+
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
- private int mUserShortcutTypes = UserShortcutType.EMPTY;
+ private SettingsContentObserver mSettingsContentObserver;
+
private CheckBox mSoftwareTypeCheckBox;
private CheckBox mHardwareTypeCheckBox;
- private SettingsContentObserver mSettingsContentObserver;
+
+ public static final int NOT_SET = -1;
+ // Save user's shortcutType value when savedInstance has value (e.g. device rotated).
+ protected int mSavedCheckBoxValue = NOT_SET;
// For html description of accessibility service, must follow the rule, such as
//
, a11y settings will get the resources successfully.
@@ -121,10 +127,17 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ // Restore the user shortcut type.
+ if (savedInstanceState != null && savedInstanceState.containsKey(
+ KEY_SAVED_USER_SHORTCUT_TYPE)) {
+ mSavedCheckBoxValue = savedInstanceState.getInt(KEY_SAVED_USER_SHORTCUT_TYPE, NOT_SET);
+ }
+
setupDefaultShortcutIfNecessary(getPrefContext());
final int resId = getPreferenceScreenResId();
if (resId <= 0) {
- PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
+ final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
getPrefContext());
setPreferenceScreen(preferenceScreen);
}
@@ -144,6 +157,21 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
+ // Need to be called as early as possible. Protected variables will be assigned here.
+ onProcessArguments(getArguments());
+
+ initAnimatedImagePreference();
+ initToggleServiceSwitchPreference();
+ initGeneralCategory();
+ initShortcutPreference();
+ initSettingsPreference();
+ initHtmlTextPreference();
+ initFooterPreference();
+
+ installActionBarToggleSwitch();
+
+ updateToggleServiceTitle(mToggleServiceSwitchPreference);
+
mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
removeDialog(DialogEnums.EDIT_SHORTCUT);
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
@@ -156,91 +184,16 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
super.onViewCreated(view, savedInstanceState);
final SettingsActivity activity = (SettingsActivity) getActivity();
- final SwitchBar switchBar = activity.getSwitchBar();
+ final SettingsMainSwitchBar switchBar = activity.getSwitchBar();
switchBar.hide();
- // Need to be called as early as possible. Protected variables will be assigned here.
- onProcessArguments(getArguments());
-
- PreferenceScreen preferenceScreen = getPreferenceScreen();
- if (mImageUri != null) {
- final int screenHalfHeight = getScreenHeightPixels(getPrefContext()) / /* half */ 2;
- final AnimatedImagePreference animatedImagePreference = new AnimatedImagePreference(
- getPrefContext());
- animatedImagePreference.setImageUri(mImageUri);
- animatedImagePreference.setSelectable(false);
- animatedImagePreference.setMaxHeight(screenHalfHeight);
- preferenceScreen.addPreference(animatedImagePreference);
- }
-
- mToggleServiceDividerSwitchPreference = new DividerSwitchPreference(getPrefContext());
- mToggleServiceDividerSwitchPreference.setKey(KEY_USE_SERVICE_PREFERENCE);
- if (getArguments().containsKey(AccessibilitySettings.EXTRA_CHECKED)) {
- final boolean enabled = getArguments().getBoolean(AccessibilitySettings.EXTRA_CHECKED);
- mToggleServiceDividerSwitchPreference.setChecked(enabled);
- }
-
- preferenceScreen.addPreference(mToggleServiceDividerSwitchPreference);
-
- updateToggleServiceTitle(mToggleServiceDividerSwitchPreference);
-
- final PreferenceCategory groupCategory = new PreferenceCategory(getPrefContext());
- groupCategory.setKey(KEY_GENERAL_CATEGORY);
- groupCategory.setTitle(R.string.accessibility_screen_option);
- preferenceScreen.addPreference(groupCategory);
-
- initShortcutPreference(savedInstanceState);
- groupCategory.addPreference(mShortcutPreference);
-
- // Show the "Settings" menu as if it were a preference screen.
- if (mSettingsTitle != null && mSettingsIntent != null) {
- mSettingsPreference = new Preference(getPrefContext());
- mSettingsPreference.setTitle(mSettingsTitle);
- mSettingsPreference.setIconSpaceReserved(true);
- mSettingsPreference.setIntent(mSettingsIntent);
- }
-
- // The downloaded app may not show Settings. The framework app has Settings.
- if (mSettingsPreference != null) {
- groupCategory.addPreference(mSettingsPreference);
- }
-
- if (!TextUtils.isEmpty(mHtmlDescription)) {
- final PreferenceCategory introductionCategory = new PreferenceCategory(
- getPrefContext());
- final CharSequence title = getString(R.string.accessibility_introduction_title,
- mPackageName);
- introductionCategory.setKey(KEY_INTRODUCTION_CATEGORY);
- introductionCategory.setTitle(title);
- preferenceScreen.addPreference(introductionCategory);
-
- final HtmlTextPreference htmlTextPreference = new HtmlTextPreference(getPrefContext());
- htmlTextPreference.setSummary(mHtmlDescription);
- htmlTextPreference.setImageGetter(mImageGetter);
- htmlTextPreference.setSelectable(false);
- introductionCategory.addPreference(htmlTextPreference);
- }
-
- if (!TextUtils.isEmpty(mDescription)) {
- createFooterPreference(mDescription);
- }
-
- if (TextUtils.isEmpty(mHtmlDescription) && TextUtils.isEmpty(mDescription)) {
- final CharSequence defaultDescription = getText(
- R.string.accessibility_service_default_description);
- createFooterPreference(defaultDescription);
- }
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- installActionBarToggleSwitch();
+ updatePreferenceOrder();
}
@Override
public void onResume() {
super.onResume();
+
final AccessibilityManager am = getPrefContext().getSystemService(
AccessibilityManager.class);
am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
@@ -260,7 +213,10 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
@Override
public void onSaveInstanceState(Bundle outState) {
- outState.putInt(EXTRA_SHORTCUT_TYPE, mUserShortcutTypesCache);
+ final int value = getShortcutTypeCheckBoxValue();
+ if (value != NOT_SET) {
+ outState.putInt(KEY_SAVED_USER_SHORTCUT_TYPE, value);
+ }
super.onSaveInstanceState(outState);
}
@@ -271,9 +227,12 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
case DialogEnums.EDIT_SHORTCUT:
final CharSequence dialogTitle = getPrefContext().getString(
R.string.accessibility_shortcut_title, mPackageName);
- dialog = AccessibilityEditDialogUtils.showEditShortcutDialog(
- getPrefContext(), dialogTitle, this::callOnAlertDialogCheckboxClicked);
- initializeDialogCheckBox(dialog);
+ final int dialogType = WizardManagerHelper.isAnySetupWizard(getIntent())
+ ? DialogType.EDIT_SHORTCUT_GENERIC_SUW : DialogType.EDIT_SHORTCUT_GENERIC;
+ dialog = AccessibilityDialogUtils.showEditShortcutDialog(
+ getPrefContext(), dialogType, dialogTitle,
+ this::callOnAlertDialogCheckboxClicked);
+ setupEditShortcutDialog(dialog);
return dialog;
case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
dialog = AccessibilityGestureNavigationTutorial
@@ -298,92 +257,58 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
}
}
- /** Denotes the dialog emuns for show dialog */
- @Retention(RetentionPolicy.SOURCE)
- protected @interface DialogEnums {
-
- /** OPEN: Settings > Accessibility > Any toggle service > Shortcut > Settings. */
- int EDIT_SHORTCUT = 1;
-
- /** OPEN: Settings > Accessibility > Magnification > Shortcut > Settings. */
- int MAGNIFICATION_EDIT_SHORTCUT = 1001;
-
- /**
- * OPEN: Settings > Accessibility > Downloaded toggle service > Toggle use service to
- * enable service.
- */
- int ENABLE_WARNING_FROM_TOGGLE = 1002;
-
- /** OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox. */
- int ENABLE_WARNING_FROM_SHORTCUT = 1003;
-
- /**
- * OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox
- * toggle.
- */
- int ENABLE_WARNING_FROM_SHORTCUT_TOGGLE = 1004;
-
- /**
- * OPEN: Settings > Accessibility > Downloaded toggle service > Toggle use service to
- * disable service.
- */
- int DISABLE_WARNING_FROM_TOGGLE = 1005;
-
- /**
- * OPEN: Settings > Accessibility > Magnification > Toggle user service in button
- * navigation.
- */
- int ACCESSIBILITY_BUTTON_TUTORIAL = 1006;
-
- /**
- * OPEN: Settings > Accessibility > Magnification > Toggle user service in gesture
- * navigation.
- */
- int GESTURE_NAVIGATION_TUTORIAL = 1007;
-
- /**
- * OPEN: Settings > Accessibility > Downloaded toggle service > Toggle user service > Show
- * launch tutorial.
- */
- int LAUNCH_ACCESSIBILITY_TUTORIAL = 1008;
- }
-
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_SERVICE;
}
+ @Override
+ public int getHelpResource() {
+ return 0;
+ }
+
@Override
public void onDestroyView() {
super.onDestroyView();
removeActionBarToggleSwitch();
}
+ @Override
+ public void onSwitchChanged(Switch switchView, boolean isChecked) {
+ onPreferenceToggled(mPreferenceKey, isChecked);
+ }
+
/**
* Returns the shortcut type list which has been checked by user.
*/
abstract int getUserShortcutTypes();
- protected void updateToggleServiceTitle(SwitchPreference switchPreference) {
- switchPreference.setTitle(R.string.accessibility_service_master_switch_title);
+ protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) {
+ switchPreference.setTitle(R.string.accessibility_service_primary_switch_title);
}
protected abstract void onPreferenceToggled(String preferenceKey, boolean enabled);
protected void onInstallSwitchPreferenceToggleSwitch() {
// Implement this to set a checked listener.
+ updateSwitchBarToggleSwitch();
+ mToggleServiceSwitchPreference.addOnSwitchChangeListener(this);
}
protected void onRemoveSwitchPreferenceToggleSwitch() {
// Implement this to reset a checked listener.
}
+ protected void updateSwitchBarToggleSwitch() {
+ // Implement this to update the state of switch.
+ }
+
private void installActionBarToggleSwitch() {
onInstallSwitchPreferenceToggleSwitch();
}
private void removeActionBarToggleSwitch() {
- mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener(null);
+ mToggleServiceSwitchPreference.setOnPreferenceClickListener(null);
onRemoveSwitchPreferenceToggleSwitch();
}
@@ -415,6 +340,31 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
}
}
+ /** Customizes the order by preference key. */
+ protected List getPreferenceOrderList() {
+ final List lists = new ArrayList<>();
+ lists.add(KEY_ANIMATED_IMAGE);
+ lists.add(KEY_USE_SERVICE_PREFERENCE);
+ lists.add(KEY_GENERAL_CATEGORY);
+ lists.add(KEY_HTML_DESCRIPTION_PREFERENCE);
+ return lists;
+ }
+
+ private void updatePreferenceOrder() {
+ final List lists = getPreferenceOrderList();
+
+ final PreferenceScreen preferenceScreen = getPreferenceScreen();
+ preferenceScreen.setOrderingAsAdded(false);
+
+ final int size = lists.size();
+ for (int i = 0; i < size; i++) {
+ final Preference preference = preferenceScreen.findPreference(lists.get(i));
+ if (preference != null) {
+ preference.setOrder(i);
+ }
+ }
+ }
+
private Drawable getDrawableFromUri(Uri imageUri) {
if (mImageGetterCacheView == null) {
mImageGetterCacheView = new ImageView(getPrefContext());
@@ -432,8 +382,8 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
mImageGetterCacheView.setImageURI(null);
final int imageWidth = drawable.getIntrinsicWidth();
final int imageHeight = drawable.getIntrinsicHeight();
- final int screenHalfHeight = getScreenHeightPixels(getPrefContext()) / /* half */ 2;
- if ((imageWidth > getScreenWidthPixels(getPrefContext()))
+ final int screenHalfHeight = AccessibilityUtil.getScreenHeightPixels(getPrefContext()) / 2;
+ if ((imageWidth > AccessibilityUtil.getScreenWidthPixels(getPrefContext()))
|| (imageHeight > screenHalfHeight)) {
return null;
}
@@ -444,60 +394,161 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
return drawable;
}
- static final class AccessibilityUserShortcutType {
- private static final char COMPONENT_NAME_SEPARATOR = ':';
- private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
- new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
+ private void initAnimatedImagePreference() {
+ if (mImageUri == null) {
+ return;
+ }
- private String mComponentName;
- private int mType;
+ final IllustrationPreference illustrationPreference =
+ new IllustrationPreference(getPrefContext());
+ illustrationPreference.setImageUri(mImageUri);
+ illustrationPreference.setSelectable(false);
+ illustrationPreference.setKey(KEY_ANIMATED_IMAGE);
- AccessibilityUserShortcutType(String componentName, int type) {
- this.mComponentName = componentName;
- this.mType = type;
- }
+ getPreferenceScreen().addPreference(illustrationPreference);
+ }
- AccessibilityUserShortcutType(String flattenedString) {
- sStringColonSplitter.setString(flattenedString);
- if (sStringColonSplitter.hasNext()) {
- this.mComponentName = sStringColonSplitter.next();
- this.mType = Integer.parseInt(sStringColonSplitter.next());
- }
+ private void initToggleServiceSwitchPreference() {
+ mToggleServiceSwitchPreference = new SettingsMainSwitchPreference(getPrefContext());
+ mToggleServiceSwitchPreference.setKey(KEY_USE_SERVICE_PREFERENCE);
+ if (getArguments().containsKey(AccessibilitySettings.EXTRA_CHECKED)) {
+ final boolean enabled = getArguments().getBoolean(AccessibilitySettings.EXTRA_CHECKED);
+ mToggleServiceSwitchPreference.setChecked(enabled);
}
- String getComponentName() {
- return mComponentName;
- }
+ getPreferenceScreen().addPreference(mToggleServiceSwitchPreference);
+ }
+
+ private void initGeneralCategory() {
+ final PreferenceCategory generalCategory = new PreferenceCategory(getPrefContext());
+ generalCategory.setKey(KEY_GENERAL_CATEGORY);
+ generalCategory.setTitle(R.string.accessibility_screen_option);
+
+ getPreferenceScreen().addPreference(generalCategory);
+ }
+
+ protected void initShortcutPreference() {
+ // Initial the shortcut preference.
+ mShortcutPreference = new ShortcutPreference(getPrefContext(), /* attrs= */ null);
+ mShortcutPreference.setPersistent(false);
+ mShortcutPreference.setKey(getShortcutPreferenceKey());
+ mShortcutPreference.setOnClickCallback(this);
- void setComponentName(String componentName) {
- this.mComponentName = componentName;
+ final CharSequence title = getString(R.string.accessibility_shortcut_title, mPackageName);
+ mShortcutPreference.setTitle(title);
+
+ final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
+ generalCategory.addPreference(mShortcutPreference);
+ }
+
+ protected void initSettingsPreference() {
+ if (mSettingsTitle == null || mSettingsIntent == null) {
+ return;
}
- int getType() {
- return mType;
+ // Show the "Settings" menu as if it were a preference screen.
+ mSettingsPreference = new Preference(getPrefContext());
+ mSettingsPreference.setTitle(mSettingsTitle);
+ mSettingsPreference.setIconSpaceReserved(false);
+ mSettingsPreference.setIntent(mSettingsIntent);
+
+ final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
+ generalCategory.addPreference(mSettingsPreference);
+ }
+
+ private void initHtmlTextPreference() {
+ if (TextUtils.isEmpty(mHtmlDescription)) {
+ return;
}
+ final PreferenceScreen screen = getPreferenceScreen();
+ final CharSequence htmlDescription = Html.fromHtml(mHtmlDescription.toString(),
+ Html.FROM_HTML_MODE_COMPACT, mImageGetter, /* tagHandler= */ null);
+ final String iconContentDescription =
+ getString(R.string.accessibility_introduction_title, mPackageName);
+
+ final AccessibilityFooterPreference htmlFooterPreference =
+ new AccessibilityFooterPreference(screen.getContext());
+ htmlFooterPreference.setKey(KEY_HTML_DESCRIPTION_PREFERENCE);
+ htmlFooterPreference.setSummary(htmlDescription);
+ htmlFooterPreference.setContentDescription(
+ generateFooterContentDescription(htmlDescription));
+
+ // Only framework tools support help link
+ if (getHelpResource() != 0) {
+ htmlFooterPreference.setLearnMoreAction(view -> {
+ final Intent helpIntent = HelpUtils.getHelpIntent(
+ getContext(), getContext().getString(getHelpResource()),
+ getContext().getClass().getName());
+ view.startActivityForResult(helpIntent, 0);
+ });
+
+ final String learnMoreContentDescription = getPrefContext().getString(
+ R.string.footer_learn_more_content_description, mPackageName);
+ htmlFooterPreference.setLearnMoreContentDescription(learnMoreContentDescription);
+ htmlFooterPreference.setLinkEnabled(true);
+ } else {
+ htmlFooterPreference.setLinkEnabled(false);
+ }
+ screen.addPreference(htmlFooterPreference);
+ }
- void setType(int type) {
- this.mType = type;
+ private void initFooterPreference() {
+ if (!TextUtils.isEmpty(mDescription)) {
+ createFooterPreference(getPreferenceScreen(), mDescription,
+ getString(R.string.accessibility_introduction_title, mPackageName));
}
- String flattenToString() {
- final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR));
- joiner.add(mComponentName);
- joiner.add(String.valueOf(mType));
- return joiner.toString();
+ if (TextUtils.isEmpty(mHtmlDescription) && TextUtils.isEmpty(mDescription)) {
+ final CharSequence defaultDescription =
+ getText(R.string.accessibility_service_default_description);
+ createFooterPreference(getPreferenceScreen(), defaultDescription,
+ getString(R.string.accessibility_introduction_title, mPackageName));
}
}
- private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) {
- final View dialogTextArea = dialogView.findViewById(R.id.container);
- dialogTextArea.setOnClickListener(v -> {
- checkBox.toggle();
- updateUserShortcutType(/* saveChanges= */ false);
- });
- }
- private void initializeDialogCheckBox(Dialog dialog) {
+ /**
+ * Creates {@link AccessibilityFooterPreference} and append into {@link PreferenceScreen}
+ *
+ * @param screen The preference screen to add the footer preference
+ * @param summary The summary of the preference summary.
+ * @param iconContentDescription The content description of icon in the footer.
+ */
+ @VisibleForTesting
+ void createFooterPreference(PreferenceScreen screen, CharSequence summary,
+ String iconContentDescription) {
+ final AccessibilityFooterPreference footerPreference =
+ new AccessibilityFooterPreference(screen.getContext());
+ footerPreference.setSummary(summary);
+ footerPreference.setContentDescription(
+ generateFooterContentDescription(summary));
+
+ // Only framework tools support help link
+ if (getHelpResource() != 0) {
+ footerPreference.setLearnMoreAction(view -> {
+ final Intent helpIntent = HelpUtils.getHelpIntent(
+ getContext(), getContext().getString(getHelpResource()),
+ getContext().getClass().getName());
+ view.startActivityForResult(helpIntent, 0);
+ });
+
+ final String learnMoreContentDescription = getPrefContext().getString(
+ R.string.footer_learn_more_content_description, mPackageName);
+ footerPreference.setLearnMoreContentDescription(learnMoreContentDescription);
+ }
+ screen.addPreference(footerPreference);
+ }
+
+ private CharSequence generateFooterContentDescription(CharSequence footerContent) {
+ final StringBuffer sb = new StringBuffer();
+ sb.append(getPrefContext().getString(
+ R.string.accessibility_introduction_title, mPackageName))
+ .append("\n\n")
+ .append(footerContent);
+ return sb;
+ }
+ @VisibleForTesting
+ void setupEditShortcutDialog(Dialog dialog) {
final View dialogSoftwareView = dialog.findViewById(R.id.software_shortcut);
mSoftwareTypeCheckBox = dialogSoftwareView.findViewById(R.id.checkbox);
setDialogTextAreaClickListener(dialogSoftwareView, mSoftwareTypeCheckBox);
@@ -506,57 +557,58 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
mHardwareTypeCheckBox = dialogHardwareView.findViewById(R.id.checkbox);
setDialogTextAreaClickListener(dialogHardwareView, mHardwareTypeCheckBox);
- updateAlertDialogCheckState();
+ updateEditShortcutDialogCheckBox();
}
- private void updateAlertDialogCheckState() {
- if (mUserShortcutTypesCache != UserShortcutType.EMPTY) {
- updateCheckStatus(mSoftwareTypeCheckBox, UserShortcutType.SOFTWARE);
- updateCheckStatus(mHardwareTypeCheckBox, UserShortcutType.HARDWARE);
- }
+ private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) {
+ final View dialogTextArea = dialogView.findViewById(R.id.container);
+ dialogTextArea.setOnClickListener(v -> checkBox.toggle());
}
- private void updateCheckStatus(CheckBox checkBox, @UserShortcutType int type) {
- checkBox.setChecked((mUserShortcutTypesCache & type) == type);
+ private void updateEditShortcutDialogCheckBox() {
+ // If it is during onConfigChanged process then restore the value, or get the saved value
+ // when shortcutPreference is checked.
+ int value = restoreOnConfigChangedValue();
+ if (value == NOT_SET) {
+ final int lastNonEmptyUserShortcutType = PreferredShortcuts.retrieveUserShortcutType(
+ getPrefContext(), mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
+ value = mShortcutPreference.isChecked() ? lastNonEmptyUserShortcutType
+ : UserShortcutType.EMPTY;
+ }
+
+ mSoftwareTypeCheckBox.setChecked(
+ hasShortcutType(value, UserShortcutType.SOFTWARE));
+ mHardwareTypeCheckBox.setChecked(
+ hasShortcutType(value, UserShortcutType.HARDWARE));
}
- private void updateUserShortcutType(boolean saveChanges) {
- mUserShortcutTypesCache = UserShortcutType.EMPTY;
- if (mSoftwareTypeCheckBox.isChecked()) {
- mUserShortcutTypesCache |= UserShortcutType.SOFTWARE;
- }
- if (mHardwareTypeCheckBox.isChecked()) {
- mUserShortcutTypesCache |= UserShortcutType.HARDWARE;
- }
+ private int restoreOnConfigChangedValue() {
+ final int savedValue = mSavedCheckBoxValue;
+ mSavedCheckBoxValue = NOT_SET;
+ return savedValue;
+ }
- if (saveChanges) {
- final boolean isChanged = (mUserShortcutTypesCache != UserShortcutType.EMPTY);
- if (isChanged) {
- setUserShortcutType(getPrefContext(), mUserShortcutTypesCache);
- }
- mUserShortcutTypes = mUserShortcutTypesCache;
- }
+ private boolean hasShortcutType(int value, @UserShortcutType int type) {
+ return (value & type) == type;
}
- private void setUserShortcutType(Context context, int type) {
- if (mComponentName == null) {
- return;
+ /**
+ * Returns accumulated {@link UserShortcutType} checkbox value or {@code NOT_SET} if checkboxes
+ * did not exist.
+ */
+ protected int getShortcutTypeCheckBoxValue() {
+ if (mSoftwareTypeCheckBox == null || mHardwareTypeCheckBox == null) {
+ return NOT_SET;
}
- Set info = SharedPreferenceUtils.getUserShortcutTypes(context);
- final String componentName = mComponentName.flattenToString();
- if (info.isEmpty()) {
- info = new HashSet<>();
- } else {
- final Set filtered = info.stream()
- .filter(str -> str.contains(componentName))
- .collect(Collectors.toSet());
- info.removeAll(filtered);
+ int value = UserShortcutType.EMPTY;
+ if (mSoftwareTypeCheckBox.isChecked()) {
+ value |= UserShortcutType.SOFTWARE;
}
- final AccessibilityUserShortcutType shortcut = new AccessibilityUserShortcutType(
- componentName, type);
- info.add(shortcut.flattenToString());
- SharedPreferenceUtils.setUserShortcutType(context, info);
+ if (mHardwareTypeCheckBox.isChecked()) {
+ value |= UserShortcutType.HARDWARE;
+ }
+ return value;
}
protected CharSequence getShortcutTypeSummary(Context context) {
@@ -568,20 +620,17 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
return context.getText(R.string.switch_off_text);
}
- final int shortcutTypes = getUserShortcutTypes(context, UserShortcutType.SOFTWARE);
- int resId = R.string.accessibility_shortcut_edit_summary_software;
- if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
- resId = AccessibilityUtil.isTouchExploreEnabled(context)
- ? R.string.accessibility_shortcut_edit_dialog_title_software_gesture_talkback
- : R.string.accessibility_shortcut_edit_dialog_title_software_gesture;
- }
- final CharSequence softwareTitle = context.getText(resId);
+ final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(context,
+ mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
- List list = new ArrayList<>();
- if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
+ final List list = new ArrayList<>();
+ final CharSequence softwareTitle = context.getText(
+ R.string.accessibility_shortcut_edit_summary_software);
+
+ if (hasShortcutType(shortcutTypes, UserShortcutType.SOFTWARE)) {
list.add(softwareTitle);
}
- if ((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE) {
+ if (hasShortcutType(shortcutTypes, UserShortcutType.HARDWARE)) {
final CharSequence hardwareTitle = context.getText(
R.string.accessibility_shortcut_hardware_keyword);
list.add(hardwareTitle);
@@ -591,50 +640,29 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
if (list.isEmpty()) {
list.add(softwareTitle);
}
- final String joinStrings = TextUtils.join(/* delimiter= */", ", list);
return CaseMap.toTitle().wholeString().noLowercase().apply(Locale.getDefault(), /* iter= */
- null, joinStrings);
- }
-
- protected int getUserShortcutTypes(Context context, @UserShortcutType int defaultValue) {
- if (mComponentName == null) {
- return defaultValue;
- }
-
- final Set info = SharedPreferenceUtils.getUserShortcutTypes(context);
- final String componentName = mComponentName.flattenToString();
- final Set filtered = info.stream()
- .filter(str -> str.contains(componentName))
- .collect(Collectors.toSet());
- if (filtered.isEmpty()) {
- return defaultValue;
- }
-
- final String str = (String) filtered.toArray()[0];
- final AccessibilityUserShortcutType shortcut = new AccessibilityUserShortcutType(str);
- return shortcut.getType();
+ null, LocaleUtils.getConcatenatedString(list));
}
/**
* This method will be invoked when a button in the edit shortcut dialog is clicked.
*
* @param dialog The dialog that received the click
- * @param which The button that was clicked
+ * @param which The button that was clicked
*/
protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
if (mComponentName == null) {
return;
}
- updateUserShortcutType(/* saveChanges= */ true);
- AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), mUserShortcutTypes,
- mComponentName);
- AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), ~mUserShortcutTypes,
- mComponentName);
- mShortcutPreference.setChecked(mUserShortcutTypes != UserShortcutType.EMPTY);
- mShortcutPreference.setSummary(
- getShortcutTypeSummary(getPrefContext()));
+ final int value = getShortcutTypeCheckBoxValue();
+
+ saveNonEmptyUserShortcutType(value);
+ AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), value, mComponentName);
+ AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), ~value, mComponentName);
+ mShortcutPreference.setChecked(value != UserShortcutType.EMPTY);
+ mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
protected void updateShortcutPreferenceData() {
@@ -642,47 +670,29 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
return;
}
- // Get the user shortcut type from settings provider.
- mUserShortcutTypes = AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(),
- mComponentName);
- if (mUserShortcutTypes != UserShortcutType.EMPTY) {
- setUserShortcutType(getPrefContext(), mUserShortcutTypes);
- } else {
- // Get the user shortcut type from shared_prefs if cannot get from settings provider.
- mUserShortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
+ final int shortcutTypes = AccessibilityUtil.getUserShortcutTypesFromSettings(
+ getPrefContext(), mComponentName);
+ if (shortcutTypes != UserShortcutType.EMPTY) {
+ final PreferredShortcut shortcut = new PreferredShortcut(
+ mComponentName.flattenToString(), shortcutTypes);
+ PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
}
}
- private void initShortcutPreference(Bundle savedInstanceState) {
- // Restore the user shortcut type.
- if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_SHORTCUT_TYPE)) {
- mUserShortcutTypesCache = savedInstanceState.getInt(EXTRA_SHORTCUT_TYPE,
- UserShortcutType.EMPTY);
- }
-
- // Initial the shortcut preference.
- mShortcutPreference = new ShortcutPreference(getPrefContext(), null);
- mShortcutPreference.setPersistent(false);
- mShortcutPreference.setKey(getShortcutPreferenceKey());
- mShortcutPreference.setOnClickCallback(this);
-
- final CharSequence title = getString(R.string.accessibility_shortcut_title, mPackageName);
- mShortcutPreference.setTitle(title);
- }
-
protected void updateShortcutPreference() {
if (mComponentName == null) {
return;
}
- final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
+ final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(),
+ mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
mShortcutPreference.setChecked(
- AccessibilityUtil.hasValuesInSettings(getPrefContext(), shortcutTypes,
- mComponentName));
+ AccessibilityUtil.hasValuesInSettings(getPrefContext(), shortcutTypes,
+ mComponentName));
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
- private String getShortcutPreferenceKey() {
+ protected String getShortcutPreferenceKey() {
return KEY_SHORTCUT_PREFERENCE;
}
@@ -692,7 +702,8 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
return;
}
- final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
+ final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(),
+ mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
if (preference.isChecked()) {
AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), shortcutTypes,
mComponentName);
@@ -706,20 +717,11 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
@Override
public void onSettingsClicked(ShortcutPreference preference) {
- // Do not restore shortcut in shortcut chooser dialog when shortcutPreference is turned off.
- mUserShortcutTypesCache = mShortcutPreference.isChecked()
- ? getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE)
- : UserShortcutType.EMPTY;
- }
-
- private void createFooterPreference(CharSequence title) {
- final PreferenceScreen preferenceScreen = getPreferenceScreen();
- preferenceScreen.addPreference(new FooterPreference.Builder(getActivity()).setTitle(
- title).build());
+ showDialog(DialogEnums.EDIT_SHORTCUT);
}
/**
- * Setups a configurable default if the setting has never been set.
+ * Setups a configurable default if the setting has never been set.
*/
private static void setupDefaultShortcutIfNecessary(Context context) {
final String targetKey = Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
@@ -745,4 +747,15 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
shortcutName.flattenToString());
}
}
+
+ @VisibleForTesting
+ void saveNonEmptyUserShortcutType(int type) {
+ if (type == UserShortcutType.EMPTY) {
+ return;
+ }
+
+ final PreferredShortcut shortcut = new PreferredShortcut(
+ mComponentName.flattenToString(), type);
+ PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
+ }
}
diff --git a/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java
new file mode 100644
index 0000000000000000000000000000000000000000..e60751e56b557b3d230b28e39d8f5f0d2adbec9e
--- /dev/null
+++ b/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility;
+
+import android.app.settings.SettingsEnums;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.hardware.display.ColorDisplayManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.preference.PreferenceCategory;
+import androidx.preference.SwitchPreference;
+
+import com.android.internal.accessibility.AccessibilityShortcutController;
+import com.android.settings.R;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.widget.SeekBarPreference;
+import com.android.settings.widget.SettingsMainSwitchPreference;
+import com.android.settingslib.search.SearchIndexable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Settings for reducing brightness. */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class ToggleReduceBrightColorsPreferenceFragment extends ToggleFeaturePreferenceFragment {
+
+ private static final String REDUCE_BRIGHT_COLORS_ACTIVATED_KEY =
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED;
+ private static final String KEY_INTENSITY = "rbc_intensity";
+ private static final String KEY_PERSIST = "rbc_persist";
+
+ private final Handler mHandler = new Handler();
+ private SettingsContentObserver mSettingsContentObserver;
+ private ReduceBrightColorsIntensityPreferenceController mRbcIntensityPreferenceController;
+ private ReduceBrightColorsPersistencePreferenceController mRbcPersistencePreferenceController;
+ private ColorDisplayManager mColorDisplayManager;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority(getPrefContext().getPackageName())
+ .appendPath(String.valueOf(R.raw.extra_dim_banner))
+ .build();
+ mComponentName = AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME;
+ mPackageName = getText(R.string.reduce_bright_colors_preference_title);
+ mHtmlDescription = getText(R.string.reduce_bright_colors_preference_subtitle);
+ final List enableServiceFeatureKeys = new ArrayList<>(/* initialCapacity= */ 1);
+ enableServiceFeatureKeys.add(REDUCE_BRIGHT_COLORS_ACTIVATED_KEY);
+ mRbcIntensityPreferenceController =
+ new ReduceBrightColorsIntensityPreferenceController(getContext(), KEY_INTENSITY);
+ mRbcPersistencePreferenceController =
+ new ReduceBrightColorsPersistencePreferenceController(getContext(), KEY_PERSIST);
+ mRbcIntensityPreferenceController.displayPreference(getPreferenceScreen());
+ mRbcPersistencePreferenceController.displayPreference(getPreferenceScreen());
+ mSettingsContentObserver = new SettingsContentObserver(mHandler, enableServiceFeatureKeys) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateSwitchBarToggleSwitch();
+ }
+ };
+ mColorDisplayManager = getContext().getSystemService(ColorDisplayManager.class);
+ final View view = super.onCreateView(inflater, container, savedInstanceState);
+ // Parent sets the title when creating the view, so set it after calling super
+ mToggleServiceSwitchPreference.setTitle(R.string.reduce_bright_colors_switch_title);
+ updateGeneralCategoryOrder();
+ return view;
+ }
+
+ private void updateGeneralCategoryOrder() {
+ final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
+ final SeekBarPreference intensity = findPreference(KEY_INTENSITY);
+ getPreferenceScreen().removePreference(intensity);
+ intensity.setOrder(mShortcutPreference.getOrder() - 2);
+ generalCategory.addPreference(intensity);
+ final SwitchPreference persist = findPreference(KEY_PERSIST);
+ getPreferenceScreen().removePreference(persist);
+ persist.setOrder(mShortcutPreference.getOrder() - 1);
+ generalCategory.addPreference(persist);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateSwitchBarToggleSwitch();
+ mSettingsContentObserver.register(getContentResolver());
+ }
+
+ @Override
+ public void onPause() {
+ mSettingsContentObserver.unregister(getContentResolver());
+ super.onPause();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.REDUCE_BRIGHT_COLORS_SETTINGS;
+ }
+
+ @Override
+ public int getHelpResource() {
+ // TODO(170973645): Link to help support page
+ return 0;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.reduce_bright_colors_settings;
+ }
+
+
+ @Override
+ protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
+ AccessibilityStatsLogUtils.logAccessibilityServiceEnabled(mComponentName, enabled);
+ mColorDisplayManager.setReduceBrightColorsActivated(enabled);
+ }
+
+ @Override
+ protected void onRemoveSwitchPreferenceToggleSwitch() {
+ super.onRemoveSwitchPreferenceToggleSwitch();
+ mToggleServiceSwitchPreference.setOnPreferenceClickListener(
+ /* onPreferenceClickListener= */ null);
+ }
+
+ @Override
+ protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) {
+ switchPreference.setTitle(R.string.reduce_bright_colors_preference_title);
+ }
+
+ @Override
+ int getUserShortcutTypes() {
+ return AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(),
+ mComponentName);
+ }
+
+ @Override
+ protected void updateSwitchBarToggleSwitch() {
+ final boolean checked = mColorDisplayManager.isReduceBrightColorsActivated();
+ mRbcIntensityPreferenceController.updateState(getPreferenceScreen()
+ .findPreference(KEY_INTENSITY));
+ mRbcPersistencePreferenceController.updateState(getPreferenceScreen()
+ .findPreference(KEY_PERSIST));
+ if (mToggleServiceSwitchPreference.isChecked() != checked) {
+ mToggleServiceSwitchPreference.setChecked(checked);
+ }
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.reduce_bright_colors_settings) {
+ @Override
+ protected boolean isPageSearchEnabled(Context context) {
+ return ColorDisplayManager.isReduceBrightColorsAvailable(context);
+ }
+ };
+}
diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
index 0b221e561da0bee75ab2cdfdc95b6b48c799ca70..9266f720ca2172a0804815f5dae965dca614f441 100644
--- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
@@ -17,6 +17,7 @@
package com.android.settings.accessibility;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
@@ -38,41 +39,44 @@ import android.view.accessibility.AccessibilityManager.TouchExplorationStateChan
import android.widget.CheckBox;
import androidx.appcompat.app.AlertDialog;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.DialogCreatable;
import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityDialogUtils.DialogType;
import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
+import com.android.settings.utils.LocaleUtils;
+
+import com.google.android.setupcompat.util.WizardManagerHelper;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
import java.util.Locale;
-import java.util.Set;
import java.util.StringJoiner;
-import java.util.stream.Collectors;
/**
* Fragment that shows the actual UI for providing basic magnification accessibility service setup
* and does not have toggle bar to turn on service to use.
*/
public class ToggleScreenMagnificationPreferenceFragment extends
- ToggleFeaturePreferenceFragment {
-
- private static final String EXTRA_SHORTCUT_TYPE = "shortcut_type";
- private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
+ ToggleFeaturePreferenceFragment implements
+ MagnificationModePreferenceController.DialogHelper {
+ // TODO(b/147021230): Move duplicated functions with android/internal/accessibility into util.
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
- private int mUserShortcutType = UserShortcutType.EMPTY;
+
private CheckBox mSoftwareTypeCheckBox;
private CheckBox mHardwareTypeCheckBox;
private CheckBox mTripleTapTypeCheckBox;
- // TODO(b/147021230): Will move common functions and variables to
- // android/internal/accessibility folder. For now, magnification need to be treated
- // individually.
private static final char COMPONENT_NAME_SEPARATOR = ':';
private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
+ private MagnificationModePreferenceController mModePreferenceController;
+ private DialogCreatable mDialogDelegate;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -85,7 +89,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends
mPackageName = getString(R.string.accessibility_screen_magnification_title);
mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(getPrefContext().getPackageName())
- .appendPath(String.valueOf(R.drawable.accessibility_magnification_banner))
+ .appendPath(String.valueOf(R.raw.accessibility_magnification_banner))
.build();
mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
removeDialog(DialogEnums.EDIT_SHORTCUT);
@@ -94,19 +98,6 @@ public class ToggleScreenMagnificationPreferenceFragment extends
return super.onCreateView(inflater, container, savedInstanceState);
}
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- initShortcutPreference();
-
- super.onViewCreated(view, savedInstanceState);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- outState.putInt(EXTRA_SHORTCUT_TYPE, mUserShortcutTypesCache);
- super.onSaveInstanceState(outState);
- }
-
@Override
public void onResume() {
super.onResume();
@@ -114,9 +105,6 @@ public class ToggleScreenMagnificationPreferenceFragment extends
final AccessibilityManager am = getPrefContext().getSystemService(
AccessibilityManager.class);
am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
-
- updateShortcutPreferenceData();
- updateShortcutPreference();
}
@Override
@@ -130,6 +118,12 @@ public class ToggleScreenMagnificationPreferenceFragment extends
@Override
public Dialog onCreateDialog(int dialogId) {
+ if (mDialogDelegate != null) {
+ final Dialog dialog = mDialogDelegate.onCreateDialog(dialogId);
+ if (dialog != null) {
+ return dialog;
+ }
+ }
final AlertDialog dialog;
switch (dialogId) {
case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
@@ -138,25 +132,71 @@ public class ToggleScreenMagnificationPreferenceFragment extends
case DialogEnums.MAGNIFICATION_EDIT_SHORTCUT:
final CharSequence dialogTitle = getPrefContext().getString(
R.string.accessibility_shortcut_title, mPackageName);
- dialog = AccessibilityEditDialogUtils.showMagnificationEditShortcutDialog(
- getPrefContext(), dialogTitle,
- this::callOnAlertDialogCheckboxClicked);
- initializeDialogCheckBox(dialog);
+ final int dialogType = WizardManagerHelper.isAnySetupWizard(getIntent())
+ ? DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW
+ : DialogType.EDIT_SHORTCUT_MAGNIFICATION;
+ dialog = AccessibilityDialogUtils.showEditShortcutDialog(getPrefContext(),
+ dialogType, dialogTitle, this::callOnAlertDialogCheckboxClicked);
+ setupMagnificationEditShortcutDialog(dialog);
return dialog;
default:
return super.onCreateDialog(dialogId);
}
}
- private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) {
- final View dialogTextArea = dialogView.findViewById(R.id.container);
- dialogTextArea.setOnClickListener(v -> {
- checkBox.toggle();
- updateUserShortcutType(/* saveChanges= */ false);
- });
+ @Override
+ protected void initSettingsPreference() {
+ // If the device doesn't support magnification area, it should hide the settings preference.
+ if (!getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_magnification_area)) {
+ return;
+ }
+ mSettingsPreference = new Preference(getPrefContext());
+ mSettingsPreference.setTitle(R.string.accessibility_magnification_mode_title);
+ mSettingsPreference.setKey(MagnificationModePreferenceController.PREF_KEY);
+ mSettingsPreference.setPersistent(false);
+
+ final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
+ generalCategory.addPreference(mSettingsPreference);
+
+ mModePreferenceController = new MagnificationModePreferenceController(getContext(),
+ MagnificationModePreferenceController.PREF_KEY);
+ mModePreferenceController.setDialogHelper(this);
+ getSettingsLifecycle().addObserver(mModePreferenceController);
+ mModePreferenceController.displayPreference(getPreferenceScreen());
+ }
+
+ @Override
+ public void showDialog(int dialogId) {
+ super.showDialog(dialogId);
}
- private void initializeDialogCheckBox(AlertDialog dialog) {
+ @Override
+ public void setDialogDelegate(DialogCreatable delegate) {
+ mDialogDelegate = delegate;
+ }
+
+ @Override
+ protected int getShortcutTypeCheckBoxValue() {
+ if (mSoftwareTypeCheckBox == null || mHardwareTypeCheckBox == null) {
+ return NOT_SET;
+ }
+
+ int value = UserShortcutType.EMPTY;
+ if (mSoftwareTypeCheckBox.isChecked()) {
+ value |= UserShortcutType.SOFTWARE;
+ }
+ if (mHardwareTypeCheckBox.isChecked()) {
+ value |= UserShortcutType.HARDWARE;
+ }
+ if (mTripleTapTypeCheckBox.isChecked()) {
+ value |= UserShortcutType.TRIPLETAP;
+ }
+ return value;
+ }
+
+ @VisibleForTesting
+ void setupMagnificationEditShortcutDialog(AlertDialog dialog) {
final View dialogSoftwareView = dialog.findViewById(R.id.software_shortcut);
mSoftwareTypeCheckBox = dialogSoftwareView.findViewById(R.id.checkbox);
setDialogTextAreaClickListener(dialogSoftwareView, mSoftwareTypeCheckBox);
@@ -170,67 +210,46 @@ public class ToggleScreenMagnificationPreferenceFragment extends
setDialogTextAreaClickListener(dialogTripleTapView, mTripleTapTypeCheckBox);
final View advancedView = dialog.findViewById(R.id.advanced_shortcut);
- updateAlertDialogCheckState();
-
- // Window magnification mode doesn't support advancedView.
- if (isWindowMagnification(getPrefContext())) {
- advancedView.setVisibility(View.GONE);
- return;
- }
- // Shows the triple tap checkbox directly if clicked.
if (mTripleTapTypeCheckBox.isChecked()) {
advancedView.setVisibility(View.GONE);
dialogTripleTapView.setVisibility(View.VISIBLE);
}
- }
- private void updateAlertDialogCheckState() {
- if (mUserShortcutTypesCache != UserShortcutType.EMPTY) {
- updateCheckStatus(mSoftwareTypeCheckBox, UserShortcutType.SOFTWARE);
- updateCheckStatus(mHardwareTypeCheckBox, UserShortcutType.HARDWARE);
- updateCheckStatus(mTripleTapTypeCheckBox, UserShortcutType.TRIPLETAP);
- }
+ updateMagnificationEditShortcutDialogCheckBox();
}
- private void updateCheckStatus(CheckBox checkBox, @UserShortcutType int type) {
- checkBox.setChecked((mUserShortcutTypesCache & type) == type);
+ private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) {
+ final View dialogTextArea = dialogView.findViewById(R.id.container);
+ dialogTextArea.setOnClickListener(v -> checkBox.toggle());
}
- private void updateUserShortcutType(boolean saveChanges) {
- mUserShortcutTypesCache = UserShortcutType.EMPTY;
- if (mSoftwareTypeCheckBox.isChecked()) {
- mUserShortcutTypesCache |= UserShortcutType.SOFTWARE;
- }
- if (mHardwareTypeCheckBox.isChecked()) {
- mUserShortcutTypesCache |= UserShortcutType.HARDWARE;
- }
- if (mTripleTapTypeCheckBox.isChecked()) {
- mUserShortcutTypesCache |= UserShortcutType.TRIPLETAP;
+ private void updateMagnificationEditShortcutDialogCheckBox() {
+ // If it is during onConfigChanged process then restore the value, or get the saved value
+ // when shortcutPreference is checked.
+ int value = restoreOnConfigChangedValue();
+ if (value == NOT_SET) {
+ final int lastNonEmptyUserShortcutType = PreferredShortcuts.retrieveUserShortcutType(
+ getPrefContext(), MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.SOFTWARE);
+ value = mShortcutPreference.isChecked() ? lastNonEmptyUserShortcutType
+ : UserShortcutType.EMPTY;
}
- if (saveChanges) {
- final boolean isChanged = (mUserShortcutTypesCache != UserShortcutType.EMPTY);
- if (isChanged) {
- setUserShortcutType(getPrefContext(), mUserShortcutTypesCache);
- }
- mUserShortcutType = mUserShortcutTypesCache;
- }
+ mSoftwareTypeCheckBox.setChecked(
+ hasShortcutType(value, UserShortcutType.SOFTWARE));
+ mHardwareTypeCheckBox.setChecked(
+ hasShortcutType(value, UserShortcutType.HARDWARE));
+ mTripleTapTypeCheckBox.setChecked(
+ hasShortcutType(value, UserShortcutType.TRIPLETAP));
}
- private void setUserShortcutType(Context context, int type) {
- Set info = SharedPreferenceUtils.getUserShortcutTypes(context);
- if (info.isEmpty()) {
- info = new HashSet<>();
- } else {
- final Set filtered = info.stream().filter(
- str -> str.contains(MAGNIFICATION_CONTROLLER_NAME)).collect(
- Collectors.toSet());
- info.removeAll(filtered);
- }
- final AccessibilityUserShortcutType shortcut = new AccessibilityUserShortcutType(
- MAGNIFICATION_CONTROLLER_NAME, type);
- info.add(shortcut.flattenToString());
- SharedPreferenceUtils.setUserShortcutType(context, info);
+ private int restoreOnConfigChangedValue() {
+ final int savedValue = mSavedCheckBoxValue;
+ mSavedCheckBoxValue = NOT_SET;
+ return savedValue;
+ }
+
+ private boolean hasShortcutType(int value, @UserShortcutType int type) {
+ return (value & type) == type;
}
@Override
@@ -239,26 +258,23 @@ public class ToggleScreenMagnificationPreferenceFragment extends
return context.getText(R.string.switch_off_text);
}
- final int shortcutType = getUserShortcutTypes(context, UserShortcutType.EMPTY);
- int resId = R.string.accessibility_shortcut_edit_summary_software;
- if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
- resId = AccessibilityUtil.isTouchExploreEnabled(context)
- ? R.string.accessibility_shortcut_edit_dialog_title_software_gesture_talkback
- : R.string.accessibility_shortcut_edit_dialog_title_software_gesture;
- }
- final CharSequence softwareTitle = context.getText(resId);
+ final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(context,
+ MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.SOFTWARE);
- List list = new ArrayList<>();
- if ((shortcutType & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
+ final List list = new ArrayList<>();
+ final CharSequence softwareTitle = context.getText(
+ R.string.accessibility_shortcut_edit_summary_software);
+
+ if (hasShortcutType(shortcutTypes, UserShortcutType.SOFTWARE)) {
list.add(softwareTitle);
}
- if ((shortcutType & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE) {
+ if (hasShortcutType(shortcutTypes, UserShortcutType.HARDWARE)) {
final CharSequence hardwareTitle = context.getText(
R.string.accessibility_shortcut_hardware_keyword);
list.add(hardwareTitle);
}
- if ((shortcutType & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP) {
+ if (hasShortcutType(shortcutTypes, UserShortcutType.TRIPLETAP)) {
final CharSequence tripleTapTitle = context.getText(
R.string.accessibility_shortcut_triple_tap_keyword);
list.add(tripleTapTitle);
@@ -268,35 +284,26 @@ public class ToggleScreenMagnificationPreferenceFragment extends
if (list.isEmpty()) {
list.add(softwareTitle);
}
- final String joinStrings = TextUtils.join(/* delimiter= */", ", list);
return CaseMap.toTitle().wholeString().noLowercase().apply(Locale.getDefault(), /* iter= */
- null, joinStrings);
+ null, LocaleUtils.getConcatenatedString(list));
}
@Override
- protected int getUserShortcutTypes(Context context, @UserShortcutType int defaultValue) {
- final Set info = SharedPreferenceUtils.getUserShortcutTypes(context);
- final Set filtered = info.stream().filter(
- str -> str.contains(MAGNIFICATION_CONTROLLER_NAME)).collect(
- Collectors.toSet());
- if (filtered.isEmpty()) {
- return defaultValue;
- }
+ protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
+ final int value = getShortcutTypeCheckBoxValue();
- final String str = (String) filtered.toArray()[0];
- final AccessibilityUserShortcutType shortcut = new AccessibilityUserShortcutType(str);
- return shortcut.getType();
+ saveNonEmptyUserShortcutType(value);
+ optInAllMagnificationValuesToSettings(getPrefContext(), value);
+ optOutAllMagnificationValuesFromSettings(getPrefContext(), ~value);
+ mShortcutPreference.setChecked(value != UserShortcutType.EMPTY);
+ mShortcutPreference.setSummary(
+ getShortcutTypeSummary(getPrefContext()));
}
@Override
- protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
- updateUserShortcutType(/* saveChanges= */ true);
- optInAllMagnificationValuesToSettings(getPrefContext(), mUserShortcutType);
- optOutAllMagnificationValuesFromSettings(getPrefContext(), ~mUserShortcutType);
- mShortcutPreference.setChecked(mUserShortcutType != UserShortcutType.EMPTY);
- mShortcutPreference.setSummary(
- getShortcutTypeSummary(getPrefContext()));
+ public int getHelpResource() {
+ return R.string.help_url_magnification;
}
@Override
@@ -307,6 +314,13 @@ public class ToggleScreenMagnificationPreferenceFragment extends
@Override
public int getDialogMetricsCategory(int dialogId) {
+ if (mDialogDelegate != null) {
+ final int category = mDialogDelegate.getDialogMetricsCategory(dialogId);
+ if (category != 0) {
+ return category;
+ }
+ }
+
switch (dialogId) {
case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION;
@@ -336,13 +350,13 @@ public class ToggleScreenMagnificationPreferenceFragment extends
@Override
protected void onInstallSwitchPreferenceToggleSwitch() {
- super.onInstallSwitchPreferenceToggleSwitch();
- mToggleServiceDividerSwitchPreference.setVisible(false);
+ mToggleServiceSwitchPreference.setVisible(false);
}
@Override
public void onToggleClicked(ShortcutPreference preference) {
- final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
+ final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(),
+ MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.SOFTWARE);
if (preference.isChecked()) {
optInAllMagnificationValuesToSettings(getPrefContext(), shortcutTypes);
showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
@@ -354,44 +368,54 @@ public class ToggleScreenMagnificationPreferenceFragment extends
@Override
public void onSettingsClicked(ShortcutPreference preference) {
- // Do not restore shortcut in shortcut chooser dialog when shortcutPreference is turned off.
- mUserShortcutTypesCache = mShortcutPreference.isChecked()
- ? getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE)
- : UserShortcutType.EMPTY;
showDialog(DialogEnums.MAGNIFICATION_EDIT_SHORTCUT);
}
@Override
protected void updateShortcutPreferenceData() {
- // Get the user shortcut type from settings provider.
- mUserShortcutType = getUserShortcutTypeFromSettings(getPrefContext());
- if (mUserShortcutType != UserShortcutType.EMPTY) {
- setUserShortcutType(getPrefContext(), mUserShortcutType);
- } else {
- // Get the user shortcut type from shared_prefs if cannot get from settings provider.
- mUserShortcutType = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
+ final int shortcutTypes = getUserShortcutTypeFromSettings(getPrefContext());
+ if (shortcutTypes != UserShortcutType.EMPTY) {
+ final PreferredShortcut shortcut = new PreferredShortcut(
+ MAGNIFICATION_CONTROLLER_NAME, shortcutTypes);
+ PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
}
}
- private void initShortcutPreference() {
+ @Override
+ protected void initShortcutPreference() {
mShortcutPreference = new ShortcutPreference(getPrefContext(), null);
mShortcutPreference.setPersistent(false);
- mShortcutPreference.setKey(KEY_SHORTCUT_PREFERENCE);
+ mShortcutPreference.setKey(getShortcutPreferenceKey());
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
mShortcutPreference.setOnClickCallback(this);
final CharSequence title = getString(R.string.accessibility_shortcut_title, mPackageName);
mShortcutPreference.setTitle(title);
+
+ final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
+ generalCategory.addPreference(mShortcutPreference);
}
@Override
protected void updateShortcutPreference() {
- final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
+ final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(),
+ MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.SOFTWARE);
mShortcutPreference.setChecked(
hasMagnificationValuesInSettings(getPrefContext(), shortcutTypes));
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
+ @VisibleForTesting
+ void saveNonEmptyUserShortcutType(int type) {
+ if (type == UserShortcutType.EMPTY) {
+ return;
+ }
+
+ final PreferredShortcut shortcut = new PreferredShortcut(
+ MAGNIFICATION_CONTROLLER_NAME, type);
+ PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
+ }
+
@VisibleForTesting
static void optInAllMagnificationValuesToSettings(Context context, int shortcutTypes) {
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java
index 3b786ff3107bbcc5e96419f2f6ce9195159be8a5..016ac05987349592de105ba67c23a66a01f0c4b1 100644
--- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java
+++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java
@@ -18,6 +18,7 @@ package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import android.os.Bundle;
+import android.view.View;
public class ToggleScreenMagnificationPreferenceFragmentForSetupWizard
extends ToggleScreenMagnificationPreferenceFragment {
@@ -32,14 +33,28 @@ public class ToggleScreenMagnificationPreferenceFragmentForSetupWizard
// Log the final choice in value if it's different from the previous value.
Bundle args = getArguments();
if ((args != null) && args.containsKey(AccessibilitySettings.EXTRA_CHECKED)) {
- if (mToggleServiceDividerSwitchPreference.isChecked() != args.getBoolean(
+ if (mToggleServiceSwitchPreference.isChecked() != args.getBoolean(
AccessibilitySettings.EXTRA_CHECKED)) {
// TODO: Distinguish between magnification modes
mMetricsFeatureProvider.action(getContext(),
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION,
- mToggleServiceDividerSwitchPreference.isChecked());
+ mToggleServiceSwitchPreference.isChecked());
}
}
super.onStop();
}
+
+ @Override
+ public int getHelpResource() {
+ // Hides help center in action bar and footer bar in SuW
+ return 0;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ // Hide the setting from the vision settings.
+ mSettingsPreference.setVisible(false);
+ }
}
diff --git a/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java
index 4d7b65359bb5d273da086a6d4664f891d1cad944..c0d54e8eeed7b18cf13a7c05e36ca1a13a6f23ec 100644
--- a/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java
+++ b/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java
@@ -28,7 +28,7 @@ public class ToggleScreenReaderPreferenceFragmentForSetupWizard
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- mToggleSwitchWasInitiallyChecked = mToggleServiceDividerSwitchPreference.isChecked();
+ mToggleSwitchWasInitiallyChecked = mToggleServiceSwitchPreference.isChecked();
}
@Override
@@ -39,10 +39,10 @@ public class ToggleScreenReaderPreferenceFragmentForSetupWizard
@Override
public void onStop() {
// Log the final choice in value if it's different from the previous value.
- if (mToggleServiceDividerSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
+ if (mToggleServiceSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
mMetricsFeatureProvider.action(getContext(),
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SCREEN_READER,
- mToggleServiceDividerSwitchPreference.isChecked());
+ mToggleServiceSwitchPreference.isChecked());
}
super.onStop();
}
diff --git a/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java
index 87b846945a78725d0a69d27a7aad57fde952b2c8..4334cd0e7a0404aff8b1a5d09a00c36b2294c8ba 100644
--- a/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java
+++ b/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java
@@ -28,7 +28,7 @@ public class ToggleSelectToSpeakPreferenceFragmentForSetupWizard
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- mToggleSwitchWasInitiallyChecked = mToggleServiceDividerSwitchPreference.isChecked();
+ mToggleSwitchWasInitiallyChecked = mToggleServiceSwitchPreference.isChecked();
}
@Override
@@ -39,10 +39,10 @@ public class ToggleSelectToSpeakPreferenceFragmentForSetupWizard
@Override
public void onStop() {
// Log the final choice in value if it's different from the previous value.
- if (mToggleServiceDividerSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
+ if (mToggleServiceSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
mMetricsFeatureProvider.action(getContext(),
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SELECT_TO_SPEAK,
- mToggleServiceDividerSwitchPreference.isChecked());
+ mToggleServiceSwitchPreference.isChecked());
}
super.onStop();
diff --git a/src/com/android/settings/accessibility/VideoPlayer.java b/src/com/android/settings/accessibility/VideoPlayer.java
index 8f94b768f67f2e7093d2db8ed103dc184c85dd67..d4aa6a6a69b6a288bddd0419f0a50037a56c956c 100644
--- a/src/com/android/settings/accessibility/VideoPlayer.java
+++ b/src/com/android/settings/accessibility/VideoPlayer.java
@@ -25,6 +25,7 @@ import android.view.TextureView.SurfaceTextureListener;
import androidx.annotation.GuardedBy;
import androidx.annotation.RawRes;
+import androidx.annotation.VisibleForTesting;
/**
* Plays the video by {@link MediaPlayer} on {@link TextureView}, calls {@link #create(Context, int,
@@ -32,19 +33,25 @@ import androidx.annotation.RawRes;
* is no longer used, call {@link #release()} so that MediaPlayer object can be released.
*/
public class VideoPlayer implements SurfaceTextureListener {
- private final Context context;
- private final Object mediaPlayerLock = new Object();
+ private final Context mContext;
+ private final Object mMediaPlayerLock = new Object();
// Media player object can't be used after it has been released, so it will be set to null. But
// VideoPlayer is asynchronized, media player object might be paused or resumed again before
// released media player is set to null. Therefore, lock mediaPlayer and mediaPlayerState by
// mediaPlayerLock keep their states consistent.
+ @VisibleForTesting
@GuardedBy("mediaPlayerLock")
- private MediaPlayer mediaPlayer;
+ MediaPlayer mMediaPlayer;
+
+ @VisibleForTesting
@GuardedBy("mediaPlayerLock")
- private State mediaPlayerState = State.NONE;
+ State mMediaPlayerState = State.NONE;
+
@RawRes
- private final int videoRes;
- private Surface animationSurface;
+ private final int mVideoRes;
+
+ @VisibleForTesting
+ Surface mAnimationSurface;
/**
@@ -58,54 +65,54 @@ public class VideoPlayer implements SurfaceTextureListener {
}
private VideoPlayer(Context context, @RawRes int videoRes, TextureView textureView) {
- this.context = context;
- this.videoRes = videoRes;
+ this.mContext = context;
+ this.mVideoRes = videoRes;
textureView.setSurfaceTextureListener(this);
}
public void pause() {
- synchronized (mediaPlayerLock) {
- if (mediaPlayerState == State.STARTED) {
- mediaPlayerState = State.PAUSED;
- mediaPlayer.pause();
+ synchronized (mMediaPlayerLock) {
+ if (mMediaPlayerState == State.STARTED) {
+ mMediaPlayerState = State.PAUSED;
+ mMediaPlayer.pause();
}
}
}
public void resume() {
- synchronized (mediaPlayerLock) {
- if (mediaPlayerState == State.PAUSED) {
- mediaPlayer.start();
- mediaPlayerState = State.STARTED;
+ synchronized (mMediaPlayerLock) {
+ if (mMediaPlayerState == State.PAUSED) {
+ mMediaPlayer.start();
+ mMediaPlayerState = State.STARTED;
}
}
}
/** Release media player when it's no longer needed. */
public void release() {
- synchronized (mediaPlayerLock) {
- if (mediaPlayerState != State.NONE && mediaPlayerState != State.END) {
- mediaPlayerState = State.END;
- mediaPlayer.release();
- mediaPlayer = null;
+ synchronized (mMediaPlayerLock) {
+ if (mMediaPlayerState != State.NONE && mMediaPlayerState != State.END) {
+ mMediaPlayerState = State.END;
+ mMediaPlayer.release();
+ mMediaPlayer = null;
}
}
- if (animationSurface != null) {
- animationSurface.release();
- animationSurface = null;
+ if (mAnimationSurface != null) {
+ mAnimationSurface.release();
+ mAnimationSurface = null;
}
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
- animationSurface = new Surface(surface);
- synchronized (mediaPlayerLock) {
- mediaPlayer = MediaPlayer.create(context, videoRes);
- mediaPlayerState = State.PREPARED;
- mediaPlayer.setSurface(animationSurface);
- mediaPlayer.setLooping(true);
- mediaPlayer.start();
- mediaPlayerState = State.STARTED;
+ mAnimationSurface = new Surface(surface);
+ synchronized (mMediaPlayerLock) {
+ mMediaPlayer = MediaPlayer.create(mContext, mVideoRes);
+ mMediaPlayerState = State.PREPARED;
+ mMediaPlayer.setSurface(mAnimationSurface);
+ mMediaPlayer.setLooping(true);
+ mMediaPlayer.start();
+ mMediaPlayerState = State.STARTED;
}
}
diff --git a/src/com/android/settings/accessibility/VolumeShortcutToggleAccessibilityServicePreferenceFragment.java b/src/com/android/settings/accessibility/VolumeShortcutToggleAccessibilityServicePreferenceFragment.java
index 31f97a8f6005837f0d686745f9a3079d5ad93100..eccfbce781328e3917d22495ecffc44d148e68ad 100644
--- a/src/com/android/settings/accessibility/VolumeShortcutToggleAccessibilityServicePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/VolumeShortcutToggleAccessibilityServicePreferenceFragment.java
@@ -24,8 +24,6 @@ import android.view.View;
import com.android.settings.R;
-import com.google.common.collect.ImmutableSet;
-
/**
* Fragment that only allowed hardware {@link UserShortcutType} for shortcut to open.
*
@@ -67,10 +65,9 @@ public class VolumeShortcutToggleAccessibilityServicePreferenceFragment extends
}
private void setAllowedPreferredShortcutType(int type) {
- final AccessibilityUserShortcutType shortcut = new AccessibilityUserShortcutType(
- mComponentName.flattenToString(), type);
+ final String componentNameString = mComponentName.flattenToString();
+ final PreferredShortcut shortcut = new PreferredShortcut(componentNameString, type);
- SharedPreferenceUtils.setUserShortcutType(getPrefContext(),
- ImmutableSet.of(shortcut.flattenToString()));
+ PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
}
}
diff --git a/src/com/android/settings/accessibility/VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard.java
index 6e4a233c60806a23a357391b058901a24bc6fb40..433717744965885e3c20a1ed86e6aabab2c8cde4 100644
--- a/src/com/android/settings/accessibility/VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard.java
+++ b/src/com/android/settings/accessibility/VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard.java
@@ -29,7 +29,7 @@ public class VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- mToggleSwitchWasInitiallyChecked = mToggleServiceDividerSwitchPreference.isChecked();
+ mToggleSwitchWasInitiallyChecked = mToggleServiceSwitchPreference.isChecked();
}
@Override
@@ -40,10 +40,10 @@ public class VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard
@Override
public void onStop() {
// Log the final choice in value if it's different from the previous value.
- if (mToggleServiceDividerSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
+ if (mToggleServiceSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
mMetricsFeatureProvider.action(getContext(),
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SCREEN_READER,
- mToggleServiceDividerSwitchPreference.isChecked());
+ mToggleServiceSwitchPreference.isChecked());
}
super.onStop();
diff --git a/src/com/android/settings/accessibility/VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard.java
index 3dd648cd592ff3e925f929b4b2acbbec91dab9a1..acdfdc980d9927f5880b14b8c3b88f4a33578211 100644
--- a/src/com/android/settings/accessibility/VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard.java
+++ b/src/com/android/settings/accessibility/VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard.java
@@ -29,7 +29,7 @@ public class VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- mToggleSwitchWasInitiallyChecked = mToggleServiceDividerSwitchPreference.isChecked();
+ mToggleSwitchWasInitiallyChecked = mToggleServiceSwitchPreference.isChecked();
}
@Override
@@ -40,10 +40,10 @@ public class VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard
@Override
public void onStop() {
// Log the final choice in value if it's different from the previous value.
- if (mToggleServiceDividerSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
+ if (mToggleServiceSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
mMetricsFeatureProvider.action(getContext(),
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SELECT_TO_SPEAK,
- mToggleServiceDividerSwitchPreference.isChecked());
+ mToggleServiceSwitchPreference.isChecked());
}
super.onStop();
diff --git a/src/com/android/settings/accessibility/rtt/TelecomUtil.java b/src/com/android/settings/accessibility/rtt/TelecomUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..53c988ac79f611dd5b2257da189de891303eed35
--- /dev/null
+++ b/src/com/android/settings/accessibility/rtt/TelecomUtil.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.accessibility.rtt;
+
+import android.content.Context;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * A util class checks some SIM card information and permissions.
+ */
+public abstract class TelecomUtil {
+
+ private static final String TAG = "TelecomUtil";
+
+ /** Get a list of phone accounts which are call capable. */
+ public static List getCallCapablePhoneAccounts(Context context) {
+ return Optional.ofNullable(getTelecomManager(context).getCallCapablePhoneAccounts())
+ .orElse(new ArrayList<>());
+ }
+
+ /** Returns a {@link TelecomManager} instance. */
+ public static TelecomManager getTelecomManager(Context context) {
+ return context.getApplicationContext().getSystemService(TelecomManager.class);
+ }
+
+ /** Returns a subscription id of the SIM. */
+ public static int getSubIdForPhoneAccountHandle(
+ Context context, PhoneAccountHandle phoneAccountHandle) {
+ Optional info = getSubscriptionInfo(context, phoneAccountHandle);
+ return info.map(SubscriptionInfo::getSubscriptionId)
+ .orElse(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+
+ /**
+ * @return the {@link SubscriptionInfo} of the SIM if {@code phoneAccountHandle} corresponds
+ * to a valid SIM. Absent otherwise.
+ */
+ private static Optional getSubscriptionInfo(
+ Context context, PhoneAccountHandle phoneAccountHandle) {
+ if (TextUtils.isEmpty(phoneAccountHandle.getId())) {
+ return Optional.empty();
+ }
+ SubscriptionManager subscriptionManager = context.getSystemService(
+ SubscriptionManager.class);
+ List subscriptionInfos =
+ subscriptionManager.getActiveSubscriptionInfoList();
+ if (subscriptionInfos == null) {
+ return Optional.empty();
+ }
+ for (SubscriptionInfo info : subscriptionInfos) {
+ if (phoneAccountHandle.getId().startsWith(info.getIccId())) {
+ return Optional.of(info);
+ }
+ }
+ Log.d(TAG, "Failed to find SubscriptionInfo for phoneAccountHandle");
+ return Optional.empty();
+ }
+}
diff --git a/src/com/android/settings/accounts/AccountDashboardFragment.java b/src/com/android/settings/accounts/AccountDashboardFragment.java
index 7b50b46ae53f9076100c64e2bddcfdcd12545094..f57b124919ddcfb6d003afe2a432510a84171857 100644
--- a/src/com/android/settings/accounts/AccountDashboardFragment.java
+++ b/src/com/android/settings/accounts/AccountDashboardFragment.java
@@ -27,6 +27,9 @@ import android.os.UserManager;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.applications.autofill.PasswordsPreferenceController;
+import com.android.settings.applications.defaultapps.DefaultAutofillPreferenceController;
+import com.android.settings.applications.defaultapps.DefaultWorkAutofillPreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.search.BaseSearchIndexProvider;
@@ -66,16 +69,30 @@ public class AccountDashboardFragment extends DashboardFragment {
return R.string.help_url_user_and_account_dashboard;
}
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ getSettingsLifecycle().addObserver(use(PasswordsPreferenceController.class));
+ }
+
@Override
protected List createPreferenceControllers(Context context) {
+ final List controllers = new ArrayList<>();
+ buildAutofillPreferenceControllers(context, controllers);
final String[] authorities = getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
- return buildPreferenceControllers(context, this /* parent */, authorities);
+ buildAccountPreferenceControllers(context, this /* parent */, authorities, controllers);
+ return controllers;
}
- private static List buildPreferenceControllers(Context context,
- SettingsPreferenceFragment parent, String[] authorities) {
- final List controllers = new ArrayList<>();
+ static void buildAutofillPreferenceControllers(
+ Context context, List controllers) {
+ controllers.add(new DefaultAutofillPreferenceController(context));
+ controllers.add(new DefaultWorkAutofillPreferenceController(context));
+ }
+ private static void buildAccountPreferenceControllers(
+ Context context, SettingsPreferenceFragment parent, String[] authorities,
+ List controllers) {
final AccountPreferenceController accountPrefController =
new AccountPreferenceController(context, parent, authorities,
ProfileSelectFragment.ProfileType.ALL);
@@ -86,7 +103,6 @@ public class AccountDashboardFragment extends DashboardFragment {
controllers.add(new AutoSyncDataPreferenceController(context, parent));
controllers.add(new AutoSyncPersonalDataPreferenceController(context, parent));
controllers.add(new AutoSyncWorkDataPreferenceController(context, parent));
- return controllers;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
@@ -95,8 +111,11 @@ public class AccountDashboardFragment extends DashboardFragment {
@Override
public List createPreferenceControllers(
Context context) {
- return buildPreferenceControllers(
- context, null /* parent */, null /* authorities*/);
+ final List controllers = new ArrayList<>();
+ buildAccountPreferenceControllers(
+ context, null /* parent */, null /* authorities*/, controllers);
+ buildAutofillPreferenceControllers(context, controllers);
+ return controllers;
}
@Override
diff --git a/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java b/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java
index f29326e4f58724b328587e7d3d177123a6b955b3..9ad12067233107fd57a7af304e97a92684a00c89 100644
--- a/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java
+++ b/src/com/android/settings/accounts/AccountPersonalDashboardFragment.java
@@ -18,11 +18,14 @@ package com.android.settings.accounts;
import static android.provider.Settings.EXTRA_AUTHORITIES;
+import static com.android.settings.accounts.AccountDashboardFragment.buildAutofillPreferenceControllers;
+
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.applications.autofill.PasswordsPreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.users.AutoSyncDataPreferenceController;
@@ -60,15 +63,23 @@ public class AccountPersonalDashboardFragment extends DashboardFragment {
}
@Override
- protected List createPreferenceControllers(Context context) {
- final String[] authorities = getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
- return buildPreferenceControllers(context, this /* parent */, authorities);
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ getSettingsLifecycle().addObserver(use(PasswordsPreferenceController.class));
}
- private static List buildPreferenceControllers(Context context,
- SettingsPreferenceFragment parent, String[] authorities) {
+ @Override
+ protected List createPreferenceControllers(Context context) {
final List controllers = new ArrayList<>();
+ buildAutofillPreferenceControllers(context, controllers);
+ final String[] authorities = getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
+ buildAccountPreferenceControllers(context, this /* parent */, authorities, controllers);
+ return controllers;
+ }
+ private static void buildAccountPreferenceControllers(
+ Context context, SettingsPreferenceFragment parent, String[] authorities,
+ List controllers) {
final AccountPreferenceController accountPrefController =
new AccountPreferenceController(context, parent, authorities,
ProfileSelectFragment.ProfileType.PERSONAL);
@@ -78,7 +89,6 @@ public class AccountPersonalDashboardFragment extends DashboardFragment {
controllers.add(accountPrefController);
controllers.add(new AutoSyncDataPreferenceController(context, parent));
controllers.add(new AutoSyncPersonalDataPreferenceController(context, parent));
- return controllers;
}
// TODO: b/141601408. After featureFlag settings_work_profile is launched, unmark this
@@ -88,6 +98,7 @@ public class AccountPersonalDashboardFragment extends DashboardFragment {
// @Override
// public List createPreferenceControllers(
// Context context) {
+// ..Add autofill here too..
// return buildPreferenceControllers(
// context, null /* parent */, null /* authorities*/);
// }
diff --git a/src/com/android/settings/accounts/AccountPreferenceController.java b/src/com/android/settings/accounts/AccountPreferenceController.java
index ff5bc780910d1bf741ff785b8125050e36857d6f..42b2334f2324fe2d4b12676629022393d3f534a6 100644
--- a/src/com/android/settings/accounts/AccountPreferenceController.java
+++ b/src/com/android/settings/accounts/AccountPreferenceController.java
@@ -75,7 +75,7 @@ public class AccountPreferenceController extends AbstractPreferenceController
private static final String TAG = "AccountPrefController";
- private static final int ORDER_ACCOUNT_PROFILES = 1;
+ private static final int ORDER_ACCOUNT_PROFILES = 101;
private static final int ORDER_LAST = 1002;
private static final int ORDER_NEXT_TO_LAST = 1001;
private static final int ORDER_NEXT_TO_NEXT_TO_LAST = 1000;
@@ -323,11 +323,12 @@ public class AccountPreferenceController extends AbstractPreferenceController
mHelper.createAccessiblePreferenceCategory(
mFragment.getPreferenceManager().getContext());
preferenceGroup.setOrder(mAccountProfileOrder++);
+ preferenceGroup.setTitle(R.string.account_settings); // default title; may be modified below
if (isSingleProfile()) {
- preferenceGroup.setTitle(context.getString(R.string.account_for_section_header,
- BidiFormatter.getInstance().unicodeWrap(userInfo.name)));
- preferenceGroup.setContentDescription(
- mContext.getString(R.string.account_settings));
+ final String title = context.getString(R.string.account_for_section_header,
+ BidiFormatter.getInstance().unicodeWrap(userInfo.name));
+ preferenceGroup.setTitle(title);
+ preferenceGroup.setContentDescription(title);
} else if (userInfo.isManagedProfile()) {
if (mType == ProfileSelectFragment.ProfileType.ALL) {
preferenceGroup.setTitle(R.string.category_work);
diff --git a/src/com/android/settings/accounts/AccountSyncSettings.java b/src/com/android/settings/accounts/AccountSyncSettings.java
index a56dc94c2a35555b36ff858c9baee5ee5b3c5190..23b6157c97dca0a6abbccd26e41ea4cb128aff1e 100644
--- a/src/com/android/settings/accounts/AccountSyncSettings.java
+++ b/src/com/android/settings/accounts/AccountSyncSettings.java
@@ -294,7 +294,7 @@ public class AccountSyncSettings extends AccountPreferenceBase {
}
// if we're enabling sync, this will request a sync as well
ContentResolver.setSyncAutomaticallyAsUser(account, authority, syncOn, userId);
- // if the master sync switch is off, the request above will
+ // if the primary sync switch is off, the request above will
// get dropped. when the user clicks on this toggle,
// we want to force the sync, however.
if (!ContentResolver.getMasterSyncAutomaticallyAsUser(userId) || !syncOn) {
diff --git a/src/com/android/settings/accounts/AccountTypePreference.java b/src/com/android/settings/accounts/AccountTypePreference.java
index c82a5990d53e2eb073aba07c909c709ce19346ae..8df576753d9cb7cd0122fb1e1df23890b38e98f8 100644
--- a/src/com/android/settings/accounts/AccountTypePreference.java
+++ b/src/com/android/settings/accounts/AccountTypePreference.java
@@ -30,7 +30,7 @@ import androidx.preference.Preference.OnPreferenceClickListener;
import com.android.settings.Utils;
import com.android.settings.core.SubSettingLauncher;
-import com.android.settingslib.widget.apppreference.AppPreference;
+import com.android.settingslib.widget.AppPreference;
public class AccountTypePreference extends AppPreference implements OnPreferenceClickListener {
/**
diff --git a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java
index c639d1df2eb95d86af2c4ff706b5748041b85869..42bb34a0ee444a57bfb12714883b3ffac7048588 100644
--- a/src/com/android/settings/accounts/AccountTypePreferenceLoader.java
+++ b/src/com/android/settings/accounts/AccountTypePreferenceLoader.java
@@ -58,7 +58,6 @@ public class AccountTypePreferenceLoader {
private static final String LAUNCHING_LOCATION_SETTINGS =
"com.android.settings.accounts.LAUNCHING_LOCATION_SETTINGS";
-
private AuthenticatorHelper mAuthenticatorHelper;
private UserHandle mUserHandle;
private PreferenceFragmentCompat mFragment;
diff --git a/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java b/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java
index 853c66b2fae10005c4172b839d1648f320504a7d..1fdd3f6c4284abaeac5fa88be71b6025d7df5273 100644
--- a/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java
+++ b/src/com/android/settings/accounts/AccountWorkProfileDashboardFragment.java
@@ -18,11 +18,14 @@ package com.android.settings.accounts;
import static android.provider.Settings.EXTRA_AUTHORITIES;
+import static com.android.settings.accounts.AccountDashboardFragment.buildAutofillPreferenceControllers;
+
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.applications.autofill.PasswordsPreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.users.AutoSyncDataPreferenceController;
@@ -60,15 +63,23 @@ public class AccountWorkProfileDashboardFragment extends DashboardFragment {
}
@Override
- protected List createPreferenceControllers(Context context) {
- final String[] authorities = getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
- return buildPreferenceControllers(context, this /* parent */, authorities);
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ getSettingsLifecycle().addObserver(use(PasswordsPreferenceController.class));
}
- private static List buildPreferenceControllers(Context context,
- SettingsPreferenceFragment parent, String[] authorities) {
+ @Override
+ protected List createPreferenceControllers(Context context) {
final List controllers = new ArrayList<>();
+ buildAutofillPreferenceControllers(context, controllers);
+ final String[] authorities = getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
+ buildAccountPreferenceControllers(context, this /* parent */, authorities, controllers);
+ return controllers;
+ }
+ private static void buildAccountPreferenceControllers(
+ Context context, SettingsPreferenceFragment parent, String[] authorities,
+ List controllers) {
final AccountPreferenceController accountPrefController =
new AccountPreferenceController(context, parent, authorities,
ProfileSelectFragment.ProfileType.WORK);
@@ -78,7 +89,6 @@ public class AccountWorkProfileDashboardFragment extends DashboardFragment {
controllers.add(accountPrefController);
controllers.add(new AutoSyncDataPreferenceController(context, parent));
controllers.add(new AutoSyncWorkDataPreferenceController(context, parent));
- return controllers;
}
// TODO: b/141601408. After featureFlag settings_work_profile is launched, unmark this
@@ -88,6 +98,7 @@ public class AccountWorkProfileDashboardFragment extends DashboardFragment {
// @Override
// public List createPreferenceControllers(
// Context context) {
+// ..Add autofill here too..
// return buildPreferenceControllers(
// context, null /* parent */, null /* authorities*/);
// }
diff --git a/src/com/android/settings/accounts/AddAccountSettings.java b/src/com/android/settings/accounts/AddAccountSettings.java
index d4d93dda217fba071a21f1aa0d2e936a9ae501a9..81db4df3290f59e60eccf730647ba96e45eb6899 100644
--- a/src/com/android/settings/accounts/AddAccountSettings.java
+++ b/src/com/android/settings/accounts/AddAccountSettings.java
@@ -163,11 +163,13 @@ public class AddAccountSettings extends Activity {
} else {
// If the user is locked by fbe: we couldn't start the authenticator. So we must ask the
// user to unlock it first.
- ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this);
- if (!helper.launchConfirmationActivity(UNLOCK_WORK_PROFILE_REQUEST,
- getString(R.string.unlock_set_unlock_launch_picker_title),
- false,
- mUserHandle.getIdentifier())) {
+ final ChooseLockSettingsHelper.Builder builder =
+ new ChooseLockSettingsHelper.Builder(this);
+ final boolean launched = builder.setRequestCode(UNLOCK_WORK_PROFILE_REQUEST)
+ .setTitle(getString(R.string.unlock_set_unlock_launch_picker_title))
+ .setUserId(mUserHandle.getIdentifier())
+ .show();
+ if (!launched) {
requestChooseAccount();
}
}
@@ -237,7 +239,7 @@ public class AddAccountSettings extends Activity {
* or broadcasts.
*
* Unfortunately for legacy reasons we still need to support this. But
- * we can cripple the intent so that 3rd party authenticators can't
+ * we can disable the intent so that 3rd party authenticators can't
* fill in addressing information and launch arbitrary actions.
*/
Intent identityIntent = new Intent();
@@ -245,7 +247,8 @@ public class AddAccountSettings extends Activity {
identityIntent.setAction(SHOULD_NOT_RESOLVE);
identityIntent.addCategory(SHOULD_NOT_RESOLVE);
- mPendingIntent = PendingIntent.getBroadcast(this, 0, identityIntent, 0);
+ mPendingIntent = PendingIntent.getBroadcast(this, 0, identityIntent,
+ PendingIntent.FLAG_IMMUTABLE);
addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS, Utils.hasMultipleUsers(this));
AccountManager.get(this).addAccountAsUser(
diff --git a/src/com/android/settings/accounts/AvatarViewMixin.java b/src/com/android/settings/accounts/AvatarViewMixin.java
index 7eb8cab8f41777921f1e89c8f7e98fb9391bbbec..7a2565c45b2f9bf01faef7ad2d96b4781690db4e 100644
--- a/src/com/android/settings/accounts/AvatarViewMixin.java
+++ b/src/com/android/settings/accounts/AvatarViewMixin.java
@@ -17,7 +17,6 @@
package com.android.settings.accounts;
import android.accounts.Account;
-import android.app.ActivityManager;
import android.app.settings.SettingsEnums;
import android.content.ContentResolver;
import android.content.Context;
@@ -40,7 +39,6 @@ import androidx.lifecycle.OnLifecycleEvent;
import com.android.settings.R;
import com.android.settings.homepage.SettingsHomepageActivity;
import com.android.settings.overlay.FeatureFactory;
-import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.utils.ThreadUtils;
import java.net.URISyntaxException;
@@ -60,19 +58,29 @@ public class AvatarViewMixin implements LifecycleObserver {
private static final String METHOD_GET_ACCOUNT_AVATAR = "getAccountAvatar";
private static final String KEY_AVATAR_BITMAP = "account_avatar";
private static final String KEY_ACCOUNT_NAME = "account_name";
+ private static final String KEY_AVATAR_ICON = "avatar_icon";
private static final String EXTRA_ACCOUNT_NAME = "extra.accountName";
private final Context mContext;
private final ImageView mAvatarView;
private final MutableLiveData mAvatarImage;
- private final ActivityManager mActivityManager;
@VisibleForTesting
String mAccountName;
+ /**
+ * @return true if the avatar icon is supported.
+ */
+ public static boolean isAvatarSupported(Context context) {
+ if (!context.getResources().getBoolean(R.bool.config_show_avatar_in_homepage)) {
+ Log.d(TAG, "Feature disabled by config. Skipping");
+ return false;
+ }
+ return true;
+ }
+
public AvatarViewMixin(SettingsHomepageActivity activity, ImageView avatarView) {
mContext = activity.getApplicationContext();
- mActivityManager = mContext.getSystemService(ActivityManager.class);
mAvatarView = avatarView;
mAvatarView.setOnClickListener(v -> {
Intent intent;
@@ -97,11 +105,8 @@ public class AvatarViewMixin implements LifecycleObserver {
return;
}
- final MetricsFeatureProvider metricsFeatureProvider = FeatureFactory.getFactory(
- mContext).getMetricsFeatureProvider();
- metricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
- SettingsEnums.CLICK_ACCOUNT_AVATAR, SettingsEnums.SETTINGS_HOMEPAGE,
- null /* key */, Integer.MIN_VALUE /* value */);
+ FeatureFactory.getFactory(mContext).getMetricsFeatureProvider()
+ .logSettingsTileClick(KEY_AVATAR_ICON, SettingsEnums.SETTINGS_HOMEPAGE);
// Here may have two different UI while start the activity.
// It will display adding account UI when device has no any account.
@@ -117,14 +122,6 @@ public class AvatarViewMixin implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStart() {
- if (!mContext.getResources().getBoolean(R.bool.config_show_avatar_in_homepage)) {
- Log.d(TAG, "Feature disabled by config. Skipping");
- return;
- }
- if (mActivityManager.isLowRamDevice()) {
- Log.d(TAG, "Feature disabled on low ram device. Skipping");
- return;
- }
if (hasAccount()) {
loadAccount();
} else {
diff --git a/src/com/android/settings/accounts/EnterpriseDisclosurePreferenceController.java b/src/com/android/settings/accounts/EnterpriseDisclosurePreferenceController.java
index b4dbf3d2c8ae977865d85cd4aa396403b238d8a1..238e93790d38e80a26f6bd29e808c95754f21881 100644
--- a/src/com/android/settings/accounts/EnterpriseDisclosurePreferenceController.java
+++ b/src/com/android/settings/accounts/EnterpriseDisclosurePreferenceController.java
@@ -17,16 +17,19 @@
package com.android.settings.accounts;
import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.widget.FooterPreference;
public class EnterpriseDisclosurePreferenceController extends BasePreferenceController {
-
private final EnterprisePrivacyFeatureProvider mFeatureProvider;
public EnterpriseDisclosurePreferenceController(Context context, String key) {
@@ -36,6 +39,16 @@ public class EnterpriseDisclosurePreferenceController extends BasePreferenceCont
.getEnterprisePrivacyFeatureProvider(mContext);
}
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ final CharSequence disclosure = getDisclosure();
+ if (disclosure == null) {
+ return;
+ }
+ updateFooterPreference(screen, disclosure);
+ }
+
@Override
public int getAvailabilityStatus() {
if (getDisclosure() == null) {
@@ -49,12 +62,18 @@ public class EnterpriseDisclosurePreferenceController extends BasePreferenceCont
return mFeatureProvider.getDeviceOwnerDisclosure();
}
- @Override
- public void updateState(Preference preference) {
- final CharSequence disclosure = getDisclosure();
- if (disclosure == null) {
- return;
- }
- preference.setTitle(disclosure);
+ void updateFooterPreference(PreferenceScreen screen, CharSequence disclosure) {
+ final FooterPreference footerPreference = screen.findPreference(getPreferenceKey());
+ footerPreference.setTitle(disclosure);
+ footerPreference.setLearnMoreAction(view -> {
+ mContext.startActivity(new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS));
+ });
+ final String learnMoreContentDescription = mContext.getString(
+ R.string.footer_learn_more_content_description, getLabelName());
+ footerPreference.setLearnMoreContentDescription(learnMoreContentDescription);
+ }
+
+ private String getLabelName() {
+ return mContext.getString(R.string.header_add_an_account);
}
}
diff --git a/src/com/android/settings/accounts/TopLevelAccountEntryPreferenceController.java b/src/com/android/settings/accounts/TopLevelAccountEntryPreferenceController.java
index e4e32e54d1627e50e3acfe2fa1e49a97ee271de4..152bb6733b1e1cc07a30dfeec211231bb4069987 100644
--- a/src/com/android/settings/accounts/TopLevelAccountEntryPreferenceController.java
+++ b/src/com/android/settings/accounts/TopLevelAccountEntryPreferenceController.java
@@ -17,17 +17,9 @@
package com.android.settings.accounts;
import android.content.Context;
-import android.icu.text.ListFormatter;
-import android.os.UserHandle;
-import android.text.BidiFormatter;
-import android.text.TextUtils;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
-import com.android.settingslib.accounts.AuthenticatorHelper;
-
-import java.util.ArrayList;
-import java.util.List;
public class TopLevelAccountEntryPreferenceController extends BasePreferenceController {
public TopLevelAccountEntryPreferenceController(Context context, String preferenceKey) {
@@ -41,28 +33,6 @@ public class TopLevelAccountEntryPreferenceController extends BasePreferenceCont
@Override
public CharSequence getSummary() {
- final AuthenticatorHelper authHelper = new AuthenticatorHelper(mContext,
- UserHandle.of(UserHandle.myUserId()), null /* OnAccountsUpdateListener */);
- final String[] types = authHelper.getEnabledAccountTypes();
- final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- final List summaries = new ArrayList<>();
-
- if (types == null || types.length == 0) {
- summaries.add(mContext.getString(R.string.account_dashboard_default_summary));
- } else {
- // Show up to 3 account types, ignore any null value
- int accountToAdd = Math.min(3, types.length);
-
- for (int i = 0; i < types.length && accountToAdd > 0; i++) {
- final CharSequence label = authHelper.getLabelForType(mContext, types[i]);
- if (TextUtils.isEmpty(label)) {
- continue;
- }
-
- summaries.add(bidiFormatter.unicodeWrap(label));
- accountToAdd--;
- }
- }
- return ListFormatter.getInstance().format(summaries);
+ return mContext.getString(R.string.account_dashboard_default_summary);
}
}
diff --git a/src/com/android/settings/applications/AllAppsInfoPreferenceController.java b/src/com/android/settings/applications/AllAppsInfoPreferenceController.java
deleted file mode 100644
index 325b25a3618994d8dd9232dcd207576de307561f..0000000000000000000000000000000000000000
--- a/src/com/android/settings/applications/AllAppsInfoPreferenceController.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.applications;
-
-import android.app.usage.UsageStats;
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.core.BasePreferenceController;
-
-import java.util.List;
-
-public class AllAppsInfoPreferenceController extends BasePreferenceController
- implements RecentAppStatsMixin.RecentAppStatsListener {
-
- @VisibleForTesting
- Preference mPreference;
-
- public AllAppsInfoPreferenceController(Context context, String key) {
- super(context, key);
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AVAILABLE;
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mPreference = screen.findPreference(getPreferenceKey());
- // In most cases, device has recently opened apps. So, we hide it by default.
- mPreference.setVisible(false);
- }
-
- @Override
- public void onReloadDataCompleted(@NonNull List recentApps) {
- // If device has recently opened apps, we don't show all apps preference.
- if (!recentApps.isEmpty()) {
- mPreference.setVisible(false);
- return;
- }
-
- mPreference.setVisible(true);
- // Show total number of installed apps as See all's summary.
- new InstalledAppCounter(mContext, InstalledAppCounter.IGNORE_INSTALL_REASON,
- mContext.getPackageManager()) {
- @Override
- protected void onCountComplete(int num) {
- mPreference.setSummary(mContext.getString(R.string.apps_summary, num));
- }
- }.execute();
- }
-}
diff --git a/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java b/src/com/android/settings/applications/AppDashboardFragment.java
similarity index 53%
rename from src/com/android/settings/applications/AppAndNotificationDashboardFragment.java
rename to src/com/android/settings/applications/AppDashboardFragment.java
index 8b0f85119de0d237108d0a5563fa51257f2ee7fe..7e203b00e3e989d9e068010d3860291e42b8f586 100644
--- a/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java
+++ b/src/com/android/settings/applications/AppDashboardFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,40 +17,35 @@
package com.android.settings.applications;
import android.app.settings.SettingsEnums;
-import android.app.usage.UsageStats;
import android.content.Context;
-import android.os.Bundle;
import android.provider.SearchIndexableResource;
-import android.view.View;
-
-import androidx.annotation.NonNull;
import com.android.settings.R;
-import com.android.settings.Utils;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.notification.EmergencyBroadcastPreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;
-import com.android.settingslib.widget.AppEntitiesHeaderController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+/** Settings page for apps. */
@SearchIndexable
-public class AppAndNotificationDashboardFragment extends DashboardFragment
- implements RecentAppStatsMixin.RecentAppStatsListener {
+public class AppDashboardFragment extends DashboardFragment {
- private static final String TAG = "AppAndNotifDashboard";
+ private static final String TAG = "AppDashboardFragment";
+ private AppsPreferenceController mAppsPreferenceController;
- private RecentAppStatsMixin mRecentAppStatsMixin;
- private RecentAppsPreferenceController mRecentAppsPreferenceController;
- private AllAppsInfoPreferenceController mAllAppsInfoPreferenceController;
+ private static List buildPreferenceControllers(Context context) {
+ final List controllers = new ArrayList<>();
+ controllers.add(new AppsPreferenceController(context));
+ return controllers;
+ }
@Override
public int getMetricsCategory() {
- return SettingsEnums.SETTINGS_APP_NOTIF_CATEGORY;
+ return SettingsEnums.MANAGE_APPLICATIONS;
}
@Override
@@ -65,48 +60,20 @@ public class AppAndNotificationDashboardFragment extends DashboardFragment
@Override
protected int getPreferenceScreenResId() {
- return R.xml.app_and_notification;
+ return R.xml.apps;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
-
use(SpecialAppAccessPreferenceController.class).setSession(getSettingsLifecycle());
+ mAppsPreferenceController = use(AppsPreferenceController.class);
+ mAppsPreferenceController.setFragment(this /* fragment */);
+ getSettingsLifecycle().addObserver(mAppsPreferenceController);
- mRecentAppStatsMixin = new RecentAppStatsMixin(context,
- AppEntitiesHeaderController.MAXIMUM_APPS);
- getSettingsLifecycle().addObserver(mRecentAppStatsMixin);
- mRecentAppStatsMixin.addListener(this);
-
- mRecentAppsPreferenceController = use(RecentAppsPreferenceController.class);
- mRecentAppsPreferenceController.setFragment(this /* fragment */);
- mRecentAppStatsMixin.addListener(mRecentAppsPreferenceController);
-
- mAllAppsInfoPreferenceController = use(AllAppsInfoPreferenceController.class);
- mRecentAppStatsMixin.addListener(mAllAppsInfoPreferenceController);
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- setPinnedHeaderView(R.layout.progress_header);
- showPinnedHeader(false);
- }
-
- @Override
- public void onStart() {
- super.onStart();
- showPinnedHeader(true);
- }
-
- @Override
- public void onReloadDataCompleted(@NonNull List recentApps) {
- showPinnedHeader(false);
- if (!recentApps.isEmpty()) {
- Utils.setActionBarShadowAnimation(getActivity(), getSettingsLifecycle(),
- getListView());
- }
+ final HibernatedAppsPreferenceController hibernatedAppsPreferenceController =
+ use(HibernatedAppsPreferenceController.class);
+ getSettingsLifecycle().addObserver(hibernatedAppsPreferenceController);
}
@Override
@@ -114,20 +81,13 @@ public class AppAndNotificationDashboardFragment extends DashboardFragment
return buildPreferenceControllers(context);
}
- private static List buildPreferenceControllers(Context context) {
- final List controllers = new ArrayList<>();
- controllers.add(new EmergencyBroadcastPreferenceController(context,
- "app_and_notif_cell_broadcast_settings"));
- return controllers;
- }
-
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List getXmlResourcesToIndex(
Context context, boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
- sir.xmlResId = R.xml.app_and_notification;
+ sir.xmlResId = R.xml.apps;
return Arrays.asList(sir);
}
diff --git a/src/com/android/settings/applications/AppLaunchSettings.java b/src/com/android/settings/applications/AppLaunchSettings.java
deleted file mode 100644
index 86bc7ed9c5a014a799ba9351edd1a779ae666734..0000000000000000000000000000000000000000
--- a/src/com/android/settings/applications/AppLaunchSettings.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.applications;
-
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-
-import android.app.settings.SettingsEnums;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.view.View;
-import android.view.View.OnClickListener;
-
-import androidx.appcompat.app.AlertDialog;
-import androidx.preference.Preference;
-
-import com.android.settings.R;
-import com.android.settings.Utils;
-import com.android.settings.core.SubSettingLauncher;
-import com.android.settingslib.applications.AppUtils;
-
-public class AppLaunchSettings extends AppInfoWithHeader implements OnClickListener,
- Preference.OnPreferenceChangeListener {
- private static final String TAG = "AppLaunchSettings";
- private static final String KEY_APP_LINK_STATE = "app_link_state";
- private static final String KEY_SUPPORTED_DOMAIN_URLS = "app_launch_supported_domain_urls";
- private static final String KEY_CLEAR_DEFAULTS = "app_launch_clear_defaults";
- private static final String FRAGMENT_OPEN_SUPPORTED_LINKS =
- "com.android.settings.applications.OpenSupportedLinks";
-
- private PackageManager mPm;
-
- private boolean mIsBrowser;
- private boolean mHasDomainUrls;
- private Preference mAppLinkState;
- private AppDomainsPreference mAppDomainUrls;
- private ClearDefaultsPreference mClearDefaultsPreference;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- addPreferencesFromResource(R.xml.installed_app_launch_settings);
- mAppDomainUrls = (AppDomainsPreference) findPreference(KEY_SUPPORTED_DOMAIN_URLS);
- mClearDefaultsPreference = (ClearDefaultsPreference) findPreference(KEY_CLEAR_DEFAULTS);
- mAppLinkState = findPreference(KEY_APP_LINK_STATE);
- mAppLinkState.setOnPreferenceClickListener(preference -> {
- final Bundle args = new Bundle();
- args.putString(ARG_PACKAGE_NAME, mPackageName);
- args.putInt(ARG_PACKAGE_UID, mUserId);
-
- new SubSettingLauncher(this.getContext())
- .setDestination(FRAGMENT_OPEN_SUPPORTED_LINKS)
- .setArguments(args)
- .setSourceMetricsCategory(SettingsEnums.APPLICATIONS_APP_LAUNCH)
- .setTitleRes(-1)
- .launch();
- return true;
- });
-
- mPm = getActivity().getPackageManager();
-
- mIsBrowser = AppUtils.isBrowserApp(this.getContext(), mPackageName, UserHandle.myUserId());
- mHasDomainUrls =
- (mAppEntry.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0;
-
- if (!mIsBrowser) {
- CharSequence[] entries = getEntries(mPackageName);
- mAppDomainUrls.setTitles(entries);
- mAppDomainUrls.setValues(new int[entries.length]);
- mAppLinkState.setEnabled(mHasDomainUrls);
- } else {
- // Browsers don't show the app-link prefs
- mAppLinkState.setShouldDisableView(true);
- mAppLinkState.setEnabled(false);
- mAppDomainUrls.setShouldDisableView(true);
- mAppDomainUrls.setEnabled(false);
- }
- }
-
- private int linkStateToResourceId(int state) {
- switch (state) {
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
- return R.string.app_link_open_always; // Always
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
- return R.string.app_link_open_never; // Never
- default:
- return R.string.app_link_open_ask; // Ask
- }
- }
-
- private CharSequence[] getEntries(String packageName) {
- ArraySet result = Utils.getHandledDomains(mPm, packageName);
- return result.toArray(new CharSequence[result.size()]);
- }
-
- private void setAppLinkStateSummary() {
- final int state = mPm.getIntentVerificationStatusAsUser(mPackageName,
- UserHandle.myUserId());
- mAppLinkState.setSummary(linkStateToResourceId(state));
- }
-
- @Override
- protected boolean refreshUi() {
- if (mHasDomainUrls) {
- //Update the summary after return from the OpenSupportedLinks
- setAppLinkStateSummary();
- }
- mClearDefaultsPreference.setPackageName(mPackageName);
- mClearDefaultsPreference.setAppEntry(mAppEntry);
- return true;
- }
-
- @Override
- protected AlertDialog createDialog(int id, int errorCode) {
- // No dialogs for preferred launch settings.
- return null;
- }
-
- @Override
- public void onClick(View v) {
- // Nothing to do
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- // actual updates are handled by the app link dropdown callback
- return true;
- }
-
- @Override
- public int getMetricsCategory() {
- return SettingsEnums.APPLICATIONS_APP_LAUNCH;
- }
-}
diff --git a/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java b/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java
new file mode 100644
index 0000000000000000000000000000000000000000..cf938a5e3d44f38e4fe26e9641a760291a4ea176
--- /dev/null
+++ b/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications;
+
+import android.Manifest;
+import android.app.AlarmManager;
+import android.app.AppGlobals;
+import android.app.compat.CompatChanges;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.applications.ApplicationsState.AppFilter;
+
+import libcore.util.EmptyArray;
+
+import java.util.List;
+
+/**
+ * Connects app op info to the ApplicationsState. Extends {@link AppStateAppOpsBridge} to tailor
+ * to the semantics of {@link Manifest.permission#SCHEDULE_EXACT_ALARM}.
+ * Also provides app filters that can use the info.
+ */
+public class AppStateAlarmsAndRemindersBridge extends AppStateBaseBridge {
+ private static final String PERMISSION = Manifest.permission.SCHEDULE_EXACT_ALARM;
+ private static final String TAG = "AlarmsAndRemindersBridge";
+
+ @VisibleForTesting
+ AlarmManager mAlarmManager;
+ @VisibleForTesting
+ String[] mRequesterPackages;
+
+ public AppStateAlarmsAndRemindersBridge(Context context, ApplicationsState appState,
+ Callback callback) {
+ super(appState, callback);
+
+ mAlarmManager = context.getSystemService(AlarmManager.class);
+ final IPackageManager iPm = AppGlobals.getPackageManager();
+ try {
+ mRequesterPackages = iPm.getAppOpPermissionPackages(PERMISSION);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Cannot reach package manager", re);
+ mRequesterPackages = EmptyArray.STRING;
+ }
+ }
+
+ private boolean isChangeEnabled(String packageName, int userId) {
+ return CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
+ packageName, UserHandle.of(userId));
+ }
+
+ /**
+ * Returns information regarding {@link Manifest.permission#SCHEDULE_EXACT_ALARM} for the given
+ * package and uid.
+ */
+ public AlarmsAndRemindersState createPermissionState(String packageName, int uid) {
+ final int userId = UserHandle.getUserId(uid);
+
+ final boolean permissionRequested = ArrayUtils.contains(mRequesterPackages, packageName)
+ && isChangeEnabled(packageName, userId);
+ final boolean permissionGranted = mAlarmManager.hasScheduleExactAlarm(packageName, userId);
+ return new AlarmsAndRemindersState(permissionRequested, permissionGranted);
+ }
+
+ @Override
+ protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
+ app.extraInfo = createPermissionState(pkg, uid);
+ }
+
+ @Override
+ protected void loadAllExtraInfo() {
+ final List allApps = mAppSession.getAllApps();
+ for (int i = 0; i < allApps.size(); i++) {
+ final AppEntry currentEntry = allApps.get(i);
+ updateExtraInfo(currentEntry, currentEntry.info.packageName, currentEntry.info.uid);
+ }
+ }
+
+ public static final AppFilter FILTER_CLOCK_APPS = new AppFilter() {
+
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry info) {
+ if (info.extraInfo instanceof AlarmsAndRemindersState) {
+ final AlarmsAndRemindersState state = (AlarmsAndRemindersState) info.extraInfo;
+ return state.shouldBeVisible();
+ }
+ return false;
+ }
+ };
+
+ /**
+ * Class to denote the state of an app regarding
+ * {@link Manifest.permission#SCHEDULE_EXACT_ALARM}.
+ */
+ public static class AlarmsAndRemindersState {
+ private boolean mPermissionRequested;
+ private boolean mPermissionGranted;
+
+ AlarmsAndRemindersState(boolean permissionRequested, boolean permissionGranted) {
+ mPermissionRequested = permissionRequested;
+ mPermissionGranted = permissionGranted;
+ }
+
+ /** Should the app associated with this state appear on the Settings screen */
+ public boolean shouldBeVisible() {
+ return mPermissionRequested;
+ }
+
+ /** Is the permission granted to the app associated with this state */
+ public boolean isAllowed() {
+ return mPermissionGranted;
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/AppStateAppOpsBridge.java b/src/com/android/settings/applications/AppStateAppOpsBridge.java
index b4f6e4837114f495efd71ea5b2a46f1a9b8c4998..2ccdc78413add0900e2a4ec4edbc5c18c5c2bc84 100755
--- a/src/com/android/settings/applications/AppStateAppOpsBridge.java
+++ b/src/com/android/settings/applications/AppStateAppOpsBridge.java
@@ -277,7 +277,7 @@ public abstract class AppStateAppOpsBridge extends AppStateBaseBridge {
if (pe == null) {
Log.w(TAG, "AppOp permission exists for package " + packageOp.getPackageName()
+ " of user " + userId + " but package doesn't exist or did not request "
- + mPermissions + " access");
+ + Arrays.toString(mPermissions) + " access");
continue;
}
diff --git a/src/com/android/settings/applications/AppStateMediaManagementAppsBridge.java b/src/com/android/settings/applications/AppStateMediaManagementAppsBridge.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff2b4d85ad1f93b0b9b0bd12c435ddea04f0bf37
--- /dev/null
+++ b/src/com/android/settings/applications/AppStateMediaManagementAppsBridge.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.applications.ApplicationsState.AppFilter;
+
+import java.util.List;
+
+/**
+ * Retrieves information from {@link AppOpsManager} and {@link android.content.pm.PackageManager}
+ * regarding {@link AppOpsManager#OP_MANAGE_MEDIA} and
+ * {@link Manifest.permission#MANAGE_MEDIA}.
+ */
+public class AppStateMediaManagementAppsBridge extends AppStateAppOpsBridge {
+
+ private final AppOpsManager mAppOpsManager;
+
+ public AppStateMediaManagementAppsBridge(Context context, ApplicationsState appState,
+ Callback callback) {
+ super(context, appState, callback,
+ AppOpsManager.strOpToOp(AppOpsManager.OPSTR_MANAGE_MEDIA),
+ new String[]{Manifest.permission.MANAGE_MEDIA});
+
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+ }
+
+ @Override
+ protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
+ app.extraInfo = createPermissionState(pkg, uid);
+ }
+
+ @Override
+ protected void loadAllExtraInfo() {
+ super.loadAllExtraInfo();
+ final List allApps = mAppSession.getAllApps();
+ final int appCount = allApps.size();
+ for (int i = 0; i < appCount; i++) {
+ final AppEntry appEntry = allApps.get(i);
+ if (appEntry.extraInfo instanceof PermissionState) {
+ updateExtraInfo(appEntry, appEntry.info.packageName, appEntry.info.uid);
+ }
+ }
+ }
+
+ /**
+ * Returns information regarding {@link Manifest.permission#MANAGE_MEDIA} for the given
+ * package and uid.
+ */
+ public PermissionState createPermissionState(String packageName, int uid) {
+ final PermissionState permissionState = getPermissionInfo(packageName, uid);
+ permissionState.appOpMode = mAppOpsManager.unsafeCheckOpNoThrow(
+ AppOpsManager.OPSTR_MANAGE_MEDIA, uid, packageName);
+ return permissionState;
+ }
+
+ /**
+ * Used by {@link com.android.settings.applications.manageapplications.AppFilterRegistry} to
+ * determine which apps get to appear on the Special App Access list.
+ */
+ public static final AppFilter FILTER_MEDIA_MANAGEMENT_APPS = new AppFilter() {
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry info) {
+ return info.extraInfo != null;
+ }
+ };
+}
diff --git a/src/com/android/settings/applications/AppStateNotificationBridge.java b/src/com/android/settings/applications/AppStateNotificationBridge.java
index c8bb5f9a667c2a11ca527b9157ef4d669e5e9f8d..3bcf94f34457a92947c342729dd2fd413bfa1a82 100644
--- a/src/com/android/settings/applications/AppStateNotificationBridge.java
+++ b/src/com/android/settings/applications/AppStateNotificationBridge.java
@@ -24,8 +24,10 @@ import android.os.UserManager;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.Slog;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.CompoundButton;
import android.widget.Switch;
import com.android.settings.R;
@@ -222,26 +224,18 @@ public class AppStateNotificationBridge extends AppStateBaseBridge {
return userId + "|" + pkg;
}
- public View.OnClickListener getSwitchOnClickListener(final AppEntry entry) {
- if (entry != null) {
- return v -> {
- ViewGroup view = (ViewGroup) v;
- Switch toggle = view.findViewById(R.id.switchWidget);
- if (toggle != null) {
- if (!toggle.isEnabled()) {
- return;
- }
- toggle.toggle();
- mBackend.setNotificationsEnabledForPackage(
- entry.info.packageName, entry.info.uid, toggle.isChecked());
- NotificationsSentState stats = getNotificationsSentState(entry);
- if (stats != null) {
- stats.blocked = !toggle.isChecked();
- }
- }
- };
+ public CompoundButton.OnCheckedChangeListener getSwitchOnCheckedListener(final AppEntry entry) {
+ if (entry == null) {
+ return null;
}
- return null;
+ return (buttonView, isChecked) -> {
+ mBackend.setNotificationsEnabledForPackage(
+ entry.info.packageName, entry.info.uid, isChecked);
+ NotificationsSentState stats = getNotificationsSentState(entry);
+ if (stats != null) {
+ stats.blocked = !isChecked;
+ }
+ };
}
public static final AppFilter FILTER_APP_NOTIFICATION_RECENCY = new AppFilter() {
diff --git a/src/com/android/settings/applications/AppStatePowerBridge.java b/src/com/android/settings/applications/AppStatePowerBridge.java
index 31412a7684b13e4b00386c6ab407433ae367731b..e5236f08cdb86c5e8fa6517372be85b44473ec1a 100644
--- a/src/com/android/settings/applications/AppStatePowerBridge.java
+++ b/src/com/android/settings/applications/AppStatePowerBridge.java
@@ -21,20 +21,20 @@ import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.AppFilter;
import com.android.settingslib.applications.ApplicationsState.CompoundFilter;
-import com.android.settingslib.fuelgauge.PowerWhitelistBackend;
+import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
import java.util.ArrayList;
/**
- * Connects data from the PowerWhitelistBackend to ApplicationsState.
+ * Connects data from the PowerAllowlistBackend to ApplicationsState.
*/
public class AppStatePowerBridge extends AppStateBaseBridge {
- private final PowerWhitelistBackend mBackend;
+ private final PowerAllowlistBackend mBackend;
public AppStatePowerBridge(Context context, ApplicationsState appState, Callback callback) {
super(appState, callback);
- mBackend = PowerWhitelistBackend.getInstance(context);
+ mBackend = PowerAllowlistBackend.getInstance(context);
}
@Override
@@ -43,17 +43,17 @@ public class AppStatePowerBridge extends AppStateBaseBridge {
final int N = apps.size();
for (int i = 0; i < N; i++) {
AppEntry app = apps.get(i);
- app.extraInfo = mBackend.isWhitelisted(app.info.packageName)
+ app.extraInfo = mBackend.isAllowlisted(app.info.packageName)
? Boolean.TRUE : Boolean.FALSE;
}
}
@Override
protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
- app.extraInfo = mBackend.isWhitelisted(pkg) ? Boolean.TRUE : Boolean.FALSE;
+ app.extraInfo = mBackend.isAllowlisted(pkg) ? Boolean.TRUE : Boolean.FALSE;
}
- public static final AppFilter FILTER_POWER_WHITELISTED = new CompoundFilter(
+ public static final AppFilter FILTER_POWER_ALLOWLISTED = new CompoundFilter(
ApplicationsState.FILTER_WITHOUT_DISABLED_UNTIL_USED, new AppFilter() {
@Override
public void init() {
diff --git a/src/com/android/settings/applications/AppStorageSettings.java b/src/com/android/settings/applications/AppStorageSettings.java
index d095e37a5bf50b5a58bc96cd50612fe777afca28..91eeace0fd3bb0abcf60e5fa0c2586f1e08cc9db 100644
--- a/src/com/android/settings/applications/AppStorageSettings.java
+++ b/src/com/android/settings/applications/AppStorageSettings.java
@@ -368,7 +368,12 @@ public class AppStorageSettings extends AppInfoWithHeader
}
ActivityManager am = (ActivityManager)
getActivity().getSystemService(Context.ACTIVITY_SERVICE);
- boolean res = am.clearApplicationUserData(packageName, mClearDataObserver);
+ boolean res = false;
+ try {
+ res = am.clearApplicationUserData(packageName, mClearDataObserver);
+ } catch (SecurityException e) {
+ Log.i(TAG, "Failed to clear application user data: " + e);
+ }
if (!res) {
// Clearing data failed for some obscure reason. Just log error for now
Log.i(TAG, "Couldn't clear application user data for package:" + packageName);
diff --git a/src/com/android/settings/applications/AppStoreUtil.java b/src/com/android/settings/applications/AppStoreUtil.java
index 13e551692c653f6b33fdffa2369e63dcd9b1f37a..79a4f35e19f965e8a689d93d2a6431d1dcde8f29 100644
--- a/src/com/android/settings/applications/AppStoreUtil.java
+++ b/src/com/android/settings/applications/AppStoreUtil.java
@@ -18,6 +18,9 @@ package com.android.settings.applications;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.InstallSourceInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.util.Log;
@@ -31,15 +34,30 @@ public class AppStoreUtil {
.setClassName(result.activityInfo.packageName, result.activityInfo.name) : null;
}
- // Returns the package name of the app which installed a given packageName, if one is
- // available.
+ // Returns the package name of the app that we consider to be the user-visible 'installer'
+ // of given packageName, if one is available.
public static String getInstallerPackageName(Context context, String packageName) {
- String installerPackageName = null;
+ String installerPackageName;
try {
- installerPackageName =
- context.getPackageManager().getInstallerPackageName(packageName);
- } catch (IllegalArgumentException e) {
+ InstallSourceInfo source =
+ context.getPackageManager().getInstallSourceInfo(packageName);
+ // By default, use the installing package name.
+ installerPackageName = source.getInstallingPackageName();
+ // Use the recorded originating package name only if the initiating package is a system
+ // app (eg. Package Installer). The originating package is not verified by the platform,
+ // so we choose to ignore this when supplied by a non-system app.
+ String originatingPackageName = source.getOriginatingPackageName();
+ String initiatingPackageName = source.getInitiatingPackageName();
+ if (originatingPackageName != null && initiatingPackageName != null) {
+ ApplicationInfo ai = context.getPackageManager().getApplicationInfo(
+ initiatingPackageName, 0);
+ if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ installerPackageName = originatingPackageName;
+ }
+ }
+ } catch (NameNotFoundException e) {
Log.e(LOG_TAG, "Exception while retrieving the package installer of " + packageName, e);
+ installerPackageName = null;
}
return installerPackageName;
}
diff --git a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
index 153e339d24d868e45af358ac2d48466870136ee4..5b74a8884aa00a2a18499f64e6dceb494b7ea829 100644
--- a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
+++ b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
@@ -159,7 +159,7 @@ public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvide
keepEnabledPackages.add(euicc.packageName);
}
- keepEnabledPackages.addAll(getEnabledPackageWhitelist());
+ keepEnabledPackages.addAll(getEnabledPackageAllowlist());
final LocationManager locationManager =
(LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
@@ -170,7 +170,7 @@ public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvide
return keepEnabledPackages;
}
- private Set getEnabledPackageWhitelist() {
+ private Set getEnabledPackageAllowlist() {
final Set keepEnabledPackages = new ArraySet<>();
// Keep Settings intelligence enabled, otherwise search feature will be disabled.
diff --git a/src/com/android/settings/applications/AppsPreferenceController.java b/src/com/android/settings/applications/AppsPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..9fbd5c13e8d887b43a3077ee1624e5657c246f1e
--- /dev/null
+++ b/src/com/android/settings/applications/AppsPreferenceController.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications;
+
+import android.app.Application;
+import android.app.usage.UsageStats;
+import android.content.Context;
+import android.icu.text.RelativeDateTimeFormatter;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.Utils;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.utils.StringUtil;
+import com.android.settingslib.widget.AppPreference;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This controller displays up to four recently used apps.
+ * If there is no recently used app, we only show up an "App Info" preference.
+ */
+public class AppsPreferenceController extends BasePreferenceController implements
+ LifecycleObserver {
+
+ public static final int SHOW_RECENT_APP_COUNT = 4;
+
+ @VisibleForTesting
+ static final String KEY_RECENT_APPS_CATEGORY = "recent_apps_category";
+ @VisibleForTesting
+ static final String KEY_GENERAL_CATEGORY = "general_category";
+ @VisibleForTesting
+ static final String KEY_ALL_APP_INFO = "all_app_infos";
+ @VisibleForTesting
+ static final String KEY_SEE_ALL = "see_all_apps";
+
+ private final ApplicationsState mApplicationsState;
+ private final int mUserId;
+
+ @VisibleForTesting
+ List mRecentApps;
+ @VisibleForTesting
+ PreferenceCategory mRecentAppsCategory;
+ @VisibleForTesting
+ PreferenceCategory mGeneralCategory;
+ @VisibleForTesting
+ Preference mAllAppsInfoPref;
+ @VisibleForTesting
+ Preference mSeeAllPref;
+
+ private Fragment mHost;
+ private boolean mInitialLaunch = false;
+
+ public AppsPreferenceController(Context context) {
+ super(context, KEY_RECENT_APPS_CATEGORY);
+ mApplicationsState = ApplicationsState.getInstance(
+ (Application) mContext.getApplicationContext());
+ mUserId = UserHandle.myUserId();
+ }
+
+ public void setFragment(Fragment fragment) {
+ mHost = fragment;
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE_UNSEARCHABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ initPreferences(screen);
+ refreshUi();
+ mInitialLaunch = true;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ if (!mInitialLaunch) {
+ refreshUi();
+ }
+ }
+
+ /**
+ * Called when the apps page pauses.
+ */
+ @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+ public void onPause() {
+ mInitialLaunch = false;
+ }
+
+ @VisibleForTesting
+ void refreshUi() {
+ loadAllAppsCount();
+ mRecentApps = loadRecentApps();
+ if (!mRecentApps.isEmpty()) {
+ displayRecentApps();
+ mAllAppsInfoPref.setVisible(false);
+ mRecentAppsCategory.setVisible(true);
+ mGeneralCategory.setVisible(true);
+ mSeeAllPref.setVisible(true);
+ } else {
+ mAllAppsInfoPref.setVisible(true);
+ mRecentAppsCategory.setVisible(false);
+ mGeneralCategory.setVisible(false);
+ mSeeAllPref.setVisible(false);
+ }
+ }
+
+ @VisibleForTesting
+ void loadAllAppsCount() {
+ // Show total number of installed apps as See all's summary.
+ new InstalledAppCounter(mContext, InstalledAppCounter.IGNORE_INSTALL_REASON,
+ mContext.getPackageManager()) {
+ @Override
+ protected void onCountComplete(int num) {
+ if (!mRecentApps.isEmpty()) {
+ mSeeAllPref.setTitle(
+ mContext.getResources().getQuantityString(R.plurals.see_all_apps_title,
+ num, num));
+ } else {
+ mAllAppsInfoPref.setSummary(mContext.getString(R.string.apps_summary, num));
+ }
+ }
+ }.execute();
+ }
+
+ @VisibleForTesting
+ List loadRecentApps() {
+ final RecentAppStatsMixin recentAppStatsMixin = new RecentAppStatsMixin(mContext,
+ SHOW_RECENT_APP_COUNT);
+ recentAppStatsMixin.loadDisplayableRecentApps(SHOW_RECENT_APP_COUNT);
+ return recentAppStatsMixin.mRecentApps;
+ }
+
+ private void initPreferences(PreferenceScreen screen) {
+ mRecentAppsCategory = screen.findPreference(KEY_RECENT_APPS_CATEGORY);
+ mGeneralCategory = screen.findPreference(KEY_GENERAL_CATEGORY);
+ mAllAppsInfoPref = screen.findPreference(KEY_ALL_APP_INFO);
+ mSeeAllPref = screen.findPreference(KEY_SEE_ALL);
+ mRecentAppsCategory.setVisible(false);
+ mGeneralCategory.setVisible(false);
+ mAllAppsInfoPref.setVisible(false);
+ mSeeAllPref.setVisible(false);
+ }
+
+ private void displayRecentApps() {
+ if (mRecentAppsCategory != null) {
+ final Map existedAppPreferences = new ArrayMap<>();
+ final int prefCount = mRecentAppsCategory.getPreferenceCount();
+ for (int i = 0; i < prefCount; i++) {
+ final Preference pref = mRecentAppsCategory.getPreference(i);
+ final String key = pref.getKey();
+ if (!TextUtils.equals(key, KEY_SEE_ALL)) {
+ existedAppPreferences.put(key, pref);
+ }
+ }
+
+ int showAppsCount = 0;
+ for (UsageStats stat : mRecentApps) {
+ final String pkgName = stat.getPackageName();
+ final ApplicationsState.AppEntry appEntry =
+ mApplicationsState.getEntry(pkgName, mUserId);
+ if (appEntry == null) {
+ continue;
+ }
+
+ boolean rebindPref = true;
+ Preference pref = existedAppPreferences.remove(pkgName);
+ if (pref == null) {
+ pref = new AppPreference(mContext);
+ rebindPref = false;
+ }
+
+ pref.setKey(pkgName);
+ pref.setTitle(appEntry.label);
+ pref.setIcon(Utils.getBadgedIcon(mContext, appEntry.info));
+ pref.setSummary(StringUtil.formatRelativeTime(mContext,
+ System.currentTimeMillis() - stat.getLastTimeUsed(), false,
+ RelativeDateTimeFormatter.Style.SHORT));
+ pref.setOrder(showAppsCount++);
+ pref.setOnPreferenceClickListener(preference -> {
+ AppInfoBase.startAppInfoFragment(AppInfoDashboardFragment.class,
+ R.string.application_info_label, pkgName, appEntry.info.uid,
+ mHost, 1001 /*RequestCode*/, getMetricsCategory());
+ return true;
+ });
+
+ if (!rebindPref) {
+ mRecentAppsCategory.addPreference(pref);
+ }
+ }
+
+ // Remove unused preferences from pref category.
+ for (Preference unusedPref : existedAppPreferences.values()) {
+ mRecentAppsCategory.removePreference(unusedPref);
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/ClearDefaultsPreference.java b/src/com/android/settings/applications/ClearDefaultsPreference.java
index f9466364ff2c074d21470165a16faf6eee0c2003..0952b9c8ca7f2711f34d747d8cc05e6866611b0c 100644
--- a/src/com/android/settings/applications/ClearDefaultsPreference.java
+++ b/src/com/android/settings/applications/ClearDefaultsPreference.java
@@ -117,7 +117,7 @@ public class ClearDefaultsPreference extends Preference {
if (mUsbManager != null) {
final int userId = UserHandle.myUserId();
mPm.clearPackagePreferredActivities(mPackageName);
- if (isDefaultBrowser(mPackageName)) {
+ if (AppUtils.isDefaultBrowser(getContext(), mPackageName)) {
mPm.setDefaultBrowserPackageNameAsUser(null, userId);
}
try {
@@ -141,7 +141,7 @@ public class ClearDefaultsPreference extends Preference {
TextView autoLaunchView = (TextView) view.findViewById(R.id.auto_launch);
boolean autoLaunchEnabled = AppUtils.hasPreferredActivities(mPm, mPackageName)
- || isDefaultBrowser(mPackageName)
+ || AppUtils.isDefaultBrowser(getContext(), mPackageName)
|| AppUtils.hasUsbDefaults(mUsbManager, mPackageName);
if (!autoLaunchEnabled && !hasBindAppWidgetPermission) {
resetLaunchDefaultsUi(autoLaunchView);
@@ -185,11 +185,6 @@ public class ClearDefaultsPreference extends Preference {
return true;
}
- private boolean isDefaultBrowser(String packageName) {
- final String defaultBrowser = mPm.getDefaultBrowserPackageNameAsUser(UserHandle.myUserId());
- return packageName.equals(defaultBrowser);
- }
-
private void resetLaunchDefaultsUi(TextView autoLaunchView) {
autoLaunchView.setText(R.string.auto_launch_disable_text);
// Disable clear activities button
diff --git a/src/com/android/settings/applications/ConvertToFbe.java b/src/com/android/settings/applications/ConvertToFbe.java
index 2a6bdaf2c4baa3f6c717deef321ca21873402af7..d4700115ffca680fbf65e68e13b947ab5a7d88bd 100644
--- a/src/com/android/settings/applications/ConvertToFbe.java
+++ b/src/com/android/settings/applications/ConvertToFbe.java
@@ -39,9 +39,11 @@ public class ConvertToFbe extends InstrumentedFragment {
private boolean runKeyguardConfirmation(int request) {
Resources res = getActivity().getResources();
- return new ChooseLockSettingsHelper(getActivity(), this)
- .launchConfirmationActivity(request,
- res.getText(R.string.convert_to_file_encryption));
+ final ChooseLockSettingsHelper.Builder builder =
+ new ChooseLockSettingsHelper.Builder(getActivity(), this);
+ return builder.setRequestCode(request)
+ .setTitle(res.getText(R.string.convert_to_file_encryption))
+ .show();
}
@Override
diff --git a/tests/unit/src/com/android/settings/applications/ExternalSourcesSettingsTest.java b/src/com/android/settings/applications/GameSettingsFeatureProvider.java
similarity index 52%
rename from tests/unit/src/com/android/settings/applications/ExternalSourcesSettingsTest.java
rename to src/com/android/settings/applications/GameSettingsFeatureProvider.java
index b35d6cbff7d3f12eb6a2d0e08b76ff0d250a85c5..ac2d6bddbe5e9bef2534adb4b4e46343cc341787 100644
--- a/tests/unit/src/com/android/settings/applications/ExternalSourcesSettingsTest.java
+++ b/src/com/android/settings/applications/GameSettingsFeatureProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,22 +16,19 @@
package com.android.settings.applications;
-import android.app.AppOpsManager;
-import android.provider.Settings;
+import android.content.Context;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class ExternalSourcesSettingsTest extends AppOpsSettingsTest {
-
- public ExternalSourcesSettingsTest() {
- super(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
- AppOpsManager.OP_REQUEST_INSTALL_PACKAGES);
- }
+/**
+ * Provider for Game Settings related feature.
+ */
+public interface GameSettingsFeatureProvider {
+ /**
+ * Returns true if the feature is supported.
+ */
+ boolean isSupported(Context context);
- // Test cases are in the superclass.
+ /**
+ * Launch GameSettings
+ */
+ void launchGameSettings(Context context);
}
diff --git a/tests/unit/src/com/android/settings/applications/DrawOverlaySettingsTest.java b/src/com/android/settings/applications/GameSettingsFeatureProviderImpl.java
similarity index 54%
rename from tests/unit/src/com/android/settings/applications/DrawOverlaySettingsTest.java
rename to src/com/android/settings/applications/GameSettingsFeatureProviderImpl.java
index b6d51ff9f9f24107c19fb39c0f5559b2a901f3f9..67212e7da8ba4ca1a322c3ab8765f7defa461cff 100644
--- a/tests/unit/src/com/android/settings/applications/DrawOverlaySettingsTest.java
+++ b/src/com/android/settings/applications/GameSettingsFeatureProviderImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,21 +16,20 @@
package com.android.settings.applications;
-import android.app.AppOpsManager;
-import android.provider.Settings;
+import android.content.Context;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class DrawOverlaySettingsTest extends AppOpsSettingsTest {
-
- public DrawOverlaySettingsTest() {
- super(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, AppOpsManager.OP_SYSTEM_ALERT_WINDOW);
+/**
+ * Provider implementation for Game Settings related features.
+ */
+public class GameSettingsFeatureProviderImpl implements
+ GameSettingsFeatureProvider {
+ @Override
+ public boolean isSupported(Context context) {
+ return false;
}
- // Test cases are in the superclass.
-}
\ No newline at end of file
+ @Override
+ public void launchGameSettings(Context context) {
+ return;
+ }
+}
diff --git a/src/com/android/settings/applications/GameSettingsPreferenceController.java b/src/com/android/settings/applications/GameSettingsPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..67c1bb1fea1ce7df61630367c29bf62b0aab3d1b
--- /dev/null
+++ b/src/com/android/settings/applications/GameSettingsPreferenceController.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import androidx.preference.Preference;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+
+/** Contains logic that deals with showing Game Settings in app settings. */
+public class GameSettingsPreferenceController extends BasePreferenceController {
+
+ @VisibleForTesting
+ final GameSettingsFeatureProvider mGameSettingsFeatureProvider;
+
+ public GameSettingsPreferenceController(Context context, String key) {
+ super(context, key);
+ mGameSettingsFeatureProvider =
+ FeatureFactory.getFactory(context).getGameSettingsFeatureProvider();
+ }
+
+ GameSettingsPreferenceController(Context context, String key,
+ GameSettingsFeatureProvider gameSettingsFeatureProvider) {
+ super(context, key);
+ mGameSettingsFeatureProvider = gameSettingsFeatureProvider;
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mGameSettingsFeatureProvider.isSupported(mContext)
+ ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (TextUtils.equals(getPreferenceKey(), preference.getKey())) {
+ mGameSettingsFeatureProvider.launchGameSettings(mContext);
+ return true;
+ }
+ return super.handlePreferenceTreeClick(preference);
+ }
+}
diff --git a/src/com/android/settings/applications/HibernatedAppsPreferenceController.java b/src/com/android/settings/applications/HibernatedAppsPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..9562fd7b9609d2cf6a7cd4c9b398de22cedb25c6
--- /dev/null
+++ b/src/com/android/settings/applications/HibernatedAppsPreferenceController.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications;
+
+import static android.app.usage.UsageStatsManager.INTERVAL_MONTHLY;
+import static android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION;
+
+import static com.android.settings.Utils.PROPERTY_APP_HIBERNATION_ENABLED;
+
+import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
+import android.apphibernation.AppHibernationManager;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.provider.DeviceConfig;
+import android.util.ArrayMap;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A preference controller handling the logic for updating summary of hibernated apps.
+ */
+public final class HibernatedAppsPreferenceController extends BasePreferenceController
+ implements LifecycleObserver {
+ private static final String TAG = "HibernatedAppsPrefController";
+ private static final String PROPERTY_HIBERNATION_UNUSED_THRESHOLD_MILLIS =
+ "auto_revoke_unused_threshold_millis2";
+ private static final long DEFAULT_UNUSED_THRESHOLD_MS = TimeUnit.DAYS.toMillis(90);
+ private PreferenceScreen mScreen;
+ private int mUnusedCount = 0;
+ private boolean mLoadingUnusedApps;
+ private boolean mLoadedUnusedCount;
+ private final Executor mBackgroundExecutor;
+ private final Executor mMainExecutor;
+
+ public HibernatedAppsPreferenceController(Context context, String preferenceKey) {
+ this(context, preferenceKey, Executors.newSingleThreadExecutor(),
+ context.getMainExecutor());
+ }
+
+ @VisibleForTesting
+ HibernatedAppsPreferenceController(Context context, String preferenceKey,
+ Executor bgExecutor, Executor mainExecutor) {
+ super(context, preferenceKey);
+ mBackgroundExecutor = bgExecutor;
+ mMainExecutor = mainExecutor;
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return isHibernationEnabled() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return mLoadedUnusedCount
+ ? mContext.getResources().getQuantityString(
+ R.plurals.unused_apps_summary, mUnusedCount, mUnusedCount)
+ : mContext.getResources().getString(R.string.summary_placeholder);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mScreen = screen;
+ }
+
+ /**
+ * On lifecycle resume event.
+ */
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ public void onResume() {
+ updatePreference();
+ }
+
+ private void updatePreference() {
+ if (mScreen == null) {
+ return;
+ }
+ if (!mLoadingUnusedApps) {
+ loadUnusedCount(unusedCount -> {
+ mUnusedCount = unusedCount;
+ mLoadingUnusedApps = false;
+ mLoadedUnusedCount = true;
+ mMainExecutor.execute(() -> {
+ Preference pref = mScreen.findPreference(mPreferenceKey);
+ refreshSummary(pref);
+ });
+ });
+ mLoadingUnusedApps = true;
+ }
+ }
+
+ /**
+ * Asynchronously load the count of unused apps.
+ *
+ * @param callback callback to call when the number of unused apps is calculated
+ */
+ private void loadUnusedCount(@NonNull UnusedCountLoadedCallback callback) {
+ mBackgroundExecutor.execute(() -> {
+ final int unusedCount = getUnusedCount();
+ callback.onUnusedCountLoaded(unusedCount);
+ });
+ }
+
+ @WorkerThread
+ private int getUnusedCount() {
+ // TODO(b/187465752): Find a way to export this logic from PermissionController module
+ final PackageManager pm = mContext.getPackageManager();
+ final AppHibernationManager ahm = mContext.getSystemService(AppHibernationManager.class);
+ final List hibernatedPackages = ahm.getHibernatingPackagesForUser();
+ int numHibernated = hibernatedPackages.size();
+
+ // Also need to count packages that are auto revoked but not hibernated.
+ int numAutoRevoked = 0;
+ final UsageStatsManager usm = mContext.getSystemService(UsageStatsManager.class);
+ final long now = System.currentTimeMillis();
+ final long unusedThreshold = DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
+ PROPERTY_HIBERNATION_UNUSED_THRESHOLD_MILLIS, DEFAULT_UNUSED_THRESHOLD_MS);
+ final List usageStatsList = usm.queryUsageStats(INTERVAL_MONTHLY,
+ now - unusedThreshold, now);
+ final Map recentlyUsedPackages = new ArrayMap<>();
+ for (UsageStats us : usageStatsList) {
+ recentlyUsedPackages.put(us.mPackageName, us);
+ }
+ final List packages = pm.getInstalledPackages(
+ PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_PERMISSIONS);
+ for (PackageInfo pi : packages) {
+ final String packageName = pi.packageName;
+ final UsageStats usageStats = recentlyUsedPackages.get(packageName);
+ // Only count packages that have not been used recently as auto-revoked permissions may
+ // stay revoked even after use if the user has not regranted them.
+ final boolean usedRecently = (usageStats != null
+ && (now - usageStats.getLastTimeAnyComponentUsed() < unusedThreshold
+ || now - usageStats.getLastTimeVisible() < unusedThreshold));
+ if (!hibernatedPackages.contains(packageName)
+ && pi.requestedPermissions != null
+ && !usedRecently) {
+ for (String perm : pi.requestedPermissions) {
+ if ((pm.getPermissionFlags(perm, packageName, mContext.getUser())
+ & PackageManager.FLAG_PERMISSION_AUTO_REVOKED) != 0) {
+ numAutoRevoked++;
+ break;
+ }
+ }
+ }
+ }
+ return numHibernated + numAutoRevoked;
+ }
+
+ private static boolean isHibernationEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, true);
+ }
+
+ /**
+ * Callback for when we've determined the number of unused apps.
+ */
+ private interface UnusedCountLoadedCallback {
+ void onUnusedCountLoaded(int unusedCount);
+ }
+}
diff --git a/src/com/android/settings/applications/InstalledAppOpenByDefaultActivity.java b/src/com/android/settings/applications/InstalledAppOpenByDefaultActivity.java
index cd30d792efd3b240146a148701734c91a119655e..799389e2672121eb107265e678736713dee9f33d 100644
--- a/src/com/android/settings/applications/InstalledAppOpenByDefaultActivity.java
+++ b/src/com/android/settings/applications/InstalledAppOpenByDefaultActivity.java
@@ -19,6 +19,7 @@ package com.android.settings.applications;
import android.content.Intent;
import com.android.settings.SettingsActivity;
+import com.android.settings.applications.intentpicker.AppLaunchSettings;
public class InstalledAppOpenByDefaultActivity extends SettingsActivity {
diff --git a/src/com/android/settings/applications/ProcessStatsPreference.java b/src/com/android/settings/applications/ProcessStatsPreference.java
index 4249381732df6d5d0a4c9366e6bf7faf76d0f2e2..b4df3ee0b173a77bd57403c405a2667ee3ccfeb1 100644
--- a/src/com/android/settings/applications/ProcessStatsPreference.java
+++ b/src/com/android/settings/applications/ProcessStatsPreference.java
@@ -22,7 +22,7 @@ import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.Log;
-import com.android.settingslib.widget.apppreference.AppPreference;
+import com.android.settingslib.widget.AppPreference;
public class ProcessStatsPreference extends AppPreference {
static final String TAG = "ProcessStatsPreference";
diff --git a/src/com/android/settings/applications/RecentAppsPreferenceController.java b/src/com/android/settings/applications/RecentAppsPreferenceController.java
deleted file mode 100644
index 20f9806d35fff1223771953bb56b4d0afa747155..0000000000000000000000000000000000000000
--- a/src/com/android/settings/applications/RecentAppsPreferenceController.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.applications;
-
-import android.app.Application;
-import android.app.usage.UsageStats;
-import android.content.Context;
-import android.icu.text.RelativeDateTimeFormatter;
-import android.os.UserHandle;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-import androidx.fragment.app.Fragment;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
-import com.android.settings.applications.manageapplications.ManageApplications;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settings.core.SubSettingLauncher;
-import com.android.settings.overlay.FeatureFactory;
-import com.android.settingslib.Utils;
-import com.android.settingslib.applications.ApplicationsState;
-import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
-import com.android.settingslib.utils.StringUtil;
-import com.android.settingslib.widget.AppEntitiesHeaderController;
-import com.android.settingslib.widget.AppEntityInfo;
-import com.android.settingslib.widget.LayoutPreference;
-
-import java.util.List;
-
-/**
- * This controller displays up to three recently used apps.
- * If there is no recently used app, we only show up an "App Info" preference.
- */
-public class RecentAppsPreferenceController extends BasePreferenceController
- implements RecentAppStatsMixin.RecentAppStatsListener {
-
- @VisibleForTesting
- static final String KEY_DIVIDER = "recent_apps_divider";
-
- @VisibleForTesting
- AppEntitiesHeaderController mAppEntitiesController;
- @VisibleForTesting
- LayoutPreference mRecentAppsPreference;
- @VisibleForTesting
- Preference mDivider;
-
- private final ApplicationsState mApplicationsState;
- private final int mUserId;
- private final MetricsFeatureProvider mMetricsFeatureProvider;
-
- private Fragment mHost;
- private List mRecentApps;
-
- public RecentAppsPreferenceController(Context context, String key) {
- super(context, key);
- mApplicationsState = ApplicationsState.getInstance(
- (Application) mContext.getApplicationContext());
- mUserId = UserHandle.myUserId();
- mMetricsFeatureProvider = FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();
- }
-
- public void setFragment(Fragment fragment) {
- mHost = fragment;
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AVAILABLE_UNSEARCHABLE;
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
-
- mDivider = screen.findPreference(KEY_DIVIDER);
- mRecentAppsPreference = screen.findPreference(getPreferenceKey());
- final View view = mRecentAppsPreference.findViewById(R.id.app_entities_header);
- mAppEntitiesController = AppEntitiesHeaderController.newInstance(mContext, view)
- .setHeaderTitleRes(R.string.recent_app_category_title)
- .setHeaderDetailsClickListener((View v) -> {
- mMetricsFeatureProvider.logClickedPreference(mRecentAppsPreference,
- getMetricsCategory());
- new SubSettingLauncher(mContext)
- .setDestination(ManageApplications.class.getName())
- .setArguments(null /* arguments */)
- .setTitleRes(R.string.application_info_label)
- .setSourceMetricsCategory(getMetricsCategory())
- .launch();
- });
- }
-
- @Override
- public void onReloadDataCompleted(@NonNull List recentApps) {
- mRecentApps = recentApps;
- refreshUi();
- // Show total number of installed apps as See all's summary.
- new InstalledAppCounter(mContext, InstalledAppCounter.IGNORE_INSTALL_REASON,
- mContext.getPackageManager()) {
- @Override
- protected void onCountComplete(int num) {
- mAppEntitiesController.setHeaderDetails(
- mContext.getResources().getQuantityString(R.plurals.see_all_apps_title,
- num, num));
- mAppEntitiesController.apply();
- }
- }.execute();
- }
-
- private void refreshUi() {
- if (!mRecentApps.isEmpty()) {
- displayRecentApps();
- mRecentAppsPreference.setVisible(true);
- mDivider.setVisible(true);
- } else {
- mDivider.setVisible(false);
- mRecentAppsPreference.setVisible(false);
- }
- }
-
- private void displayRecentApps() {
- int showAppsCount = 0;
-
- for (UsageStats stat : mRecentApps) {
- final AppEntityInfo appEntityInfoInfo = createAppEntity(stat);
- if (appEntityInfoInfo != null) {
- mAppEntitiesController.setAppEntity(showAppsCount++, appEntityInfoInfo);
- }
-
- if (showAppsCount == AppEntitiesHeaderController.MAXIMUM_APPS) {
- break;
- }
- }
- }
-
- private AppEntityInfo createAppEntity(UsageStats stat) {
- final String pkgName = stat.getPackageName();
- final ApplicationsState.AppEntry appEntry =
- mApplicationsState.getEntry(pkgName, mUserId);
- if (appEntry == null) {
- return null;
- }
-
- return new AppEntityInfo.Builder()
- .setIcon(Utils.getBadgedIcon(mContext, appEntry.info))
- .setTitle(appEntry.label)
- .setSummary(StringUtil.formatRelativeTime(mContext,
- System.currentTimeMillis() - stat.getLastTimeUsed(), false,
- RelativeDateTimeFormatter.Style.SHORT))
- .setOnClickListener(v -> {
- mMetricsFeatureProvider.logClickedPreference(mRecentAppsPreference,
- getMetricsCategory());
- AppInfoBase.startAppInfoFragment(AppInfoDashboardFragment.class,
- R.string.application_info_label, pkgName, appEntry.info.uid,
- mHost, 1001 /*RequestCode*/, getMetricsCategory());
- })
- .build();
- }
-}
diff --git a/src/com/android/settings/applications/RunningServices.java b/src/com/android/settings/applications/RunningServices.java
index 4d13241126f115f5a243ddca6d93756eec739bdb..b1689d5c5913680e04eea9ebd57c56c62eb77f96 100644
--- a/src/com/android/settings/applications/RunningServices.java
+++ b/src/com/android/settings/applications/RunningServices.java
@@ -72,7 +72,11 @@ public class RunningServices extends SettingsPreferenceFragment {
public void onResume() {
super.onResume();
boolean haveData = mRunningProcessesView.doResume(this, mRunningProcessesAvail);
- mLoadingViewController.handleLoadingContainer(haveData /* done */, false /* animate */);
+ if (haveData) {
+ mLoadingViewController.showContent(false /* animate */);
+ } else {
+ mLoadingViewController.showLoadingView();
+ }
}
@Override
diff --git a/src/com/android/settings/applications/RunningState.java b/src/com/android/settings/applications/RunningState.java
index d4abe6c4e28597b30e98f9cc1e6c609289608b6d..5429b053c61d4f97d7dc0b244651a0a4db6b82bf 100644
--- a/src/com/android/settings/applications/RunningState.java
+++ b/src/com/android/settings/applications/RunningState.java
@@ -92,27 +92,27 @@ public class RunningState {
// entry.
final SparseArray> mServiceProcessesByName
= new SparseArray>();
-
+
// Processes that are hosting a service we are interested in, organized
// by their pid. These disappear and re-appear as services are restarted.
final SparseArray mServiceProcessesByPid
= new SparseArray();
-
+
// Used to sort the interesting processes.
final ServiceProcessComparator mServiceProcessComparator
= new ServiceProcessComparator();
-
+
// Additional interesting processes to be shown to the user, even if
// there is no service running in them.
final ArrayList mInterestingProcesses = new ArrayList();
-
+
// All currently running processes, for finding dependencies etc.
final SparseArray mRunningProcesses
= new SparseArray();
-
+
// The processes associated with services, in sorted order.
final ArrayList mProcessItems = new ArrayList();
-
+
// All processes, used for retrieving memory information.
final ArrayList mAllProcessItems = new ArrayList();
@@ -139,69 +139,74 @@ public class RunningState {
int mSequence = 0;
- final Comparator mBackgroundComparator
- = new Comparator() {
- @Override
- public int compare(MergedItem lhs, MergedItem rhs) {
- if (DEBUG_COMPARE) {
- Log.i(TAG, "Comparing " + lhs + " with " + rhs);
- Log.i(TAG, " Proc " + lhs.mProcess + " with " + rhs.mProcess);
- Log.i(TAG, " UserId " + lhs.mUserId + " with " + rhs.mUserId);
- }
- if (lhs.mUserId != rhs.mUserId) {
- if (lhs.mUserId == mMyUserId) return -1;
- if (rhs.mUserId == mMyUserId) return 1;
- return lhs.mUserId < rhs.mUserId ? -1 : 1;
- }
- if (lhs.mProcess == rhs.mProcess) {
- if (lhs.mLabel == rhs.mLabel) {
+ final Comparator mBackgroundComparator =
+ new Comparator() {
+ @Override
+ public int compare(MergedItem lhs, MergedItem rhs) {
+ if (DEBUG_COMPARE) {
+ Log.i(TAG, "Comparing " + lhs + " with " + rhs);
+ Log.i(TAG, " Proc " + lhs.mProcess + " with " + rhs.mProcess);
+ Log.i(TAG, " UserId " + lhs.mUserId + " with " + rhs.mUserId);
+ }
+ if (lhs.mUserId != rhs.mUserId) {
+ if (lhs.mUserId == mMyUserId) return -1;
+ if (rhs.mUserId == mMyUserId) return 1;
+ return lhs.mUserId < rhs.mUserId ? -1 : 1;
+ }
+ if (lhs.mProcess == rhs.mProcess) {
+ if (lhs.mLabel == rhs.mLabel) {
+ return 0;
+ }
+ return lhs.mLabel != null ? lhs.mLabel.compareTo(rhs.mLabel) : -1;
+ }
+ if (lhs.mProcess == null) return -1;
+ if (rhs.mProcess == null) return 1;
+ if (DEBUG_COMPARE) {
+ Log.i(TAG, " Label " + lhs.mProcess.mLabel
+ + " with " + rhs.mProcess.mLabel);
+ }
+ final ActivityManager.RunningAppProcessInfo lhsInfo =
+ lhs.mProcess.mRunningProcessInfo;
+ final ActivityManager.RunningAppProcessInfo rhsInfo =
+ rhs.mProcess.mRunningProcessInfo;
+ final boolean lhsBg = lhsInfo.importance
+ >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
+ final boolean rhsBg = rhsInfo.importance
+ >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
+ if (DEBUG_COMPARE) Log.i(TAG, " Bg " + lhsBg + " with " + rhsBg);
+ if (lhsBg != rhsBg) {
+ return lhsBg ? 1 : -1;
+ }
+ final boolean lhsA = (lhsInfo.flags
+ & ActivityManager.RunningAppProcessInfo.FLAG_HAS_ACTIVITIES) != 0;
+ final boolean rhsA = (rhsInfo.flags
+ & ActivityManager.RunningAppProcessInfo.FLAG_HAS_ACTIVITIES) != 0;
+ if (DEBUG_COMPARE) Log.i(TAG, " Act " + lhsA + " with " + rhsA);
+ if (lhsA != rhsA) {
+ return lhsA ? -1 : 1;
+ }
+ if (DEBUG_COMPARE) {
+ Log.i(TAG,
+ " Lru " + lhsInfo.lru + " with " + rhsInfo.lru);
+ }
+ if (lhsInfo.lru != rhsInfo.lru) {
+ return lhsInfo.lru < rhsInfo.lru ? -1 : 1;
+ }
+ if (lhs.mProcess.mLabel == rhs.mProcess.mLabel) {
return 0;
}
- return lhs.mLabel != null ? lhs.mLabel.compareTo(rhs.mLabel) : -1;
- }
- if (lhs.mProcess == null) return -1;
- if (rhs.mProcess == null) return 1;
- if (DEBUG_COMPARE) Log.i(TAG, " Label " + lhs.mProcess.mLabel
- + " with " + rhs.mProcess.mLabel);
- final ActivityManager.RunningAppProcessInfo lhsInfo
- = lhs.mProcess.mRunningProcessInfo;
- final ActivityManager.RunningAppProcessInfo rhsInfo
- = rhs.mProcess.mRunningProcessInfo;
- final boolean lhsBg = lhsInfo.importance
- >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
- final boolean rhsBg = rhsInfo.importance
- >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
- if (DEBUG_COMPARE) Log.i(TAG, " Bg " + lhsBg + " with " + rhsBg);
- if (lhsBg != rhsBg) {
- return lhsBg ? 1 : -1;
- }
- final boolean lhsA = (lhsInfo.flags
- & ActivityManager.RunningAppProcessInfo.FLAG_HAS_ACTIVITIES) != 0;
- final boolean rhsA = (rhsInfo.flags
- & ActivityManager.RunningAppProcessInfo.FLAG_HAS_ACTIVITIES) != 0;
- if (DEBUG_COMPARE) Log.i(TAG, " Act " + lhsA + " with " + rhsA);
- if (lhsA != rhsA) {
- return lhsA ? -1 : 1;
+ if (lhs.mProcess.mLabel == null) return 1;
+ if (rhs.mProcess.mLabel == null) return -1;
+ return lhs.mProcess.mLabel.compareTo(rhs.mProcess.mLabel);
}
- if (DEBUG_COMPARE) Log.i(TAG, " Lru " + lhsInfo.lru + " with " + rhsInfo.lru);
- if (lhsInfo.lru != rhsInfo.lru) {
- return lhsInfo.lru < rhsInfo.lru ? -1 : 1;
- }
- if (lhs.mProcess.mLabel == rhs.mProcess.mLabel) {
- return 0;
- }
- if (lhs.mProcess.mLabel == null) return 1;
- if (rhs.mProcess.mLabel == null) return -1;
- return lhs.mProcess.mLabel.compareTo(rhs.mProcess.mLabel);
- }
- };
+ };
// ----- following protected by mLock -----
-
+
// Lock for protecting the state that will be shared between the
// background update thread and the UI thread.
final Object mLock = new Object();
-
+
boolean mResumed;
boolean mHaveData;
boolean mWatchingBackgroundItems;
@@ -210,7 +215,7 @@ public class RunningState {
ArrayList mMergedItems = new ArrayList();
ArrayList mBackgroundItems = new ArrayList();
ArrayList mUserBackgroundItems = new ArrayList();
-
+
int mNumBackgroundProcesses;
long mBackgroundProcessMemory;
int mNumForegroundProcesses;
@@ -221,6 +226,7 @@ public class RunningState {
// ----- BACKGROUND MONITORING THREAD -----
final HandlerThread mBackgroundThread;
+
final class BackgroundHandler extends Handler {
public BackgroundHandler(Looper looper) {
super(looper);
@@ -372,9 +378,9 @@ public class RunningState {
ActivityManager.RunningServiceInfo mRunningService;
ServiceInfo mServiceInfo;
boolean mShownAsStarted;
-
+
MergedItem mMergedItem;
-
+
public ServiceItem(int userId) {
super(false, userId);
}
@@ -385,17 +391,17 @@ public class RunningState {
= new HashMap();
final SparseArray mDependentProcesses
= new SparseArray();
-
+
final int mUid;
final String mProcessName;
int mPid;
-
+
ProcessItem mClient;
int mLastNumDependentProcesses;
-
+
int mRunningSeq;
ActivityManager.RunningAppProcessInfo mRunningProcessInfo;
-
+
MergedItem mMergedItem;
boolean mInteresting;
@@ -404,7 +410,7 @@ public class RunningState {
boolean mIsSystem;
boolean mIsStarted;
long mActiveSince;
-
+
public ProcessItem(Context context, int uid, String processName) {
super(true, UserHandle.getUserId(uid));
mDescription = context.getResources().getString(
@@ -412,12 +418,12 @@ public class RunningState {
mUid = uid;
mProcessName = processName;
}
-
+
void ensureLabel(PackageManager pm) {
if (mLabel != null) {
return;
}
-
+
try {
ApplicationInfo ai = pm.getApplicationInfo(mProcessName,
PackageManager.MATCH_ANY_USER);
@@ -429,11 +435,11 @@ public class RunningState {
}
} catch (PackageManager.NameNotFoundException e) {
}
-
+
// If we couldn't get information about the overall
// process, try to find something about the uid.
String[] pkgs = pm.getPackagesForUid(mUid);
-
+
// If there is one package with this uid, that is what we want.
if (pkgs.length == 1) {
try {
@@ -446,7 +452,7 @@ public class RunningState {
} catch (PackageManager.NameNotFoundException e) {
}
}
-
+
// If there are multiple, see if one gives us the official name
// for this uid.
for (String name : pkgs) {
@@ -465,7 +471,7 @@ public class RunningState {
} catch (PackageManager.NameNotFoundException e) {
}
}
-
+
// If still don't have anything to display, just use the
// service info.
if (mServices.size() > 0) {
@@ -476,7 +482,7 @@ public class RunningState {
mLabel = mDisplayLabel.toString();
return;
}
-
+
// Finally... whatever, just pick the first package's name.
try {
ApplicationInfo ai = pm.getApplicationInfo(pkgs[0],
@@ -544,16 +550,16 @@ public class RunningState {
si.mDescription = context.getResources().getString(
R.string.service_started_by_app);
}
-
+
return changed;
}
-
+
boolean updateSize(Context context, long pss, int curSeq) {
mSize = pss * 1024;
if (mCurSeq == curSeq) {
String sizeStr = Formatter.formatShortFileSize(
context, mSize);
- if (!sizeStr.equals(mSizeStr)){
+ if (!sizeStr.equals(mSizeStr)) {
mSizeStr = sizeStr;
// We update this on the second tick where we update just
// the text in the current items, so no need to say we
@@ -563,11 +569,11 @@ public class RunningState {
}
return false;
}
-
+
boolean buildDependencyChain(Context context, PackageManager pm, int curSeq) {
final int NP = mDependentProcesses.size();
boolean changed = false;
- for (int i=0; i dest,
ArrayList destProc) {
final int NP = mDependentProcesses.size();
- for (int i=0; i mOtherProcesses = new ArrayList();
final ArrayList mServices = new ArrayList();
final ArrayList mChildren = new ArrayList();
-
+
private int mLastNumProcesses = -1, mLastNumServices = -1;
MergedItem(int userId) {
@@ -646,7 +652,7 @@ public class RunningState {
int numProcesses = 0;
int numServices = 0;
mActiveSince = -1;
- for (int i=0; i 0 ? 1 : 0) + mOtherProcesses.size(),
mServices.size());
}
-
+
mActiveSince = -1;
- for (int i=0; i= 0 && mActiveSince < si.mActiveSince) {
mActiveSince = si.mActiveSince;
@@ -678,25 +684,25 @@ public class RunningState {
return false;
}
-
+
boolean updateSize(Context context) {
if (mUser != null) {
mSize = 0;
- for (int i=0; i= 0) {
- label = label.substring(tail+1, label.length());
+ label = label.substring(tail + 1, label.length());
}
return label;
}
-
+
static RunningState getInstance(Context context) {
synchronized (sGlobalLock) {
if (sInstance == null) {
@@ -776,9 +782,9 @@ public class RunningState {
private RunningState(Context context) {
mApplicationContext = context.getApplicationContext();
- mAm = (ActivityManager)mApplicationContext.getSystemService(Context.ACTIVITY_SERVICE);
+ mAm = mApplicationContext.getSystemService(ActivityManager.class);
mPm = mApplicationContext.getPackageManager();
- mUm = (UserManager)mApplicationContext.getSystemService(Context.USER_SERVICE);
+ mUm = mApplicationContext.getSystemService(UserManager.class);
mMyUserId = UserHandle.myUserId();
UserInfo userInfo = mUm.getUserInfo(mMyUserId);
mHideManagedProfiles = userInfo == null || !userInfo.canHaveProfile();
@@ -842,14 +848,14 @@ public class RunningState {
}
private boolean isInterestingProcess(ActivityManager.RunningAppProcessInfo pi) {
- if ((pi.flags&ActivityManager.RunningAppProcessInfo.FLAG_CANT_SAVE_STATE) != 0) {
+ if ((pi.flags & ActivityManager.RunningAppProcessInfo.FLAG_CANT_SAVE_STATE) != 0) {
return true;
}
- if ((pi.flags&ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT) == 0
+ if ((pi.flags & ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT) == 0
&& pi.importance >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
&& pi.importance < ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE
&& pi.importanceReasonCode
- == ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN) {
+ == ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN) {
return true;
}
return false;
@@ -897,15 +903,15 @@ public class RunningState {
final PackageManager pm = context.getPackageManager();
mSequence++;
-
+
boolean changed = false;
// Retrieve list of services, filtering out anything that definitely
// won't be shown in the UI.
- List services
+ List services
= am.getRunningServices(MAX_SERVICES);
int NS = services != null ? services.size() : 0;
- for (int i=0; i 0) {
AppProcessInfo ainfo = mTmpAppProcesses.get(si.pid);
@@ -954,7 +960,7 @@ public class RunningState {
}
// Update state we are maintaining about process that are running services.
- for (int i=0; i uidToDelete = null;
- for (int i=0; i procs = mServiceProcessesByName.valueAt(i);
Iterator pit = procs.values().iterator();
while (pit.hasNext()) {
@@ -1119,8 +1125,7 @@ public class RunningState {
if (pi.mCurSeq == mSequence) {
pi.ensureLabel(pm);
if (pi.mPid == 0) {
- // Sanity: a non-process can't be dependent on
- // anything.
+ // Validation: a non-process can't be dependent on anything.
pi.mDependentProcesses.clear();
}
} else {
@@ -1147,7 +1152,7 @@ public class RunningState {
}
}
}
-
+
if (uidToDelete != null) {
for (int i = 0; i < uidToDelete.size(); i++) {
int uid = uidToDelete.get(i);
@@ -1158,7 +1163,7 @@ public class RunningState {
if (changed) {
// First determine an order for the services.
ArrayList sortedProcesses = new ArrayList();
- for (int i=0; i newItems = new ArrayList();
ArrayList newMergedItems = new ArrayList();
mProcessItems.clear();
- for (int i=0; i 0) {
mProcessItems.add(pi);
}
-
+
// Now add the services running in it.
MergedItem mergedItem = null;
boolean haveAllMerged = false;
@@ -1216,7 +1221,7 @@ public class RunningState {
haveAllMerged = false;
}
}
-
+
if (!haveAllMerged || mergedItem == null
|| mergedItem.mServices.size() != pi.mServices.size()) {
// Whoops, we need to build a new MergedItem!
@@ -1227,11 +1232,11 @@ public class RunningState {
}
mergedItem.mProcess = pi;
mergedItem.mOtherProcesses.clear();
- for (int mpi=firstProc; mpi<(mProcessItems.size()-1); mpi++) {
+ for (int mpi = firstProc; mpi < (mProcessItems.size() - 1); mpi++) {
mergedItem.mOtherProcesses.add(mProcessItems.get(mpi));
}
}
-
+
mergedItem.update(context, false);
if (mergedItem.mUserId != mMyUserId) {
addOtherUserItem(context, newMergedItems, mOtherUserMergedItems, mergedItem);
@@ -1243,7 +1248,7 @@ public class RunningState {
// Finally, interesting processes need to be shown and will
// go at the top.
NHP = mInterestingProcesses.size();
- for (int i=0; i= mBackgroundItems.size()
|| mBackgroundItems.get(bgIndex).mProcess != proc) {
newBackgroundItems = new ArrayList(numBackgroundProcesses);
- for (int bgi=0; bgi numBackgroundProcesses) {
newBackgroundItems = new ArrayList(numBackgroundProcesses);
- for (int bgi=0; bgi();
final int NB = newBackgroundItems.size();
- for (int i=0; i getDetailFragmentClass() {
+ return AlarmsAndRemindersDetails.class;
+ }
+
+ @VisibleForTesting
+ CharSequence getPreferenceSummary() {
+ return AlarmsAndRemindersDetails.getSummary(mContext, mParent.getAppEntry());
+ }
+
+ @VisibleForTesting
+ boolean isCandidate() {
+ final PackageInfo packageInfo = mParent.getPackageInfo();
+ if (packageInfo == null) {
+ return false;
+ }
+ final AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState appState =
+ new AppStateAlarmsAndRemindersBridge(mContext, null, null).createPermissionState(
+ mPackageName, packageInfo.applicationInfo.uid);
+ return appState.shouldBeVisible();
+ }
+
+ void setPackageName(String packageName) {
+ mPackageName = packageName;
+ }
+}
diff --git a/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java b/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java
new file mode 100644
index 0000000000000000000000000000000000000000..648696ba25d9900b7f87cc4caf299bbc022cb3eb
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.applications.appinfo;
+
+import static android.app.Activity.RESULT_CANCELED;
+import static android.app.Activity.RESULT_OK;
+
+import android.app.AppOpsManager;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.Preference;
+import androidx.preference.Preference.OnPreferenceChangeListener;
+
+import com.android.settings.R;
+import com.android.settings.Settings;
+import com.android.settings.applications.AppInfoWithHeader;
+import com.android.settings.applications.AppStateAlarmsAndRemindersBridge;
+import com.android.settingslib.RestrictedSwitchPreference;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+/**
+ * App specific activity to show details about
+ * {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM}.
+ */
+public class AlarmsAndRemindersDetails extends AppInfoWithHeader
+ implements OnPreferenceChangeListener {
+
+ private static final String KEY_SWITCH = "alarms_and_reminders_switch";
+ private static final String UNCOMMITTED_STATE_KEY = "uncommitted_state";
+
+ private AppStateAlarmsAndRemindersBridge mAppBridge;
+ private AppOpsManager mAppOpsManager;
+ private RestrictedSwitchPreference mSwitchPref;
+ private AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState mPermissionState;
+ private volatile Boolean mUncommittedState;
+
+ /**
+ * Returns the string that states whether the app has access to
+ * {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM}.
+ */
+ public static CharSequence getSummary(Context context, AppEntry entry) {
+ final AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState state =
+ new AppStateAlarmsAndRemindersBridge(context, /*appState=*/null,
+ /*callback=*/null).createPermissionState(entry.info.packageName,
+ entry.info.uid);
+
+ return context.getString(state.isAllowed() ? R.string.app_permission_summary_allowed
+ : R.string.app_permission_summary_not_allowed);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Context context = getActivity();
+ mAppBridge = new AppStateAlarmsAndRemindersBridge(context, mState, /*callback=*/null);
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+
+ if (savedInstanceState != null) {
+ mUncommittedState = (Boolean) savedInstanceState.get(UNCOMMITTED_STATE_KEY);
+ if (mUncommittedState != null && isAppSpecific()) {
+ setResult(mUncommittedState ? RESULT_OK : RESULT_CANCELED);
+ }
+ }
+ addPreferencesFromResource(R.xml.alarms_and_reminders);
+ mSwitchPref = findPreference(KEY_SWITCH);
+ mSwitchPref.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mUncommittedState != null) {
+ outState.putObject(UNCOMMITTED_STATE_KEY, mUncommittedState);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference == mSwitchPref) {
+ mUncommittedState = (Boolean) newValue;
+ if (isAppSpecific()) {
+ setResult(mUncommittedState ? RESULT_OK : RESULT_CANCELED);
+ }
+ refreshUi();
+ return true;
+ }
+ return false;
+ }
+
+ private void setCanScheduleAlarms(boolean newState) {
+ final int uid = mPackageInfo.applicationInfo.uid;
+ mAppOpsManager.setUidMode(AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, uid,
+ newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
+ }
+
+ private void logPermissionChange(boolean newState, String packageName) {
+ mMetricsFeatureProvider.action(
+ mMetricsFeatureProvider.getAttribution(getActivity()),
+ SettingsEnums.ACTION_ALARMS_AND_REMINDERS_TOGGLE,
+ getMetricsCategory(),
+ packageName,
+ newState ? 1 : 0);
+ }
+
+ private boolean isAppSpecific() {
+ return Settings.AlarmsAndRemindersAppActivity.class.getName().equals(
+ getIntent().getComponent().getClassName());
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (getActivity().isChangingConfigurations()) {
+ return;
+ }
+ if (mPermissionState != null && mUncommittedState != null
+ && mUncommittedState != mPermissionState.isAllowed()) {
+ setCanScheduleAlarms(mUncommittedState);
+ logPermissionChange(mUncommittedState, mPackageName);
+ mUncommittedState = null;
+ }
+ }
+
+ @Override
+ protected boolean refreshUi() {
+ if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
+ return false;
+ }
+ mPermissionState = mAppBridge.createPermissionState(mPackageName,
+ mPackageInfo.applicationInfo.uid);
+ mSwitchPref.setEnabled(mPermissionState.shouldBeVisible());
+ mSwitchPref.setChecked(
+ mUncommittedState != null ? mUncommittedState : mPermissionState.isAllowed());
+ return true;
+ }
+
+ @Override
+ protected AlertDialog createDialog(int id, int errorCode) {
+ return null;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.ALARMS_AND_REMINDERS;
+ }
+}
diff --git a/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
index 6e4818ab92f41998c3e015b68cc7b3643a2d4e3f..8d293010238044a276a25f361c31e8d209f3ab20 100644
--- a/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
@@ -18,57 +18,79 @@ package com.android.settings.applications.appinfo;
import android.content.Context;
import android.content.pm.PackageInfo;
-import android.os.BatteryStats;
+import android.content.pm.PackageManager;
+import android.os.AsyncTask;
+import android.os.BatteryUsageStats;
import android.os.Bundle;
+import android.os.UidBatteryConsumer;
+import android.os.UserHandle;
import android.os.UserManager;
+import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
-import com.android.internal.os.BatterySipper;
-import com.android.internal.os.BatteryStatsHelper;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.fuelgauge.AdvancedPowerUsageDetail;
+import com.android.settings.fuelgauge.BatteryChartPreferenceController;
+import com.android.settings.fuelgauge.BatteryDiffEntry;
import com.android.settings.fuelgauge.BatteryEntry;
-import com.android.settings.fuelgauge.BatteryStatsHelperLoader;
+import com.android.settings.fuelgauge.BatteryUsageStatsLoader;
import com.android.settings.fuelgauge.BatteryUtils;
+import com.android.settings.fuelgauge.ConvertUtils;
+import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
-import java.util.ArrayList;
import java.util.List;
public class AppBatteryPreferenceController extends BasePreferenceController
- implements LoaderManager.LoaderCallbacks,
- LifecycleObserver, OnResume, OnPause {
+ implements LifecycleObserver, OnResume, OnPause {
+ private static final String TAG = "AppBatteryPreferenceController";
private static final String KEY_BATTERY = "battery";
@VisibleForTesting
- BatterySipper mSipper;
- @VisibleForTesting
- BatteryStatsHelper mBatteryHelper;
+ final BatteryUsageStatsLoaderCallbacks mBatteryUsageStatsLoaderCallbacks =
+ new BatteryUsageStatsLoaderCallbacks();
@VisibleForTesting
BatteryUtils mBatteryUtils;
+ @VisibleForTesting
+ BatteryUsageStats mBatteryUsageStats;
+ @VisibleForTesting
+ UidBatteryConsumer mUidBatteryConsumer;
+ @VisibleForTesting
+ BatteryDiffEntry mBatteryDiffEntry;
+ @VisibleForTesting
+ boolean mIsChartGraphEnabled;
private Preference mPreference;
private final AppInfoDashboardFragment mParent;
private String mBatteryPercent;
private final String mPackageName;
+ private final int mUid;
+ private final int mUserId;
+ private boolean mBatteryUsageStatsLoaded = false;
+ private boolean mBatteryDiffEntriesLoaded = false;
public AppBatteryPreferenceController(Context context, AppInfoDashboardFragment parent,
- String packageName, Lifecycle lifecycle) {
+ String packageName, int uid, Lifecycle lifecycle) {
super(context, KEY_BATTERY);
mParent = parent;
mBatteryUtils = BatteryUtils.getInstance(mContext);
mPackageName = packageName;
+ mUid = uid;
+ mUserId = mContext.getUserId();
+ refreshFeatureFlag(mContext);
if (lifecycle != null) {
lifecycle.addObserver(this);
}
@@ -86,6 +108,7 @@ public class AppBatteryPreferenceController extends BasePreferenceController
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
mPreference.setEnabled(false);
+ loadBatteryDiffEntries();
}
@Override
@@ -93,14 +116,40 @@ public class AppBatteryPreferenceController extends BasePreferenceController
if (!KEY_BATTERY.equals(preference.getKey())) {
return false;
}
+
+ if (mBatteryDiffEntry != null) {
+ Log.i(TAG, "BatteryDiffEntry not null, launch : "
+ + mBatteryDiffEntry.getPackageName()
+ + " | uid : "
+ + mBatteryDiffEntry.mBatteryHistEntry.mUid
+ + " with DiffEntry data");
+ AdvancedPowerUsageDetail.startBatteryDetailPage(
+ mParent.getActivity(),
+ mParent,
+ mBatteryDiffEntry,
+ Utils.formatPercentage(
+ mBatteryDiffEntry.getPercentOfTotal(), /* round */ true),
+ /*isValidToShowSummary=*/ true,
+ /*slotInformation=*/ null);
+ return true;
+ }
+
if (isBatteryStatsAvailable()) {
final UserManager userManager =
(UserManager) mContext.getSystemService(Context.USER_SERVICE);
- final BatteryEntry entry = new BatteryEntry(mContext, null, userManager, mSipper);
- entry.defaultPackageName = mPackageName;
- AdvancedPowerUsageDetail.startBatteryDetailPage(mParent.getActivity(), mParent,
- mBatteryHelper, BatteryStats.STATS_SINCE_CHARGED, entry, mBatteryPercent);
+ final BatteryEntry entry = new BatteryEntry(mContext, /* handler */null, userManager,
+ mUidBatteryConsumer, /* isHidden */ false,
+ mUidBatteryConsumer.getUid(), /* packages */ null, mPackageName);
+ Log.i(TAG, "Battery consumer available, launch : "
+ + entry.getDefaultPackageName()
+ + " | uid : "
+ + entry.getUid()
+ + " with BatteryEntry data");
+ AdvancedPowerUsageDetail.startBatteryDetailPage(mParent.getActivity(), mParent, entry,
+ mIsChartGraphEnabled ? Utils.formatPercentage(0) : mBatteryPercent,
+ !mIsChartGraphEnabled);
} else {
+ Log.i(TAG, "Launch : " + mPackageName + " with package name");
AdvancedPowerUsageDetail.startBatteryDetailPage(mParent.getActivity(), mParent,
mPackageName);
}
@@ -110,48 +159,121 @@ public class AppBatteryPreferenceController extends BasePreferenceController
@Override
public void onResume() {
mParent.getLoaderManager().restartLoader(
- mParent.LOADER_BATTERY, Bundle.EMPTY, this);
+ AppInfoDashboardFragment.LOADER_BATTERY_USAGE_STATS, Bundle.EMPTY,
+ mBatteryUsageStatsLoaderCallbacks);
}
@Override
public void onPause() {
- mParent.getLoaderManager().destroyLoader(mParent.LOADER_BATTERY);
+ mParent.getLoaderManager().destroyLoader(
+ AppInfoDashboardFragment.LOADER_BATTERY_USAGE_STATS);
}
- @Override
- public Loader onCreateLoader(int id, Bundle args) {
- return new BatteryStatsHelperLoader(mContext);
+ private void loadBatteryDiffEntries() {
+ new AsyncTask() {
+ @Override
+ protected BatteryDiffEntry doInBackground(Void... unused) {
+ if (mPackageName == null) {
+ return null;
+ }
+ final List batteryDiffEntries =
+ BatteryChartPreferenceController.getBatteryLast24HrUsageData(mContext);
+ if (batteryDiffEntries == null) {
+ return null;
+ }
+ // Filter entry with consumer type to avoid system app,
+ // then use user id to divide normal app and work profile app,
+ // return target application from filter list by package name.
+ return batteryDiffEntries.stream()
+ .filter(entry -> entry.mBatteryHistEntry.mConsumerType
+ == ConvertUtils.CONSUMER_TYPE_UID_BATTERY)
+ .filter(entry -> entry.mBatteryHistEntry.mUserId == mUserId)
+ .filter(entry -> {
+ if (mPackageName.equals(entry.getPackageName())) {
+ Log.i(TAG, "Return target application: "
+ + entry.mBatteryHistEntry.mPackageName
+ + " | uid: " + entry.mBatteryHistEntry.mUid
+ + " | userId: " + entry.mBatteryHistEntry.mUserId);
+ return true;
+ }
+ return false;
+ })
+ .findFirst()
+ .orElse(/* other */null);
+ }
+
+ @Override
+ protected void onPostExecute(BatteryDiffEntry batteryDiffEntry) {
+ mBatteryDiffEntry = batteryDiffEntry;
+ updateBatteryWithDiffEntry();
+ }
+ }.execute();
}
- @Override
- public void onLoadFinished(Loader loader,
- BatteryStatsHelper batteryHelper) {
- mBatteryHelper = batteryHelper;
+ @VisibleForTesting
+ void updateBatteryWithDiffEntry() {
+ if (mIsChartGraphEnabled) {
+ if (mBatteryDiffEntry != null && mBatteryDiffEntry.mConsumePower > 0) {
+ mBatteryPercent = Utils.formatPercentage(
+ mBatteryDiffEntry.getPercentOfTotal(), /* round */ true);
+ mPreference.setSummary(mContext.getString(
+ R.string.battery_summary_24hr, mBatteryPercent));
+ } else {
+ mPreference.setSummary(
+ mContext.getString(R.string.no_battery_summary_24hr));
+ }
+ }
+
+ mBatteryDiffEntriesLoaded = true;
+ mPreference.setEnabled(mBatteryUsageStatsLoaded);
+ }
+
+ private void onLoadFinished() {
+ if (mBatteryUsageStats == null) {
+ return;
+ }
+
final PackageInfo packageInfo = mParent.getPackageInfo();
if (packageInfo != null) {
- mSipper = findTargetSipper(batteryHelper, packageInfo.applicationInfo.uid);
+ mUidBatteryConsumer = findTargetUidBatteryConsumer(mBatteryUsageStats,
+ packageInfo.applicationInfo.uid);
if (mParent.getActivity() != null) {
updateBattery();
}
}
}
- @Override
- public void onLoaderReset(Loader loader) {
+ private void refreshFeatureFlag(Context context) {
+ if (isWorkProfile(context)) {
+ try {
+ context = context.createPackageContextAsUser(
+ context.getPackageName(), 0, UserHandle.OWNER);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "context.createPackageContextAsUser() fail: " + e);
+ }
+ }
+
+ final PowerUsageFeatureProvider powerUsageFeatureProvider =
+ FeatureFactory.getFactory(context).getPowerUsageFeatureProvider(context);
+ mIsChartGraphEnabled = powerUsageFeatureProvider.isChartGraphEnabled(context);
+ }
+
+ private boolean isWorkProfile(Context context) {
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ return userManager.isManagedProfile() && !userManager.isSystemUser();
}
@VisibleForTesting
void updateBattery() {
- mPreference.setEnabled(true);
+ mBatteryUsageStatsLoaded = true;
+ mPreference.setEnabled(mBatteryDiffEntriesLoaded);
+ if (mIsChartGraphEnabled) {
+ return;
+ }
if (isBatteryStatsAvailable()) {
- final int dischargeAmount = mBatteryHelper.getStats().getDischargeAmount(
- BatteryStats.STATS_SINCE_CHARGED);
-
- final List usageList = new ArrayList<>(mBatteryHelper.getUsageList());
- final double hiddenAmount = mBatteryUtils.removeHiddenBatterySippers(usageList);
final int percentOfMax = (int) mBatteryUtils.calculateBatteryPercent(
- mSipper.totalPowerMah, mBatteryHelper.getTotalPower(), hiddenAmount,
- dischargeAmount);
+ mUidBatteryConsumer.getConsumedPower(), mBatteryUsageStats.getConsumedPower(),
+ mBatteryUsageStats.getDischargePercentage());
mBatteryPercent = Utils.formatPercentage(percentOfMax);
mPreference.setSummary(mContext.getString(R.string.battery_summary, mBatteryPercent));
} else {
@@ -161,19 +283,38 @@ public class AppBatteryPreferenceController extends BasePreferenceController
@VisibleForTesting
boolean isBatteryStatsAvailable() {
- return mBatteryHelper != null && mSipper != null;
+ return mUidBatteryConsumer != null;
}
@VisibleForTesting
- BatterySipper findTargetSipper(BatteryStatsHelper batteryHelper, int uid) {
- final List usageList = batteryHelper.getUsageList();
+ UidBatteryConsumer findTargetUidBatteryConsumer(BatteryUsageStats batteryUsageStats, int uid) {
+ final List usageList = batteryUsageStats.getUidBatteryConsumers();
for (int i = 0, size = usageList.size(); i < size; i++) {
- final BatterySipper sipper = usageList.get(i);
- if (sipper.getUid() == uid) {
- return sipper;
+ final UidBatteryConsumer consumer = usageList.get(i);
+ if (consumer.getUid() == uid) {
+ return consumer;
}
}
return null;
}
+ private class BatteryUsageStatsLoaderCallbacks
+ implements LoaderManager.LoaderCallbacks {
+ @Override
+ @NonNull
+ public Loader onCreateLoader(int id, Bundle args) {
+ return new BatteryUsageStatsLoader(mContext, /* includeBatteryHistory */ false);
+ }
+
+ @Override
+ public void onLoadFinished(Loader loader,
+ BatteryUsageStats batteryUsageStats) {
+ mBatteryUsageStats = batteryUsageStats;
+ AppBatteryPreferenceController.this.onLoadFinished();
+ }
+
+ @Override
+ public void onLoaderReset(Loader loader) {
+ }
+ }
}
diff --git a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java
index 36ad2ce9746c7aa8ff94183c22def30724e054e2..09bf86a812068ec6a90eecadc5510ab2f5c045d5 100644
--- a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java
@@ -269,7 +269,14 @@ public class AppButtonsPreferenceController extends BasePreferenceController imp
@Override
public void onClick(View v) {
+ mMetricsFeatureProvider.action(
+ mActivity, SettingsEnums.ACTION_APP_INFO_FORCE_STOP);
// force stop
+ if (mPm.isPackageStateProtected(mAppEntry.info.packageName, mUserId)) {
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mActivity,
+ RestrictedLockUtilsInternal.getDeviceOwner(mActivity));
+ return;
+ }
if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
mActivity, mAppsControlDisallowedAdmin);
@@ -613,8 +620,7 @@ public class AppButtonsPreferenceController extends BasePreferenceController imp
/** Returns whether there is only one user on this device, not including the system-only user */
private boolean isSingleUser() {
final int userCount = mUserManager.getUserCount();
- return userCount == 1
- || (mUserManager.isSplitSystemUser() && userCount == 2);
+ return userCount == 1;
}
private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
@@ -725,6 +731,8 @@ public class AppButtonsPreferenceController extends BasePreferenceController imp
mPackageName, AUTO_REVOKED_APP_INTERACTION__ACTION__OPEN_IN_SETTINGS);
}
mContext.startActivityAsUser(mAppLaunchIntent, new UserHandle(mUserId));
+ mMetricsFeatureProvider.action(mActivity,
+ SettingsEnums.ACTION_APP_INFO_OPEN, mPackageName);
}
}
diff --git a/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceController.java b/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceController.java
index c9f0e31f1713495f42b48ed6d5f94468155f815b..285493a16c9e354956de4fdca1e700fb2f97bb60 100644
--- a/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppHeaderViewPreferenceController.java
@@ -29,11 +29,10 @@ import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.widget.LayoutPreference;
public class AppHeaderViewPreferenceController extends BasePreferenceController
- implements AppInfoDashboardFragment.Callback, LifecycleObserver, OnStart {
+ implements AppInfoDashboardFragment.Callback, LifecycleObserver {
private static final String KEY_HEADER = "header_view";
@@ -67,19 +66,13 @@ public class AppHeaderViewPreferenceController extends BasePreferenceController
final Activity activity = mParent.getActivity();
mEntityHeaderController = EntityHeaderController
.newInstance(activity, mParent, mHeader.findViewById(R.id.entity_header))
+ .setRecyclerView(mParent.getListView(), mLifecycle)
.setPackageName(mPackageName)
.setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
EntityHeaderController.ActionType.ACTION_NONE)
.bindHeaderButtons();
}
- @Override
- public void onStart() {
- mEntityHeaderController
- .setRecyclerView(mParent.getListView(), mLifecycle)
- .styleActionBar(mParent.getActivity());
- }
-
@Override
public void refreshUi() {
setAppLabelAndIcon(mParent.getPackageInfo(), mParent.getAppEntry());
diff --git a/src/com/android/settings/applications/appinfo/AppHibernationPreferenceCategoryController.java b/src/com/android/settings/applications/appinfo/AppHibernationPreferenceCategoryController.java
new file mode 100644
index 0000000000000000000000000000000000000000..ef891686420420f8dd31a1516ac649baec617069
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/AppHibernationPreferenceCategoryController.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.appinfo;
+
+import android.content.Context;
+
+import com.android.settings.widget.PreferenceCategoryController;
+
+/**
+ * A preference category controller serves as the parent for app hibernation related preference.
+ */
+public final class AppHibernationPreferenceCategoryController extends PreferenceCategoryController {
+ public AppHibernationPreferenceCategoryController(Context context, String key) {
+ super(context, key);
+ }
+}
diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
index 7aad2452b051849aab83300d63b9c20f81c2d48a..e1ea8e47afa32b0b7dfe4a2099004ee0ec04a160 100755
--- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
+++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
@@ -93,6 +93,7 @@ public class AppInfoDashboardFragment extends DashboardFragment
static final int LOADER_CHART_DATA = 2;
static final int LOADER_STORAGE = 3;
static final int LOADER_BATTERY = 4;
+ static final int LOADER_BATTERY_USAGE_STATS = 5;
public static final String ARG_PACKAGE_NAME = "package";
public static final String ARG_PACKAGE_UID = "uid";
@@ -108,6 +109,7 @@ public class AppInfoDashboardFragment extends DashboardFragment
private PackageInfo mPackageInfo;
private int mUserId;
private String mPackageName;
+ private int mUid;
private DevicePolicyManager mDpm;
private UserManager mUserManager;
@@ -163,6 +165,14 @@ public class AppInfoDashboardFragment extends DashboardFragment
use(AppStoragePreferenceController.class).setParentFragment(this);
use(AppVersionPreferenceController.class).setParentFragment(this);
use(InstantAppDomainsPreferenceController.class).setParentFragment(this);
+ use(ExtraAppInfoPreferenceController.class).setPackageName(packageName);
+
+ final HibernationSwitchPreferenceController appHibernationSettings =
+ use(HibernationSwitchPreferenceController.class);
+ appHibernationSettings.setParentFragment(this);
+ appHibernationSettings.setPackage(packageName);
+ use(AppHibernationPreferenceCategoryController.class).setChildren(
+ Arrays.asList(appHibernationSettings));
final WriteSystemSettingsPreferenceController writeSystemSettings =
use(WriteSystemSettingsPreferenceController.class);
@@ -187,8 +197,14 @@ public class AppInfoDashboardFragment extends DashboardFragment
acrossProfiles.setPackageName(packageName);
acrossProfiles.setParentFragment(this);
+ final AlarmsAndRemindersDetailPreferenceController alarmsAndReminders =
+ use(AlarmsAndRemindersDetailPreferenceController.class);
+ alarmsAndReminders.setPackageName(packageName);
+ alarmsAndReminders.setParentFragment(this);
+
use(AdvancedAppInfoPreferenceCategoryController.class).setChildren(Arrays.asList(
- writeSystemSettings, drawOverlay, pip, externalSource, acrossProfiles));
+ writeSystemSettings, drawOverlay, pip, externalSource, acrossProfiles,
+ alarmsAndReminders));
}
@Override
@@ -281,7 +297,8 @@ public class AppInfoDashboardFragment extends DashboardFragment
(SettingsActivity) getActivity(), this, lifecycle, packageName, mState,
REQUEST_UNINSTALL, REQUEST_REMOVE_DEVICE_ADMIN);
controllers.add(mAppButtonsPreferenceController);
- controllers.add(new AppBatteryPreferenceController(context, this, packageName, lifecycle));
+ controllers.add(new AppBatteryPreferenceController(
+ context, this, packageName, getUid(), lifecycle));
controllers.add(new AppMemoryPreferenceController(context, this, lifecycle));
controllers.add(new DefaultHomeShortcutPreferenceController(context, packageName));
controllers.add(new DefaultBrowserShortcutPreferenceController(context, packageName));
@@ -292,11 +309,6 @@ public class AppInfoDashboardFragment extends DashboardFragment
return controllers;
}
- @Override
- protected boolean isParalleledControllers() {
- return true;
- }
-
void addToCallbackList(Callback callback) {
if (callback != null) {
mCallbacks.add(callback);
@@ -526,7 +538,7 @@ public class AppInfoDashboardFragment extends DashboardFragment
@VisibleForTesting
int getNumberOfUserWithPackageInstalled(String packageName) {
- final List userInfos = mUserManager.getUsers(true);
+ final List userInfos = mUserManager.getAliveUsers();
int count = 0;
for (final UserInfo userInfo : userInfos) {
@@ -552,7 +564,7 @@ public class AppInfoDashboardFragment extends DashboardFragment
final Bundle args = getArguments();
mPackageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null;
if (mPackageName == null) {
- final Intent intent = (args == null) ?
+ final Intent intent = args == null ?
getActivity().getIntent() : (Intent) args.getParcelable("intent");
if (intent != null) {
mPackageName = intent.getData().getSchemeSpecificPart();
@@ -561,6 +573,21 @@ public class AppInfoDashboardFragment extends DashboardFragment
return mPackageName;
}
+ private int getUid() {
+ if (mUid > 0) {
+ return mUid;
+ }
+ final Bundle args = getArguments();
+ mUid = (args != null) ? args.getInt(ARG_PACKAGE_UID) : -1;
+ if (mUid <= 0) {
+ final Intent intent = args == null
+ ? getActivity().getIntent() : (Intent) args.getParcelable("intent");
+ mUid = intent != null && intent.getExtras() != null
+ ? mUid = intent.getIntExtra("uId", -1) : -1;
+ }
+ return mUid;
+ }
+
@VisibleForTesting
void retrieveAppEntry() {
final Activity activity = getActivity();
diff --git a/src/com/android/settings/applications/appinfo/AppNotificationPreferenceController.java b/src/com/android/settings/applications/appinfo/AppNotificationPreferenceController.java
index eab9a562872fa4ea6fea6983b1825678ec2fb0ae..bc87b44a69c67eebfaf32ba274ca6e302ebad839 100644
--- a/src/com/android/settings/applications/appinfo/AppNotificationPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppNotificationPreferenceController.java
@@ -72,6 +72,9 @@ public class AppNotificationPreferenceController extends AppInfoPreferenceContro
private CharSequence getNotificationSummary(ApplicationsState.AppEntry appEntry,
Context context, NotificationBackend backend) {
+ if (appEntry == null) {
+ return "";
+ }
NotificationBackend.AppRow appRow =
backend.loadAppRow(context, context.getPackageManager(), appEntry.info);
return getNotificationSummary(appRow, context);
diff --git a/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceController.java b/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceController.java
index f9c88fc6956133869a5f8db856c9cb7b5b6d7a3a..2c76f056a7cc9b70059378202eb1a391e96d6cab 100644
--- a/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceController.java
@@ -19,29 +19,29 @@ package com.android.settings.applications.appinfo;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.hardware.usb.IUsbManager;
-import android.os.ServiceManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationUserState;
import android.os.UserHandle;
+import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.applications.AppLaunchSettings;
+import com.android.settings.applications.intentpicker.AppLaunchSettings;
+import com.android.settings.applications.intentpicker.IntentPickerUtils;
+import com.android.settingslib.R;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
public class AppOpenByDefaultPreferenceController extends AppInfoPreferenceControllerBase {
- private IUsbManager mUsbManager;
- private PackageManager mPackageManager;
+ private final DomainVerificationManager mDomainVerificationManager;
private String mPackageName;
public AppOpenByDefaultPreferenceController(Context context, String key) {
super(context, key);
- mUsbManager = IUsbManager.Stub.asInterface(ServiceManager.getService(Context.USB_SERVICE));
- mPackageManager = context.getPackageManager();
+ mDomainVerificationManager = context.getSystemService(DomainVerificationManager.class);
}
/** Set a package name for this controller. */
@@ -69,8 +69,7 @@ public class AppOpenByDefaultPreferenceController extends AppInfoPreferenceContr
&& !AppUtils.isBrowserApp(mContext, packageInfo.packageName,
UserHandle.myUserId())) {
preference.setVisible(true);
- preference.setSummary(AppUtils.getLaunchByDefaultSummary(mParent.getAppEntry(),
- mUsbManager, mPackageManager, mContext));
+ preference.setSummary(getSubtext());
} else {
preference.setVisible(false);
}
@@ -80,4 +79,18 @@ public class AppOpenByDefaultPreferenceController extends AppInfoPreferenceContr
protected Class extends SettingsPreferenceFragment> getDetailFragmentClass() {
return AppLaunchSettings.class;
}
+
+ @VisibleForTesting
+ CharSequence getSubtext() {
+ return mContext.getText(isLinkHandlingAllowed()
+ ? R.string.app_link_open_always : R.string.app_link_open_never);
+ }
+
+ @VisibleForTesting
+ boolean isLinkHandlingAllowed() {
+ final DomainVerificationUserState userState =
+ IntentPickerUtils.getDomainVerificationUserState(mDomainVerificationManager,
+ mPackageName);
+ return userState == null ? false : userState.isLinkHandlingAllowed();
+ }
}
diff --git a/src/com/android/settings/applications/appinfo/AppVersionPreferenceController.java b/src/com/android/settings/applications/appinfo/AppVersionPreferenceController.java
index 205b6d275eef69bd9ce3e4771dff673b893145f8..23dd9602f77f6e3b33dae5297c756ee4a5b03704 100644
--- a/src/com/android/settings/applications/appinfo/AppVersionPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppVersionPreferenceController.java
@@ -17,6 +17,7 @@
package com.android.settings.applications.appinfo;
import android.content.Context;
+import android.content.pm.PackageInfo;
import android.text.BidiFormatter;
import com.android.settings.R;
@@ -29,7 +30,13 @@ public class AppVersionPreferenceController extends AppInfoPreferenceControllerB
@Override
public CharSequence getSummary() {
+ // TODO(b/168333280): Review the null case in detail since this is just a quick
+ // workaround to fix NPE.
+ final PackageInfo packageInfo = mParent.getPackageInfo();
+ if (packageInfo == null) {
+ return null;
+ }
return mContext.getString(R.string.version_text,
- BidiFormatter.getInstance().unicodeWrap(mParent.getPackageInfo().versionName));
+ BidiFormatter.getInstance().unicodeWrap(packageInfo.versionName));
}
}
diff --git a/src/com/android/settings/applications/appinfo/DefaultAppShortcutPreferenceControllerBase.java b/src/com/android/settings/applications/appinfo/DefaultAppShortcutPreferenceControllerBase.java
index 5df30c28d9854e688d53f6a066d57c5fb4e18a17..5a1883b33610756ecbb580d4fec207d891534f0b 100644
--- a/src/com/android/settings/applications/appinfo/DefaultAppShortcutPreferenceControllerBase.java
+++ b/src/com/android/settings/applications/appinfo/DefaultAppShortcutPreferenceControllerBase.java
@@ -14,9 +14,7 @@
package com.android.settings.applications.appinfo;
-import android.app.role.RoleControllerManager;
import android.app.role.RoleManager;
-import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.os.UserManager;
@@ -58,14 +56,12 @@ public abstract class DefaultAppShortcutPreferenceControllerBase extends BasePre
mRoleManager = context.getSystemService(RoleManager.class);
- final RoleControllerManager roleControllerManager =
- mContext.getSystemService(RoleControllerManager.class);
final Executor executor = mContext.getMainExecutor();
- roleControllerManager.isRoleVisible(mRoleName, executor, visible -> {
+ mRoleManager.isRoleVisible(mRoleName, executor, visible -> {
mRoleVisible = visible;
refreshAvailability();
});
- roleControllerManager.isApplicationVisibleForRole(mRoleName, mPackageName, executor,
+ mRoleManager.isApplicationVisibleForRole(mRoleName, mPackageName, executor,
visible -> {
mAppVisible = visible;
refreshAvailability();
diff --git a/src/com/android/settings/applications/appinfo/DrawOverlayDetails.java b/src/com/android/settings/applications/appinfo/DrawOverlayDetails.java
index 0f90c69c9ae028f944179ad513d5dc0343881281..5f7e56fa0e46822d75960300d66f77561b94cf6f 100644
--- a/src/com/android/settings/applications/appinfo/DrawOverlayDetails.java
+++ b/src/com/android/settings/applications/appinfo/DrawOverlayDetails.java
@@ -158,6 +158,10 @@ public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenc
}
public static CharSequence getSummary(Context context, AppEntry entry) {
+ if (entry == null) {
+ return "";
+ }
+
OverlayState state;
if (entry.extraInfo instanceof OverlayState) {
state = (OverlayState) entry.extraInfo;
diff --git a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java
index fe1d81c17c12af162b19f593644415646d390e4c..b723274436525a663dfcfcf82e6cabdeaaeb559a 100644
--- a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java
+++ b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java
@@ -18,7 +18,6 @@ package com.android.settings.applications.appinfo;
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
-import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
@@ -30,7 +29,6 @@ import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.Settings;
import com.android.settings.applications.AppInfoWithHeader;
@@ -46,7 +44,6 @@ public class ExternalSourcesDetails extends AppInfoWithHeader
private AppStateInstallAppsBridge mAppBridge;
private AppOpsManager mAppOpsManager;
- private ActivityManager mActivityManager;
private UserManager mUserManager;
private RestrictedSwitchPreference mSwitchPref;
private InstallAppsState mInstallAppsState;
@@ -58,7 +55,6 @@ public class ExternalSourcesDetails extends AppInfoWithHeader
final Context context = getActivity();
mAppBridge = new AppStateInstallAppsBridge(context, mState, null);
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- mActivityManager = context.getSystemService(ActivityManager.class);
mUserManager = UserManager.get(context);
addPreferencesFromResource(R.xml.external_sources_details);
@@ -103,21 +99,10 @@ public class ExternalSourcesDetails extends AppInfoWithHeader
: R.string.app_permission_summary_not_allowed);
}
- @VisibleForTesting
- void setCanInstallApps(boolean newState) {
+ private void setCanInstallApps(boolean newState) {
mAppOpsManager.setMode(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
mPackageInfo.applicationInfo.uid, mPackageName,
newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
- if (!newState) {
- killApp(mPackageInfo.applicationInfo.uid);
- }
- }
-
- private void killApp(int uid) {
- if (UserHandle.isCore(uid)) {
- return;
- }
- mActivityManager.killUid(uid, "User denied OP_REQUEST_INSTALL_PACKAGES");
}
@Override
diff --git a/src/com/android/settings/applications/appinfo/ExtraAppInfoFeatureProvider.java b/src/com/android/settings/applications/appinfo/ExtraAppInfoFeatureProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec70f572cdc5c264a8654899b5e1c2093d35e437
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/ExtraAppInfoFeatureProvider.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.appinfo;
+
+import android.content.Context;
+
+/**
+ * Provider for Extra App Info related feature
+ */
+public interface ExtraAppInfoFeatureProvider {
+ /** Returns true if the feature is supported. */
+ boolean isSupported(Context context);
+
+ /**
+ * Launch ExtraAppInfoSettings
+ */
+ void launchExtraAppInfoSettings(Context context);
+
+ /**
+ * Sets the package name
+ */
+ void setPackageName(String packageName);
+
+ /**
+ * Checks if enabled
+ */
+ boolean isEnabled(Context context);
+
+ /**
+ * Gets the summary name
+ */
+ String getSummary(Context context);
+}
diff --git a/src/com/android/settings/applications/appinfo/ExtraAppInfoFeatureProviderImpl.java b/src/com/android/settings/applications/appinfo/ExtraAppInfoFeatureProviderImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d800e684933de2dc4cc1b6ce04cd9aba3b868d2
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/ExtraAppInfoFeatureProviderImpl.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.appinfo;
+
+import android.content.Context;
+
+/**
+ * Provider for Extra App Info related feature
+ */
+public class ExtraAppInfoFeatureProviderImpl implements
+ ExtraAppInfoFeatureProvider {
+ @Override
+ public boolean isSupported(Context context) {
+ return false;
+ }
+
+ @Override
+ public void launchExtraAppInfoSettings(Context context) {
+ return;
+ }
+
+ @Override
+ public void setPackageName(String packageName) {
+ return;
+ }
+
+ @Override
+ public boolean isEnabled(Context context) {
+ return false;
+ }
+
+ @Override
+ public String getSummary(Context context) {
+ return "";
+ }
+}
diff --git a/src/com/android/settings/applications/appinfo/ExtraAppInfoPreferenceController.java b/src/com/android/settings/applications/appinfo/ExtraAppInfoPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..43796d0d85cd0c121d739965b3c3998fb7d2f81f
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/ExtraAppInfoPreferenceController.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.appinfo;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+
+/** Contains logic that deals with showing extra app info in app settings. */
+public class ExtraAppInfoPreferenceController extends BasePreferenceController {
+
+ private final ExtraAppInfoFeatureProvider mExtraAppInfoFeatureProvider;
+
+ public ExtraAppInfoPreferenceController(Context context, String key) {
+ super(context, key);
+ mExtraAppInfoFeatureProvider =
+ FeatureFactory.getFactory(context).getExtraAppInfoFeatureProvider();
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mExtraAppInfoFeatureProvider.isSupported(mContext)
+ ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (TextUtils.equals(getPreferenceKey(), preference.getKey())) {
+ mExtraAppInfoFeatureProvider.launchExtraAppInfoSettings(mContext);
+ return true;
+ }
+ return super.handlePreferenceTreeClick(preference);
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return mExtraAppInfoFeatureProvider.getSummary(mContext);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ if (mExtraAppInfoFeatureProvider != null) {
+ final Preference preference = screen.findPreference(getPreferenceKey());
+ preference.setEnabled(mExtraAppInfoFeatureProvider.isEnabled(preference.getContext()));
+ }
+ }
+
+ /**
+ * Set the local package name
+ */
+ public void setPackageName(String packageName) {
+ if (mExtraAppInfoFeatureProvider != null) {
+ mExtraAppInfoFeatureProvider.setPackageName(packageName);
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/appinfo/HibernationSwitchPreferenceController.java b/src/com/android/settings/applications/appinfo/HibernationSwitchPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..1d34a13e1ff69a51b5d790901a055ca74b0fa8c6
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/HibernationSwitchPreferenceController.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.appinfo;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED;
+import static android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION;
+
+import static com.android.settings.Utils.PROPERTY_APP_HIBERNATION_ENABLED;
+import static com.android.settings.Utils.PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreference;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * A PreferenceController handling the logic for exempting hibernation of app
+ */
+public final class HibernationSwitchPreferenceController extends AppInfoPreferenceControllerBase
+ implements LifecycleObserver, AppOpsManager.OnOpChangedListener,
+ Preference.OnPreferenceChangeListener {
+ private static final String TAG = "HibernationSwitchPrefController";
+ private String mPackageName;
+ private final AppOpsManager mAppOpsManager;
+ private int mPackageUid;
+ @VisibleForTesting
+ boolean mIsPackageSet;
+ private boolean mIsPackageExemptByDefault;
+
+ public HibernationSwitchPreferenceController(Context context,
+ String preferenceKey) {
+ super(context, preferenceKey);
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ public void onResume() {
+ if (mIsPackageSet) {
+ mAppOpsManager.startWatchingMode(
+ OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, mPackageName, this);
+ }
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+ public void onPause() {
+ mAppOpsManager.stopWatchingMode(this);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return isHibernationEnabled() && mIsPackageSet ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ /**
+ * Set the package. And also retrieve details from package manager. Some packages may be
+ * exempted from hibernation by default. This method should only be called to initialize the
+ * controller.
+ * @param packageName The name of the package whose hibernation state to be managed.
+ */
+ void setPackage(@NonNull String packageName) {
+ mPackageName = packageName;
+ final PackageManager packageManager = mContext.getPackageManager();
+
+ // Q- packages exempt by default, except R- on Auto since Auto-Revoke was skipped in R
+ final int maxTargetSdkVersionForExemptApps =
+ packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ ? android.os.Build.VERSION_CODES.R
+ : android.os.Build.VERSION_CODES.Q;
+ try {
+ mPackageUid = packageManager.getPackageUid(packageName, /* flags */ 0);
+ mIsPackageExemptByDefault =
+ hibernationTargetsPreSApps()
+ ? false
+ : packageManager.getTargetSdkVersion(packageName)
+ <= maxTargetSdkVersionForExemptApps;
+ mIsPackageSet = true;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Package [" + mPackageName + "] is not found!");
+ mIsPackageSet = false;
+ }
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ ((SwitchPreference) preference).setChecked(!isPackageHibernationExemptByUser());
+ }
+
+ @VisibleForTesting
+ boolean isPackageHibernationExemptByUser() {
+ if (!mIsPackageSet) return true;
+ final int mode = mAppOpsManager.unsafeCheckOpNoThrow(
+ OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, mPackageUid, mPackageName);
+
+ return mode == MODE_DEFAULT ? mIsPackageExemptByDefault : mode != MODE_ALLOWED;
+ }
+
+ @Override
+ public void onOpChanged(String op, String packageName) {
+ if (OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED.equals(op)
+ && TextUtils.equals(mPackageName, packageName)) {
+ updateState(mPreference);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object isChecked) {
+ try {
+ mAppOpsManager.setUidMode(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, mPackageUid,
+ (boolean) isChecked ? MODE_ALLOWED : MODE_IGNORED);
+ } catch (RuntimeException e) {
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean isHibernationEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, true);
+ }
+
+ private static boolean hibernationTargetsPreSApps() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_APP_HIBERNATION, PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS, false);
+ }
+}
diff --git a/src/com/android/settings/applications/appinfo/InstantAppDomainsPreferenceController.java b/src/com/android/settings/applications/appinfo/InstantAppDomainsPreferenceController.java
index 34c67f135901d51ff4b021396fee4cecfd0df13d..86b9fcfcc11ae2cedb1161dca14cb4947de158d6 100644
--- a/src/com/android/settings/applications/appinfo/InstantAppDomainsPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/InstantAppDomainsPreferenceController.java
@@ -51,7 +51,7 @@ public class InstantAppDomainsPreferenceController extends AppInfoPreferenceCont
final String[] handledDomains =
handledDomainSet.toArray(new String[handledDomainSet.size()]);
instantAppDomainsPreference.setTitles(handledDomains);
- // Dummy values, unused in the implementation
+ // placeholder values, unused in the implementation
instantAppDomainsPreference.setValues(new int[handledDomains.length]);
}
diff --git a/src/com/android/settings/applications/appinfo/MediaManagementAppsDetails.java b/src/com/android/settings/applications/appinfo/MediaManagementAppsDetails.java
new file mode 100644
index 0000000000000000000000000000000000000000..f60fb4fdaa057f3a0cca831c7606fe3cf365aef2
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/MediaManagementAppsDetails.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.applications.appinfo;
+
+import android.app.AppOpsManager;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.Preference;
+import androidx.preference.Preference.OnPreferenceChangeListener;
+import androidx.preference.SwitchPreference;
+
+import com.android.settings.R;
+import com.android.settings.applications.AppInfoWithHeader;
+import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
+import com.android.settings.applications.AppStateMediaManagementAppsBridge;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+/**
+ * Class for displaying app info related to {@link AppOpsManager#OP_MANAGE_MEDIA}.
+ */
+public class MediaManagementAppsDetails extends AppInfoWithHeader implements
+ OnPreferenceChangeListener {
+
+ private static final String KEY_SWITCH_PREF = "media_management_apps_toggle";
+
+ private AppStateMediaManagementAppsBridge mAppBridge;
+ private AppOpsManager mAppOpsManager;
+ private SwitchPreference mSwitchPref;
+ private PermissionState mPermissionState;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Context context = getActivity();
+ mAppBridge = new AppStateMediaManagementAppsBridge(context, mState, null /* callback */);
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+
+ // initialize preferences
+ addPreferencesFromResource(R.xml.media_management_apps);
+ mSwitchPref = findPreference(KEY_SWITCH_PREF);
+
+ // install event listeners
+ mSwitchPref.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean value = (Boolean) newValue;
+ if (preference == mSwitchPref) {
+ if (mPermissionState != null && value != mPermissionState.isPermissible()) {
+ setCanManageMedia(value);
+ logPermissionChange(value, mPackageName);
+ refreshUi();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void setCanManageMedia(boolean newState) {
+ mAppOpsManager.setUidMode(AppOpsManager.OP_MANAGE_MEDIA, mPackageInfo.applicationInfo.uid,
+ newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
+ }
+
+ private void logPermissionChange(boolean newState, String packageName) {
+ mMetricsFeatureProvider.action(
+ mMetricsFeatureProvider.getAttribution(getActivity()),
+ SettingsEnums.ACTION_MEDIA_MANAGEMENT_APPS_TOGGLE,
+ getMetricsCategory(),
+ packageName,
+ newState ? 1 : 0);
+ }
+
+ @Override
+ protected boolean refreshUi() {
+ if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
+ return false;
+ }
+
+ mPermissionState = mAppBridge.createPermissionState(mPackageName,
+ mPackageInfo.applicationInfo.uid);
+ mSwitchPref.setEnabled(mPermissionState.permissionDeclared);
+ mSwitchPref.setChecked(mPermissionState.isPermissible());
+ return true;
+ }
+
+ @Override
+ protected AlertDialog createDialog(int id, int errorCode) {
+ return null;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.MEDIA_MANAGEMENT_APPS;
+ }
+
+ /**
+ * Returns the string that states whether the app has access to
+ * {@link android.Manifest.permission#MANAGE_MEDIA}.
+ */
+ public static int getSummary(Context context, AppEntry entry) {
+ final PermissionState state;
+ if (entry.extraInfo instanceof PermissionState) {
+ state = (PermissionState) entry.extraInfo;
+ } else {
+ state = new AppStateMediaManagementAppsBridge(context, null /* appState */,
+ null /* callback */).createPermissionState(entry.info.packageName,
+ entry.info.uid);
+ }
+
+ return state.isPermissible() ? R.string.app_permission_summary_allowed
+ : R.string.app_permission_summary_not_allowed;
+ }
+}
diff --git a/src/com/android/settings/applications/appops/AppOpsState.java b/src/com/android/settings/applications/appops/AppOpsState.java
index cbffe47cbc04eca4ceca82d0f13e34d551029cf8..eab3dda0cdc1dd0c41713fb588a5b37806b65941 100644
--- a/src/com/android/settings/applications/appops/AppOpsState.java
+++ b/src/com/android/settings/applications/appops/AppOpsState.java
@@ -588,7 +588,7 @@ public class AppOpsState {
if (appEntry == null) {
continue;
}
- List dummyOps = null;
+ List stubOps = null;
AppOpsManager.PackageOps pkgOps = null;
if (appInfo.requestedPermissions != null) {
for (int j=0; j();
+ if (stubOps == null) {
+ stubOps = new ArrayList();
pkgOps = new AppOpsManager.PackageOps(
- appInfo.packageName, appInfo.applicationInfo.uid, dummyOps);
+ appInfo.packageName, appInfo.applicationInfo.uid, stubOps);
}
AppOpsManager.OpEntry opEntry = new AppOpsManager.OpEntry(
permOps.get(k), AppOpsManager.MODE_ALLOWED, Collections.emptyMap());
- dummyOps.add(opEntry);
+ stubOps.add(opEntry);
addOp(entries, pkgOps, appEntry, opEntry, packageName == null,
packageName == null ? 0 : opToOrder[opEntry.getOp()]);
}
diff --git a/src/com/android/settings/applications/assist/DefaultVoiceInputPicker.java b/src/com/android/settings/applications/assist/DefaultVoiceInputPicker.java
index e5953dbe723d2a43a692c761d9c1e4c58cbd6f4b..d4ea4a97eff8ac826874de0933db88508f65ad51 100644
--- a/src/com/android/settings/applications/assist/DefaultVoiceInputPicker.java
+++ b/src/com/android/settings/applications/assist/DefaultVoiceInputPicker.java
@@ -24,7 +24,6 @@ import android.content.pm.PackageManager;
import android.provider.Settings;
import android.text.TextUtils;
-import com.android.internal.app.AssistUtils;
import com.android.settings.R;
import com.android.settings.applications.defaultapps.DefaultAppPickerFragment;
import com.android.settingslib.applications.DefaultAppInfo;
@@ -35,8 +34,6 @@ import java.util.List;
public class DefaultVoiceInputPicker extends DefaultAppPickerFragment {
private VoiceInputHelper mHelper;
- private AssistUtils mAssistUtils;
- private String mAssistRestrict;
@Override
public int getMetricsCategory() {
@@ -46,13 +43,8 @@ public class DefaultVoiceInputPicker extends DefaultAppPickerFragment {
@Override
public void onAttach(Context context) {
super.onAttach(context);
- mAssistUtils = new AssistUtils(context);
mHelper = new VoiceInputHelper(context);
mHelper.buildUi();
- final ComponentName assist = getCurrentAssist();
- if (isCurrentAssistVoiceService(assist, getCurrentService(mHelper))) {
- mAssistRestrict = assist.flattenToShortString();
- }
}
@Override
@@ -64,16 +56,9 @@ public class DefaultVoiceInputPicker extends DefaultAppPickerFragment {
protected List getCandidates() {
final List candidates = new ArrayList<>();
final Context context = getContext();
- boolean hasEnabled = true;
- for (VoiceInputHelper.InteractionInfo info : mHelper.mAvailableInteractionInfos) {
- final boolean enabled = TextUtils.equals(info.key, mAssistRestrict);
- hasEnabled |= enabled;
- candidates.add(new VoiceInputDefaultAppInfo(context, mPm, mUserId, info, enabled));
- }
- final boolean assistIsService = !hasEnabled;
for (VoiceInputHelper.RecognizerInfo info : mHelper.mAvailableRecognizerInfos) {
- final boolean enabled = !assistIsService;
+ final boolean enabled = true;
candidates.add(new VoiceInputDefaultAppInfo(context, mPm, mUserId, info, enabled));
}
return candidates;
@@ -90,23 +75,8 @@ public class DefaultVoiceInputPicker extends DefaultAppPickerFragment {
@Override
protected boolean setDefaultKey(String value) {
- for (VoiceInputHelper.InteractionInfo info : mHelper.mAvailableInteractionInfos) {
- if (TextUtils.equals(value, info.key)) {
- Settings.Secure.putString(getContext().getContentResolver(),
- Settings.Secure.VOICE_INTERACTION_SERVICE, value);
- Settings.Secure.putString(getContext().getContentResolver(),
- Settings.Secure.VOICE_RECOGNITION_SERVICE,
- new ComponentName(info.service.packageName,
- info.serviceInfo.getRecognitionService())
- .flattenToShortString());
- return true;
- }
- }
-
for (VoiceInputHelper.RecognizerInfo info : mHelper.mAvailableRecognizerInfos) {
if (TextUtils.equals(value, info.key)) {
- Settings.Secure.putString(getContext().getContentResolver(),
- Settings.Secure.VOICE_INTERACTION_SERVICE, "");
Settings.Secure.putString(getContext().getContentResolver(),
Settings.Secure.VOICE_RECOGNITION_SERVICE, value);
return true;
@@ -116,23 +86,7 @@ public class DefaultVoiceInputPicker extends DefaultAppPickerFragment {
}
public static ComponentName getCurrentService(VoiceInputHelper helper) {
- if (helper.mCurrentVoiceInteraction != null) {
- return helper.mCurrentVoiceInteraction;
- } else if (helper.mCurrentRecognizer != null) {
- return helper.mCurrentRecognizer;
- } else {
- return null;
- }
- }
-
- private ComponentName getCurrentAssist() {
- return mAssistUtils.getAssistComponentForUser(mUserId);
- }
-
- public static boolean isCurrentAssistVoiceService(ComponentName currentAssist,
- ComponentName currentVoiceService) {
- return currentAssist == null && currentVoiceService == null ||
- currentAssist != null && currentAssist.equals(currentVoiceService);
+ return helper.mCurrentRecognizer;
}
public static class VoiceInputDefaultAppInfo extends DefaultAppInfo {
@@ -152,11 +106,7 @@ public class DefaultVoiceInputPicker extends DefaultAppPickerFragment {
@Override
public CharSequence loadLabel() {
- if (mInfo instanceof VoiceInputHelper.InteractionInfo) {
- return mInfo.appLabel;
- } else {
- return mInfo.label;
- }
+ return mInfo.label;
}
public Intent getSettingIntent() {
diff --git a/src/com/android/settings/applications/assist/DefaultVoiceInputPreferenceController.java b/src/com/android/settings/applications/assist/DefaultVoiceInputPreferenceController.java
index 1f8b9d1a5ccca7b8c965f4199a98ed0e917e9fb4..59f573177cdfadcf1127592f452e87116f67f601 100644
--- a/src/com/android/settings/applications/assist/DefaultVoiceInputPreferenceController.java
+++ b/src/com/android/settings/applications/assist/DefaultVoiceInputPreferenceController.java
@@ -19,13 +19,13 @@ package com.android.settings.applications.assist;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.text.TextUtils;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
-import com.android.internal.app.AssistUtils;
import com.android.settings.applications.defaultapps.DefaultAppPreferenceController;
import com.android.settingslib.applications.DefaultAppInfo;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -41,15 +41,13 @@ public class DefaultVoiceInputPreferenceController extends DefaultAppPreferenceC
private static final String KEY_VOICE_INPUT = "voice_input_settings";
private VoiceInputHelper mHelper;
- private AssistUtils mAssistUtils;
private PreferenceScreen mScreen;
private Preference mPreference;
- private SettingObserver mSettingObserver;
+ private Context mContext;
public DefaultVoiceInputPreferenceController(Context context, Lifecycle lifecycle) {
super(context);
- mSettingObserver = new SettingObserver();
- mAssistUtils = new AssistUtils(context);
+ mContext = context;
mHelper = new VoiceInputHelper(context);
mHelper.buildUi();
if (lifecycle != null) {
@@ -59,13 +57,8 @@ public class DefaultVoiceInputPreferenceController extends DefaultAppPreferenceC
@Override
public boolean isAvailable() {
- // If current assist is also voice service, don't show voice preference.
- final ComponentName currentVoiceService =
- DefaultVoiceInputPicker.getCurrentService(mHelper);
- final ComponentName currentAssist =
- mAssistUtils.getAssistComponentForUser(mUserId);
- return !DefaultVoiceInputPicker.isCurrentAssistVoiceService(
- currentAssist, currentVoiceService);
+ return mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_VOICE_RECOGNIZERS);
}
@Override
@@ -82,7 +75,6 @@ public class DefaultVoiceInputPreferenceController extends DefaultAppPreferenceC
@Override
public void onResume() {
- mSettingObserver.register(mContext.getContentResolver(), true);
updatePreference();
}
@@ -93,9 +85,7 @@ public class DefaultVoiceInputPreferenceController extends DefaultAppPreferenceC
}
@Override
- public void onPause() {
- mSettingObserver.register(mContext.getContentResolver(), false);
- }
+ public void onPause() {}
@Override
protected DefaultAppInfo getDefaultAppInfo() {
@@ -103,12 +93,6 @@ public class DefaultVoiceInputPreferenceController extends DefaultAppPreferenceC
if (defaultKey == null) {
return null;
}
- for (VoiceInputHelper.InteractionInfo info : mHelper.mAvailableInteractionInfos) {
- if (TextUtils.equals(defaultKey, info.key)) {
- return new DefaultVoiceInputPicker.VoiceInputDefaultAppInfo(mContext,
- mPackageManager, mUserId, info, true /* enabled */);
- }
- }
for (VoiceInputHelper.RecognizerInfo info : mHelper.mAvailableRecognizerInfos) {
if (TextUtils.equals(defaultKey, info.key)) {
@@ -151,16 +135,4 @@ public class DefaultVoiceInputPreferenceController extends DefaultAppPreferenceC
}
return currentService.flattenToShortString();
}
-
- class SettingObserver extends AssistSettingObserver {
- @Override
- protected List getSettingUris() {
- return null;
- }
-
- @Override
- public void onSettingChange() {
- updatePreference();
- }
- }
}
diff --git a/src/com/android/settings/applications/assist/VoiceInputHelper.java b/src/com/android/settings/applications/assist/VoiceInputHelper.java
index 58c0d49a0a05894614ec486efb543678eb08027e..285f4f75b0433a6529beb763bf42e6672a8a1b96 100644
--- a/src/com/android/settings/applications/assist/VoiceInputHelper.java
+++ b/src/com/android/settings/applications/assist/VoiceInputHelper.java
@@ -26,10 +26,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.provider.Settings;
-import android.service.voice.VoiceInteractionService;
-import android.service.voice.VoiceInteractionServiceInfo;
import android.speech.RecognitionService;
-import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
@@ -46,9 +43,9 @@ public final class VoiceInputHelper {
static final String TAG = "VoiceInputHelper";
final Context mContext;
- final List mAvailableVoiceInteractions;
final List mAvailableRecognition;
+ // TODO: Remove this superclass as we only have 1 class now (RecognizerInfo).
static public class BaseInfo implements Comparable {
public final ServiceInfo service;
public final ComponentName componentName;
@@ -75,71 +72,33 @@ public final class VoiceInputHelper {
}
}
- static public class InteractionInfo extends BaseInfo {
- public final VoiceInteractionServiceInfo serviceInfo;
-
- public InteractionInfo(PackageManager pm, VoiceInteractionServiceInfo _service) {
- super(pm, _service.getServiceInfo(), _service.getSettingsActivity());
- serviceInfo = _service;
- }
- }
-
static public class RecognizerInfo extends BaseInfo {
- public RecognizerInfo(PackageManager pm, ServiceInfo _service, String _settings) {
- super(pm, _service, _settings);
+ public final boolean mSelectableAsDefault;
+
+ public RecognizerInfo(PackageManager pm,
+ ServiceInfo serviceInfo,
+ String settings,
+ boolean selectableAsDefault) {
+ super(pm, serviceInfo, settings);
+ this.mSelectableAsDefault = selectableAsDefault;
}
}
- final ArrayList mAvailableInteractionInfos = new ArrayList<>();
final ArrayList mAvailableRecognizerInfos = new ArrayList<>();
- ComponentName mCurrentVoiceInteraction;
ComponentName mCurrentRecognizer;
public VoiceInputHelper(Context context) {
mContext = context;
- mAvailableVoiceInteractions = mContext.getPackageManager().queryIntentServices(
- new Intent(VoiceInteractionService.SERVICE_INTERFACE),
- PackageManager.GET_META_DATA);
mAvailableRecognition = mContext.getPackageManager().queryIntentServices(
new Intent(RecognitionService.SERVICE_INTERFACE),
PackageManager.GET_META_DATA);
}
public void buildUi() {
- // Get the currently selected interactor from the secure setting.
- String currentSetting = Settings.Secure.getString(
- mContext.getContentResolver(), Settings.Secure.VOICE_INTERACTION_SERVICE);
- if (currentSetting != null && !currentSetting.isEmpty()) {
- mCurrentVoiceInteraction = ComponentName.unflattenFromString(currentSetting);
- } else {
- mCurrentVoiceInteraction = null;
- }
-
- ArraySet interactorRecognizers = new ArraySet<>();
-
- // Iterate through all the available interactors and load up their info to show
- // in the preference.
- int size = mAvailableVoiceInteractions.size();
- for (int i = 0; i < size; i++) {
- ResolveInfo resolveInfo = mAvailableVoiceInteractions.get(i);
- VoiceInteractionServiceInfo info = new VoiceInteractionServiceInfo(
- mContext.getPackageManager(), resolveInfo.serviceInfo);
- if (info.getParseError() != null) {
- Log.w("VoiceInteractionService", "Error in VoiceInteractionService "
- + resolveInfo.serviceInfo.packageName + "/"
- + resolveInfo.serviceInfo.name + ": " + info.getParseError());
- continue;
- }
- mAvailableInteractionInfos.add(new InteractionInfo(mContext.getPackageManager(), info));
- interactorRecognizers.add(new ComponentName(resolveInfo.serviceInfo.packageName,
- info.getRecognitionService()));
- }
- Collections.sort(mAvailableInteractionInfos);
-
// Get the currently selected recognizer from the secure setting.
- currentSetting = Settings.Secure.getString(
+ String currentSetting = Settings.Secure.getString(
mContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
if (currentSetting != null && !currentSetting.isEmpty()) {
mCurrentRecognizer = ComponentName.unflattenFromString(currentSetting);
@@ -149,20 +108,17 @@ public final class VoiceInputHelper {
// Iterate through all the available recognizers and load up their info to show
// in the preference.
- size = mAvailableRecognition.size();
+ int size = mAvailableRecognition.size();
for (int i = 0; i < size; i++) {
ResolveInfo resolveInfo = mAvailableRecognition.get(i);
ComponentName comp = new ComponentName(resolveInfo.serviceInfo.packageName,
resolveInfo.serviceInfo.name);
- if (interactorRecognizers.contains(comp)) {
- //continue;
- }
ServiceInfo si = resolveInfo.serviceInfo;
- XmlResourceParser parser = null;
String settingsActivity = null;
- try {
- parser = si.loadXmlMetaData(mContext.getPackageManager(),
- RecognitionService.SERVICE_META_DATA);
+ // Always show in voice input settings unless specifically set to False.
+ boolean selectableAsDefault = true;
+ try (XmlResourceParser parser = si.loadXmlMetaData(mContext.getPackageManager(),
+ RecognitionService.SERVICE_META_DATA)) {
if (parser == null) {
throw new XmlPullParserException("No " + RecognitionService.SERVICE_META_DATA +
" meta-data for " + si.packageName);
@@ -188,6 +144,9 @@ public final class VoiceInputHelper {
com.android.internal.R.styleable.RecognitionService);
settingsActivity = array.getString(
com.android.internal.R.styleable.RecognitionService_settingsActivity);
+ selectableAsDefault = array.getBoolean(
+ com.android.internal.R.styleable.RecognitionService_selectableAsDefault,
+ true);
array.recycle();
} catch (XmlPullParserException e) {
Log.e(TAG, "error parsing recognition service meta-data", e);
@@ -195,11 +154,13 @@ public final class VoiceInputHelper {
Log.e(TAG, "error parsing recognition service meta-data", e);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "error parsing recognition service meta-data", e);
- } finally {
- if (parser != null) parser.close();
}
- mAvailableRecognizerInfos.add(new RecognizerInfo(mContext.getPackageManager(),
- resolveInfo.serviceInfo, settingsActivity));
+ // The current recognizer must always be shown in the settings, whatever its
+ // selectableAsDefault value is.
+ if (selectableAsDefault || comp.equals(mCurrentRecognizer)) {
+ mAvailableRecognizerInfos.add(new RecognizerInfo(mContext.getPackageManager(),
+ resolveInfo.serviceInfo, settingsActivity, selectableAsDefault));
+ }
}
Collections.sort(mAvailableRecognizerInfos);
}
diff --git a/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java b/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..1d67fb7539d95714be7cbf8313b7d78715d1ca16
--- /dev/null
+++ b/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.autofill;
+
+import static android.service.autofill.AutofillService.EXTRA_RESULT;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.autofill.AutofillService;
+import android.service.autofill.AutofillServiceInfo;
+import android.service.autofill.IAutoFillService;
+import android.text.TextUtils;
+import android.util.IconDrawableFactory;
+import android.util.Log;
+
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.IResultReceiver;
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.AppPreference;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Queries available autofill services and adds preferences for those that declare passwords
+ * settings.
+ *
+ * The controller binds to each service to fetch the number of saved passwords in each.
+ */
+public class PasswordsPreferenceController extends BasePreferenceController
+ implements LifecycleObserver {
+ private static final String TAG = "AutofillSettings";
+ private static final boolean DEBUG = false;
+
+ private final PackageManager mPm;
+ private final IconDrawableFactory mIconFactory;
+ private final List mServices;
+
+ private LifecycleOwner mLifecycleOwner;
+
+ public PasswordsPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mPm = context.getPackageManager();
+ mIconFactory = IconDrawableFactory.newInstance(mContext);
+ mServices = new ArrayList<>();
+ }
+
+ @OnLifecycleEvent(ON_CREATE)
+ void onCreate(LifecycleOwner lifecycleOwner) {
+ init(lifecycleOwner, AutofillServiceInfo.getAvailableServices(mContext, getUser()));
+ }
+
+ @VisibleForTesting
+ void init(LifecycleOwner lifecycleOwner, List availableServices) {
+ mLifecycleOwner = lifecycleOwner;
+
+ for (int i = availableServices.size() - 1; i >= 0; i--) {
+ final String passwordsActivity = availableServices.get(i).getPasswordsActivity();
+ if (TextUtils.isEmpty(passwordsActivity)) {
+ availableServices.remove(i);
+ }
+ }
+ // TODO: Reverse the loop above and add to mServices directly.
+ mServices.clear();
+ mServices.addAll(availableServices);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mServices.isEmpty() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ final PreferenceGroup group = screen.findPreference(getPreferenceKey());
+ addPasswordPreferences(screen.getContext(), getUser(), group);
+ }
+
+ private void addPasswordPreferences(
+ Context prefContext, @UserIdInt int user, PreferenceGroup group) {
+ for (int i = 0; i < mServices.size(); i++) {
+ final AutofillServiceInfo service = mServices.get(i);
+ final AppPreference pref = new AppPreference(prefContext);
+ final ServiceInfo serviceInfo = service.getServiceInfo();
+ pref.setTitle(serviceInfo.loadLabel(mPm));
+ final Drawable icon =
+ mIconFactory.getBadgedIcon(
+ serviceInfo,
+ serviceInfo.applicationInfo,
+ user);
+ pref.setIcon(Utils.getSafeIcon(icon));
+ pref.setOnPreferenceClickListener(p -> {
+ final Intent intent =
+ new Intent(Intent.ACTION_MAIN)
+ .setClassName(
+ serviceInfo.packageName,
+ service.getPasswordsActivity());
+ prefContext.startActivityAsUser(intent, UserHandle.of(user));
+ return true;
+ });
+ // Set a placeholder summary to avoid a UI flicker when the value loads.
+ pref.setSummary(R.string.autofill_passwords_count_placeholder);
+
+ final MutableLiveData passwordCount = new MutableLiveData<>();
+ passwordCount.observe(
+ mLifecycleOwner, count -> {
+ // TODO(b/169455298): Validate the result.
+ final CharSequence summary =
+ mContext.getResources().getQuantityString(
+ R.plurals.autofill_passwords_count, count, count);
+ pref.setSummary(summary);
+ });
+ // TODO(b/169455298): Limit the number of concurrent queries.
+ // TODO(b/169455298): Cache the results for some time.
+ requestSavedPasswordCount(service, user, passwordCount);
+
+ group.addPreference(pref);
+ }
+ }
+
+ private void requestSavedPasswordCount(
+ AutofillServiceInfo service, @UserIdInt int user, MutableLiveData data) {
+ final Intent intent =
+ new Intent(AutofillService.SERVICE_INTERFACE)
+ .setComponent(service.getServiceInfo().getComponentName());
+ final AutofillServiceConnection connection = new AutofillServiceConnection(mContext, data);
+ if (mContext.bindServiceAsUser(
+ intent, connection, Context.BIND_AUTO_CREATE, UserHandle.of(user))) {
+ connection.mBound.set(true);
+ mLifecycleOwner.getLifecycle().addObserver(connection);
+ }
+ }
+
+ private static class AutofillServiceConnection implements ServiceConnection, LifecycleObserver {
+ final WeakReference mContext;
+ final MutableLiveData mData;
+ final AtomicBoolean mBound = new AtomicBoolean();
+
+ AutofillServiceConnection(Context context, MutableLiveData data) {
+ mContext = new WeakReference<>(context);
+ mData = data;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ final IAutoFillService autofillService = IAutoFillService.Stub.asInterface(service);
+ if (DEBUG) {
+ Log.d(TAG, "Fetching password count from " + name);
+ }
+ try {
+ autofillService.onSavedPasswordCountRequest(
+ new IResultReceiver.Stub() {
+ @Override
+ public void send(int resultCode, Bundle resultData) {
+ if (DEBUG) {
+ Log.d(TAG, "Received password count result " + resultCode
+ + " from " + name);
+ }
+ if (resultCode == 0 && resultData != null) {
+ mData.postValue(resultData.getInt(EXTRA_RESULT));
+ }
+ unbind();
+ }
+ });
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to fetch password count: " + e);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+
+ @OnLifecycleEvent(ON_DESTROY)
+ void unbind() {
+ if (!mBound.getAndSet(false)) {
+ return;
+ }
+ final Context context = mContext.get();
+ if (context != null) {
+ context.unbindService(this);
+ }
+ }
+ }
+
+ private int getUser() {
+ UserHandle workUser = getWorkProfileUser();
+ return workUser != null ? workUser.getIdentifier() : UserHandle.myUserId();
+ }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
index 73d80a34363b84f8a63aa4fbd5cd2ed364a4852a..d962692e2df72d593e7df4c8c85c5464493c5aa1 100644
--- a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
+++ b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
@@ -16,7 +16,7 @@
package com.android.settings.applications.defaultapps;
-import static com.android.settingslib.TwoTargetPreference.ICON_SIZE_MEDIUM;
+import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM;
import android.content.Context;
import android.content.Intent;
@@ -33,9 +33,9 @@ import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.widget.GearPreference;
-import com.android.settingslib.TwoTargetPreference;
import com.android.settingslib.applications.DefaultAppInfo;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.widget.TwoTargetPreference;
public abstract class DefaultAppPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin {
@@ -65,11 +65,19 @@ public abstract class DefaultAppPreferenceController extends AbstractPreferenceC
((TwoTargetPreference) preference).setIconSize(ICON_SIZE_MEDIUM);
}
if (!TextUtils.isEmpty(defaultAppLabel)) {
- preference.setSummary(defaultAppLabel);
- Utils.setSafeIcon(preference, getDefaultAppIcon());
+ if (showLabelAsTitle()) {
+ preference.setTitle(defaultAppLabel);
+ } else {
+ preference.setSummary(defaultAppLabel);
+ }
+ preference.setIcon(Utils.getSafeIcon(getDefaultAppIcon()));
} else {
Log.d(TAG, "No default app");
- preference.setSummary(R.string.app_list_preference_none);
+ if (showLabelAsTitle()) {
+ preference.setTitle(R.string.app_list_preference_none);
+ } else {
+ preference.setSummary(R.string.app_list_preference_none);
+ }
preference.setIcon(null);
}
mayUpdateGearIcon(app, preference);
@@ -102,6 +110,13 @@ public abstract class DefaultAppPreferenceController extends AbstractPreferenceC
return null;
}
+ /**
+ * Whether to show the default app label as the title, instead of as the summary.
+ */
+ protected boolean showLabelAsTitle() {
+ return false;
+ }
+
public Drawable getDefaultAppIcon() {
if (!isAvailable()) {
return null;
diff --git a/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java
index d32322b6fdb42dec446d763cc3e19f1fb80d1b7e..1493e30288109ca617c20ccf1d0b904d43d3b2ee 100644
--- a/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java
+++ b/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java
@@ -69,4 +69,9 @@ public class DefaultAutofillPreferenceController extends DefaultAppPreferenceCon
}
return null;
}
+
+ @Override
+ protected boolean showLabelAsTitle() {
+ return true;
+ }
}
diff --git a/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java b/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java
new file mode 100644
index 0000000000000000000000000000000000000000..43c377aff7b533daae787bad071629f4ea38866d
--- /dev/null
+++ b/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.intentpicker;
+
+import static android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_NONE;
+import static android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_SELECTED;
+import static android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_VERIFIED;
+
+import android.app.Activity;
+import android.app.settings.SettingsEnums;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationUserState;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.applications.AppInfoBase;
+import com.android.settings.applications.ClearDefaultsPreference;
+import com.android.settings.widget.EntityHeaderController;
+import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.widget.FooterPreference;
+import com.android.settingslib.widget.MainSwitchPreference;
+import com.android.settingslib.widget.OnMainSwitchChangeListener;
+
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+/** The page of the Open by default */
+public class AppLaunchSettings extends AppInfoBase implements
+ Preference.OnPreferenceChangeListener, OnMainSwitchChangeListener {
+ private static final String TAG = "AppLaunchSettings";
+ // Preference keys
+ private static final String MAIN_SWITCH_PREF_KEY = "open_by_default_supported_links";
+ private static final String VERIFIED_LINKS_PREF_KEY = "open_by_default_verified_links";
+ private static final String ADD_LINK_PREF_KEY = "open_by_default_add_link";
+ private static final String CLEAR_DEFAULTS_PREF_KEY = "app_launch_clear_defaults";
+ private static final String FOOTER_PREF_KEY = "open_by_default_footer";
+
+ private static final String MAIN_PREF_CATEGORY_KEY = "open_by_default_main_category";
+ private static final String SELECTED_LINKS_CATEGORY_KEY =
+ "open_by_default_selected_links_category";
+ private static final String OTHER_DETAILS_PREF_CATEGORY_KEY = "app_launch_other_defaults";
+
+ private static final String LEARN_MORE_URI =
+ "https://developer.android.com/training/app-links/verify-site-associations";
+
+ // Dialogs id
+ private static final int DLG_VERIFIED_LINKS = DLG_BASE + 1;
+
+ // Arguments key
+ public static final String APP_PACKAGE_KEY = "app_package";
+
+ private ClearDefaultsPreference mClearDefaultsPreference;
+ private MainSwitchPreference mMainSwitchPreference;
+ private Preference mAddLinkPreference;
+ private PreferenceCategory mMainPreferenceCategory;
+ private PreferenceCategory mSelectedLinksPreferenceCategory;
+ private PreferenceCategory mOtherDefaultsPreferenceCategory;
+
+ private boolean mActivityCreated;
+
+ @VisibleForTesting
+ Context mContext;
+ @VisibleForTesting
+ DomainVerificationManager mDomainVerificationManager;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mContext = context;
+ mActivityCreated = false;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.installed_app_launch_settings);
+ mDomainVerificationManager = mContext.getSystemService(DomainVerificationManager.class);
+ initUIComponents();
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ createHeaderPreference();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.APPLICATIONS_APP_LAUNCH;
+ }
+
+ @Override
+ protected AlertDialog createDialog(int id, int errorCode) {
+ if (id == DLG_VERIFIED_LINKS) {
+ return createVerifiedLinksDialog();
+ }
+ return null;
+ }
+
+ @Override
+ protected boolean refreshUi() {
+ mClearDefaultsPreference.setPackageName(mPackageName);
+ mClearDefaultsPreference.setAppEntry(mAppEntry);
+ return true;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean isChecked = (boolean) newValue;
+ IntentPickerUtils.logd(
+ "onPreferenceChange: " + preference.getTitle() + " isChecked: " + isChecked);
+ if ((preference instanceof LeftSideCheckBoxPreference) && !isChecked) {
+ final Set domainSet = new ArraySet<>();
+ domainSet.add(preference.getTitle().toString());
+ removePreference(preference.getKey());
+ final DomainVerificationUserState userState =
+ IntentPickerUtils.getDomainVerificationUserState(mDomainVerificationManager,
+ mPackageName);
+ if (userState == null) {
+ return false;
+ }
+ setDomainVerificationUserSelection(userState.getIdentifier(), domainSet, /* enabled= */
+ false);
+ mAddLinkPreference.setEnabled(isAddLinksNotEmpty());
+ }
+ return true;
+ }
+
+ @Override
+ public void onSwitchChanged(Switch switchView, boolean isChecked) {
+ IntentPickerUtils.logd("onSwitchChanged: isChecked=" + isChecked);
+ if (mMainSwitchPreference != null) { //mMainSwitchPreference synced with Switch
+ mMainSwitchPreference.setChecked(isChecked);
+ }
+ if (mMainPreferenceCategory != null) {
+ mMainPreferenceCategory.setVisible(isChecked);
+ }
+ if (mDomainVerificationManager != null) {
+ try {
+ mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(mPackageName,
+ isChecked);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "onSwitchChanged: " + e.getMessage());
+ }
+ }
+ }
+
+ private void createHeaderPreference() {
+ if (mActivityCreated) {
+ Log.w(TAG, "onParentActivityCreated: ignoring duplicate call.");
+ return;
+ }
+ mActivityCreated = true;
+ if (mPackageInfo == null) {
+ Log.w(TAG, "onParentActivityCreated: PakcageInfo is null.");
+ return;
+ }
+ final Activity activity = getActivity();
+ final String summary = activity.getString(R.string.app_launch_top_intro_message);
+ final Preference pref = EntityHeaderController
+ .newInstance(activity, this, null /* header */)
+ .setRecyclerView(getListView(), getSettingsLifecycle())
+ .setIcon(Utils.getBadgedIcon(mContext, mPackageInfo.applicationInfo))
+ .setLabel(mPackageInfo.applicationInfo.loadLabel(mPm))
+ .setSummary(summary) // add intro text
+ .setIsInstantApp(AppUtils.isInstant(mPackageInfo.applicationInfo))
+ .setPackageName(mPackageName)
+ .setUid(mPackageInfo.applicationInfo.uid)
+ .setHasAppInfoLink(true)
+ .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
+ EntityHeaderController.ActionType.ACTION_NONE)
+ .done(activity, getPrefContext());
+ getPreferenceScreen().addPreference(pref);
+ }
+
+ private void initUIComponents() {
+ initMainSwitchAndCategories();
+ if (canUpdateMainSwitchAndCategories()) {
+ initVerifiedLinksPreference();
+ initAddLinkPreference();
+ addSelectedLinksPreference();
+ initFooter();
+ }
+ }
+
+ private void initMainSwitchAndCategories() {
+ mMainSwitchPreference = (MainSwitchPreference) findPreference(MAIN_SWITCH_PREF_KEY);
+ mMainPreferenceCategory = findPreference(MAIN_PREF_CATEGORY_KEY);
+ mSelectedLinksPreferenceCategory = findPreference(SELECTED_LINKS_CATEGORY_KEY);
+ // Initialize the "Other Default Category" section
+ initOtherDefaultsSection();
+ }
+
+ private boolean canUpdateMainSwitchAndCategories() {
+ final DomainVerificationUserState userState =
+ IntentPickerUtils.getDomainVerificationUserState(mDomainVerificationManager,
+ mPackageName);
+ if (userState == null) {
+ disabledPreference();
+ return false;
+ }
+
+ IntentPickerUtils.logd("isLinkHandlingAllowed() : " + userState.isLinkHandlingAllowed());
+ mMainSwitchPreference.updateStatus(userState.isLinkHandlingAllowed());
+ mMainSwitchPreference.addOnSwitchChangeListener(this);
+ mMainPreferenceCategory.setVisible(userState.isLinkHandlingAllowed());
+ return true;
+ }
+
+ /** Initialize verified links preference */
+ private void initVerifiedLinksPreference() {
+ final VerifiedLinksPreference verifiedLinksPreference =
+ (VerifiedLinksPreference) mMainPreferenceCategory.findPreference(
+ VERIFIED_LINKS_PREF_KEY);
+ verifiedLinksPreference.setWidgetFrameClickListener(l -> {
+ showVerifiedLinksDialog();
+ });
+ final int verifiedLinksNo = getLinksNumber(DOMAIN_STATE_VERIFIED);
+ verifiedLinksPreference.setTitle(getVerifiedLinksTitle(verifiedLinksNo));
+ verifiedLinksPreference.setCheckBoxVisible(verifiedLinksNo > 0);
+ verifiedLinksPreference.setEnabled(verifiedLinksNo > 0);
+ }
+
+ private void showVerifiedLinksDialog() {
+ final int linksNo = getLinksNumber(DOMAIN_STATE_VERIFIED);
+ if (linksNo == 0) {
+ return;
+ }
+ showDialogInner(DLG_VERIFIED_LINKS, /* moveErrorCode= */ 0);
+ }
+
+ private AlertDialog createVerifiedLinksDialog() {
+ final int linksNo = getLinksNumber(DOMAIN_STATE_VERIFIED);
+
+ final View titleView = LayoutInflater.from(mContext).inflate(
+ R.layout.app_launch_verified_links_title, /* root= */ null);
+ ((TextView) titleView.findViewById(R.id.dialog_title)).setText(
+ getVerifiedLinksTitle(linksNo));
+ ((TextView) titleView.findViewById(R.id.dialog_message)).setText(
+ getVerifiedLinksMessage(linksNo));
+
+ final List