Merge Android R (rvc-dev-plus-aosp-without-vendor@6692709)

Bug: 166295507
Merged-In: Ie9d2c4d6d4618a167af1c5627d5d7918a404f398
Change-Id: I2ae428e37fd96226ce4e06032e2c0beaacbd0301
This commit is contained in:
Xin Li
2020-08-28 13:20:55 -07:00
2628 changed files with 514897 additions and 439664 deletions

View File

@@ -19,6 +19,7 @@ package com.android.settings;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.PhoneStateListener;
@@ -62,8 +63,6 @@ public class AirplaneModeEnabler extends GlobalSettingsChangeListener {
@VisibleForTesting
PhoneStateListener mPhoneStateListener;
private GlobalSettingsChangeListener mAirplaneModeObserver;
public AirplaneModeEnabler(Context context, OnAirplaneModeChangedListener listener) {
super(context, Settings.Global.AIRPLANE_MODE_ON);
@@ -73,7 +72,7 @@ public class AirplaneModeEnabler extends GlobalSettingsChangeListener {
mTelephonyManager = context.getSystemService(TelephonyManager.class);
mPhoneStateListener = new PhoneStateListener() {
mPhoneStateListener = new PhoneStateListener(Looper.getMainLooper()) {
@Override
public void onRadioPowerStateChanged(int state) {
if (DEBUG) {
@@ -87,6 +86,7 @@ public class AirplaneModeEnabler extends GlobalSettingsChangeListener {
/**
* Implementation of GlobalSettingsChangeListener.onChanged
*/
@Override
public void onChanged(String field) {
if (DEBUG) {
Log.d(LOG_TAG, "Airplane mode configuration update");
@@ -94,12 +94,18 @@ public class AirplaneModeEnabler extends GlobalSettingsChangeListener {
onAirplaneModeChanged();
}
public void resume() {
/**
* Start listening to the phone state change
*/
public void start() {
mTelephonyManager.listen(mPhoneStateListener,
PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED);
}
public void pause() {
/**
* Stop listening to the phone state change
*/
public void stop() {
mTelephonyManager.listen(mPhoneStateListener,
PhoneStateListener.LISTEN_NONE);
}

View File

@@ -0,0 +1,440 @@
/*
* 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 static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
import static android.net.ConnectivityManager.TETHERING_WIFI;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_CHANGED_ACTION;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.FeatureFlagUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import com.android.settings.core.FeatureFlags;
import com.android.settings.dashboard.RestrictedDashboardFragment;
import com.android.settings.datausage.DataSaverBackend;
import com.android.settings.network.BluetoothTetherPreferenceController;
import com.android.settings.network.EthernetTetherPreferenceController;
import com.android.settings.network.TetherEnabler;
import com.android.settings.network.UsbTetherPreferenceController;
import com.android.settings.network.WifiTetherDisablePreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.SwitchBar;
import com.android.settings.widget.SwitchBarController;
import com.android.settings.wifi.tether.WifiTetherApBandPreferenceController;
import com.android.settings.wifi.tether.WifiTetherAutoOffPreferenceController;
import com.android.settings.wifi.tether.WifiTetherBasePreferenceController;
import com.android.settings.wifi.tether.WifiTetherFooterPreferenceController;
import com.android.settings.wifi.tether.WifiTetherPasswordPreferenceController;
import com.android.settings.wifi.tether.WifiTetherSSIDPreferenceController;
import com.android.settings.wifi.tether.WifiTetherSecurityPreferenceController;
import com.android.settingslib.TetherUtil;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* Displays preferences for all Tethering options.
*/
@SearchIndexable
public class AllInOneTetherSettings extends RestrictedDashboardFragment
implements DataSaverBackend.Listener,
WifiTetherBasePreferenceController.OnTetherConfigUpdateListener {
// TODO(b/148622133): Should clean up the postfix once this fragment replaced TetherSettings.
public static final String DEDUP_POSTFIX = "_2";
@VisibleForTesting
static final String KEY_WIFI_TETHER_NETWORK_NAME = "wifi_tether_network_name" + DEDUP_POSTFIX;
@VisibleForTesting
static final String KEY_WIFI_TETHER_NETWORK_PASSWORD =
"wifi_tether_network_password" + DEDUP_POSTFIX;
@VisibleForTesting
static final String KEY_WIFI_TETHER_AUTO_OFF = "wifi_tether_auto_turn_off" + DEDUP_POSTFIX;
@VisibleForTesting
static final String KEY_WIFI_TETHER_NETWORK_AP_BAND =
"wifi_tether_network_ap_band" + DEDUP_POSTFIX;
@VisibleForTesting
static final String KEY_WIFI_TETHER_SECURITY = "wifi_tether_security" + DEDUP_POSTFIX;
private static final String KEY_DATA_SAVER_FOOTER = "disabled_on_data_saver" + DEDUP_POSTFIX;
private static final String KEY_WIFI_TETHER_GROUP = "wifi_tether_settings_group";
public static final String WIFI_TETHER_DISABLE_KEY = "disable_wifi_tethering";
public static final String USB_TETHER_KEY = "enable_usb_tethering";
public static final String BLUETOOTH_TETHER_KEY = "enable_bluetooth_tethering" + DEDUP_POSTFIX;
public static final String ETHERNET_TETHER_KEY = "enable_ethernet_tethering" + DEDUP_POSTFIX;
@VisibleForTesting
static final int EXPANDED_CHILD_COUNT_DEFAULT = 4;
@VisibleForTesting
static final int EXPANDED_CHILD_COUNT_WITH_SECURITY_NON = 3;
@VisibleForTesting
static final int EXPANDED_CHILD_COUNT_MAX = Integer.MAX_VALUE;
private static final String TAG = "AllInOneTetherSettings";
private boolean mUnavailable;
private DataSaverBackend mDataSaverBackend;
private boolean mDataSaverEnabled;
private Preference mDataSaverFooter;
private WifiManager mWifiManager;
private boolean mRestartWifiApAfterConfigChange;
private final AtomicReference<BluetoothPan> mBluetoothPan = new AtomicReference<>();
private WifiTetherSSIDPreferenceController mSSIDPreferenceController;
private WifiTetherPasswordPreferenceController mPasswordPreferenceController;
private WifiTetherApBandPreferenceController mApBandPreferenceController;
private WifiTetherSecurityPreferenceController mSecurityPreferenceController;
private PreferenceGroup mWifiTetherGroup;
private boolean mShouldShowWifiConfig = true;
private boolean mHasShownAdvance;
private TetherEnabler mTetherEnabler;
@VisibleForTesting
final TetherEnabler.OnTetherStateUpdateListener mStateUpdateListener =
state -> {
mShouldShowWifiConfig = TetherEnabler.isTethering(state, TETHERING_WIFI)
|| state == TetherEnabler.TETHERING_OFF;
getPreferenceScreen().setInitialExpandedChildrenCount(
getInitialExpandedChildCount());
mWifiTetherGroup.setVisible(mShouldShowWifiConfig);
};
private final BroadcastReceiver mTetherChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context content, Intent intent) {
String action = intent.getAction();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG,
"updating display config due to receiving broadcast action " + action);
}
updateDisplayWithNewConfig();
if (TextUtils.equals(action, ACTION_TETHER_STATE_CHANGED)) {
restartWifiTetherIfNeed(mWifiManager.getWifiApState());
} else if (TextUtils.equals(action, WIFI_AP_STATE_CHANGED_ACTION)) {
restartWifiTetherIfNeed(intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE, 0));
}
}
private void restartWifiTetherIfNeed(int state) {
if (state == WifiManager.WIFI_AP_STATE_DISABLED
&& mRestartWifiApAfterConfigChange) {
mRestartWifiApAfterConfigChange = false;
mTetherEnabler.startTethering(TETHERING_WIFI);
}
}
};
private final BluetoothProfile.ServiceListener mProfileServiceListener =
new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
mBluetoothPan.set((BluetoothPan) proxy);
}
public void onServiceDisconnected(int profile) {
mBluetoothPan.set(null);
}
};
@Override
public int getMetricsCategory() {
return SettingsEnums.TETHER;
}
public AllInOneTetherSettings() {
super(UserManager.DISALLOW_CONFIG_TETHERING);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mSSIDPreferenceController = use(WifiTetherSSIDPreferenceController.class);
mSecurityPreferenceController = use(WifiTetherSecurityPreferenceController.class);
mPasswordPreferenceController = use(WifiTetherPasswordPreferenceController.class);
mApBandPreferenceController = use(WifiTetherApBandPreferenceController.class);
getSettingsLifecycle().addObserver(use(UsbTetherPreferenceController.class));
getSettingsLifecycle().addObserver(use(BluetoothTetherPreferenceController.class));
getSettingsLifecycle().addObserver(use(EthernetTetherPreferenceController.class));
getSettingsLifecycle().addObserver(use(WifiTetherDisablePreferenceController.class));
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mDataSaverBackend = new DataSaverBackend(getContext());
mDataSaverEnabled = mDataSaverBackend.isDataSaverEnabled();
mDataSaverFooter = findPreference(KEY_DATA_SAVER_FOOTER);
mWifiTetherGroup = findPreference(KEY_WIFI_TETHER_GROUP);
setIfOnlyAvailableForAdmins(true);
if (isUiRestricted()) {
mUnavailable = true;
return;
}
mDataSaverBackend.addListener(this);
// Set initial state based on Data Saver mode.
onDataSaverChanged(mDataSaverBackend.isDataSaverEnabled());
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (mUnavailable) {
return;
}
// Assume we are in a SettingsActivity. This is only safe because we currently use
// SettingsActivity as base for all preference fragments.
final SettingsActivity activity = (SettingsActivity) getActivity();
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter != null) {
adapter.getProfileProxy(activity.getApplicationContext(), mProfileServiceListener,
BluetoothProfile.PAN);
}
final SwitchBar switchBar = activity.getSwitchBar();
mTetherEnabler = new TetherEnabler(activity,
new SwitchBarController(switchBar), mBluetoothPan);
getSettingsLifecycle().addObserver(mTetherEnabler);
use(UsbTetherPreferenceController.class).setTetherEnabler(mTetherEnabler);
use(BluetoothTetherPreferenceController.class).setTetherEnabler(mTetherEnabler);
use(EthernetTetherPreferenceController.class).setTetherEnabler(mTetherEnabler);
use(WifiTetherDisablePreferenceController.class).setTetherEnabler(mTetherEnabler);
switchBar.show();
}
@Override
public void onStart() {
super.onStart();
if (mUnavailable) {
if (!isUiRestrictedByOnlyAdmin()) {
getEmptyTextView().setText(R.string.tethering_settings_not_available);
}
getPreferenceScreen().removeAll();
return;
}
final Context context = getContext();
if (context != null) {
IntentFilter filter = new IntentFilter(ACTION_TETHER_STATE_CHANGED);
filter.addAction(WIFI_AP_STATE_CHANGED_ACTION);
context.registerReceiver(mTetherChangeReceiver, filter);
}
}
@Override
public void onResume() {
super.onResume();
if (mUnavailable) {
return;
}
if (mTetherEnabler != null) {
mTetherEnabler.addListener(mStateUpdateListener);
}
}
@Override
public void onPause() {
super.onPause();
if (mUnavailable) {
return;
}
if (mTetherEnabler != null) {
mTetherEnabler.removeListener(mStateUpdateListener);
}
}
@Override
public void onStop() {
super.onStop();
if (mUnavailable) {
return;
}
final Context context = getContext();
if (context != null) {
context.unregisterReceiver(mTetherChangeReceiver);
}
}
@Override
public void onDestroy() {
mDataSaverBackend.remListener(this);
super.onDestroy();
}
@Override
public void onDataSaverChanged(boolean isDataSaving) {
mDataSaverEnabled = isDataSaving;
mDataSaverFooter.setVisible(mDataSaverEnabled);
}
@Override
public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) {
// Do nothing
}
@Override
public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) {
// Do nothing
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
return buildPreferenceControllers(context, this);
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
WifiTetherBasePreferenceController.OnTetherConfigUpdateListener listener) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(
new WifiTetherSSIDPreferenceController(context, listener));
controllers.add(
new WifiTetherPasswordPreferenceController(context, listener));
controllers.add(
new WifiTetherApBandPreferenceController(context, listener));
controllers.add(
new WifiTetherSecurityPreferenceController(context, listener));
controllers.add(
new WifiTetherAutoOffPreferenceController(context, KEY_WIFI_TETHER_AUTO_OFF));
controllers.add(
new WifiTetherFooterPreferenceController(context));
return controllers;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.all_tether_prefs;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
public int getHelpResource() {
return R.string.help_url_tether;
}
@Override
public void onTetherConfigUpdated(AbstractPreferenceController controller) {
final SoftApConfiguration config = buildNewConfig();
mPasswordPreferenceController.updateVisibility(config.getSecurityType());
mWifiManager.setSoftApConfiguration(config);
if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Wifi AP config changed while enabled, stop and restart");
}
mRestartWifiApAfterConfigChange = true;
mTetherEnabler.stopTethering(TETHERING_WIFI);
}
}
private SoftApConfiguration buildNewConfig() {
final SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
final int securityType = mSecurityPreferenceController.getSecurityType();
configBuilder.setSsid(mSSIDPreferenceController.getSSID());
if (securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) {
configBuilder.setPassphrase(
mPasswordPreferenceController.getPasswordValidated(securityType),
SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
}
configBuilder.setBand(mApBandPreferenceController.getBandIndex());
return configBuilder.build();
}
private void updateDisplayWithNewConfig() {
mSSIDPreferenceController.updateDisplay();
mSecurityPreferenceController.updateDisplay();
mPasswordPreferenceController.updateDisplay();
mApBandPreferenceController.updateDisplay();
}
@Override
public int getInitialExpandedChildCount() {
if (mHasShownAdvance || !mShouldShowWifiConfig) {
mHasShownAdvance = true;
return EXPANDED_CHILD_COUNT_MAX;
}
if (mSecurityPreferenceController == null) {
return EXPANDED_CHILD_COUNT_DEFAULT;
}
return (mSecurityPreferenceController.getSecurityType()
== SoftApConfiguration.SECURITY_TYPE_OPEN)
? EXPANDED_CHILD_COUNT_WITH_SECURITY_NON : EXPANDED_CHILD_COUNT_DEFAULT;
}
@Override
public void onExpandButtonClick() {
super.onExpandButtonClick();
mHasShownAdvance = true;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.all_tether_prefs) {
@Override
public List<String> getNonIndexableKeys(Context context) {
final List<String> keys = super.getNonIndexableKeys(context);
if (!TetherUtil.isTetherAvailable(context)) {
keys.add(KEY_WIFI_TETHER_NETWORK_NAME);
keys.add(KEY_WIFI_TETHER_NETWORK_PASSWORD);
keys.add(KEY_WIFI_TETHER_AUTO_OFF);
keys.add(KEY_WIFI_TETHER_NETWORK_AP_BAND);
keys.add(KEY_WIFI_TETHER_SECURITY);
}
return keys;
}
@Override
protected boolean isPageSearchEnabled(Context context) {
return FeatureFlagUtils.isEnabled(context, FeatureFlags.TETHER_ALL_IN_ONE);
}
@Override
public List<AbstractPreferenceController> createPreferenceControllers(
Context context) {
return buildPreferenceControllers(context, null /*listener*/);
}
};
}

View File

@@ -46,12 +46,15 @@ public class BugreportPreference extends CustomDialogPreferenceCompat {
}
@Override
protected void onPrepareDialogBuilder(Builder builder, DialogInterface.OnClickListener listener) {
protected void onPrepareDialogBuilder(Builder builder,
DialogInterface.OnClickListener listener) {
super.onPrepareDialogBuilder(builder, listener);
final View dialogView = View.inflate(getContext(), R.layout.bugreport_options_dialog, null);
mInteractiveTitle = (CheckedTextView) dialogView.findViewById(R.id.bugreport_option_interactive_title);
mInteractiveSummary = (TextView) dialogView.findViewById(R.id.bugreport_option_interactive_summary);
mInteractiveTitle = (CheckedTextView) dialogView
.findViewById(R.id.bugreport_option_interactive_title);
mInteractiveSummary = (TextView) dialogView
.findViewById(R.id.bugreport_option_interactive_summary);
mFullTitle = (CheckedTextView) dialogView.findViewById(R.id.bugreport_option_full_title);
mFullSummary = (TextView) dialogView.findViewById(R.id.bugreport_option_full_summary);
final View.OnClickListener l = new View.OnClickListener() {
@@ -86,21 +89,21 @@ public class BugreportPreference extends CustomDialogPreferenceCompat {
Log.v(TAG, "Taking full bugreport right away");
FeatureFactory.getFactory(context).getMetricsFeatureProvider().action(context,
SettingsEnums.ACTION_BUGREPORT_FROM_SETTINGS_FULL);
takeBugreport(ActivityManager.BUGREPORT_OPTION_FULL);
try {
ActivityManager.getService().requestFullBugReport();
} catch (RemoteException e) {
Log.e(TAG, "error taking bugreport (bugreportType=Full)", e);
}
} else {
Log.v(TAG, "Taking interactive bugreport right away");
FeatureFactory.getFactory(context).getMetricsFeatureProvider().action(context,
SettingsEnums.ACTION_BUGREPORT_FROM_SETTINGS_INTERACTIVE);
takeBugreport(ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
try {
ActivityManager.getService().requestInteractiveBugReport();
} catch (RemoteException e) {
Log.e(TAG, "error taking bugreport (bugreportType=Interactive)", e);
}
}
}
}
private void takeBugreport(int bugreportType) {
try {
ActivityManager.getService().requestBugReport(bugreportType);
} catch (RemoteException e) {
Log.e(TAG, "error taking bugreport (bugreportType=" + bugreportType + ")", e);
}
}
}

View File

@@ -57,6 +57,7 @@ import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import android.widget.Button;
import android.widget.ImeAwareEditText;
import android.widget.ProgressBar;
import android.widget.TextView;
@@ -64,7 +65,6 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
import com.android.internal.widget.LockPatternView.Cell;
import com.android.internal.widget.LockPatternView.DisplayMode;
import com.android.settings.widget.ImeAwareEditText;
import java.util.List;
@@ -726,7 +726,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
public void onPatternDetected(List<LockPatternView.Cell> pattern) {
mLockPatternView.setEnabled(false);
if (pattern.size() >= MIN_LENGTH_BEFORE_REPORT) {
new DecryptTask().execute(LockPatternUtils.patternToString(pattern));
new DecryptTask().execute(new String(LockPatternUtils.patternToByteArray(pattern)));
} else {
// Allow user to make as many of these as they want.
fakeUnlockAttempt(mLockPatternView);

View File

@@ -19,7 +19,6 @@ package com.android.settings;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.provider.SearchIndexableResource;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.display.BrightnessLevelPreferenceController;
@@ -34,7 +33,6 @@ import com.android.settings.display.ThemePreferenceController;
import com.android.settings.display.TimeoutPreferenceController;
import com.android.settings.display.VrDisplayPreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
@@ -95,18 +93,8 @@ public class DisplaySettings extends DashboardFragment {
return controllers;
}
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
boolean enabled) {
final ArrayList<SearchIndexableResource> result = new ArrayList<>();
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.display_settings;
result.add(sir);
return result;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.display_settings) {
@Override
public List<AbstractPreferenceController> createPreferenceControllers(

View File

@@ -31,7 +31,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;

View File

@@ -39,6 +39,7 @@ import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets.Type;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ListView;
@@ -609,7 +610,8 @@ public class IccLockSettings extends SettingsPreferenceFragment
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
params.setFitInsetsTypes(params.getFitInsetsTypes() & ~Type.statusBars());
params.setTitle(errorMessage);
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE

View File

@@ -17,17 +17,11 @@
package com.android.settings;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.provider.SearchIndexableResource;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settingslib.search.SearchIndexable;
import java.util.Arrays;
import java.util.List;
@SearchIndexable
public class LegalSettings extends DashboardFragment {
@@ -48,15 +42,6 @@ public class LegalSettings extends DashboardFragment {
return R.xml.about_legal;
}
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(
Context context, boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.about_legal;
return Arrays.asList(sir);
}
};
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.about_legal);
}

View File

@@ -74,7 +74,7 @@ public class LinkifyUtils {
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
ds.setUnderlineText(true);
}
};
spannableContent.setSpan(spannableLink, beginIndex, endIndex,

View File

@@ -344,6 +344,8 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL
noCancelMobilePlan.setVisibility(View.VISIBLE);
mEsimStorage.setChecked(true /* checked */);
}
} else {
mEsimStorage.setChecked(false /* checked */);
}
final UserManager um = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);

View File

@@ -22,6 +22,8 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.app.ActionBar;
import android.app.Activity;
import android.app.ProgressDialog;
import android.app.admin.DevicePolicyManager;
import android.app.admin.FactoryResetProtectionPolicy;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
@@ -49,6 +51,7 @@ import com.android.settingslib.RestrictedLockUtilsInternal;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupcompat.template.FooterButton.ButtonType;
import com.google.android.setupcompat.util.WizardManagerHelper;
import com.google.android.setupdesign.GlifLayout;
/**
@@ -82,14 +85,9 @@ public class MasterClearConfirm extends InstrumentedFragment {
final PersistentDataBlockManager pdbManager = (PersistentDataBlockManager)
getActivity().getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
final OemLockManager oemLockManager = (OemLockManager)
getActivity().getSystemService(Context.OEM_LOCK_SERVICE);
if (pdbManager != null && !oemLockManager.isOemUnlockAllowed() &&
Utils.isDeviceProvisioned(getActivity())) {
// if OEM unlock is allowed, the persistent data block will be wiped during FR
// process. If disabled, it will be wiped here, unless the device is still being
// provisioned, in which case the persistent data block will be preserved.
if (shouldWipePersistentDataBlock(pdbManager)) {
new AsyncTask<Void, Void, Void>() {
int mOldOrientation;
ProgressDialog mProgressDialog;
@@ -139,6 +137,49 @@ public class MasterClearConfirm extends InstrumentedFragment {
}
};
@VisibleForTesting
boolean shouldWipePersistentDataBlock(PersistentDataBlockManager pdbManager) {
if (pdbManager == null) {
return false;
}
// The persistent data block will persist if the device is still being provisioned.
if (isDeviceStillBeingProvisioned()) {
return false;
}
// If OEM unlock is allowed, the persistent data block will be wiped during FR
// process. If disabled, it will be wiped here instead.
if (isOemUnlockedAllowed()) {
return false;
}
final DevicePolicyManager dpm = (DevicePolicyManager) getActivity()
.getSystemService(Context.DEVICE_POLICY_SERVICE);
// Do not erase the factory reset protection data (from Settings) if factory reset
// protection policy is not supported on the device.
if (!dpm.isFactoryResetProtectionPolicySupported()) {
return false;
}
// Do not erase the factory reset protection data (from Settings) if the
// device is an organization-owned managed profile device and a factory
// reset protection policy has been set.
FactoryResetProtectionPolicy frpPolicy = dpm.getFactoryResetProtectionPolicy(null);
if (dpm.isOrganizationOwnedDeviceWithManagedProfile() && frpPolicy != null
&& frpPolicy.isNotEmpty()) {
return false;
}
return true;
}
@VisibleForTesting
boolean isOemUnlockedAllowed() {
return ((OemLockManager) getActivity().getSystemService(
Context.OEM_LOCK_SERVICE)).isOemUnlockAllowed();
}
@VisibleForTesting
boolean isDeviceStillBeingProvisioned() {
return !WizardManagerHelper.isDeviceProvisioned(getActivity());
}
private void doMasterClear() {
Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
intent.setPackage("android");

View File

@@ -60,12 +60,6 @@ public class RegulatoryInfoDisplayActivity extends Activity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Resources resources = getResources();
if (!resources.getBoolean(R.bool.config_show_regulatory_info)) {
finish(); // no regulatory info to display for this device
}
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setTitle(R.string.regulatory_labels)
.setOnDismissListener(this);
@@ -95,7 +89,8 @@ public class RegulatoryInfoDisplayActivity extends Activity implements
}
}
CharSequence regulatoryText = resources.getText(R.string.regulatory_info_text);
CharSequence regulatoryText = getResources()
.getText(R.string.regulatory_info_text);
if (regulatoryInfoDrawableExists) {
View view = getLayoutInflater().inflate(R.layout.regulatory_info, null);

View File

@@ -19,13 +19,13 @@ package com.android.settings;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.content.Context;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.widget.RadioButton;
import android.widget.TextView;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.utils.ColorUtil;
public class RestrictedRadioButton extends RadioButton {
private Context mContext;
@@ -67,10 +67,10 @@ public class RestrictedRadioButton extends RadioButton {
RestrictedLockUtilsInternal.setTextViewAsDisabledByAdmin(mContext,
(TextView) this, mDisabledByAdmin);
if (mDisabledByAdmin) {
getButtonDrawable().setColorFilter(mContext.getColor(R.color.disabled_text_color),
PorterDuff.Mode.MULTIPLY);
getButtonDrawable().setAlpha(
(int) (255 * ColorUtil.getDisabledAlpha(mContext)));
} else {
getButtonDrawable().clearColorFilter();
getButtonDrawable().setAlpha(0);
}
}
}

View File

@@ -16,8 +16,12 @@
package com.android.settings;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.FeatureFlagUtils;
import com.android.settings.core.FeatureFlags;
import com.android.settings.enterprise.EnterprisePrivacySettings;
/**
@@ -33,9 +37,43 @@ public class Settings extends SettingsActivity {
public static class CreateShortcutActivity extends SettingsActivity { /* empty */ }
public static class FaceSettingsActivity extends SettingsActivity { /* empty */ }
public static class FingerprintSettingsActivity extends SettingsActivity { /* empty */ }
public static class SimSettingsActivity extends SettingsActivity { /* empty */ }
public static class TetherSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiTetherSettingsActivity extends SettingsActivity { /* empty */ }
public static class TetherSettingsActivity extends SettingsActivity {
// TODO(b/147675042): Clean the override up when we enable the new Fragment persistently.
@Override
public Intent getIntent() {
return wrapIntentWithAllInOneTetherSettingsIfNeeded(
getApplicationContext(), super.getIntent());
}
}
public static class WifiTetherSettingsActivity extends SettingsActivity {
// TODO(b/147675042): Clean the override up when we enable the new Fragment persistently.
@Override
public Intent getIntent() {
return wrapIntentWithAllInOneTetherSettingsIfNeeded(
getApplicationContext(), super.getIntent());
}
}
private static Intent wrapIntentWithAllInOneTetherSettingsIfNeeded(
Context context, Intent superIntent) {
if (!FeatureFlagUtils.isEnabled(context, FeatureFlags.TETHER_ALL_IN_ONE)) {
return superIntent;
}
final Intent modIntent = new Intent(superIntent);
modIntent.putExtra(EXTRA_SHOW_FRAGMENT,
AllInOneTetherSettings.class.getCanonicalName());
Bundle args = superIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
if (args != null) {
args = new Bundle(args);
} else {
args = new Bundle();
}
args.putParcelable("intent", superIntent);
modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
return modIntent;
}
public static class VpnSettingsActivity extends SettingsActivity { /* empty */ }
public static class DataSaverSummaryActivity extends SettingsActivity{ /* empty */ }
public static class DateTimeSettingsActivity extends SettingsActivity { /* empty */ }
@@ -43,6 +81,7 @@ public class Settings extends SettingsActivity {
public static class PrivateVolumeSettingsActivity extends SettingsActivity { /* empty */ }
public static class PublicVolumeSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiSettings2Activity extends SettingsActivity { /* empty */ }
public static class WifiP2pSettingsActivity extends SettingsActivity { /* empty */ }
public static class AvailableVirtualKeyboardActivity extends SettingsActivity { /* empty */ }
public static class KeyboardLayoutPickerActivity extends SettingsActivity { /* empty */ }
@@ -97,6 +136,7 @@ public class Settings extends SettingsActivity {
public static class NotificationStationActivity extends SettingsActivity { /* empty */ }
public static class UserSettingsActivity extends SettingsActivity { /* empty */ }
public static class NotificationAccessSettingsActivity extends SettingsActivity { /* empty */ }
public static class NotificationAccessDetailsActivity extends SettingsActivity { /* empty */ }
public static class VrListenersSettingsActivity extends SettingsActivity { /* empty */ }
public static class PictureInPictureSettingsActivity extends SettingsActivity { /* empty */ }
public static class AppPictureInPictureSettingsActivity extends SettingsActivity { /* empty */ }
@@ -117,6 +157,7 @@ public class Settings extends SettingsActivity {
public static class ZenModeEventRuleSettingsActivity extends SettingsActivity { /* empty */ }
public static class SoundSettingsActivity extends SettingsActivity { /* empty */ }
public static class ConfigureNotificationSettingsActivity extends SettingsActivity { /* empty */ }
public static class ConversationListSettingsActivity extends SettingsActivity { /* empty */ }
public static class AppBubbleNotificationSettingsActivity extends SettingsActivity { /* empty */ }
public static class NotificationAssistantSettingsActivity extends SettingsActivity{ /* empty */ }
public static class NotificationAppListActivity extends SettingsActivity { /* empty */ }
@@ -130,12 +171,21 @@ public class Settings extends SettingsActivity {
public static class PhotosStorageActivity extends SettingsActivity {
/* empty */
}
public static class GestureNavigationSettingsActivity extends SettingsActivity { /* empty */ }
public static class InteractAcrossProfilesSettingsActivity extends SettingsActivity {
/* empty */
}
public static class AppInteractAcrossProfilesSettingsActivity extends SettingsActivity {
/* empty */
}
public static class ApnSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiCallingSettingsActivity extends SettingsActivity { /* empty */ }
public static class MemorySettingsActivity extends SettingsActivity { /* empty */ }
public static class AppMemoryUsageActivity extends SettingsActivity { /* empty */ }
public static class OverlaySettingsActivity extends SettingsActivity { /* empty */ }
public static class ManageExternalStorageActivity extends SettingsActivity { /* empty */ }
public static class AppManageExternalStorageActivity extends SettingsActivity { /* empty */ }
public static class WriteSettingsActivity extends SettingsActivity { /* empty */ }
public static class ChangeWifiStateActivity extends SettingsActivity { /* empty */ }
public static class AppDrawOverlaySettingsActivity extends SettingsActivity { /* empty */ }
@@ -147,6 +197,7 @@ public class Settings extends SettingsActivity {
public static class ManagedProfileSettingsActivity extends SettingsActivity { /* empty */ }
public static class DeletionHelperActivity extends SettingsActivity { /* empty */ }
public static class ApnEditorActivity extends SettingsActivity { /* empty */ }
public static class ChooseAccountActivity extends SettingsActivity { /* empty */ }
public static class IccLockSettingsActivity extends SettingsActivity { /* empty */ }
@@ -168,6 +219,12 @@ public class Settings extends SettingsActivity {
public static class WifiCallingDisclaimerActivity extends SettingsActivity { /* empty */ }
public static class MobileNetworkListActivity extends SettingsActivity {}
public static class GlobalActionsPanelSettingsActivity extends SettingsActivity {}
public static class PowerMenuSettingsActivity extends SettingsActivity {}
public static class QuickControlsSettingsActivity extends SettingsActivity {}
/**
* Activity for BugReportHandlerPicker.
*/
public static class BugReportHandlerPickerActivity extends SettingsActivity { /* empty */ }
// Top level categories for new IA
public static class NetworkDashboardActivity extends SettingsActivity {}
@@ -178,4 +235,9 @@ public class Settings extends SettingsActivity {
public static class AccountDashboardActivity extends SettingsActivity {}
public static class SystemDashboardActivity extends SettingsActivity {}
/**
* Activity for MediaControlsSettings
*/
public static class MediaControlsSettingsActivity extends SettingsActivity {}
}

View File

@@ -16,6 +16,8 @@
package com.android.settings;
import static com.android.settings.applications.appinfo.AppButtonsPreferenceController.KEY_REMOVE_TASK_WHEN_FINISHING;
import android.app.ActionBar;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
@@ -28,6 +30,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources.Theme;
import android.graphics.drawable.Icon;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.UserHandle;
@@ -50,7 +53,6 @@ import androidx.preference.PreferenceManager;
import com.android.internal.util.ArrayUtils;
import com.android.settings.Settings.WifiSettingsActivity;
import com.android.settings.applications.manageapplications.ManageApplications;
import com.android.settings.backup.UserBackupSettingsActivity;
import com.android.settings.core.OnActivityResultListener;
import com.android.settings.core.SettingsBaseActivity;
import com.android.settings.core.SubSettingLauncher;
@@ -134,6 +136,14 @@ public class SettingsActivity extends SettingsBaseActivity
public static final String EXTRA_SHOW_FRAGMENT_AS_SUBSETTING =
":settings:show_fragment_as_subsetting";
/**
* Personal or Work profile tab of {@link ProfileSelectFragment}
* <p>0: Personal tab.
* <p>1: Work profile tab.
*/
public static final String EXTRA_SHOW_FRAGMENT_TAB =
":settings:show_fragment_tab";
public static final String META_DATA_KEY_FRAGMENT_CLASS =
"com.android.settings.FRAGMENT_CLASS";
@@ -203,10 +213,14 @@ public class SettingsActivity extends SettingsBaseActivity
}
private String getMetricsTag() {
String tag = getClass().getName();
String tag = null;
if (getIntent() != null && getIntent().hasExtra(EXTRA_SHOW_FRAGMENT)) {
tag = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
}
if (TextUtils.isEmpty(tag)) {
Log.w(LOG_TAG, "MetricsTag is invalid " + tag);
tag = getClass().getName();
}
if (tag.startsWith("com.android.settings.")) {
tag = tag.replace("com.android.settings.", "");
}
@@ -268,12 +282,12 @@ public class SettingsActivity extends SettingsBaseActivity
launchSettingFragment(initialFragmentName, intent);
}
final boolean deviceProvisioned = Utils.isDeviceProvisioned(this);
final boolean isInSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
final ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(deviceProvisioned);
actionBar.setHomeButtonEnabled(deviceProvisioned);
actionBar.setDisplayHomeAsUpEnabled(!isInSetupWizard);
actionBar.setHomeButtonEnabled(!isInSetupWizard);
actionBar.setDisplayShowTitleEnabled(true);
}
mSwitchBar = findViewById(R.id.switch_bar);
@@ -478,7 +492,7 @@ public class SettingsActivity extends SettingsBaseActivity
@Override
public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
taskDescription.setIcon(R.drawable.ic_launcher_settings);
taskDescription.setIcon(Icon.createWithResource(this, R.drawable.ic_launcher_settings));
super.setTaskDescription(taskDescription);
}
@@ -542,7 +556,12 @@ public class SettingsActivity extends SettingsBaseActivity
*/
public void finishPreferencePanel(int resultCode, Intent resultData) {
setResult(resultCode, resultData);
finish();
if (resultData != null &&
resultData.getBooleanExtra(KEY_REMOVE_TASK_WHEN_FINISHING, false)) {
finishAndRemoveTask();
} else {
finish();
}
}
/**
@@ -555,7 +574,7 @@ public class SettingsActivity extends SettingsBaseActivity
throw new IllegalArgumentException("Invalid fragment for this activity: "
+ fragmentName);
}
Fragment f = Fragment.instantiate(this, fragmentName, args);
Fragment f = Utils.getTargetFragment(this, fragmentName, args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.main_content, f);
if (titleResId > 0) {
@@ -573,12 +592,7 @@ public class SettingsActivity extends SettingsBaseActivity
// Generally the items that are will be changing from these updates will
// not be in the top list of tiles, so run it in the background and the
// SettingsBaseActivity will pick up on the updates automatically.
AsyncTask.execute(new Runnable() {
@Override
public void run() {
doUpdateTilesList();
}
});
AsyncTask.execute(() -> doUpdateTilesList());
}
private void doUpdateTilesList() {
@@ -632,17 +646,12 @@ public class SettingsActivity extends SettingsBaseActivity
showDev, isAdmin)
|| somethingChanged;
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
UserBackupSettingsActivity.class.getName()), true, isAdmin)
|| somethingChanged;
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
Settings.WifiDisplaySettingsActivity.class.getName()),
WifiDisplaySettings.isAvailable(this), isAdmin)
|| somethingChanged;
if (UserHandle.MU_ENABLED && !isAdmin) {
// When on restricted users, disable all extra categories (but only the settings ones).
final List<DashboardCategory> categories = mDashboardFeatureProvider.getAllCategories();
synchronized (categories) {

View File

@@ -102,11 +102,11 @@ public class SettingsDumpService extends Service {
JSONObject obj = new JSONObject();
DataUsageController controller = new DataUsageController(this);
ConnectivityManager connectivityManager = getSystemService(ConnectivityManager.class);
SubscriptionManager manager = SubscriptionManager.from(this);
SubscriptionManager manager = this.getSystemService(SubscriptionManager.class);
TelephonyManager telephonyManager = this.getSystemService(TelephonyManager.class);
if (connectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) {
JSONArray array = new JSONArray();
for (SubscriptionInfo info : manager.getAllSubscriptionInfoList()) {
for (SubscriptionInfo info : manager.getAvailableSubscriptionInfoList()) {
telephonyManager = telephonyManager
.createForSubscriptionId(info.getSubscriptionId());
NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(

View File

@@ -30,7 +30,6 @@ import androidx.fragment.app.FragmentActivity;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import com.android.settings.users.RestrictedProfileSettings;
import com.android.settingslib.license.LicenseHtmlLoaderCompat;
import java.io.File;
@@ -78,7 +77,7 @@ public class SettingsLicenseActivity extends FragmentActivity implements
@VisibleForTesting
Uri getUriFromGeneratedHtmlFile(File generatedHtmlFile) {
return FileProvider.getUriForFile(this, RestrictedProfileSettings.FILE_PROVIDER_AUTHORITY,
return FileProvider.getUriForFile(this, Utils.FILE_PROVIDER_AUTHORITY,
generatedHtmlFile);
}

View File

@@ -44,7 +44,6 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.search.Indexable;
import com.android.settings.search.actionbar.SearchMenuController;
import com.android.settings.support.actionbar.HelpMenuController;
import com.android.settings.support.actionbar.HelpResourceProvider;
@@ -53,7 +52,7 @@ import com.android.settings.widget.LoadingViewController;
import com.android.settingslib.CustomDialogPreferenceCompat;
import com.android.settingslib.CustomEditTextPreferenceCompat;
import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.widget.FooterPreferenceMixinCompat;
import com.android.settingslib.search.Indexable;
import com.android.settingslib.widget.LayoutPreference;
import java.util.UUID;
@@ -68,10 +67,6 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
protected final FooterPreferenceMixinCompat mFooterPreferenceMixin =
new FooterPreferenceMixinCompat(this, getSettingsLifecycle());
private static final int ORDER_FIRST = -1;
private SettingsDialogFragment mDialogFragment;
@@ -113,8 +108,8 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
}
};
private ViewGroup mPinnedHeaderFrameLayout;
private ViewGroup mButtonBar;
@VisibleForTesting
ViewGroup mPinnedHeaderFrameLayout;
private LayoutPreference mHeader;
@@ -145,7 +140,6 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
Bundle savedInstanceState) {
final View root = super.onCreateView(inflater, container, savedInstanceState);
mPinnedHeaderFrameLayout = root.findViewById(R.id.pinned_header);
mButtonBar = root.findViewById(R.id.button_bar);
return root;
}
@@ -169,10 +163,6 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
}
}
public ViewGroup getButtonBar() {
return mButtonBar;
}
public View setPinnedHeaderView(int layoutResId) {
final LayoutInflater inflater = getActivity().getLayoutInflater();
final View pinnedHeader =
@@ -186,6 +176,10 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
mPinnedHeaderFrameLayout.setVisibility(View.VISIBLE);
}
public void showPinnedHeader(boolean show) {
mPinnedHeaderFrameLayout.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@@ -287,11 +281,13 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
protected void setHeaderView(int resource) {
mHeader = new LayoutPreference(getPrefContext(), resource);
mHeader.setSelectable(false);
addPreferenceToTop(mHeader);
}
protected void setHeaderView(View view) {
mHeader = new LayoutPreference(getPrefContext(), view);
mHeader.setSelectable(false);
addPreferenceToTop(mHeader);
}
@@ -322,8 +318,7 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
if (getPreferenceScreen() != null) {
final View listContainer = getActivity().findViewById(android.R.id.list_container);
boolean show = (getPreferenceScreen().getPreferenceCount()
- (mHeader != null ? 1 : 0)
- (mFooterPreferenceMixin.hasFooter() ? 1 : 0)) <= 0
- (mHeader != null ? 1 : 0)) <= 0
|| (listContainer != null && listContainer.getVisibility() != View.VISIBLE);
mEmptyView.setVisibility(show ? View.VISIBLE : View.GONE);
} else {
@@ -704,4 +699,9 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
}
getActivity().setResult(result);
}
protected boolean isFinishingOrDestroyed() {
final Activity activity = getActivity();
return activity == null || activity.isFinishing() || activity.isDestroyed();
}
}

View File

@@ -20,9 +20,12 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import com.android.settings.notification.RedactionInterstitial;
import com.google.android.setupcompat.util.WizardManagerHelper;
/**
* Setup Wizard's version of RedactionInterstitial screen. It inherits the logic and basic structure
* from RedactionInterstitial class, and should remain similar to that behaviorally. This class
@@ -46,6 +49,15 @@ public class SetupRedactionInterstitial extends RedactionInterstitial {
PackageManager.DONT_KILL_APP);
}
@Override
protected void onCreate(Bundle savedInstance) {
// Only allow to start the activity from Setup Wizard.
if (!WizardManagerHelper.isAnySetupWizard(getIntent())) {
finish();
}
super.onCreate(savedInstance);
}
@Override
public Intent getIntent() {
Intent modIntent = new Intent(super.getIntent());

View File

@@ -9,6 +9,7 @@ import com.android.settings.Settings.TestingSettingsActivity;
public class TestingSettingsBroadcastReceiver extends BroadcastReceiver {
public TestingSettingsBroadcastReceiver() {
}

View File

@@ -33,6 +33,7 @@ import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
import android.net.EthernetManager;
import android.net.TetheringManager;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
@@ -40,14 +41,15 @@ import android.os.HandlerExecutor;
import android.os.UserManager;
import android.provider.SearchIndexableResource;
import android.text.TextUtils;
import android.util.FeatureFlagUtils;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
import com.android.settings.core.FeatureFlags;
import com.android.settings.datausage.DataSaverBackend;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settings.wifi.tether.WifiTetherPreferenceController;
import com.android.settingslib.TetherUtil;
import com.android.settingslib.search.SearchIndexable;
@@ -75,6 +77,8 @@ public class TetherSettings extends RestrictedSettingsFragment
static final String KEY_ENABLE_BLUETOOTH_TETHERING = "enable_bluetooth_tethering";
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";
private static final String TAG = "TetheringSettings";
@@ -132,9 +136,6 @@ public class TetherSettings extends RestrictedSettingsFragment
super.onCreate(icicle);
addPreferencesFromResource(R.xml.tether_prefs);
mFooterPreferenceMixin.createFooterPreference()
.setTitle(R.string.tethering_footer_info);
mDataSaverBackend = new DataSaverBackend(getContext());
mDataSaverEnabled = mDataSaverBackend.isDataSaverEnabled();
mDataSaverFooter = findPreference(KEY_DATA_SAVER_FOOTER);
@@ -156,6 +157,7 @@ public class TetherSettings extends RestrictedSettingsFragment
mUsbTether = (SwitchPreference) findPreference(KEY_USB_TETHER_SETTINGS);
mBluetoothTether = (SwitchPreference) findPreference(KEY_ENABLE_BLUETOOTH_TETHERING);
mEthernetTether = (SwitchPreference) findPreference(KEY_ENABLE_ETHERNET_TETHERING);
setFooterPreferenceTitle();
mDataSaverBackend.addListener(this);
@@ -169,7 +171,7 @@ public class TetherSettings extends RestrictedSettingsFragment
com.android.internal.R.string.config_ethernet_iface_regex);
final boolean usbAvailable = mUsbRegexs.length != 0;
final boolean bluetoothAvailable = mBluetoothRegexs.length != 0;
final boolean bluetoothAvailable = adapter != null && mBluetoothRegexs.length != 0;
final boolean ethernetAvailable = !TextUtils.isEmpty(mEthernetRegex);
if (!usbAvailable || Utils.isMonkeyRunning()) {
@@ -223,6 +225,18 @@ public class TetherSettings extends RestrictedSettingsFragment
public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) {
}
@VisibleForTesting
void setFooterPreferenceTitle() {
final Preference footerPreference = findPreference(KEY_TETHER_PREFS_FOOTER);
final WifiManager wifiManager =
(WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
if (wifiManager.isStaApConcurrencySupported()) {
footerPreference.setTitle(R.string.tethering_footer_info_sta_ap_concurrency);
} else {
footerPreference.setTitle(R.string.tethering_footer_info);
}
}
private class TetherChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context content, Intent intent) {
@@ -311,7 +325,8 @@ public class TetherSettings extends RestrictedSettingsFragment
if (intent != null) mTetherChangeReceiver.onReceive(activity, intent);
mEthernetListener = new EthernetListener();
mEm.addListener(mEthernetListener);
if (mEm != null)
mEm.addListener(mEthernetListener);
updateState();
}
@@ -325,7 +340,8 @@ public class TetherSettings extends RestrictedSettingsFragment
}
getActivity().unregisterReceiver(mTetherChangeReceiver);
mTm.unregisterTetheringEventCallback(mTetheringEventCallback);
mEm.removeListener(mEthernetListener);
if (mEm != null)
mEm.removeListener(mEthernetListener);
mTetherChangeReceiver = null;
mStartTetheringCallback = null;
mTetheringEventCallback = null;
@@ -423,7 +439,7 @@ public class TetherSettings extends RestrictedSettingsFragment
if (isTethered) {
mEthernetTether.setEnabled(!mDataSaverEnabled);
mEthernetTether.setChecked(true);
} else if (isAvailable || mEm.isAvailable()) {
} else if (isAvailable || (mEm != null && mEm.isAvailable())) {
mEthernetTether.setEnabled(!mDataSaverEnabled);
mEthernetTether.setChecked(false);
} else {
@@ -487,7 +503,7 @@ public class TetherSettings extends RestrictedSettingsFragment
}
};
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(
@@ -497,6 +513,11 @@ public class TetherSettings extends RestrictedSettingsFragment
return Arrays.asList(sir);
}
@Override
protected boolean isPageSearchEnabled(Context context) {
return !FeatureFlagUtils.isEnabled(context, FeatureFlags.TETHER_ALL_IN_ONE);
}
@Override
public List<String> getNonIndexableKeys(Context context) {
final List<String> keys = super.getNonIndexableKeys(context);

View File

@@ -42,6 +42,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.Cursor;
@@ -57,6 +58,7 @@ import android.net.LinkProperties;
import android.net.Network;
import android.net.wifi.WifiManager;
import android.os.BatteryManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@@ -73,7 +75,6 @@ import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Profile;
import android.provider.ContactsContract.RawContacts;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.Spannable;
@@ -91,8 +92,11 @@ import android.widget.EditText;
import android.widget.ListView;
import android.widget.TabWidget;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.core.graphics.drawable.IconCompat;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.preference.Preference;
@@ -101,8 +105,8 @@ import androidx.preference.PreferenceGroup;
import com.android.internal.app.UnlaunchableAppActivity;
import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.core.FeatureFlags;
import com.android.settings.development.featureflags.FeatureFlagPersistent;
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;
@@ -115,6 +119,8 @@ public final class Utils extends com.android.settingslib.Utils {
private static final String TAG = "Settings";
public static final String FILE_PROVIDER_AUTHORITY = "com.android.settings.files";
/**
* Set the preference's title to the matching activity's label.
*/
@@ -130,6 +136,11 @@ public final class Utils extends com.android.settingslib.Utils {
public static final String PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED =
"device_identifier_access_restrictions_disabled";
/**
* Whether to show the Permissions Hub.
*/
public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
/**
* Finds a matching activity for a preference's intent. If a matching
* activity is not found, it will remove the preference.
@@ -148,19 +159,19 @@ public final class Utils extends com.android.settingslib.Utils {
public static boolean updatePreferenceToSpecificActivityOrRemove(Context context,
PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags) {
Preference preference = parentPreferenceGroup.findPreference(preferenceKey);
final Preference preference = parentPreferenceGroup.findPreference(preferenceKey);
if (preference == null) {
return false;
}
Intent intent = preference.getIntent();
final Intent intent = preference.getIntent();
if (intent != null) {
// Find the activity that is in the system image
PackageManager pm = context.getPackageManager();
List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
int listSize = list.size();
final PackageManager pm = context.getPackageManager();
final List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
final int listSize = list.size();
for (int i = 0; i < listSize; i++) {
ResolveInfo resolveInfo = list.get(i);
final ResolveInfo resolveInfo = list.get(i);
if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
!= 0) {
@@ -185,19 +196,6 @@ public final class Utils extends com.android.settingslib.Utils {
return false;
}
/**
* Returns the UserManager for a given context
*
* @throws IllegalStateException if no UserManager could be retrieved.
*/
public static UserManager getUserManager(Context context) {
UserManager um = UserManager.get(context);
if (um == null) {
throw new IllegalStateException("Unable to load UserManager");
}
return um;
}
/**
* Returns true if Monkey is running.
*/
@@ -209,7 +207,7 @@ public final class Utils extends com.android.settingslib.Utils {
* Returns whether the device is voice-capable (meaning, it is also a phone).
*/
public static boolean isVoiceCapable(Context context) {
TelephonyManager telephony =
final TelephonyManager telephony =
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
return telephony != null && telephony.isVoiceCapable();
}
@@ -220,12 +218,12 @@ public final class Utils extends com.android.settingslib.Utils {
* @return the formatted and newline-separated IP addresses, or null if none.
*/
public static String getWifiIpAddresses(Context context) {
WifiManager wifiManager = context.getSystemService(WifiManager.class);
Network currentNetwork = wifiManager.getCurrentNetwork();
final WifiManager wifiManager = context.getSystemService(WifiManager.class);
final Network currentNetwork = wifiManager.getCurrentNetwork();
if (currentNetwork != null) {
ConnectivityManager cm = (ConnectivityManager)
final ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
LinkProperties prop = cm.getLinkProperties(currentNetwork);
final LinkProperties prop = cm.getLinkProperties(currentNetwork);
return formatIpAddresses(prop);
}
return null;
@@ -233,7 +231,7 @@ public final class Utils extends com.android.settingslib.Utils {
private static String formatIpAddresses(LinkProperties prop) {
if (prop == null) return null;
Iterator<InetAddress> iter = prop.getAllAddresses().iterator();
final Iterator<InetAddress> iter = prop.getAllAddresses().iterator();
// If there are no entries, return null
if (!iter.hasNext()) return null;
// Concatenate all available addresses, comma separated
@@ -254,7 +252,7 @@ public final class Utils extends com.android.settingslib.Utils {
// And : new Locale("en_US").toString() => "en_us"
if (null == localeStr)
return Locale.getDefault();
String[] brokenDownLocale = localeStr.split("_", 3);
final String[] brokenDownLocale = localeStr.split("_", 3);
// split may not return a 0-length array.
if (1 == brokenDownLocale.length) {
return new Locale(brokenDownLocale[0]);
@@ -379,7 +377,7 @@ public final class Utils extends com.android.settingslib.Utils {
}
public static boolean hasMultipleUsers(Context context) {
return ((UserManager) context.getSystemService(Context.USER_SERVICE))
return context.getSystemService(UserManager.class)
.getUsers().size() > 1;
}
@@ -388,7 +386,7 @@ public final class Utils extends com.android.settingslib.Utils {
* exists but it is disabled.
*/
public static UserHandle getManagedProfile(UserManager userManager) {
List<UserHandle> userProfiles = userManager.getUserProfiles();
final List<UserHandle> userProfiles = userManager.getUserProfiles();
for (UserHandle profile : userProfiles) {
if (profile.getIdentifier() == userManager.getUserHandle()) {
continue;
@@ -412,7 +410,7 @@ public final class Utils extends com.android.settingslib.Utils {
// we need to use UserManager.getProfiles that is available on API 23 (the one currently
// used for Settings Robolectric tests).
final int myUserId = UserHandle.myUserId();
List<UserInfo> profiles = userManager.getProfiles(myUserId);
final List<UserInfo> profiles = userManager.getProfiles(myUserId);
final int count = profiles.size();
for (int i = 0; i < count; i++) {
final UserInfo profile = profiles.get(i);
@@ -430,7 +428,7 @@ public final class Utils extends com.android.settingslib.Utils {
* @return the managed profile id or UserHandle.USER_NULL if there is none.
*/
public static int getManagedProfileId(UserManager um, int parentUserId) {
int[] profileIds = um.getProfileIdsWithDisabled(parentUserId);
final int[] profileIds = um.getProfileIdsWithDisabled(parentUserId);
for (int profileId : profileIds) {
if (profileId != parentUserId) {
return profileId;
@@ -456,13 +454,14 @@ public final class Utils extends com.android.settingslib.Utils {
*/
public static UserHandle getSecureTargetUser(IBinder activityToken,
UserManager um, @Nullable Bundle arguments, @Nullable Bundle intentExtras) {
UserHandle currentUser = new UserHandle(UserHandle.myUserId());
IActivityManager am = ActivityManager.getService();
final UserHandle currentUser = new UserHandle(UserHandle.myUserId());
final IActivityManager am = ActivityManager.getService();
try {
String launchedFromPackage = am.getLaunchedFromPackage(activityToken);
boolean launchedFromSettingsApp = SETTINGS_PACKAGE_NAME.equals(launchedFromPackage);
final String launchedFromPackage = am.getLaunchedFromPackage(activityToken);
final boolean launchedFromSettingsApp =
SETTINGS_PACKAGE_NAME.equals(launchedFromPackage);
UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId(
final UserHandle launchedFromUser = new UserHandle(UserHandle.getUserId(
am.getLaunchedFromUid(activityToken)));
if (launchedFromUser != null && !launchedFromUser.equals(currentUser)) {
// Check it's secure
@@ -470,14 +469,14 @@ public final class Utils extends com.android.settingslib.Utils {
return launchedFromUser;
}
}
UserHandle extrasUser = getUserHandleFromBundle(intentExtras);
final UserHandle extrasUser = getUserHandleFromBundle(intentExtras);
if (extrasUser != null && !extrasUser.equals(currentUser)) {
// Check it's secure
if (launchedFromSettingsApp && isProfileOf(um, extrasUser)) {
return extrasUser;
}
}
UserHandle argumentsUser = getUserHandleFromBundle(arguments);
final UserHandle argumentsUser = getUserHandleFromBundle(arguments);
if (argumentsUser != null && !argumentsUser.equals(currentUser)) {
// Check it's secure
if (launchedFromSettingsApp && isProfileOf(um, argumentsUser)) {
@@ -519,21 +518,6 @@ public final class Utils extends com.android.settingslib.Utils {
|| um.getUserProfiles().contains(otherUser);
}
/**
* Return whether or not the user should have a SIM Cards option in Settings.
* TODO: Change back to returning true if count is greater than one after testing.
* TODO: See bug 16533525.
*/
public static boolean showSimCardTile(Context context) {
if (FeatureFlagPersistent.isEnabled(context, FeatureFlags.NETWORK_INTERNET_V2)) {
return false;
}
final TelephonyManager tm =
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
return tm.getSimCount() > 1;
}
/**
* Queries for the UserInfo of a user. Returns null if the user doesn't exist (was removed).
* @param userManager Instance of UserManager
@@ -562,10 +546,11 @@ public final class Utils extends com.android.settingslib.Utils {
}
public static ArraySet<String> getHandledDomains(PackageManager pm, String packageName) {
List<IntentFilterVerificationInfo> iviList = pm.getIntentFilterVerifications(packageName);
List<IntentFilter> filters = pm.getAllIntentFilters(packageName);
final List<IntentFilterVerificationInfo> iviList =
pm.getIntentFilterVerifications(packageName);
final List<IntentFilter> filters = pm.getAllIntentFilters(packageName);
ArraySet<String> result = new ArraySet<>();
final ArraySet<String> result = new ArraySet<>();
if (iviList != null && iviList.size() > 0) {
for (IntentFilterVerificationInfo ivi : iviList) {
for (String host : ivi.getDomains()) {
@@ -589,16 +574,16 @@ public final class Utils extends com.android.settingslib.Utils {
* Returns the application info of the currently installed MDM package.
*/
public static ApplicationInfo getAdminApplicationInfo(Context context, int profileId) {
DevicePolicyManager dpm =
final DevicePolicyManager dpm =
(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
ComponentName mdmPackage = dpm.getProfileOwnerAsUser(profileId);
final ComponentName mdmPackage = dpm.getProfileOwnerAsUser(profileId);
if (mdmPackage == null) {
return null;
}
String mdmPackageName = mdmPackage.getPackageName();
final String mdmPackageName = mdmPackage.getPackageName();
try {
IPackageManager ipm = AppGlobals.getPackageManager();
ApplicationInfo mdmApplicationInfo =
final IPackageManager ipm = AppGlobals.getPackageManager();
final ApplicationInfo mdmApplicationInfo =
ipm.getApplicationInfo(mdmPackageName, 0, profileId);
return mdmApplicationInfo;
} catch (RemoteException e) {
@@ -625,7 +610,7 @@ public final class Utils extends com.android.settingslib.Utils {
*/
public static SpannableString createAccessibleSequence(CharSequence displayText,
String accessibileText) {
SpannableString str = new SpannableString(displayText);
final SpannableString str = new SpannableString(displayText);
str.setSpan(new TtsSpan.TextBuilder(accessibileText).build(), 0,
displayText.length(),
Spannable.SPAN_INCLUSIVE_INCLUSIVE);
@@ -659,7 +644,7 @@ public final class Utils extends com.android.settingslib.Utils {
}
final boolean allowAnyUser = isInternal
&& bundle.getBoolean(ChooseLockSettingsHelper.EXTRA_ALLOW_ANY_USER, false);
int userId = bundle.getInt(Intent.EXTRA_USER_ID, UserHandle.myUserId());
final int userId = bundle.getInt(Intent.EXTRA_USER_ID, UserHandle.myUserId());
if (userId == LockPatternUtils.USER_FRP) {
return allowAnyUser ? userId : enforceSystemUser(context, userId);
} else {
@@ -686,7 +671,7 @@ public final class Utils extends com.android.settingslib.Utils {
* @throws SecurityException if the given userId does not belong to the current user group.
*/
public static int enforceSameOwner(Context context, int userId) {
final UserManager um = getUserManager(context);
final UserManager um = context.getSystemService(UserManager.class);
final int[] profileIds = um.getProfileIdsWithDisabled(UserHandle.myUserId());
if (ArrayUtils.contains(profileIds, userId)) {
return userId;
@@ -706,10 +691,19 @@ public final class Utils extends com.android.settingslib.Utils {
* Returns the user id of the credential owner of the given user id.
*/
public static int getCredentialOwnerUserId(Context context, int userId) {
UserManager um = getUserManager(context);
final UserManager um = context.getSystemService(UserManager.class);
return um.getCredentialOwnerProfile(userId);
}
/**
* Returns the credential type of the given user id.
*/
public static @LockPatternUtils.CredentialType int getCredentialType(Context context,
int userId) {
final LockPatternUtils lpu = new LockPatternUtils(context);
return lpu.getCredentialTypeForUser(userId);
}
private static final StringBuilder sBuilder = new StringBuilder(50);
private static final java.util.Formatter sFormatter = new java.util.Formatter(
sBuilder, Locale.getDefault());
@@ -724,11 +718,6 @@ public final class Utils extends com.android.settingslib.Utils {
}
}
public static boolean isDeviceProvisioned(Context context) {
return Settings.Global.getInt(context.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 0) != 0;
}
public static boolean startQuietModeDialogIfNecessary(Context context, UserManager um,
int userId) {
if (um.isQuietModeEnabled(UserHandle.of(userId))) {
@@ -774,7 +763,7 @@ public final class Utils extends com.android.settingslib.Utils {
| PackageManager.MATCH_ANY_USER);
return appInfo.loadLabel(context.getPackageManager());
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Unable to find info for package: " + packageName);
Log.e(TAG, "Unable to find info for package: " + packageName);
}
return null;
}
@@ -811,7 +800,7 @@ public final class Utils extends com.android.settingslib.Utils {
}
public static boolean hasFingerprintHardware(Context context) {
FingerprintManager fingerprintManager = getFingerprintManagerOrNull(context);
final FingerprintManager fingerprintManager = getFingerprintManagerOrNull(context);
return fingerprintManager != null && fingerprintManager.isHardwareDetected();
}
@@ -824,7 +813,7 @@ public final class Utils extends com.android.settingslib.Utils {
}
public static boolean hasFaceHardware(Context context) {
FaceManager faceManager = getFaceManagerOrNull(context);
final FaceManager faceManager = getFaceManagerOrNull(context);
return faceManager != null && faceManager.isHardwareDetected();
}
@@ -848,7 +837,8 @@ public final class Utils extends com.android.settingslib.Utils {
}
public static boolean isDemoUser(Context context) {
return UserManager.isDeviceInDemoMode(context) && getUserManager(context).isDemoUser();
return UserManager.isDeviceInDemoMode(context)
&& context.getSystemService(UserManager.class).isDemoUser();
}
public static ComponentName getDeviceOwnerComponent(Context context) {
@@ -877,7 +867,7 @@ public final class Utils extends com.android.settingslib.Utils {
public static VolumeInfo maybeInitializeVolume(StorageManager sm, Bundle bundle) {
final String volumeId = bundle.getString(VolumeInfo.EXTRA_VOLUME_ID,
VolumeInfo.ID_PRIVATE_INTERNAL);
VolumeInfo volume = sm.findVolumeById(volumeId);
final VolumeInfo volume = sm.findVolumeById(volumeId);
return isVolumeValid(volume) ? volume : null;
}
@@ -890,12 +880,13 @@ public final class Utils extends com.android.settingslib.Utils {
*/
public static boolean isProfileOrDeviceOwner(UserManager userManager,
DevicePolicyManager devicePolicyManager, String packageName) {
List<UserInfo> userInfos = userManager.getUsers();
final List<UserInfo> userInfos = userManager.getUsers();
if (devicePolicyManager.isDeviceOwnerAppOnAnyUser(packageName)) {
return true;
}
for (int i = 0, size = userInfos.size(); i < size; i++) {
ComponentName cn = devicePolicyManager.getProfileOwnerAsUser(userInfos.get(i).id);
final ComponentName cn = devicePolicyManager
.getProfileOwnerAsUser(userInfos.get(i).id);
if (cn != null && cn.getPackageName().equals(packageName)) {
return true;
}
@@ -903,6 +894,27 @@ public final class Utils extends com.android.settingslib.Utils {
return false;
}
/**
* Return {@code true} if the supplied package is the device owner or profile owner of a
* given user.
*
* @param devicePolicyManager used to check whether it is device owner and profile owner app
* @param packageName package to check about
* @param userId the if of the relevant user
*/
public static boolean isProfileOrDeviceOwner(DevicePolicyManager devicePolicyManager,
String packageName, int userId) {
if ((devicePolicyManager.getDeviceOwnerUserId() == userId)
&& devicePolicyManager.isDeviceOwnerApp(packageName)) {
return true;
}
final ComponentName cn = devicePolicyManager.getProfileOwnerAsUser(userId);
if (cn != null && cn.getPackageName().equals(packageName)) {
return true;
}
return false;
}
/**
* Return the resource id to represent the install status for an app
*/
@@ -950,9 +962,9 @@ public final class Utils extends com.android.settingslib.Utils {
return original;
}
float scaleWidth = ((float) maxWidth) / actualWidth;
float scaleHeight = ((float) maxHeight) / actualHeight;
float scale = Math.min(scaleWidth, scaleHeight);
final float scaleWidth = ((float) maxWidth) / actualWidth;
final float scaleHeight = ((float) maxHeight) / actualHeight;
final float scale = Math.min(scaleWidth, scaleHeight);
final int width = (int) (actualWidth * scale);
final int height = (int) (actualHeight * scale);
@@ -1060,4 +1072,69 @@ public final class Utils extends com.android.settingslib.Utils {
ActionBarShadowController.attachToView(activity, lifecycle, scrollView);
}
}
/**
* Return correct target fragment based on argument
*
* @param activity the activity target fragment will be launched.
* @param fragmentName initial target fragment name.
* @param args fragment launch arguments.
*/
public static Fragment getTargetFragment(Activity activity, String fragmentName, Bundle args) {
Fragment f = null;
final boolean isPersonal = args != null ? args.getInt(ProfileSelectFragment.EXTRA_PROFILE)
== 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);
}
return f;
}
/**
* Returns true if current binder uid is Settings Intelligence.
*/
public static boolean isSettingsIntelligence(Context context) {
final int callingUid = Binder.getCallingUid();
final String callingPackage = context.getPackageManager().getPackagesForUid(callingUid)[0];
final boolean isSettingsIntelligence = TextUtils.equals(callingPackage,
context.getString(R.string.config_settingsintelligence_package_name));
return isSettingsIntelligence;
}
/**
* Returns true if the night mode is enabled.
*/
public static boolean isNightMode(Context context) {
final int currentNightMode =
context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
}
/**
* Returns a bitmap with rounded corner.
*
* @param context application context.
* @param source bitmap to apply round corner.
* @param cornerRadius corner radius value.
*/
public static Bitmap convertCornerRadiusBitmap(@NonNull Context context,
@NonNull Bitmap source, @NonNull float cornerRadius) {
final Bitmap roundedBitmap = Bitmap.createBitmap(source.getWidth(), source.getHeight(),
Bitmap.Config.ARGB_8888);
final RoundedBitmapDrawable drawable =
RoundedBitmapDrawableFactory.create(context.getResources(), source);
drawable.setAntiAlias(true);
drawable.setCornerRadius(cornerRadius);
final Canvas canvas = new Canvas(roundedBitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return roundedBitmap;
}
}

View File

@@ -19,7 +19,6 @@ package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.res.Resources;
import android.provider.SearchIndexableResource;
import androidx.preference.Preference;
@@ -31,10 +30,10 @@ import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@SearchIndexable
/** Settings fragment containing accessibility control timeout preference. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public final class AccessibilityControlTimeoutPreferenceFragment extends DashboardFragment
implements AccessibilityTimeoutController.OnChangeListener {
@@ -88,6 +87,11 @@ public final class AccessibilityControlTimeoutPreferenceFragment extends Dashboa
return buildPreferenceControllers(context, getSettingsLifecycle());
}
@Override
public int getHelpResource() {
return R.string.help_url_timeout;
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
Lifecycle lifecycle) {
if (sControllers.size() == 0) {
@@ -104,21 +108,8 @@ public final class AccessibilityControlTimeoutPreferenceFragment extends Dashboa
return sControllers;
}
public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.accessibility_control_timeout_settings;
return Arrays.asList(sir);
}
@Override
public List<String> getNonIndexableKeys(Context context) {
final List<String> keys = super.getNonIndexableKeys(context);
return keys;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_control_timeout_settings) {
@Override
public List<AbstractPreferenceController> createPreferenceControllers(

View File

@@ -135,19 +135,12 @@ public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment {
final String packageName = serviceInfo.packageName;
final ComponentName componentName = new ComponentName(packageName, serviceInfo.name);
final List<AccessibilityServiceInfo> enabledServiceInfos = AccessibilityManager.getInstance(
getActivity()).getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
final Set<ComponentName> enabledServices =
AccessibilityUtils.getEnabledServicesFromSettings(getActivity());
final boolean serviceEnabled = enabledServices.contains(componentName);
String description = info.loadDescription(getActivity().getPackageManager());
if (TextUtils.isEmpty(description)) {
description = getString(R.string.accessibility_service_default_description);
}
if (serviceEnabled && AccessibilityUtils.hasServiceCrashed(
packageName, serviceInfo.name, enabledServiceInfos)) {
if (serviceEnabled && info.crashed) {
// Update the summaries for services that have crashed.
description = getString(R.string.accessibility_description_state_stopped);
}
@@ -168,7 +161,10 @@ public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment {
new ComponentName(packageName, settingsClassName).flattenToString());
}
extras.putParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME, componentName);
extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, info.getAnimatedImageRes());
final String htmlDescription = info.loadHtmlDescription(getActivity().getPackageManager());
extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription);
return extras;
}

View File

@@ -0,0 +1,327 @@
/*
* 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;
}
}

View File

@@ -16,38 +16,55 @@
package com.android.settings.accessibility;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
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;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.accessibility.AccessibilityManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextSwitcher;
import android.widget.TextView;
import androidx.annotation.AnimRes;
import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.util.Preconditions;
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;
import java.util.ArrayList;
import java.util.List;
/**
* Utility class for creating the dialog that guides users for gesture navigation for
* accessibility services.
*/
public class AccessibilityGestureNavigationTutorial {
public final class AccessibilityGestureNavigationTutorial {
/** IntDef enum for dialog type. */
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -62,6 +79,8 @@ public class AccessibilityGestureNavigationTutorial {
int GESTURE_NAVIGATION_SETTINGS = 2;
}
private AccessibilityGestureNavigationTutorial() {}
private static final DialogInterface.OnClickListener mOnClickListener =
(DialogInterface dialog, int which) -> dialog.dismiss();
@@ -83,7 +102,7 @@ public class AccessibilityGestureNavigationTutorial {
final AlertDialog alertDialog = createDialog(context,
DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON);
if (!isGestureNavigateEnabled(context)) {
if (!AccessibilityUtil.isGestureNavigateEnabled(context)) {
updateMessageWithIcon(context, alertDialog);
}
@@ -94,6 +113,13 @@ public class AccessibilityGestureNavigationTutorial {
return createDialog(context, DialogType.LAUNCH_SERVICE_BY_GESTURE_NAVIGATION);
}
static AlertDialog createAccessibilityTutorialDialog(Context context, int shortcutTypes) {
return new AlertDialog.Builder(context)
.setView(createShortcutNavigationContentView(context, shortcutTypes))
.setNegativeButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener)
.create();
}
/**
* Get a content View for a dialog to confirm that they want to enable a service.
*
@@ -119,13 +145,13 @@ public class AccessibilityGestureNavigationTutorial {
R.id.gesture_tutorial_video);
final TextView gestureTutorialMessage = content.findViewById(
R.id.gesture_tutorial_message);
VideoPlayer.create(context, isTouchExploreOn(context)
VideoPlayer.create(context, AccessibilityUtil.isTouchExploreEnabled(context)
? R.raw.illustration_accessibility_gesture_three_finger
: R.raw.illustration_accessibility_gesture_two_finger,
gestureTutorialVideo);
gestureTutorialMessage.setText(isTouchExploreOn(context)
? R.string.accessibility_tutorial_dialog_message_gesture_with_talkback
: R.string.accessibility_tutorial_dialog_message_gesture_without_talkback);
gestureTutorialMessage.setText(AccessibilityUtil.isTouchExploreEnabled(context)
? R.string.accessibility_tutorial_dialog_message_gesture_talkback
: R.string.accessibility_tutorial_dialog_message_gesture);
break;
case DialogType.GESTURE_NAVIGATION_SETTINGS:
content = inflater.inflate(
@@ -134,14 +160,14 @@ public class AccessibilityGestureNavigationTutorial {
R.id.gesture_tutorial_video);
final TextView gestureSettingsTutorialMessage = content.findViewById(
R.id.gesture_tutorial_message);
VideoPlayer.create(context, isTouchExploreOn(context)
VideoPlayer.create(context, AccessibilityUtil.isTouchExploreEnabled(context)
? R.raw.illustration_accessibility_gesture_three_finger
: R.raw.illustration_accessibility_gesture_two_finger,
gestureSettingsTutorialVideo);
gestureSettingsTutorialMessage.setText(isTouchExploreOn(context)
?
R.string.accessibility_tutorial_dialog_message_gesture_settings_with_talkback
: R.string.accessibility_tutorial_dialog_message_gesture_settings_without_talkback);
final int stringResId = AccessibilityUtil.isTouchExploreEnabled(context)
? R.string.accessibility_tutorial_dialog_message_gesture_settings_talkback
: R.string.accessibility_tutorial_dialog_message_gesture_settings;
gestureSettingsTutorialMessage.setText(stringResId);
break;
}
@@ -179,10 +205,11 @@ public class AccessibilityGestureNavigationTutorial {
final int indexIconStart = messageString.indexOf("%s");
final int indexIconEnd = indexIconStart + 2;
final Drawable icon = context.getDrawable(R.drawable.ic_accessibility_new);
icon.setTint(getThemeAttrColor(context, android.R.attr.textColorPrimary));
final ImageSpan imageSpan = new ImageSpan(icon);
imageSpan.setContentDescription("");
icon.setBounds(0, 0, lineHeight, lineHeight);
spannableMessage.setSpan(
new ImageSpan(icon), indexIconStart, indexIconEnd,
imageSpan, indexIconStart, indexIconEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannableMessage;
@@ -204,14 +231,314 @@ public class AccessibilityGestureNavigationTutorial {
return colorResId;
}
private static boolean isGestureNavigateEnabled(Context context) {
return context.getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode)
== NAV_BAR_MODE_GESTURAL;
private static class TutorialPagerAdapter extends PagerAdapter {
private final List<TutorialPage> mTutorialPages;
private TutorialPagerAdapter(List<TutorialPage> tutorialPages) {
this.mTutorialPages = tutorialPages;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
final View itemView = mTutorialPages.get(position).getImageView();
container.addView(itemView);
return itemView;
}
@Override
public int getCount() {
return mTutorialPages.size();
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
return view == o;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position,
@NonNull Object object) {
final View itemView = mTutorialPages.get(position).getImageView();
container.removeView(itemView);
}
}
private static boolean isTouchExploreOn(Context context) {
return ((AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE))
.isTouchExplorationEnabled();
private static ImageView createImageView(Context context, int imageRes) {
final ImageView imageView = new ImageView(context);
imageView.setImageResource(imageRes);
imageView.setAdjustViewBounds(true);
return imageView;
}
private static View createShortcutNavigationContentView(Context context, int shortcutTypes) {
final LayoutInflater inflater = context.getSystemService(LayoutInflater.class);
final View contentView = inflater.inflate(
R.layout.accessibility_shortcut_tutorial_dialog, /* root= */ null);
final List<TutorialPage> tutorialPages =
createShortcutTutorialPages(context, shortcutTypes);
Preconditions.checkArgument(!tutorialPages.isEmpty(),
/* errorMessage= */ "Unexpected tutorial pages size");
final LinearLayout indicatorContainer = contentView.findViewById(R.id.indicator_container);
indicatorContainer.setVisibility(tutorialPages.size() > 1 ? VISIBLE : GONE);
for (TutorialPage page : tutorialPages) {
indicatorContainer.addView(page.getIndicatorIcon());
}
tutorialPages.get(/* firstIndex */ 0).getIndicatorIcon().setEnabled(true);
final TextSwitcher title = contentView.findViewById(R.id.title);
title.setFactory(() -> makeTitleView(context));
title.setText(tutorialPages.get(/* firstIndex */ 0).getTitle());
final TextSwitcher instruction = contentView.findViewById(R.id.instruction);
instruction.setFactory(() -> makeInstructionView(context));
instruction.setText(tutorialPages.get(/* firstIndex */ 0).getInstruction());
final ViewPager viewPager = contentView.findViewById(R.id.view_pager);
viewPager.setAdapter(new TutorialPagerAdapter(tutorialPages));
viewPager.setContentDescription(context.getString(R.string.accessibility_tutorial_pager,
/* firstPage */ 1, tutorialPages.size()));
viewPager.setImportantForAccessibility(tutorialPages.size() > 1
? View.IMPORTANT_FOR_ACCESSIBILITY_YES
: View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
viewPager.addOnPageChangeListener(
new TutorialPageChangeListener(context, viewPager, title, instruction,
tutorialPages));
return contentView;
}
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));
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));
return textView;
}
private static TutorialPage createSoftwareTutorialPage(@NonNull Context context) {
final CharSequence title = getSoftwareTitle(context);
final ImageView image = createSoftwareImage(context);
final CharSequence instruction = getSoftwareInstruction(context);
final ImageView indicatorIcon =
createImageView(context, R.drawable.ic_accessibility_page_indicator);
indicatorIcon.setEnabled(false);
return new TutorialPage(title, image, indicatorIcon, instruction);
}
private static TutorialPage createHardwareTutorialPage(@NonNull Context context) {
final CharSequence title =
context.getText(R.string.accessibility_tutorial_dialog_title_volume);
final ImageView image =
createImageView(context, R.drawable.accessibility_shortcut_type_hardware);
final ImageView indicatorIcon =
createImageView(context, R.drawable.ic_accessibility_page_indicator);
final CharSequence instruction =
context.getText(R.string.accessibility_tutorial_dialog_message_volume);
indicatorIcon.setEnabled(false);
return new TutorialPage(title, image, indicatorIcon, instruction);
}
private static TutorialPage createTripleTapTutorialPage(@NonNull Context context) {
final CharSequence title =
context.getText(R.string.accessibility_tutorial_dialog_title_triple);
final ImageView image =
createImageView(context, R.drawable.accessibility_shortcut_type_triple_tap);
final CharSequence instruction =
context.getText(R.string.accessibility_tutorial_dialog_message_triple);
final ImageView indicatorIcon =
createImageView(context, R.drawable.ic_accessibility_page_indicator);
indicatorIcon.setEnabled(false);
return new TutorialPage(title, image, indicatorIcon, instruction);
}
@VisibleForTesting
static List<TutorialPage> createShortcutTutorialPages(@NonNull Context context,
int shortcutTypes) {
final List<TutorialPage> tutorialPages = new ArrayList<>();
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
tutorialPages.add(createSoftwareTutorialPage(context));
}
if ((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE) {
tutorialPages.add(createHardwareTutorialPage(context));
}
if ((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP) {
tutorialPages.add(createTripleTapTutorialPage(context));
}
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;
}
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;
}
private static CharSequence getSoftwareInstructionWithIcon(Context context, CharSequence text) {
final String message = text.toString();
final SpannableString spannableInstruction = SpannableString.valueOf(message);
final int indexIconStart = message.indexOf("%s");
final int indexIconEnd = indexIconStart + 2;
final ImageView iconView = new ImageView(context);
iconView.setImageDrawable(context.getDrawable(R.drawable.ic_accessibility_new));
final Drawable icon = iconView.getDrawable().mutate();
final ImageSpan imageSpan = new ImageSpan(icon);
imageSpan.setContentDescription("");
icon.setBounds(/* left= */ 0, /* top= */ 0,
icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
spannableInstruction.setSpan(imageSpan, indexIconStart, indexIconEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannableInstruction;
}
private static class TutorialPage {
private final CharSequence mTitle;
private final ImageView mImageView;
private final ImageView mIndicatorIcon;
private final CharSequence mInstruction;
TutorialPage(CharSequence title, ImageView imageView, ImageView indicatorIcon,
CharSequence instruction) {
this.mTitle = title;
this.mImageView = imageView;
this.mIndicatorIcon = indicatorIcon;
this.mInstruction = instruction;
}
public CharSequence getTitle() {
return mTitle;
}
public ImageView getImageView() {
return mImageView;
}
public ImageView getIndicatorIcon() {
return mIndicatorIcon;
}
public CharSequence getInstruction() {
return mInstruction;
}
}
private static class TutorialPageChangeListener implements ViewPager.OnPageChangeListener {
private int mLastTutorialPagePosition = 0;
private final Context mContext;
private final TextSwitcher mTitle;
private final TextSwitcher mInstruction;
private final List<TutorialPage> mTutorialPages;
private final ViewPager mViewPager;
TutorialPageChangeListener(Context context, ViewPager viewPager, ViewGroup title,
ViewGroup instruction, List<TutorialPage> tutorialPages) {
this.mContext = context;
this.mViewPager = viewPager;
this.mTitle = (TextSwitcher) title;
this.mInstruction = (TextSwitcher) instruction;
this.mTutorialPages = tutorialPages;
}
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
// Do nothing.
}
@Override
public void onPageSelected(int position) {
final boolean isPreviousPosition =
mLastTutorialPagePosition > position;
@AnimRes
final int inAnimationResId = isPreviousPosition
? android.R.anim.slide_in_left
: com.android.internal.R.anim.slide_in_right;
@AnimRes
final int outAnimationResId = isPreviousPosition
? android.R.anim.slide_out_right
: com.android.internal.R.anim.slide_out_left;
mTitle.setInAnimation(mContext, inAnimationResId);
mTitle.setOutAnimation(mContext, outAnimationResId);
mTitle.setText(mTutorialPages.get(position).getTitle());
mInstruction.setInAnimation(mContext, inAnimationResId);
mInstruction.setOutAnimation(mContext, outAnimationResId);
mInstruction.setText(mTutorialPages.get(position).getInstruction());
for (TutorialPage page : mTutorialPages) {
page.getIndicatorIcon().setEnabled(false);
}
mTutorialPages.get(position).getIndicatorIcon().setEnabled(true);
mLastTutorialPagePosition = position;
final int currentPageNumber = position + 1;
mViewPager.setContentDescription(
mContext.getString(R.string.accessibility_tutorial_pager,
currentPageNumber, mTutorialPages.size()));
}
@Override
public void onPageScrollStateChanged(int state) {
// Do nothing.
}
}
}

View File

@@ -31,9 +31,6 @@ import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Lifecycle.Event;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -43,6 +40,9 @@ import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import java.util.Iterator;
import java.util.List;
@@ -53,7 +53,7 @@ import java.util.concurrent.FutureTask;
* Controller that shows and updates the bluetooth device name
*/
public class AccessibilityHearingAidPreferenceController extends BasePreferenceController
implements LifecycleObserver {
implements LifecycleObserver, OnStart, OnStop {
private static final String TAG = "AccessibilityHearingAidPreferenceController";
private Preference mHearingAidPreference;
@@ -104,8 +104,8 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
return mHearingAidProfileSupported ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@OnLifecycleEvent(Event.ON_RESUME)
public void onResume() {
@Override
public void onStart() {
if (mHearingAidProfileSupported) {
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
@@ -114,8 +114,8 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
}
}
@OnLifecycleEvent(Event.ON_PAUSE)
public void onPause() {
@Override
public void onStop() {
if (mHearingAidProfileSupported) {
mContext.unregisterReceiver(mHearingAidChangedReceiver);
}

View File

@@ -19,9 +19,9 @@ package com.android.settings.accessibility;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.os.storage.StorageManager;
import android.text.BidiFormatter;
@@ -60,10 +60,11 @@ public class AccessibilityServiceWarning {
return false;
};
public static Dialog createCapabilitiesDialog(Activity parentActivity,
/** 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) {
final AlertDialog ad = new AlertDialog.Builder(parentActivity)
.setView(createEnableDialogContentView(parentActivity, info, listener))
final AlertDialog ad = new AlertDialog.Builder(context)
.setView(createEnableDialogContentView(context, info, listener))
.create();
Window window = ad.getWindow();
@@ -76,18 +77,8 @@ public class AccessibilityServiceWarning {
return ad;
}
public static Dialog createDisableDialog(Activity parentActivity,
AccessibilityServiceInfo info, View.OnClickListener listener) {
final AlertDialog ad = new AlertDialog.Builder(parentActivity)
.setView(createDisableDialogContentView(parentActivity, info, listener))
.setCancelable(true)
.create();
return ad;
}
/**
* Return whether the device is encrypted with legacy full disk encryption. Newer devices
* Returns whether the device is encrypted with legacy full disk encryption. Newer devices
* should be using File Based Encryption.
*
* @return true if device is encrypted
@@ -96,13 +87,6 @@ public class AccessibilityServiceWarning {
return StorageManager.isNonDefaultBlockEncrypted();
}
/**
* Get a content View for a dialog to confirm that they want to enable a service.
*
* @param context A valid context
* @param info The info about a service
* @return A content view suitable for viewing
*/
private static View createEnableDialogContentView(Context context,
AccessibilityServiceInfo info, View.OnClickListener listener) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(
@@ -148,31 +132,21 @@ public class AccessibilityServiceWarning {
return content;
}
private static View createDisableDialogContentView(Context context,
AccessibilityServiceInfo info, View.OnClickListener listener) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
/** Returns a {@link Dialog} to be shown to confirm that they want to disable a service. */
public static Dialog createDisableDialog(Context context,
AccessibilityServiceInfo info, DialogInterface.OnClickListener listener) {
final AlertDialog dialog = new AlertDialog.Builder(context)
.setTitle(context.getString(R.string.disable_service_title,
info.getResolveInfo().loadLabel(context.getPackageManager())))
.setMessage(context.getString(R.string.disable_service_message,
context.getString(R.string.accessibility_dialog_button_stop),
getServiceName(context, info)))
.setCancelable(true)
.setPositiveButton(R.string.accessibility_dialog_button_stop, listener)
.setNegativeButton(R.string.accessibility_dialog_button_cancel, listener)
.create();
View content = inflater.inflate(R.layout.disable_accessibility_service_dialog_content,
null);
TextView permissionDialogTitle = content.findViewById(R.id.permissionDialog_disable_title);
permissionDialogTitle.setText(context.getString(R.string.disable_service_title,
getServiceName(context, info)));
TextView permissionDialogMessage = content
.findViewById(R.id.permissionDialog_disable_message);
permissionDialogMessage.setText(context.getString(R.string.disable_service_message,
context.getString(R.string.accessibility_dialog_button_stop),
getServiceName(context, info)));
Button permissionAllowButton = content.findViewById(
R.id.permission_disable_stop_button);
Button permissionDenyButton = content.findViewById(
R.id.permission_disable_cancel_button);
permissionAllowButton.setOnClickListener(listener);
permissionDenyButton.setOnClickListener(listener);
return content;
return dialog;
}
// Get the service name and bidi wrap it to protect from bidi side effects.

View File

@@ -16,13 +16,14 @@
package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -49,7 +50,6 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm
"screen_magnification_preference";
private static final String SCREEN_READER_PREFERENCE = "screen_reader_preference";
private static final String SELECT_TO_SPEAK_PREFERENCE = "select_to_speak_preference";
private static final String FONT_SIZE_PREFERENCE = "font_size_preference";
// Package names and service names used to identify screen reader and SelectToSpeak services.
private static final String SCREEN_READER_PACKAGE_NAME = "com.google.android.marvin.talkback";
@@ -100,9 +100,11 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm
public void onResume() {
super.onResume();
updateAccessibilityServicePreference(mScreenReaderPreference,
findService(SCREEN_READER_PACKAGE_NAME, SCREEN_READER_SERVICE_NAME));
SCREEN_READER_PACKAGE_NAME, SCREEN_READER_SERVICE_NAME,
VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard.class.getName());
updateAccessibilityServicePreference(mSelectToSpeakPreference,
findService(SELECT_TO_SPEAK_PACKAGE_NAME, SELECT_TO_SPEAK_SERVICE_NAME));
SELECT_TO_SPEAK_PACKAGE_NAME, SELECT_TO_SPEAK_SERVICE_NAME,
VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard.class.getName());
configureMagnificationPreferenceIfNeeded(mDisplayMagnificationPreference);
}
@@ -144,7 +146,8 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm
}
private void updateAccessibilityServicePreference(Preference preference,
AccessibilityServiceInfo info) {
String packageName, String serviceName, String targetFragment) {
final AccessibilityServiceInfo info = findService(packageName, serviceName);
if (info == null) {
getPreferenceScreen().removePreference(preference);
return;
@@ -155,6 +158,9 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm
preference.setTitle(title);
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();
@@ -165,23 +171,20 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm
extras.putString(AccessibilitySettings.EXTRA_TITLE, title);
String description = info.loadDescription(getPackageManager());
if (TextUtils.isEmpty(description)) {
description = getString(R.string.accessibility_service_default_description);
}
extras.putString(AccessibilitySettings.EXTRA_SUMMARY, description);
extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, info.getAnimatedImageRes());
final String htmlDescription = info.loadHtmlDescription(getPackageManager());
extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription);
}
private static void configureMagnificationPreferenceIfNeeded(Preference preference) {
// Some devices support only a single magnification mode. In these cases, we redirect to
// the magnification mode's UI directly, rather than showing a PreferenceScreen with a
// single list item.
final Context context = preference.getContext();
if (!MagnificationPreferenceFragment.isApplicable(context.getResources())) {
preference.setFragment(
ToggleScreenMagnificationPreferenceFragmentForSetupWizard.class.getName());
final Bundle extras = preference.getExtras();
MagnificationGesturesPreferenceController
.populateMagnificationGesturesPreferenceExtras(extras, context);
}
preference.setFragment(
ToggleScreenMagnificationPreferenceFragmentForSetupWizard.class.getName());
final Bundle extras = preference.getExtras();
MagnificationGesturesPreferenceController
.populateMagnificationGesturesPreferenceExtras(extras, context);
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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 static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import android.content.ContentResolver;
import android.content.Context;
import android.os.UserHandle;
import android.provider.Settings;
import com.android.settings.core.TogglePreferenceController;
/**
* Settings page for accessibility shortcut
*/
public class AccessibilityShortcutPreferenceController extends TogglePreferenceController {
public AccessibilityShortcutPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public boolean isChecked() {
final ContentResolver cr = mContext.getContentResolver();
// The shortcut is enabled by default on the lock screen as long as the user has
// enabled the shortcut with the warning dialog
final int dialogShown = Settings.Secure.getInt(
cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, OFF);
final boolean enabledFromLockScreen = Settings.Secure.getInt(
cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, dialogShown) == ON;
return enabledFromLockScreen;
}
@Override
public boolean setChecked(boolean isChecked) {
return Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, isChecked ? ON : OFF,
UserHandle.USER_CURRENT);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
}

View File

@@ -1,204 +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.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.Nullable;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
import android.view.accessibility.AccessibilityManager;
import android.widget.Switch;
import androidx.preference.Preference;
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.search.Indexable;
import com.android.settingslib.accessibility.AccessibilityUtils;
import com.android.settingslib.search.SearchIndexable;
/**
* Settings page for accessibility shortcut
*/
@SearchIndexable
public class AccessibilityShortcutPreferenceFragment extends ToggleFeaturePreferenceFragment
implements Indexable {
public static final String SHORTCUT_SERVICE_KEY = "accessibility_shortcut_service";
public static final String ON_LOCK_SCREEN_KEY = "accessibility_shortcut_on_lock_screen";
private Preference mServicePreference;
private SwitchPreference mOnLockScreenSwitchPreference;
private final ContentObserver mContentObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
updatePreferences();
}
};
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE;
}
@Override
public int getHelpResource() {
return R.string.help_url_accessibility_shortcut;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mServicePreference = findPreference(SHORTCUT_SERVICE_KEY);
mOnLockScreenSwitchPreference = (SwitchPreference) findPreference(ON_LOCK_SCREEN_KEY);
mOnLockScreenSwitchPreference.setOnPreferenceChangeListener((Preference p, Object o) -> {
Settings.Secure.putInt(getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
((Boolean) o) ? 1 : 0);
return true;
});
mFooterPreferenceMixin.createFooterPreference()
.setTitle(R.string.accessibility_shortcut_description);
}
@Override
public void onResume() {
super.onResume();
updatePreferences();
getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN),
false, mContentObserver);
}
@Override
public void onPause() {
getContentResolver().unregisterContentObserver(mContentObserver);
super.onPause();
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_shortcut_settings;
}
@Override
protected void onInstallSwitchBarToggleSwitch() {
super.onInstallSwitchBarToggleSwitch();
mSwitchBar.addOnSwitchChangeListener((Switch switchView, boolean enabled) -> {
Context context = getContext();
if (enabled && !shortcutFeatureAvailable(context)) {
// If no service is configured, we'll disable the shortcut shortly. Give the
// user a chance to select a service. We'll update the preferences when we resume.
Settings.Secure.putInt(
getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1);
mServicePreference.setEnabled(true);
mServicePreference.performClick();
} else {
onPreferenceToggled(Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, enabled);
}
});
}
@Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
Settings.Secure.putInt(getContentResolver(), preferenceKey, enabled ? 1 : 0);
updatePreferences();
}
private void updatePreferences() {
ContentResolver cr = getContentResolver();
Context context = getContext();
mServicePreference.setSummary(getServiceName(context));
if (!shortcutFeatureAvailable(context)) {
// If no service is configured, make sure the overall shortcut is turned off
Settings.Secure.putInt(
getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 0);
}
boolean isEnabled = Settings.Secure
.getInt(cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1) == 1;
mSwitchBar.setChecked(isEnabled);
// The shortcut is enabled by default on the lock screen as long as the user has
// enabled the shortcut with the warning dialog
final int dialogShown = Settings.Secure.getInt(
cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
final boolean enabledFromLockScreen = Settings.Secure.getInt(
cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, dialogShown) == 1;
mOnLockScreenSwitchPreference.setChecked(enabledFromLockScreen);
// Only enable changing the service and lock screen behavior if the shortcut is on
mServicePreference.setEnabled(mToggleSwitch.isChecked());
mOnLockScreenSwitchPreference.setEnabled(mToggleSwitch.isChecked());
}
/**
* Get the user-visible name of the service currently selected for the shortcut.
*
* @param context The current context
* @return The name of the service or a string saying that none is selected.
*/
public static CharSequence getServiceName(Context context) {
if (!shortcutFeatureAvailable(context)) {
return context.getString(R.string.accessibility_no_service_selected);
}
AccessibilityServiceInfo shortcutServiceInfo = getServiceInfo(context);
if (shortcutServiceInfo != null) {
return shortcutServiceInfo.getResolveInfo().loadLabel(context.getPackageManager());
}
return AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
.get(getShortcutComponent(context)).getLabel(context);
}
private static AccessibilityServiceInfo getServiceInfo(Context context) {
return AccessibilityManager.getInstance(context)
.getInstalledServiceInfoWithComponentName(getShortcutComponent(context));
}
private static boolean shortcutFeatureAvailable(Context context) {
ComponentName shortcutFeature = getShortcutComponent(context);
if (shortcutFeature == null) return false;
if (AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
.containsKey(shortcutFeature)) {
return true;
}
return getServiceInfo(context) != null;
}
private static @Nullable ComponentName getShortcutComponent(Context context) {
String componentNameString = AccessibilityUtils.getShortcutTargetServiceComponentNameString(
context, UserHandle.myUserId());
if (componentNameString == null) return null;
return ComponentName.unflattenFromString(componentNameString);
}
public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
// This fragment is for details of the shortcut. Only the shortcut itself needs
// to be indexed.
protected boolean isPageSearchEnabled(Context context) {
return false;
}
};
}

View File

@@ -16,6 +16,9 @@
package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -39,8 +42,7 @@ public class AccessibilitySlicePreferenceController extends TogglePreferenceCont
private final ComponentName mComponentName;
private final int ON = 1;
private final int OFF = 0;
private static final String EMPTY_STRING = "";
public AccessibilitySlicePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
@@ -55,8 +57,9 @@ public class AccessibilitySlicePreferenceController extends TogglePreferenceCont
@Override
public CharSequence getSummary() {
final AccessibilityServiceInfo serviceInfo = getAccessibilityServiceInfo();
return serviceInfo == null
? "" : AccessibilitySettings.getServiceSummary(mContext, serviceInfo, isChecked());
return serviceInfo == null ? EMPTY_STRING : AccessibilitySettings.getServiceSummary(
mContext, serviceInfo, isChecked());
}
@Override
@@ -91,7 +94,7 @@ public class AccessibilitySlicePreferenceController extends TogglePreferenceCont
}
@Override
public boolean isSliceable() {
public boolean isPublicSlice() {
return true;
}

View File

@@ -0,0 +1,44 @@
/*
* 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 com.android.settings.core.instrumentation.SettingsStatsLog;
/** Methods for logging accessibility states. */
public final class AccessibilityStatsLogUtils {
private AccessibilityStatsLogUtils() {}
/**
* Logs accessibility service name and its enabled status. Calls this when the user trigger
* the accessibility service to be enabled/disabled.
*
* @param componentName component name of the service
* @param enabled {@code true} if the service is enabled
*/
static void logAccessibilityServiceEnabled(ComponentName componentName, boolean enabled) {
SettingsStatsLog.write(SettingsStatsLog.ACCESSIBILITY_SERVICE_REPORTED,
componentName.flattenToString(), convertToLoggingServiceEnabled(enabled));
}
private static int convertToLoggingServiceEnabled(boolean enabled) {
return enabled ? SettingsStatsLog.ACCESSIBILITY_SERVICE_REPORTED__SERVICE_STATUS__ENABLED
: SettingsStatsLog.ACCESSIBILITY_SERVICE_REPORTED__SERVICE_STATUS__DISABLED;
}
}

View File

@@ -27,9 +27,9 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.widget.RadioButtonPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.widget.RadioButtonPreference;
import com.google.common.primitives.Ints;
@@ -87,7 +87,7 @@ public class AccessibilityTimeoutController extends AbstractPreferenceController
if (mAccessibilityTimeoutKeyToValueMap.size() == 0) {
String[] timeoutKeys = mResources.getStringArray(
R.array.accessibility_timeout_control_selector_keys);
R.array.accessibility_timeout_control_selector_keys);
int[] timeoutValues = mResources.getIntArray(
R.array.accessibility_timeout_selector_values);
@@ -141,7 +141,7 @@ public class AccessibilityTimeoutController extends AbstractPreferenceController
private int getAccessibilityTimeoutValue() {
// get accessibility control timeout value
int timeoutValue = getSecureAccessibilityTimeoutValue(mContentResolver,
CONTROL_TIMEOUT_SETTINGS_SECURE);
CONTROL_TIMEOUT_SETTINGS_SECURE);
return timeoutValue;
}

View File

@@ -0,0 +1,49 @@
/*
* 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 com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.google.common.primitives.Ints;
public class AccessibilityTimeoutPreferenceController extends BasePreferenceController {
public AccessibilityTimeoutPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public CharSequence getSummary() {
final String[] timeoutSummarys = mContext.getResources().getStringArray(
R.array.accessibility_timeout_summaries);
final int[] timeoutValues = mContext.getResources().getIntArray(
R.array.accessibility_timeout_selector_values);
final int timeoutValue = AccessibilityTimeoutController.getSecureAccessibilityTimeoutValue(
mContext.getContentResolver(),
AccessibilityTimeoutController.CONTROL_TIMEOUT_SETTINGS_SECURE);
final int idx = Ints.indexOf(timeoutValues, timeoutValue);
return timeoutSummarys[idx == -1 ? 0 : idx];
}
}

View File

@@ -0,0 +1,384 @@
/*
* 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 android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.StringJoiner;
/** Provides utility methods to accessibility settings only. */
final class AccessibilityUtil {
private AccessibilityUtil(){}
/**
* Annotation for different accessibilityService fragment UI type.
*
* {@code VOLUME_SHORTCUT_TOGGLE} for displaying basic accessibility service fragment but
* only hardware shortcut allowed.
* {@code INVISIBLE_TOGGLE} for displaying basic accessibility service fragment without
* switch bar.
* {@code TOGGLE} for displaying basic accessibility service fragment.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE,
AccessibilityServiceFragmentType.INVISIBLE_TOGGLE,
AccessibilityServiceFragmentType.TOGGLE,
})
public @interface AccessibilityServiceFragmentType {
int VOLUME_SHORTCUT_TOGGLE = 0;
int INVISIBLE_TOGGLE = 1;
int TOGGLE = 2;
}
// TODO(b/147021230): Will move common functions and variables to
// android/internal/accessibility folder
private static final char COMPONENT_NAME_SEPARATOR = ':';
private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
/**
* Annotation for different user shortcut type UI type.
*
* {@code EMPTY} for displaying default value.
* {@code SOFTWARE} for displaying specifying the accessibility services or features which
* choose accessibility button in the navigation bar as preferred shortcut.
* {@code HARDWARE} for displaying specifying the accessibility services or features which
* choose accessibility shortcut as preferred shortcut.
* {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly
* tapping screen 3 times as preferred shortcut.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
UserShortcutType.EMPTY,
UserShortcutType.SOFTWARE,
UserShortcutType.HARDWARE,
UserShortcutType.TRIPLETAP,
})
/** Denotes the user shortcut type. */
public @interface UserShortcutType {
int EMPTY = 0;
int SOFTWARE = 1; // 1 << 0
int HARDWARE = 2; // 1 << 1
int TRIPLETAP = 4; // 1 << 2
}
/** Denotes the accessibility enabled status */
@Retention(RetentionPolicy.SOURCE)
public @interface State {
int OFF = 0;
int ON = 1;
}
/**
* Return On/Off string according to the setting which specifies the integer value 1 or 0. This
* setting is defined in the secure system settings {@link android.provider.Settings.Secure}.
*/
static CharSequence getSummary(Context context, String settingsSecureKey) {
final boolean enabled = Settings.Secure.getInt(context.getContentResolver(),
settingsSecureKey, State.OFF) == State.ON;
final int resId = enabled ? R.string.accessibility_feature_state_on
: R.string.accessibility_feature_state_off;
return context.getResources().getText(resId);
}
/**
* Capitalizes a string by capitalizing the first character and making the remaining characters
* lower case.
*/
public static String capitalize(String stringToCapitalize) {
if (stringToCapitalize == null) {
return null;
}
StringBuilder capitalizedString = new StringBuilder();
if (stringToCapitalize.length() > 0) {
capitalizedString.append(stringToCapitalize.substring(0, 1).toUpperCase());
if (stringToCapitalize.length() > 1) {
capitalizedString.append(stringToCapitalize.substring(1).toLowerCase());
}
}
return capitalizedString.toString();
}
/** Determines if a gesture navigation bar is being used. */
public static boolean isGestureNavigateEnabled(Context context) {
return context.getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode)
== NAV_BAR_MODE_GESTURAL;
}
/** Determines if a touch explore is being used. */
public static boolean isTouchExploreEnabled(Context context) {
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
return am.isTouchExplorationEnabled();
}
/**
* Gets the corresponding fragment type of a given accessibility service.
*
* @param accessibilityServiceInfo The accessibilityService's info
* @return int from {@link AccessibilityServiceFragmentType}
*/
static @AccessibilityServiceFragmentType int getAccessibilityServiceFragmentType(
AccessibilityServiceInfo accessibilityServiceInfo) {
final int targetSdk = accessibilityServiceInfo.getResolveInfo()
.serviceInfo.applicationInfo.targetSdkVersion;
final boolean requestA11yButton = (accessibilityServiceInfo.flags
& AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
if (targetSdk <= Build.VERSION_CODES.Q) {
return AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE;
}
return requestA11yButton
? AccessibilityServiceFragmentType.INVISIBLE_TOGGLE
: AccessibilityServiceFragmentType.TOGGLE;
}
/**
* Opts in component name into multiple {@code shortcutTypes} colon-separated string in
* Settings.
*
* @param context The current context.
* @param shortcutTypes A combination of {@link UserShortcutType}.
* @param componentName The component name that need to be opted in Settings.
*/
static void optInAllValuesToSettings(Context context, int shortcutTypes,
@NonNull ComponentName componentName) {
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
optInValueToSettings(context, UserShortcutType.SOFTWARE, componentName);
}
if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
optInValueToSettings(context, UserShortcutType.HARDWARE, componentName);
}
}
/**
* Opts in component name into {@code shortcutType} colon-separated string in Settings.
*
* @param context The current context.
* @param shortcutType The preferred shortcut type user selected.
* @param componentName The component name that need to be opted in Settings.
*/
@VisibleForTesting
static void optInValueToSettings(Context context, @UserShortcutType int shortcutType,
@NonNull ComponentName componentName) {
final String targetKey = convertKeyFromSettings(shortcutType);
final String targetString = Settings.Secure.getString(context.getContentResolver(),
targetKey);
if (hasValueInSettings(context, shortcutType, componentName)) {
return;
}
final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR));
if (!TextUtils.isEmpty(targetString)) {
joiner.add(targetString);
}
joiner.add(componentName.flattenToString());
Settings.Secure.putString(context.getContentResolver(), targetKey, joiner.toString());
}
/**
* Opts out component name into multiple {@code shortcutTypes} colon-separated string in
* Settings.
*
* @param context The current context.
* @param shortcutTypes A combination of {@link UserShortcutType}.
* @param componentName The component name that need to be opted out from Settings.
*/
static void optOutAllValuesFromSettings(Context context, int shortcutTypes,
@NonNull ComponentName componentName) {
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
optOutValueFromSettings(context, UserShortcutType.SOFTWARE, componentName);
}
if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
optOutValueFromSettings(context, UserShortcutType.HARDWARE, componentName);
}
}
/**
* Opts out component name into {@code shortcutType} colon-separated string in Settings.
*
* @param context The current context.
* @param shortcutType The preferred shortcut type user selected.
* @param componentName The component name that need to be opted out from Settings.
*/
@VisibleForTesting
static void optOutValueFromSettings(Context context, @UserShortcutType int shortcutType,
@NonNull ComponentName componentName) {
final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR));
final String targetKey = convertKeyFromSettings(shortcutType);
final String targetString = Settings.Secure.getString(context.getContentResolver(),
targetKey);
if (TextUtils.isEmpty(targetString)) {
return;
}
sStringColonSplitter.setString(targetString);
while (sStringColonSplitter.hasNext()) {
final String name = sStringColonSplitter.next();
if (TextUtils.isEmpty(name) || (componentName.flattenToString()).equals(name)) {
continue;
}
joiner.add(name);
}
Settings.Secure.putString(context.getContentResolver(), targetKey, joiner.toString());
}
/**
* Returns if component name existed in one of {@code shortcutTypes} string in Settings.
*
* @param context The current context.
* @param shortcutTypes A combination of {@link UserShortcutType}.
* @param componentName The component name that need to be checked existed in Settings.
* @return {@code true} if componentName existed in Settings.
*/
static boolean hasValuesInSettings(Context context, int shortcutTypes,
@NonNull ComponentName componentName) {
boolean exist = false;
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
exist = hasValueInSettings(context, UserShortcutType.SOFTWARE, componentName);
}
if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
exist |= hasValueInSettings(context, UserShortcutType.HARDWARE, componentName);
}
return exist;
}
/**
* Returns if component name existed in {@code shortcutType} string Settings.
*
* @param context The current context.
* @param shortcutType The preferred shortcut type user selected.
* @param componentName The component name that need to be checked existed in Settings.
* @return {@code true} if componentName existed in Settings.
*/
@VisibleForTesting
static boolean hasValueInSettings(Context context, @UserShortcutType int shortcutType,
@NonNull ComponentName componentName) {
final String targetKey = convertKeyFromSettings(shortcutType);
final String targetString = Settings.Secure.getString(context.getContentResolver(),
targetKey);
if (TextUtils.isEmpty(targetString)) {
return false;
}
sStringColonSplitter.setString(targetString);
while (sStringColonSplitter.hasNext()) {
final String name = sStringColonSplitter.next();
if ((componentName.flattenToString()).equals(name)) {
return true;
}
}
return false;
}
/**
* Gets the corresponding user shortcut type of a given accessibility service.
*
* @param context The current context.
* @param componentName The component name that need to be checked existed in Settings.
* @return The user shortcut type if component name existed in {@code UserShortcutType} string
* Settings.
*/
static int getUserShortcutTypesFromSettings(Context context,
@NonNull ComponentName componentName) {
int shortcutTypes = UserShortcutType.EMPTY;
if (hasValuesInSettings(context, UserShortcutType.SOFTWARE, componentName)) {
shortcutTypes |= UserShortcutType.SOFTWARE;
}
if (hasValuesInSettings(context, UserShortcutType.HARDWARE, componentName)) {
shortcutTypes |= UserShortcutType.HARDWARE;
}
return shortcutTypes;
}
/**
* Converts {@link UserShortcutType} to key in Settings.
*
* @param shortcutType The shortcut type.
* @return Mapping key in Settings.
*/
static String convertKeyFromSettings(@UserShortcutType int shortcutType) {
switch (shortcutType) {
case UserShortcutType.SOFTWARE:
return Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
case UserShortcutType.HARDWARE:
return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
case UserShortcutType.TRIPLETAP:
return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
default:
throw new IllegalArgumentException(
"Unsupported userShortcutType " + shortcutType);
}
}
/**
* Gets the width of the screen.
*
* @param context the current context.
* @return the width of the screen in terms of pixels.
*/
public static int getScreenWidthPixels(Context context) {
final Resources resources = context.getResources();
final int screenWidthDp = resources.getConfiguration().screenWidthDp;
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenWidthDp,
resources.getDisplayMetrics()));
}
/**
* Gets the height of the screen.
*
* @param context the current context.
* @return the height of the screen in terms of pixels.
*/
public static int getScreenHeightPixels(Context context) {
final Resources resources = context.getResources();
final int screenHeightDp = resources.getConfiguration().screenHeightDp;
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenHeightDp,
resources.getDisplayMetrics()));
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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 <a
* href="https://developers.google.com/speed/webp/">animated webp</a> 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();
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.provider.Settings;
import android.view.accessibility.AccessibilityManager;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
public class AutoclickPreferenceController extends BasePreferenceController {
public AutoclickPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public CharSequence getSummary() {
final boolean enabled = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, 0) == 1;
if (!enabled) {
return mContext.getResources().getText(R.string.accessibility_feature_state_off);
}
final int delay = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
AccessibilityManager.AUTOCLICK_DELAY_DEFAULT);
return ToggleAutoclickPreferenceFragment.getAutoclickPreferenceSummary(
mContext.getResources(), delay);
}
}

View File

@@ -16,6 +16,8 @@
package com.android.settings.accessibility;
import static com.android.settings.Utils.isNightMode;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -113,8 +115,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(Color.BLACK);
mCenterMarkerPaint.setColor(isNightMode(context) ? Color.WHITE : Color.BLACK);
mCenterMarkerPaint.setStyle(Paint.Style.FILL);
// Remove the progress colour
setProgressTintList(ColorStateList.valueOf(Color.TRANSPARENT));

View File

@@ -0,0 +1,416 @@
/*
* 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.app.settings.SettingsEnums;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.view.accessibility.CaptioningManager;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
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.search.BaseSearchIndexProvider;
import com.android.settingslib.accessibility.AccessibilityUtils;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.widget.LayoutPreference;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/** Settings fragment containing font style of captioning properties. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class CaptionAppearanceFragment extends SettingsPreferenceFragment
implements OnPreferenceChangeListener, OnValueChangedListener {
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";
private static final String PREF_FOREGROUND_COLOR = "captioning_foreground_color";
private static final String PREF_FOREGROUND_OPACITY = "captioning_foreground_opacity";
private static final String PREF_WINDOW_COLOR = "captioning_window_color";
private static final String PREF_WINDOW_OPACITY = "captioning_window_opacity";
private static final String PREF_EDGE_COLOR = "captioning_edge_color";
private static final String PREF_EDGE_TYPE = "captioning_edge_type";
private static final String PREF_FONT_SIZE = "captioning_font_size";
private static final String PREF_TYPEFACE = "captioning_typeface";
private static final String PREF_PRESET = "captioning_preset";
private static final String PREF_CUSTOM = "custom";
/* WebVtt specifies line height as 5.3% of the viewport height. */
private static final float LINE_HEIGHT_RATIO = 0.0533f;
private CaptioningManager mCaptioningManager;
private SubtitleView mPreviewText;
private View mPreviewWindow;
private View mPreviewViewport;
// Standard options.
private ListPreference mFontSize;
private PresetPreference mPreset;
// Custom options.
private ListPreference mTypeface;
private ColorPreference mForegroundColor;
private ColorPreference mForegroundOpacity;
private EdgeTypePreference mEdgeType;
private ColorPreference mEdgeColor;
private ColorPreference mBackgroundColor;
private ColorPreference mBackgroundOpacity;
private ColorPreference mWindowColor;
private ColorPreference mWindowOpacity;
private PreferenceCategory mCustom;
private boolean mShowingCustom;
private final List<Preference> mPreferenceList = new ArrayList<>();
private final View.OnLayoutChangeListener mLayoutChangeListener =
new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
// Remove the listener once the callback is triggered.
mPreviewViewport.removeOnLayoutChangeListener(this);
refreshPreviewText();
}
};
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_CAPTION_APPEARANCE;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mCaptioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
addPreferencesFromResource(R.xml.captioning_appearance);
initializeAllPreferences();
updateAllPreferences();
refreshShowingCustom();
installUpdateListeners();
refreshPreviewText();
}
private void refreshPreviewText() {
final Context context = getActivity();
if (context == null) {
// We've been destroyed, abort!
return;
}
final SubtitleView preview = mPreviewText;
if (preview != null) {
final int styleId = mCaptioningManager.getRawUserStyle();
applyCaptionProperties(mCaptioningManager, preview, mPreviewViewport, styleId);
final Locale locale = mCaptioningManager.getLocale();
if (locale != null) {
final CharSequence localizedText = AccessibilityUtils.getTextForLocale(
context, locale, R.string.captioning_preview_text);
preview.setText(localizedText);
} else {
preview.setText(R.string.captioning_preview_text);
}
final CaptioningManager.CaptionStyle style = mCaptioningManager.getUserStyle();
if (style.hasWindowColor()) {
mPreviewWindow.setBackgroundColor(style.windowColor);
} else {
final CaptioningManager.CaptionStyle defStyle =
CaptioningManager.CaptionStyle.DEFAULT;
mPreviewWindow.setBackgroundColor(defStyle.windowColor);
}
}
}
/**
* Updates font style of captioning properties for preview screen.
*
* @param manager caption manager
* @param previewText preview text
* @param previewWindow preview window
* @param styleId font style id
*/
public static void applyCaptionProperties(CaptioningManager manager, SubtitleView previewText,
View previewWindow, int styleId) {
previewText.setStyle(styleId);
final Context context = previewText.getContext();
final ContentResolver cr = context.getContentResolver();
final float fontScale = manager.getFontScale();
if (previewWindow != null) {
// Assume the viewport is clipped with a 16:9 aspect ratio.
final float virtualHeight = Math.max(9 * previewWindow.getWidth(),
16 * previewWindow.getHeight()) / 16.0f;
previewText.setTextSize(virtualHeight * LINE_HEIGHT_RATIO * fontScale);
} else {
final float textSize = context.getResources().getDimension(
R.dimen.caption_preview_text_size);
previewText.setTextSize(textSize * fontScale);
}
final Locale locale = manager.getLocale();
if (locale != null) {
final CharSequence localizedText = AccessibilityUtils.getTextForLocale(
context, locale, R.string.captioning_preview_characters);
previewText.setText(localizedText);
} else {
previewText.setText(R.string.captioning_preview_characters);
}
}
private void initializeAllPreferences() {
final LayoutPreference captionPreview = findPreference(PREF_CAPTION_PREVIEW);
mPreviewText = captionPreview.findViewById(R.id.preview_text);
mPreviewWindow = captionPreview.findViewById(R.id.preview_window);
mPreviewViewport = captionPreview.findViewById(R.id.preview_viewport);
mPreviewViewport.addOnLayoutChangeListener(mLayoutChangeListener);
final Resources res = getResources();
final int[] presetValues = res.getIntArray(R.array.captioning_preset_selector_values);
final String[] presetTitles = res.getStringArray(R.array.captioning_preset_selector_titles);
mPreset = (PresetPreference) findPreference(PREF_PRESET);
mPreset.setValues(presetValues);
mPreset.setTitles(presetTitles);
mFontSize = (ListPreference) findPreference(PREF_FONT_SIZE);
// Initialize the preference list
mPreferenceList.add(mFontSize);
mPreferenceList.add(mPreset);
mCustom = (PreferenceCategory) findPreference(PREF_CUSTOM);
mShowingCustom = true;
final int[] colorValues = res.getIntArray(R.array.captioning_color_selector_values);
final String[] colorTitles = res.getStringArray(R.array.captioning_color_selector_titles);
mForegroundColor = (ColorPreference) mCustom.findPreference(PREF_FOREGROUND_COLOR);
mForegroundColor.setTitles(colorTitles);
mForegroundColor.setValues(colorValues);
final int[] opacityValues = res.getIntArray(R.array.captioning_opacity_selector_values);
final String[] opacityTitles = res.getStringArray(
R.array.captioning_opacity_selector_titles);
mForegroundOpacity = (ColorPreference) mCustom.findPreference(PREF_FOREGROUND_OPACITY);
mForegroundOpacity.setTitles(opacityTitles);
mForegroundOpacity.setValues(opacityValues);
mEdgeColor = (ColorPreference) mCustom.findPreference(PREF_EDGE_COLOR);
mEdgeColor.setTitles(colorTitles);
mEdgeColor.setValues(colorValues);
// Add "none" as an additional option for backgrounds.
final int[] bgColorValues = new int[colorValues.length + 1];
final String[] bgColorTitles = new String[colorTitles.length + 1];
System.arraycopy(colorValues, 0, bgColorValues, 1, colorValues.length);
System.arraycopy(colorTitles, 0, bgColorTitles, 1, colorTitles.length);
bgColorValues[0] = Color.TRANSPARENT;
bgColorTitles[0] = getString(R.string.color_none);
mBackgroundColor = (ColorPreference) mCustom.findPreference(PREF_BACKGROUND_COLOR);
mBackgroundColor.setTitles(bgColorTitles);
mBackgroundColor.setValues(bgColorValues);
mBackgroundOpacity = (ColorPreference) mCustom.findPreference(PREF_BACKGROUND_OPACITY);
mBackgroundOpacity.setTitles(opacityTitles);
mBackgroundOpacity.setValues(opacityValues);
mWindowColor = (ColorPreference) mCustom.findPreference(PREF_WINDOW_COLOR);
mWindowColor.setTitles(bgColorTitles);
mWindowColor.setValues(bgColorValues);
mWindowOpacity = (ColorPreference) mCustom.findPreference(PREF_WINDOW_OPACITY);
mWindowOpacity.setTitles(opacityTitles);
mWindowOpacity.setValues(opacityValues);
mEdgeType = (EdgeTypePreference) mCustom.findPreference(PREF_EDGE_TYPE);
mTypeface = (ListPreference) mCustom.findPreference(PREF_TYPEFACE);
}
private void installUpdateListeners() {
mPreset.setOnValueChangedListener(this);
mForegroundColor.setOnValueChangedListener(this);
mForegroundOpacity.setOnValueChangedListener(this);
mEdgeColor.setOnValueChangedListener(this);
mBackgroundColor.setOnValueChangedListener(this);
mBackgroundOpacity.setOnValueChangedListener(this);
mWindowColor.setOnValueChangedListener(this);
mWindowOpacity.setOnValueChangedListener(this);
mEdgeType.setOnValueChangedListener(this);
mTypeface.setOnPreferenceChangeListener(this);
mFontSize.setOnPreferenceChangeListener(this);
}
private void updateAllPreferences() {
final int preset = mCaptioningManager.getRawUserStyle();
mPreset.setValue(preset);
final float fontSize = mCaptioningManager.getFontScale();
mFontSize.setValue(Float.toString(fontSize));
final ContentResolver cr = getContentResolver();
final CaptioningManager.CaptionStyle attrs = CaptioningManager.CaptionStyle.getCustomStyle(
cr);
mEdgeType.setValue(attrs.edgeType);
mEdgeColor.setValue(attrs.edgeColor);
final int foregroundColor = attrs.hasForegroundColor() ? attrs.foregroundColor
: CaptioningManager.CaptionStyle.COLOR_UNSPECIFIED;
parseColorOpacity(mForegroundColor, mForegroundOpacity, foregroundColor);
final int backgroundColor = attrs.hasBackgroundColor() ? attrs.backgroundColor
: CaptioningManager.CaptionStyle.COLOR_UNSPECIFIED;
parseColorOpacity(mBackgroundColor, mBackgroundOpacity, backgroundColor);
final int windowColor = attrs.hasWindowColor() ? attrs.windowColor
: CaptioningManager.CaptionStyle.COLOR_UNSPECIFIED;
parseColorOpacity(mWindowColor, mWindowOpacity, windowColor);
final String rawTypeface = attrs.mRawTypeface;
mTypeface.setValue(rawTypeface == null ? "" : rawTypeface);
}
/**
* Unpacks the specified color value and update the preferences.
*
* @param color color preference
* @param opacity opacity preference
* @param value packed value
*/
private void parseColorOpacity(ColorPreference color, ColorPreference opacity, int value) {
final int colorValue;
final int opacityValue;
if (!CaptioningManager.CaptionStyle.hasColor(value)) {
// "Default" color with variable alpha.
colorValue = CaptioningManager.CaptionStyle.COLOR_UNSPECIFIED;
opacityValue = (value & 0xFF) << 24;
} else if ((value >>> 24) == 0) {
// "None" color with variable alpha.
colorValue = Color.TRANSPARENT;
opacityValue = (value & 0xFF) << 24;
} else {
// Normal color.
colorValue = value | 0xFF000000;
opacityValue = value & 0xFF000000;
}
// Opacity value is always white.
opacity.setValue(opacityValue | 0xFFFFFF);
color.setValue(colorValue);
}
private int mergeColorOpacity(ColorPreference color, ColorPreference opacity) {
final int colorValue = color.getValue();
final int opacityValue = opacity.getValue();
final int value;
// "Default" is 0x00FFFFFF or, for legacy support, 0x00000100.
if (!CaptioningManager.CaptionStyle.hasColor(colorValue)) {
// Encode "default" as 0x00FFFFaa.
value = 0x00FFFF00 | Color.alpha(opacityValue);
} else if (colorValue == Color.TRANSPARENT) {
// Encode "none" as 0x000000aa.
value = Color.alpha(opacityValue);
} else {
// Encode custom color normally.
value = colorValue & 0x00FFFFFF | opacityValue & 0xFF000000;
}
return value;
}
private void refreshShowingCustom() {
final boolean customPreset =
mPreset.getValue() == CaptioningManager.CaptionStyle.PRESET_CUSTOM;
if (!customPreset && mShowingCustom) {
getPreferenceScreen().removePreference(mCustom);
mShowingCustom = false;
} else if (customPreset && !mShowingCustom) {
getPreferenceScreen().addPreference(mCustom);
mShowingCustom = true;
}
}
@Override
public void onValueChanged(ListDialogPreference preference, int value) {
final ContentResolver cr = getActivity().getContentResolver();
if (mForegroundColor == preference || mForegroundOpacity == preference) {
final int merged = mergeColorOpacity(mForegroundColor, mForegroundOpacity);
Settings.Secure.putInt(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, merged);
} else if (mBackgroundColor == preference || mBackgroundOpacity == preference) {
final int merged = mergeColorOpacity(mBackgroundColor, mBackgroundOpacity);
Settings.Secure.putInt(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, merged);
} else if (mWindowColor == preference || mWindowOpacity == preference) {
final int merged = mergeColorOpacity(mWindowColor, mWindowOpacity);
Settings.Secure.putInt(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, merged);
} else if (mEdgeColor == preference) {
Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, value);
} else if (mPreset == preference) {
Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET, value);
refreshShowingCustom();
} else if (mEdgeType == preference) {
Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, value);
}
refreshPreviewText();
}
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
final ContentResolver cr = getActivity().getContentResolver();
if (mTypeface == preference) {
Settings.Secure.putString(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE, (String) value);
refreshPreviewText();
} else if (mFontSize == preference) {
Settings.Secure.putFloat(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE,
Float.parseFloat((String) value));
refreshPreviewText();
}
return true;
}
@Override
public int getHelpResource() {
return R.string.help_url_caption;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.captioning_appearance);
}

View File

@@ -0,0 +1,91 @@
/*
* 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.app.settings.SettingsEnums;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Bundle;
import android.provider.Settings;
import android.view.accessibility.CaptioningManager;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
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
implements Preference.OnPreferenceChangeListener {
private static final String PREF_LOCALE = "captioning_locale";
private CaptioningManager mCaptioningManager;
private LocalePreference mLocale;
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_CAPTION_MORE_OPTIONS;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mCaptioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
addPreferencesFromResource(R.xml.captioning_more_options);
initializeAllPreferences();
updateAllPreferences();
installUpdateListeners();
}
private void initializeAllPreferences() {
mLocale = (LocalePreference) findPreference(PREF_LOCALE);
}
private void installUpdateListeners() {
mLocale.setOnPreferenceChangeListener(this);
}
private void updateAllPreferences() {
final String rawLocale = mCaptioningManager.getRawLocale();
mLocale.setValue(rawLocale == null ? "" : rawLocale);
}
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
final ContentResolver cr = getActivity().getContentResolver();
if (mLocale == preference) {
Settings.Secure.putString(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_LOCALE, (String) value);
}
return true;
}
@Override
public int getHelpResource() {
return R.string.help_url_caption;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.captioning_more_options);
}

View File

@@ -19,92 +19,40 @@ package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.view.accessibility.CaptioningManager;
import android.view.accessibility.CaptioningManager.CaptionStyle;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.PreferenceCategory;
import androidx.preference.SwitchPreference;
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.settingslib.accessibility.AccessibilityUtils;
import com.android.settingslib.widget.LayoutPreference;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
import com.google.common.primitives.Floats;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* Settings fragment containing captioning properties.
*/
/** Settings fragment containing captioning properties. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class CaptionPropertiesFragment extends SettingsPreferenceFragment
implements OnPreferenceChangeListener, OnValueChangedListener {
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";
private static final String PREF_FOREGROUND_COLOR = "captioning_foreground_color";
private static final String PREF_FOREGROUND_OPACITY = "captioning_foreground_opacity";
private static final String PREF_WINDOW_COLOR = "captioning_window_color";
private static final String PREF_WINDOW_OPACITY = "captioning_window_opacity";
private static final String PREF_EDGE_COLOR = "captioning_edge_color";
private static final String PREF_EDGE_TYPE = "captioning_edge_type";
private static final String PREF_FONT_SIZE = "captioning_font_size";
private static final String PREF_TYPEFACE = "captioning_typeface";
private static final String PREF_LOCALE = "captioning_locale";
private static final String PREF_PRESET = "captioning_preset";
implements OnPreferenceChangeListener {
private static final String PREF_SWITCH = "captioning_preference_switch";
private static final String PREF_CUSTOM = "custom";
/** WebVtt specifies line height as 5.3% of the viewport height. */
private static final float LINE_HEIGHT_RATIO = 0.0533f;
private static final String PREF_TEXT = "captioning_caption_appearance";
private static final String PREF_MORE = "captioning_more_options";
private CaptioningManager mCaptioningManager;
private SubtitleView mPreviewText;
private View mPreviewWindow;
private View mPreviewViewport;
// Standard options.
private SwitchPreference mSwitch;
private LocalePreference mLocale;
private ListPreference mFontSize;
private PresetPreference mPreset;
// Custom options.
private ListPreference mTypeface;
private ColorPreference mForegroundColor;
private ColorPreference mForegroundOpacity;
private EdgeTypePreference mEdgeType;
private ColorPreference mEdgeColor;
private ColorPreference mBackgroundColor;
private ColorPreference mBackgroundOpacity;
private ColorPreference mWindowColor;
private ColorPreference mWindowOpacity;
private PreferenceCategory mCustom;
private boolean mShowingCustom;
private Preference mTextAppearance;
private Preference mMoreOptions;
private final List<Preference> mPreferenceList = new ArrayList<>();
private final View.OnLayoutChangeListener mLayoutChangeListener =
new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
// Remove the listener once the callback is triggered.
mPreviewViewport.removeOnLayoutChangeListener(this);
refreshPreviewText();
}
};
private float[] mFontSizeValuesArray;
@Override
public int getMetricsCategory() {
@@ -119,314 +67,70 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
addPreferencesFromResource(R.xml.captioning_settings);
initializeAllPreferences();
updateAllPreferences();
refreshShowingCustom();
installUpdateListeners();
refreshPreviewText();
}
private void setPreferenceViewEnabled(boolean enabled) {
for (Preference preference : mPreferenceList) {
preference.setEnabled(enabled);
}
}
private void refreshPreferenceViewEnabled(boolean enabled) {
setPreferenceViewEnabled(enabled);
mPreviewText.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
}
private void refreshPreviewText() {
final Context context = getActivity();
if (context == null) {
// We've been destroyed, abort!
return;
}
final SubtitleView preview = mPreviewText;
if (preview != null) {
final int styleId = mCaptioningManager.getRawUserStyle();
applyCaptionProperties(mCaptioningManager, preview, mPreviewViewport, styleId);
final Locale locale = mCaptioningManager.getLocale();
if (locale != null) {
final CharSequence localizedText = AccessibilityUtils.getTextForLocale(
context, locale, R.string.captioning_preview_text);
preview.setText(localizedText);
} else {
preview.setText(R.string.captioning_preview_text);
}
final CaptionStyle style = mCaptioningManager.getUserStyle();
if (style.hasWindowColor()) {
mPreviewWindow.setBackgroundColor(style.windowColor);
} else {
final CaptionStyle defStyle = CaptionStyle.DEFAULT;
mPreviewWindow.setBackgroundColor(defStyle.windowColor);
}
}
}
public static void applyCaptionProperties(CaptioningManager manager, SubtitleView previewText,
View previewWindow, int styleId) {
previewText.setStyle(styleId);
final Context context = previewText.getContext();
final ContentResolver cr = context.getContentResolver();
final float fontScale = manager.getFontScale();
if (previewWindow != null) {
// Assume the viewport is clipped with a 16:9 aspect ratio.
final float virtualHeight = Math.max(9 * previewWindow.getWidth(),
16 * previewWindow.getHeight()) / 16.0f;
previewText.setTextSize(virtualHeight * LINE_HEIGHT_RATIO * fontScale);
} else {
final float textSize = context.getResources().getDimension(
R.dimen.caption_preview_text_size);
previewText.setTextSize(textSize * fontScale);
}
final Locale locale = manager.getLocale();
if (locale != null) {
final CharSequence localizedText = AccessibilityUtils.getTextForLocale(
context, locale, R.string.captioning_preview_characters);
previewText.setText(localizedText);
} else {
previewText.setText(R.string.captioning_preview_characters);
}
}
private void initializeAllPreferences() {
final LayoutPreference captionPreview = findPreference(PREF_CAPTION_PREVIEW);
mPreviewText = captionPreview.findViewById(R.id.preview_text);
mPreviewWindow = captionPreview.findViewById(R.id.preview_window);
mPreviewViewport = captionPreview.findViewById(R.id.preview_viewport);
mPreviewViewport.addOnLayoutChangeListener(mLayoutChangeListener);
final Resources res = getResources();
final int[] presetValues = res.getIntArray(R.array.captioning_preset_selector_values);
final String[] presetTitles = res.getStringArray(R.array.captioning_preset_selector_titles);
mPreset = (PresetPreference) findPreference(PREF_PRESET);
mPreset.setValues(presetValues);
mPreset.setTitles(presetTitles);
mSwitch = (SwitchPreference) findPreference(PREF_SWITCH);
mLocale = (LocalePreference) findPreference(PREF_LOCALE);
mFontSize = (ListPreference) findPreference(PREF_FONT_SIZE);
// Initialize the preference list
mPreferenceList.add(mLocale);
mPreferenceList.add(mFontSize);
mPreferenceList.add(mPreset);
refreshPreferenceViewEnabled(mCaptioningManager.isEnabled());
mCustom = (PreferenceCategory) findPreference(PREF_CUSTOM);
mShowingCustom = true;
final int[] colorValues = res.getIntArray(R.array.captioning_color_selector_values);
final String[] colorTitles = res.getStringArray(R.array.captioning_color_selector_titles);
mForegroundColor = (ColorPreference) mCustom.findPreference(PREF_FOREGROUND_COLOR);
mForegroundColor.setTitles(colorTitles);
mForegroundColor.setValues(colorValues);
final int[] opacityValues = res.getIntArray(R.array.captioning_opacity_selector_values);
final String[] opacityTitles = res.getStringArray(
R.array.captioning_opacity_selector_titles);
mForegroundOpacity = (ColorPreference) mCustom.findPreference(PREF_FOREGROUND_OPACITY);
mForegroundOpacity.setTitles(opacityTitles);
mForegroundOpacity.setValues(opacityValues);
mEdgeColor = (ColorPreference) mCustom.findPreference(PREF_EDGE_COLOR);
mEdgeColor.setTitles(colorTitles);
mEdgeColor.setValues(colorValues);
// Add "none" as an additional option for backgrounds.
final int[] bgColorValues = new int[colorValues.length + 1];
final String[] bgColorTitles = new String[colorTitles.length + 1];
System.arraycopy(colorValues, 0, bgColorValues, 1, colorValues.length);
System.arraycopy(colorTitles, 0, bgColorTitles, 1, colorTitles.length);
bgColorValues[0] = Color.TRANSPARENT;
bgColorTitles[0] = getString(R.string.color_none);
mBackgroundColor = (ColorPreference) mCustom.findPreference(PREF_BACKGROUND_COLOR);
mBackgroundColor.setTitles(bgColorTitles);
mBackgroundColor.setValues(bgColorValues);
mBackgroundOpacity = (ColorPreference) mCustom.findPreference(PREF_BACKGROUND_OPACITY);
mBackgroundOpacity.setTitles(opacityTitles);
mBackgroundOpacity.setValues(opacityValues);
mWindowColor = (ColorPreference) mCustom.findPreference(PREF_WINDOW_COLOR);
mWindowColor.setTitles(bgColorTitles);
mWindowColor.setValues(bgColorValues);
mWindowOpacity = (ColorPreference) mCustom.findPreference(PREF_WINDOW_OPACITY);
mWindowOpacity.setTitles(opacityTitles);
mWindowOpacity.setValues(opacityValues);
mEdgeType = (EdgeTypePreference) mCustom.findPreference(PREF_EDGE_TYPE);
mTypeface = (ListPreference) mCustom.findPreference(PREF_TYPEFACE);
}
private void installUpdateListeners() {
mPreset.setOnValueChangedListener(this);
mForegroundColor.setOnValueChangedListener(this);
mForegroundOpacity.setOnValueChangedListener(this);
mEdgeColor.setOnValueChangedListener(this);
mBackgroundColor.setOnValueChangedListener(this);
mBackgroundOpacity.setOnValueChangedListener(this);
mWindowColor.setOnValueChangedListener(this);
mWindowOpacity.setOnValueChangedListener(this);
mEdgeType.setOnValueChangedListener(this);
mSwitch.setOnPreferenceChangeListener(this);
mTypeface.setOnPreferenceChangeListener(this);
mFontSize.setOnPreferenceChangeListener(this);
mLocale.setOnPreferenceChangeListener(this);
}
private void updateAllPreferences() {
final int preset = mCaptioningManager.getRawUserStyle();
mPreset.setValue(preset);
final float fontSize = mCaptioningManager.getFontScale();
mFontSize.setValue(Float.toString(fontSize));
final ContentResolver cr = getContentResolver();
final CaptionStyle attrs = CaptionStyle.getCustomStyle(cr);
mEdgeType.setValue(attrs.edgeType);
mEdgeColor.setValue(attrs.edgeColor);
final int foregroundColor = attrs.hasForegroundColor() ?
attrs.foregroundColor : CaptionStyle.COLOR_UNSPECIFIED;
parseColorOpacity(mForegroundColor, mForegroundOpacity, foregroundColor);
final int backgroundColor = attrs.hasBackgroundColor() ?
attrs.backgroundColor : CaptionStyle.COLOR_UNSPECIFIED;
parseColorOpacity(mBackgroundColor, mBackgroundOpacity, backgroundColor);
final int windowColor = attrs.hasWindowColor() ?
attrs.windowColor : CaptionStyle.COLOR_UNSPECIFIED;
parseColorOpacity(mWindowColor, mWindowOpacity, windowColor);
final String rawTypeface = attrs.mRawTypeface;
mTypeface.setValue(rawTypeface == null ? "" : rawTypeface);
final String rawLocale = mCaptioningManager.getRawLocale();
mLocale.setValue(rawLocale == null ? "" : rawLocale);
mSwitch.setChecked(mCaptioningManager.isEnabled());
}
/**
* Unpack the specified color value and update the preferences.
*
* @param color color preference
* @param opacity opacity preference
* @param value packed value
*/
private void parseColorOpacity(ColorPreference color, ColorPreference opacity, int value) {
final int colorValue;
final int opacityValue;
if (!CaptionStyle.hasColor(value)) {
// "Default" color with variable alpha.
colorValue = CaptionStyle.COLOR_UNSPECIFIED;
opacityValue = (value & 0xFF) << 24;
} else if ((value >>> 24) == 0) {
// "None" color with variable alpha.
colorValue = Color.TRANSPARENT;
opacityValue = (value & 0xFF) << 24;
} else {
// Normal color.
colorValue = value | 0xFF000000;
opacityValue = value & 0xFF000000;
}
// Opacity value is always white.
opacity.setValue(opacityValue | 0xFFFFFF);
color.setValue(colorValue);
}
private int mergeColorOpacity(ColorPreference color, ColorPreference opacity) {
final int colorValue = color.getValue();
final int opacityValue = opacity.getValue();
final int value;
// "Default" is 0x00FFFFFF or, for legacy support, 0x00000100.
if (!CaptionStyle.hasColor(colorValue)) {
// Encode "default" as 0x00FFFFaa.
value = 0x00FFFF00 | Color.alpha(opacityValue);
} else if (colorValue == Color.TRANSPARENT) {
// Encode "none" as 0x000000aa.
value = Color.alpha(opacityValue);
} else {
// Encode custom color normally.
value = colorValue & 0x00FFFFFF | opacityValue & 0xFF000000;
}
return value;
}
private void refreshShowingCustom() {
final boolean customPreset = mPreset.getValue() == CaptionStyle.PRESET_CUSTOM;
if (!customPreset && mShowingCustom) {
getPreferenceScreen().removePreference(mCustom);
mShowingCustom = false;
} else if (customPreset && !mShowingCustom) {
getPreferenceScreen().addPreference(mCustom);
mShowingCustom = true;
}
initFontSizeValuesArray();
}
@Override
public void onValueChanged(ListDialogPreference preference, int value) {
final ContentResolver cr = getActivity().getContentResolver();
if (mForegroundColor == preference || mForegroundOpacity == preference) {
final int merged = mergeColorOpacity(mForegroundColor, mForegroundOpacity);
Settings.Secure.putInt(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, merged);
} else if (mBackgroundColor == preference || mBackgroundOpacity == preference) {
final int merged = mergeColorOpacity(mBackgroundColor, mBackgroundOpacity);
Settings.Secure.putInt(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, merged);
} else if (mWindowColor == preference || mWindowOpacity == preference) {
final int merged = mergeColorOpacity(mWindowColor, mWindowOpacity);
Settings.Secure.putInt(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, merged);
} else if (mEdgeColor == preference) {
Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, value);
} else if (mPreset == preference) {
Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET, value);
refreshShowingCustom();
} else if (mEdgeType == preference) {
Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, value);
}
public void onResume() {
super.onResume();
updateAllPreferences();
}
refreshPreviewText();
private void initializeAllPreferences() {
mSwitch = (SwitchPreference) findPreference(PREF_SWITCH);
mTextAppearance = (Preference) findPreference(PREF_TEXT);
mMoreOptions = (Preference) findPreference(PREF_MORE);
mPreferenceList.add(mTextAppearance);
mPreferenceList.add(mMoreOptions);
}
private void installUpdateListeners() {
mSwitch.setOnPreferenceChangeListener(this);
}
private void initFontSizeValuesArray() {
final String[] fontSizeValuesStrArray = getPrefContext().getResources().getStringArray(
R.array.captioning_font_size_selector_values);
final int length = fontSizeValuesStrArray.length;
mFontSizeValuesArray = new float[length];
for (int i = 0; i < length; ++i) {
mFontSizeValuesArray[i] = Float.parseFloat(fontSizeValuesStrArray[i]);
}
}
private void updateAllPreferences() {
mSwitch.setChecked(mCaptioningManager.isEnabled());
mTextAppearance.setSummary(geTextAppearanceSummary(getPrefContext()));
}
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
final ContentResolver cr = getActivity().getContentResolver();
if (mTypeface == preference) {
Settings.Secure.putString(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE, (String) value);
refreshPreviewText();
} else if (mFontSize == preference) {
Settings.Secure.putFloat(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE,
Float.parseFloat((String) value));
refreshPreviewText();
} else if (mLocale == preference) {
Settings.Secure.putString(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_LOCALE, (String) value);
refreshPreviewText();
} else if (mSwitch == preference) {
if (mSwitch == preference) {
Settings.Secure.putInt(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, (boolean) value ? 1 : 0);
refreshPreferenceViewEnabled((boolean) value);
}
return true;
}
@Override
public int getHelpResource() {
return R.string.help_url_caption;
}
private CharSequence geTextAppearanceSummary(Context context) {
final String[] fontSizeSummaries = context.getResources().getStringArray(
R.array.captioning_font_size_selector_summaries);
final float fontSize = mCaptioningManager.getFontScale();
final int idx = Floats.indexOf(mFontSizeValuesArray, fontSize);
return fontSizeSummaries[idx == /* not exist */ -1 ? 0 : idx];
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.captioning_settings);
}

View File

@@ -0,0 +1,40 @@
/*
* 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.provider.Settings;
import com.android.settings.core.BasePreferenceController;
public class CaptioningPreferenceController extends BasePreferenceController {
public CaptioningPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public CharSequence getSummary() {
return AccessibilityUtil.getSummary(mContext,
Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED);
}
}

View File

@@ -19,30 +19,21 @@ package com.android.settings.accessibility;
import android.content.Context;
import android.provider.Settings;
import androidx.annotation.VisibleForTesting;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.TogglePreferenceController;
/** Controller that shows the color inversion summary. */
public class ColorInversionPreferenceController extends BasePreferenceController {
public class ColorInversionPreferenceController extends TogglePreferenceController {
@VisibleForTesting
static final int ON = 1;
@VisibleForTesting
static final int OFF = 0;
private static final String DISPLAY_INVERSION_ENABLED =
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED;
public ColorInversionPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public boolean isChecked() {
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, OFF) == ON;
}
@Override
public boolean setChecked(boolean isChecked) {
return Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, (isChecked ? ON : OFF));
public CharSequence getSummary() {
return AccessibilityUtil.getSummary(mContext, DISPLAY_INVERSION_ENABLED);
}
@Override

View File

@@ -0,0 +1,42 @@
/*
* 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.provider.Settings;
import com.android.settings.core.BasePreferenceController;
/** Controller that shows and updates the color correction summary. */
public class DaltonizerPreferenceController extends BasePreferenceController {
private static final String DALTONIZER_ENABLED =
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED;
public DaltonizerPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public CharSequence getSummary() {
return AccessibilityUtil.getSummary(mContext, DALTONIZER_ENABLED);
}
}

View File

@@ -0,0 +1,167 @@
/*
* 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.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.provider.Settings;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import androidx.lifecycle.LifecycleObserver;
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.Lifecycle;
import com.android.settingslib.widget.RadioButtonPreference;
import com.google.common.primitives.Ints;
import java.util.HashMap;
import java.util.Map;
/** Controller class that control radio button of accessibility daltonizer settings. */
public class DaltonizerRadioButtonPreferenceController extends BasePreferenceController implements
LifecycleObserver, RadioButtonPreference.OnClickListener {
private static final String TYPE = Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER;
// pair the preference key and daltonizer value.
private final Map<String, Integer> mAccessibilityDaltonizerKeyToValueMap = new HashMap<>();
// RadioButtonPreference key, each preference represent a daltonizer value.
private final ContentResolver mContentResolver;
private final Resources mResources;
private DaltonizerRadioButtonPreferenceController.OnChangeListener mOnChangeListener;
private RadioButtonPreference mPreference;
private int mAccessibilityDaltonizerValue;
public DaltonizerRadioButtonPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mContentResolver = context.getContentResolver();
mResources = context.getResources();
}
public DaltonizerRadioButtonPreferenceController(Context context, Lifecycle lifecycle,
String preferenceKey) {
super(context, preferenceKey);
mContentResolver = context.getContentResolver();
mResources = context.getResources();
if (lifecycle != null) {
lifecycle.addObserver(this);
}
}
protected static int getSecureAccessibilityDaltonizerValue(ContentResolver resolver,
String name) {
final String daltonizerStringValue = Settings.Secure.getString(resolver, name);
if (daltonizerStringValue == null) {
return AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY;
}
final Integer daltonizerIntValue = Ints.tryParse(daltonizerStringValue);
return daltonizerIntValue == null ? AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY
: daltonizerIntValue;
}
public void setOnChangeListener(
DaltonizerRadioButtonPreferenceController.OnChangeListener listener) {
mOnChangeListener = listener;
}
private Map<String, Integer> getDaltonizerValueToKeyMap() {
if (mAccessibilityDaltonizerKeyToValueMap.size() == 0) {
final String[] daltonizerKeys = mResources.getStringArray(
R.array.daltonizer_mode_keys);
final int[] daltonizerValues = mResources.getIntArray(
R.array.daltonizer_type_values);
final int daltonizerValueCount = daltonizerValues.length;
for (int i = 0; i < daltonizerValueCount; i++) {
mAccessibilityDaltonizerKeyToValueMap.put(daltonizerKeys[i], daltonizerValues[i]);
}
}
return mAccessibilityDaltonizerKeyToValueMap;
}
private void putSecureString(String name, String value) {
Settings.Secure.putString(mContentResolver, name, value);
}
private void handlePreferenceChange(String value) {
putSecureString(TYPE, value);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = (RadioButtonPreference)
screen.findPreference(getPreferenceKey());
mPreference.setOnClickListener(this);
mPreference.setAppendixVisibility(View.GONE);
updateState(mPreference);
}
@Override
public void onRadioButtonClicked(RadioButtonPreference preference) {
final int value = getDaltonizerValueToKeyMap().get(mPreferenceKey);
handlePreferenceChange(String.valueOf(value));
if (mOnChangeListener != null) {
mOnChangeListener.onCheckedChanged(mPreference);
}
}
private int getAccessibilityDaltonizerValue() {
final int daltonizerValue = getSecureAccessibilityDaltonizerValue(mContentResolver,
TYPE);
return daltonizerValue;
}
protected void updatePreferenceCheckedState(int value) {
if (mAccessibilityDaltonizerValue == value) {
mPreference.setChecked(true);
}
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
mAccessibilityDaltonizerValue = getAccessibilityDaltonizerValue();
// reset RadioButton
mPreference.setChecked(false);
final int preferenceValue = getDaltonizerValueToKeyMap().get(mPreference.getKey());
updatePreferenceCheckedState(preferenceValue);
}
/** Listener interface handles checked event. */
public interface OnChangeListener {
/** A hook that is called when preference checked. */
void onCheckedChanged(Preference preference);
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
import com.android.settings.core.TogglePreferenceController;
public class DisableAnimationsPreferenceController extends TogglePreferenceController {
@VisibleForTesting
static final String ANIMATION_ON_VALUE = "1";
@VisibleForTesting
static final String ANIMATION_OFF_VALUE = "0";
// Settings that should be changed when toggling animations
@VisibleForTesting
static final String[] TOGGLE_ANIMATION_TARGETS = {
Settings.Global.WINDOW_ANIMATION_SCALE, Settings.Global.TRANSITION_ANIMATION_SCALE,
Settings.Global.ANIMATOR_DURATION_SCALE
};
public DisableAnimationsPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public boolean isChecked() {
boolean allAnimationsDisabled = true;
for (String animationSetting : TOGGLE_ANIMATION_TARGETS) {
if (!TextUtils.equals(
Settings.Global.getString(mContext.getContentResolver(), animationSetting),
ANIMATION_OFF_VALUE)) {
allAnimationsDisabled = false;
break;
}
}
return allAnimationsDisabled;
}
@Override
public boolean setChecked(boolean isChecked) {
final String newAnimationValue = isChecked ? ANIMATION_OFF_VALUE : ANIMATION_ON_VALUE;
boolean allAnimationSet = true;
for (String animationPreference : TOGGLE_ANIMATION_TARGETS) {
allAnimationSet &= Settings.Global.putString(mContext.getContentResolver(),
animationPreference, newAnimationValue);
}
return allAnimationSet;
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.view.View;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreference;
/**
* A switch preference that has a divider below and above. Used for Accessibility Settings use
* service.
*/
public final class DividerSwitchPreference extends SwitchPreference {
private Boolean mDividerAllowedAbove;
private Boolean mDividerAllowBelow;
private int mSwitchVisibility;
public DividerSwitchPreference(Context context) {
super(context);
mDividerAllowedAbove = true;
mDividerAllowBelow = true;
mSwitchVisibility = View.VISIBLE;
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
holder.setDividerAllowedAbove(mDividerAllowedAbove);
holder.setDividerAllowedBelow(mDividerAllowBelow);
final View switchView = holder.itemView.findViewById(android.R.id.widget_frame);
if (switchView != null) {
switchView.setVisibility(mSwitchVisibility);
}
}
/**
* Sets divider whether to show in preference above.
*
* @param allowed true will be drawn on above this item
*/
public void setDividerAllowedAbove(boolean allowed) {
if (mDividerAllowedAbove != allowed) {
mDividerAllowedAbove = allowed;
notifyChanged();
}
}
/**
* Sets divider whether to show in preference below.
*
* @param allowed true will be drawn on below this item
*/
public void setDividerAllowedBelow(boolean allowed) {
if (mDividerAllowedAbove != allowed) {
mDividerAllowBelow = allowed;
notifyChanged();
}
}
/**
* Sets the visibility state of Settings view.
*
* @param visibility one of {@link View#VISIBLE}, {@link View#INVISIBLE}, or {@link View#GONE}.
*/
public void setSwitchVisibility(@View.Visibility int visibility) {
if (mSwitchVisibility != visibility) {
mSwitchVisibility = visibility;
notifyChanged();
}
}
}

View File

@@ -37,8 +37,7 @@ public class HearingAidDialogFragment extends InstrumentedDialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.accessibility_hearingaid_pair_instructions_first_message)
.setMessage(R.string.accessibility_hearingaid_pair_instructions_second_message)
.setMessage(R.string.accessibility_hearingaid_pair_instructions_message)
.setPositiveButton(R.string.accessibility_hearingaid_instruction_continue_button,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {

View File

@@ -0,0 +1,46 @@
/*
* 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.provider.Settings;
import com.android.settings.core.TogglePreferenceController;
public class HighTextContrastPreferenceController extends TogglePreferenceController {
public HighTextContrastPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public boolean isChecked() {
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, 0) == 1;
}
@Override
public boolean setChecked(boolean isChecked) {
return Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, (isChecked ? 1 : 0));
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.text.Html;
import android.text.TextUtils;
import android.widget.TextView;
import androidx.preference.PreferenceViewHolder;
/**
* A custom {@link android.widget.TextView} preference that shows html text with a custom tag
* filter.
*/
public final class HtmlTextPreference extends StaticTextPreference {
private int mFlag = Html.FROM_HTML_MODE_COMPACT;
private Html.ImageGetter mImageGetter;
private Html.TagHandler mTagHandler;
HtmlTextPreference(Context context) {
super(context);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final TextView summaryView = holder.itemView.findViewById(android.R.id.summary);
if (summaryView != null && !TextUtils.isEmpty(getSummary())) {
summaryView.setText(
Html.fromHtml(getSummary().toString(), mFlag, mImageGetter, mTagHandler));
}
}
/**
* Sets the flag to which text format to be applied.
*
* @param flag to indicate that html text format
*/
public void setFlag(int flag) {
if (flag != mFlag) {
mFlag = flag;
notifyChanged();
}
}
/**
* Sets image getter and help to load corresponding images when parsing.
*
* @param imageGetter to load image by image tag content
*/
public void setImageGetter(Html.ImageGetter imageGetter) {
if (imageGetter != null && !imageGetter.equals(mImageGetter)) {
mImageGetter = imageGetter;
notifyChanged();
}
}
/**
* Sets tag handler to handle the unsupported tags.
*
* @param tagHandler the handler for unhandled tags
*/
public void setTagHandler(Html.TagHandler tagHandler) {
if (tagHandler != null && !tagHandler.equals(mTagHandler)) {
mTagHandler = tagHandler;
notifyChanged();
}
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.accessibilityservice.AccessibilityServiceInfo;
import android.content.DialogInterface;
import android.view.View;
import com.android.settings.R;
import com.android.settingslib.accessibility.AccessibilityUtils;
/**
* Fragment that does not have toggle bar to turn on service to use.
*
* <p>The child {@link ToggleAccessibilityServicePreferenceFragment} shows the actual UI for
* providing basic accessibility service setup.
*
* <p>For accessibility services that target SDK > Q, and
* {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
*/
public class InvisibleToggleAccessibilityServicePreferenceFragment extends
ToggleAccessibilityServicePreferenceFragment implements ShortcutPreference.OnClickCallback {
@Override
protected void onInstallSwitchPreferenceToggleSwitch() {
super.onInstallSwitchPreferenceToggleSwitch();
mToggleServiceDividerSwitchPreference.setVisible(false);
}
/**
* {@inheritDoc}
*
* Enables accessibility service only when user had allowed permission. Disables
* accessibility service when shortcutPreference is unchecked.
*/
@Override
public void onToggleClicked(ShortcutPreference preference) {
super.onToggleClicked(preference);
boolean enabled = getArguments().getBoolean(AccessibilitySettings.EXTRA_CHECKED)
&& preference.isChecked();
AccessibilityUtils.setAccessibilityServiceState(getContext(), mComponentName, enabled);
}
/**
* {@inheritDoc}
*
* Enables accessibility service when user clicks permission allow button.
*/
@Override
void onDialogButtonFromShortcutToggleClicked(View view) {
super.onDialogButtonFromShortcutToggleClicked(view);
if (view.getId() == R.id.permission_enable_allow_button) {
AccessibilityUtils.setAccessibilityServiceState(getContext(), mComponentName,
true);
}
}
/**
* {@inheritDoc}
*
* Enables accessibility service when shortcutPreference is checked.
*/
@Override
protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
super.callOnAlertDialogCheckboxClicked(dialog, which);
final boolean enabled = mShortcutPreference.isChecked();
AccessibilityUtils.setAccessibilityServiceState(getContext(), mComponentName, enabled);
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.provider.Settings;
import androidx.annotation.VisibleForTesting;
import com.android.settings.core.TogglePreferenceController;
public class LargePointerIconPreferenceController extends TogglePreferenceController {
@VisibleForTesting
static final int ON = 1;
@VisibleForTesting
static final int OFF = 0;
public LargePointerIconPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public boolean isChecked() {
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON, OFF) != OFF;
}
@Override
public boolean setChecked(boolean isChecked) {
return Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON, isChecked ? ON : OFF);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
}

View File

@@ -0,0 +1,177 @@
/*
* 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 static com.android.settings.accessibility.AccessibilityStatsLogUtils.logAccessibilityServiceEnabled;
import android.accessibilityservice.AccessibilityShortcutInfo;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
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 androidx.annotation.Nullable;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import java.util.List;
/** Fragment for providing open activity button. */
public class LaunchAccessibilityActivityPreferenceFragment extends
ToggleFeaturePreferenceFragment {
private static final String TAG = "LaunchA11yActivity";
private static final String EMPTY_STRING = "";
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mToggleServiceDividerSwitchPreference.setSwitchVisibility(View.GONE);
}
@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;
});
}
@Override
protected void onProcessArguments(Bundle arguments) {
super.onProcessArguments(arguments);
mComponentName = arguments.getParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME);
final ActivityInfo info = getAccessibilityShortcutInfo().getActivityInfo();
mPackageName = info.loadLabel(getPackageManager()).toString();
// 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();
// Settings html description.
mHtmlDescription = arguments.getCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION);
// Settings title and intent.
final String settingsTitle = arguments.getString(
AccessibilitySettings.EXTRA_SETTINGS_TITLE);
mSettingsIntent = TextUtils.isEmpty(settingsTitle) ? null : getSettingsIntent(arguments);
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(),
mComponentName);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Do not call super. We don't want to see the "Help & feedback" option on this page so as
// not to confuse users who think they might be able to send feedback about a specific
// 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<AccessibilityShortcutInfo> infos = AccessibilityManager.getInstance(
getPrefContext()).getInstalledAccessibilityShortcutListAsUser(getPrefContext(),
UserHandle.myUserId());
for (int i = 0, count = infos.size(); i < count; i++) {
AccessibilityShortcutInfo shortcutInfo = infos.get(i);
ActivityInfo activityInfo = shortcutInfo.getActivityInfo();
if (mComponentName.getPackageName().equals(activityInfo.packageName)
&& mComponentName.getClassName().equals(activityInfo.name)) {
return shortcutInfo;
}
}
return null;
}
private void launchShortcutTargetActivity(int displayId, ComponentName name) {
final Intent intent = new Intent();
final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
intent.setComponent(name);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
final int userId = UserHandle.myUserId();
getPrefContext().startActivityAsUser(intent, bundle, UserHandle.of(userId));
} catch (ActivityNotFoundException ignore) {
// ignore the exception
Log.w(TAG, "Target activity not found.");
}
}
@Nullable
private Intent getSettingsIntent(Bundle arguments) {
final String settingsComponentName = arguments.getString(
AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME);
if (TextUtils.isEmpty(settingsComponentName)) {
return null;
}
final Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
ComponentName.unflattenFromString(settingsComponentName));
if (getPackageManager().queryIntentActivities(settingsIntent, /* flags= */ 0).isEmpty()) {
return null;
}
return settingsIntent;
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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 androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.internal.view.RotationPolicy;
import com.android.internal.view.RotationPolicy.RotationPolicyListener;
import com.android.settings.core.TogglePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
public class LockScreenRotationPreferenceController extends TogglePreferenceController implements
LifecycleObserver, OnStart, OnStop {
private Preference mPreference;
private RotationPolicyListener mRotationPolicyListener;
public LockScreenRotationPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
/**
* Returns true if rotation lock is enabled.
*/
@Override
public boolean isChecked() {
return !RotationPolicy.isRotationLocked(mContext);
}
/**
* Enables or disables screen rotation lock from Accessibility settings. If rotation is locked
* for accessibility, the toggle in Display settings is hidden to avoid confusion.
*/
@Override
public boolean setChecked(boolean isChecked) {
RotationPolicy.setRotationLockForAccessibility(mContext, !isChecked);
return true;
}
@Override
public int getAvailabilityStatus() {
return RotationPolicy.isRotationSupported(mContext) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
public void onStop() {
if (mRotationPolicyListener != null) {
RotationPolicy.unregisterRotationPolicyListener(mContext, mRotationPolicyListener);
}
}
@Override
public void onStart() {
if (mRotationPolicyListener == null) {
mRotationPolicyListener = new RotationPolicyListener() {
@Override
public void onChange() {
if (mPreference != null) {
updateState(mPreference);
}
}
};
}
RotationPolicy.registerRotationPolicyListener(mContext, mRotationPolicyListener);
}
@Override
public void displayPreference(PreferenceScreen screen) {
mPreference = screen.findPreference(getPreferenceKey());
super.displayPreference(screen);
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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;
}
}

View File

@@ -54,6 +54,7 @@ public class MagnificationGesturesPreferenceController extends TogglePreferenceC
populateMagnificationGesturesPreferenceExtras(extras, mContext);
extras.putBoolean(AccessibilitySettings.EXTRA_CHECKED, isChecked());
extras.putBoolean(AccessibilitySettings.EXTRA_LAUNCHED_FROM_SUW, mIsFromSUW);
return true;
}
return false;
}
@@ -69,6 +70,11 @@ public class MagnificationGesturesPreferenceController extends TogglePreferenceC
"screen_magnification_gestures_preference_screen");
}
@Override
public boolean isPublicSlice() {
return true;
}
@Override
public CharSequence getSummary() {
int resId = 0;
@@ -87,8 +93,8 @@ 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.putInt(AccessibilitySettings.EXTRA_SUMMARY_RES,
R.string.accessibility_screen_magnification_summary);
extras.putCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION,
context.getText(R.string.accessibility_screen_magnification_summary));
extras.putInt(AccessibilitySettings.EXTRA_VIDEO_RAW_RESOURCE_ID,
R.raw.accessibility_screen_magnification);
}

View File

@@ -0,0 +1,41 @@
/*
* 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 com.android.settings.core.BasePreferenceController;
/** Controller that shows the magnification area mode summary. */
public class MagnificationModePreferenceController extends BasePreferenceController {
public MagnificationModePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public CharSequence getSummary() {
return MagnificationSettingsFragment.getMagnificationCapabilitiesSummary(
mContext);
}
}

View File

@@ -55,10 +55,11 @@ public class MagnificationNavbarPreferenceController extends TogglePreferenceCon
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
extras.putInt(AccessibilitySettings.EXTRA_TITLE_RES,
R.string.accessibility_screen_magnification_navbar_title);
extras.putInt(AccessibilitySettings.EXTRA_SUMMARY_RES,
R.string.accessibility_screen_magnification_navbar_summary);
extras.putCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION,
mContext.getText(R.string.accessibility_screen_magnification_navbar_summary));
extras.putBoolean(AccessibilitySettings.EXTRA_CHECKED, isChecked());
extras.putBoolean(AccessibilitySettings.EXTRA_LAUNCHED_FROM_SUW, mIsFromSUW);
return true;
}
return false;
}
@@ -76,6 +77,11 @@ public class MagnificationNavbarPreferenceController extends TogglePreferenceCon
"screen_magnification_navbar_preference_screen");
}
@Override
public boolean isPublicSlice() {
return true;
}
@Override
public CharSequence getSummary() {
int resId = 0;

View File

@@ -0,0 +1,58 @@
/*
* 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.os.Bundle;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.core.BasePreferenceController;
public class MagnificationPreferenceController extends BasePreferenceController {
private Preference mPreference;
public MagnificationPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public CharSequence getSummary() {
return ToggleScreenMagnificationPreferenceFragment.getServiceSummary(mContext);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
configureMagnificationPreferenceIfNeeded();
}
private void configureMagnificationPreferenceIfNeeded() {
mPreference.setFragment(ToggleScreenMagnificationPreferenceFragment.class.getName());
final Bundle extras = mPreference.getExtras();
MagnificationGesturesPreferenceController
.populateMagnificationGesturesPreferenceExtras(extras, mContext);
}
}

View File

@@ -16,8 +16,6 @@
package com.android.settings.accessibility;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
@@ -25,9 +23,10 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.provider.SearchIndexableResource;
import android.provider.Settings;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.VisibleForTesting;
@@ -36,20 +35,15 @@ import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settings.search.actionbar.SearchMenuController;
import com.android.settings.support.actionbar.HelpResourceProvider;
import com.android.settingslib.search.SearchIndexable;
import java.util.Arrays;
import java.util.List;
@SearchIndexable
/** Settings fragment containing magnification preference. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public final class MagnificationPreferenceFragment extends DashboardFragment {
@VisibleForTesting
static final int ON = 1;
@VisibleForTesting
static final int OFF = 0;
@VisibleForTesting static final int ON = 1;
@VisibleForTesting static final int OFF = 0;
private static final String TAG = "MagnificationPreferenceFragment";
@@ -101,14 +95,19 @@ public final class MagnificationPreferenceFragment extends DashboardFragment {
// If invoked from SUW, redirect to fragment instrumented for Vision Settings metrics
preference.setFragment(
ToggleScreenMagnificationPreferenceFragmentForSetupWizard.class.getName());
Bundle args = preference.getExtras();
// Copy from AccessibilitySettingsForSetupWizardActivity, hide search and help menu
args.putInt(HelpResourceProvider.HELP_URI_RESOURCE_KEY, 0);
args.putBoolean(SearchMenuController.NEED_SEARCH_ICON_IN_ACTION_BAR, false);
}
return super.onPreferenceTreeClick(preference);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (mLaunchedFromSuw) {
// Do not call super. We don't want to see the "Help & feedback" on OOBE page.
} else {
super.onCreateOptionsMenu(menu, inflater);
}
}
static CharSequence getConfigurationWarningStringForSecureSettingsKey(String key,
Context context) {
if (!Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED.equals(key)) {
@@ -121,7 +120,7 @@ public final class MagnificationPreferenceFragment extends DashboardFragment {
final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
Context.ACCESSIBILITY_SERVICE);
final String assignedId = Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT);
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
if (!TextUtils.isEmpty(assignedId) && !MAGNIFICATION_COMPONENT_ID.equals(assignedId)) {
final ComponentName assignedComponentName = ComponentName.unflattenFromString(
assignedId);
@@ -134,7 +133,7 @@ public final class MagnificationPreferenceFragment extends DashboardFragment {
if (info.getComponentName().equals(assignedComponentName)) {
final CharSequence assignedServiceName = info.getResolveInfo().loadLabel(
context.getPackageManager());
final int messageId = isGestureNavigateEnabled(context)
final int messageId = AccessibilityUtil.isGestureNavigateEnabled(context)
? R.string.accessibility_screen_magnification_gesture_navigation_warning
: R.string.accessibility_screen_magnification_navbar_configuration_warning;
return context.getString(messageId, assignedServiceName);
@@ -161,25 +160,12 @@ public final class MagnificationPreferenceFragment extends DashboardFragment {
return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
}
private static boolean isGestureNavigateEnabled(Context context) {
return context.getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode)
== NAV_BAR_MODE_GESTURAL;
}
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.accessibility_magnification_settings;
return Arrays.asList(sir);
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_magnification_settings) {
@Override
protected boolean isPageSearchEnabled(Context context) {
return isApplicable(context.getResources());
}
};
}
}

View File

@@ -0,0 +1,234 @@
/*
* 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.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.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 {
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 static int getSecureIntValue(Context context, String key, int defaultValue) {
return Settings.Secure.getIntForUser(
context.getContentResolver(),
key, defaultValue, context.getContentResolver().getUserId());
}
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_MAGNIFICATION_SETTINGS;
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(EXTRA_CAPABILITY, mCapabilities);
super.onSaveInstanceState(outState);
}
@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());
}
}
@Override
public int getDialogMetricsCategory(int dialogId) {
switch (dialogId) {
case DIALOG_MAGNIFICATION_CAPABILITY:
return SettingsEnums.DIALOG_MAGNIFICATION_CAPABILITY;
default:
return 0;
}
}
@Override
protected String getLogTag() {
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;
}
@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;
}
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);
}

View File

@@ -0,0 +1,47 @@
/*
* 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.os.UserHandle;
import android.provider.Settings;
import com.android.settings.core.TogglePreferenceController;
public class MasterMonoPreferenceController extends TogglePreferenceController {
public MasterMonoPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public boolean isChecked() {
return Settings.System.getIntForUser(mContext.getContentResolver(),
Settings.System.MASTER_MONO, 0 /* default */, UserHandle.USER_CURRENT) == 1;
}
@Override
public boolean setChecked(boolean isChecked) {
return Settings.System.putIntForUser(mContext.getContentResolver(),
Settings.System.MASTER_MONO, isChecked ? 1 : 0, UserHandle.USER_CURRENT);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ListView;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
/** Preference that easier preview by matching name to color. */
public class PaletteListPreference extends Preference {
private ListView mListView;
private ViewTreeObserver.OnPreDrawListener mPreDrawListener;
/**
* Constructs a new PaletteListPreference with the given context's theme and the supplied
* attribute set.
*
* @param context The Context this is associated with, through which it can access the current
* theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
*/
public PaletteListPreference(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* Constructs a new PaletteListPreference with the given context's theme, the supplied
* attribute set, and default style attribute.
*
* @param context The Context this is associated with, through which it can access the
* current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
* @param defStyleAttr An attribute in the current theme that contains a reference to a style
* resource that supplies default
* values for the view. Can be 0 to not look for
* defaults.
*/
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);
}
}
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;
}
};
}
}

View File

@@ -0,0 +1,301 @@
/*
* 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.
*
* <p>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.
*
* <p>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<ColorAttributes> 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.
*
* <p>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<ColorAttributes> 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<String> 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<ColorAttributes> 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<ColorAttributes> 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;
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.provider.Settings;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import com.android.settings.Utils;
import com.android.settings.core.TogglePreferenceController;
public class PowerButtonEndsCallPreferenceController extends TogglePreferenceController {
public PowerButtonEndsCallPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public boolean isChecked() {
final int incallPowerBehavior = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT);
return incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP;
}
@Override
public boolean setChecked(boolean isChecked) {
return Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
(isChecked ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP
: Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF));
}
@Override
public int getAvailabilityStatus() {
return !KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
|| !Utils.isVoiceCapable(mContext) ? UNSUPPORTED_ON_DEVICE : AVAILABLE;
}
}

View File

@@ -52,7 +52,7 @@ public class PresetPreference extends ListDialogPreference {
final View previewViewport = view.findViewById(R.id.preview_viewport);
final SubtitleView previewText = (SubtitleView) view.findViewById(R.id.preview);
final int value = getValueAt(index);
CaptionPropertiesFragment.applyCaptionProperties(
CaptionAppearanceFragment.applyCaptionProperties(
mCaptioningManager, previewText, previewViewport, value);
final float density = getContext().getResources().getDisplayMetrics().density;

View File

@@ -11,7 +11,7 @@
* 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.accessibility;
@@ -55,6 +55,7 @@ public class RTTSettingPreferenceController extends BasePreferenceController {
mPackageManager = context.getPackageManager();
mTelecomManager = context.getSystemService(TelecomManager.class);
mRTTIntent = new Intent(context.getString(R.string.config_rtt_setting_intent_action));
}
@Override

View File

@@ -0,0 +1,96 @@
/*
* 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.provider.Settings;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import java.util.HashMap;
import java.util.Map;
public class SelectLongPressTimeoutPreferenceController extends BasePreferenceController implements
Preference.OnPreferenceChangeListener {
private final Map<String, String> mLongPressTimeoutValueToTitleMap;
private int mLongPressTimeoutDefault;
public SelectLongPressTimeoutPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mLongPressTimeoutValueToTitleMap = new HashMap<>();
initLongPressTimeoutValueToTitleMap();
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public boolean onPreferenceChange(Preference preference, Object object) {
if (!(preference instanceof ListPreference)) {
return false;
}
final ListPreference listPreference = (ListPreference) preference;
final int newValue = Integer.parseInt((String) object);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.LONG_PRESS_TIMEOUT, newValue);
updateState(listPreference);
return true;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
if (!(preference instanceof ListPreference)) {
return;
}
final ListPreference listPreference = (ListPreference) preference;
listPreference.setValue(getLongPressTimeoutValue());
}
@Override
public CharSequence getSummary() {
return mLongPressTimeoutValueToTitleMap.get(getLongPressTimeoutValue());
}
private String getLongPressTimeoutValue() {
final int longPressTimeout = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.LONG_PRESS_TIMEOUT, mLongPressTimeoutDefault);
return String.valueOf(longPressTimeout);
}
private void initLongPressTimeoutValueToTitleMap() {
if (mLongPressTimeoutValueToTitleMap.size() == 0) {
final String[] timeoutValues = mContext.getResources().getStringArray(
R.array.long_press_timeout_selector_values);
mLongPressTimeoutDefault = Integer.parseInt(timeoutValues[0]);
final String[] timeoutTitles = mContext.getResources().getStringArray(
R.array.long_press_timeout_selector_titles);
final int timeoutValueCount = timeoutValues.length;
for (int i = 0; i < timeoutValueCount; i++) {
mLongPressTimeoutValueToTitleMap.put(timeoutValues[i], timeoutTitles[i]);
}
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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<String> 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<String> data) {
SharedPreferences.Editor editor = getSharedPreferences(context, ACCESSIBILITY_PERF).edit();
editor.remove(USER_SHORTCUT_TYPE).apply();
editor.putStringSet(USER_SHORTCUT_TYPE, data).apply();
}
}

View File

@@ -0,0 +1,169 @@
/*
* 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.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Switch;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
/**
* Preference that can enable accessibility shortcut and let users choose which shortcut type they
* prefer to use.
*/
public class ShortcutPreference extends Preference {
/**
* Interface definition for a callback to be invoked when the toggle or settings has been
* clicked.
*/
public interface OnClickCallback {
/**
* Called when the settings view has been clicked.
*
* @param preference The clicked preference
*/
void onSettingsClicked(ShortcutPreference preference);
/**
* Called when the toggle in ShortcutPreference has been clicked.
*
* @param preference The clicked preference
*/
void onToggleClicked(ShortcutPreference preference);
}
private OnClickCallback mClickCallback = null;
private boolean mChecked = false;
private boolean mSettingsEditable = true;
ShortcutPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setLayoutResource(R.layout.accessibility_shortcut_secondary_action);
setWidgetLayoutResource(R.layout.preference_widget_master_switch);
setIconSpaceReserved(true);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final TypedValue outValue = new TypedValue();
getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground,
outValue, true);
final LinearLayout mainFrame = holder.itemView.findViewById(R.id.main_frame);
if (mainFrame != null) {
mainFrame.setOnClickListener(view -> callOnSettingsClicked());
mainFrame.setClickable(mSettingsEditable);
mainFrame.setFocusable(mSettingsEditable);
mainFrame.setBackgroundResource(
mSettingsEditable ? outValue.resourceId : /* Remove background */ 0);
}
Switch switchWidget = holder.itemView.findViewById(R.id.switchWidget);
if (switchWidget != null) {
// Consumes move events to ignore drag actions.
switchWidget.setOnTouchListener((v, event) -> {
return event.getActionMasked() == MotionEvent.ACTION_MOVE;
});
switchWidget.setContentDescription(
getContext().getText(R.string.accessibility_shortcut_settings));
switchWidget.setChecked(mChecked);
switchWidget.setOnClickListener(view -> callOnToggleClicked());
switchWidget.setClickable(mSettingsEditable);
switchWidget.setFocusable(mSettingsEditable);
switchWidget.setBackgroundResource(
mSettingsEditable ? outValue.resourceId : /* Remove background */ 0);
}
final View divider = holder.itemView.findViewById(R.id.divider);
if (divider != null) {
divider.setVisibility(mSettingsEditable ? View.VISIBLE : View.GONE);
}
holder.itemView.setOnClickListener(view -> callOnToggleClicked());
holder.itemView.setClickable(!mSettingsEditable);
holder.itemView.setFocusable(!mSettingsEditable);
}
/**
* Sets the shortcut toggle according to checked value.
*
* @param checked the state value of shortcut toggle
*/
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
notifyChanged();
}
}
/**
* Gets the checked value of shortcut toggle.
*
* @return the checked value of shortcut toggle
*/
public boolean isChecked() {
return mChecked;
}
/**
* Sets the editable state of Settings view. If the view cannot edited, it makes the settings
* and toggle be not touchable. The main ui handles touch event directly by {@link #onClick}.
*/
public void setSettingsEditable(boolean enabled) {
if (mSettingsEditable != enabled) {
mSettingsEditable = enabled;
notifyChanged();
}
}
public boolean isSettingsEditable() {
return mSettingsEditable;
}
/**
* Sets the callback to be invoked when this preference is clicked by the user.
*
* @param callback the callback to be invoked
*/
public void setOnClickCallback(OnClickCallback callback) {
mClickCallback = callback;
}
private void callOnSettingsClicked() {
if (mClickCallback != null) {
mClickCallback.onSettingsClicked(this);
}
}
private void callOnToggleClicked() {
setChecked(!mChecked);
if (mClickCallback != null) {
mClickCallback.onToggleClicked(this);
}
}
}

View File

@@ -1,267 +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.accessibility;
import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.view.accessibility.AccessibilityManager;
import android.view.View;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.accessibility.AccessibilityShortcutController.ToggleableFrameworkFeatureInfo;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.widget.RadioButtonPickerFragment;
import com.android.settings.widget.RadioButtonPreference;
import com.android.settingslib.accessibility.AccessibilityUtils;
import com.android.settingslib.widget.CandidateInfo;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Fragment for picking accessibility shortcut service
*/
public class ShortcutServicePickerFragment extends RadioButtonPickerFragment {
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_shortcut_service_settings;
}
@Override
protected List<? extends CandidateInfo> getCandidates() {
final Context context = getContext();
final AccessibilityManager accessibilityManager = context
.getSystemService(AccessibilityManager.class);
final List<AccessibilityServiceInfo> installedServices =
accessibilityManager.getInstalledAccessibilityServiceList();
final int numInstalledServices = installedServices.size();
final List<CandidateInfo> candidates = new ArrayList<>(numInstalledServices);
Map<ComponentName, ToggleableFrameworkFeatureInfo> frameworkFeatureInfoMap =
AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
for (ComponentName componentName : frameworkFeatureInfoMap.keySet()) {
final int iconId;
if (componentName.equals(COLOR_INVERSION_COMPONENT_NAME)) {
iconId = R.drawable.ic_color_inversion;
} else if (componentName.equals(DALTONIZER_COMPONENT_NAME)) {
iconId = R.drawable.ic_daltonizer;
} else {
iconId = R.drawable.empty_icon;
}
candidates.add(new FrameworkCandidateInfo(frameworkFeatureInfoMap.get(componentName),
iconId, componentName.flattenToString()));
}
for (int i = 0; i < numInstalledServices; i++) {
candidates.add(new ServiceCandidateInfo(installedServices.get(i)));
}
return candidates;
}
@Override
protected String getDefaultKey() {
String shortcutServiceString = AccessibilityUtils
.getShortcutTargetServiceComponentNameString(getContext(), UserHandle.myUserId());
if (shortcutServiceString != null) {
ComponentName shortcutName = ComponentName.unflattenFromString(shortcutServiceString);
if (shortcutName != null) {
return shortcutName.flattenToString();
}
}
return null;
}
@Override
protected boolean setDefaultKey(String key) {
Settings.Secure.putString(getContext().getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, key);
return true;
}
@Override
public void onRadioButtonClicked(RadioButtonPreference selected) {
final String selectedKey = selected.getKey();
if (TextUtils.isEmpty(selectedKey)) {
super.onRadioButtonClicked(selected);
} else {
final ComponentName selectedComponent = ComponentName.unflattenFromString(selectedKey);
if (AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
.containsKey(selectedComponent)) {
// This is a framework feature. It doesn't need to be confirmed.
onRadioButtonConfirmed(selectedKey);
} else {
final FragmentActivity activity = getActivity();
if (activity != null) {
ConfirmationDialogFragment.newInstance(this, selectedKey)
.show(activity.getSupportFragmentManager(),
ConfirmationDialogFragment.TAG);
}
}
}
}
private void onServiceConfirmed(String serviceKey) {
onRadioButtonConfirmed(serviceKey);
}
public static class ConfirmationDialogFragment extends InstrumentedDialogFragment
implements View.OnClickListener {
private static final String EXTRA_KEY = "extra_key";
private static final String TAG = "ConfirmationDialogFragment";
private IBinder mToken;
public static ConfirmationDialogFragment newInstance(ShortcutServicePickerFragment parent,
String key) {
final ConfirmationDialogFragment fragment = new ConfirmationDialogFragment();
final Bundle argument = new Bundle();
argument.putString(EXTRA_KEY, key);
fragment.setArguments(argument);
fragment.setTargetFragment(parent, 0);
fragment.mToken = new Binder();
return fragment;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle bundle = getArguments();
final String key = bundle.getString(EXTRA_KEY);
final ComponentName serviceComponentName = ComponentName.unflattenFromString(key);
final AccessibilityManager accessibilityManager = getActivity()
.getSystemService(AccessibilityManager.class);
AccessibilityServiceInfo info = accessibilityManager
.getInstalledServiceInfoWithComponentName(serviceComponentName);
return AccessibilityServiceWarning.createCapabilitiesDialog(getActivity(), info, this);
}
@Override
public void onClick(View view) {
final Fragment fragment = getTargetFragment();
if ((view.getId() == R.id.permission_enable_allow_button)
&& (fragment instanceof ShortcutServicePickerFragment)) {
final Bundle bundle = getArguments();
((ShortcutServicePickerFragment) fragment).onServiceConfirmed(
bundle.getString(EXTRA_KEY));
}
dismiss();
}
}
private class FrameworkCandidateInfo extends CandidateInfo {
final ToggleableFrameworkFeatureInfo mToggleableFrameworkFeatureInfo;
final int mIconResId;
final String mKey;
public FrameworkCandidateInfo(
ToggleableFrameworkFeatureInfo frameworkFeatureInfo, int iconResId, String key) {
super(true /* enabled */);
mToggleableFrameworkFeatureInfo = frameworkFeatureInfo;
mIconResId = iconResId;
mKey = key;
}
@Override
public CharSequence loadLabel() {
return mToggleableFrameworkFeatureInfo.getLabel(getContext());
}
@Override
public Drawable loadIcon() {
return getContext().getDrawable(mIconResId);
}
@Override
public String getKey() {
return mKey;
}
}
private class ServiceCandidateInfo extends CandidateInfo {
final AccessibilityServiceInfo mServiceInfo;
public ServiceCandidateInfo(AccessibilityServiceInfo serviceInfo) {
super(true /* enabled */);
mServiceInfo = serviceInfo;
}
@Override
public CharSequence loadLabel() {
final PackageManager pmw = getContext().getPackageManager();
final CharSequence label =
mServiceInfo.getResolveInfo().serviceInfo.loadLabel(pmw);
if (label != null) {
return label;
}
final ComponentName componentName = mServiceInfo.getComponentName();
if (componentName != null) {
try {
final ApplicationInfo appInfo = pmw.getApplicationInfoAsUser(
componentName.getPackageName(), 0, UserHandle.myUserId());
return appInfo.loadLabel(pmw);
} catch (PackageManager.NameNotFoundException e) {
return null;
}
}
return null;
}
@Override
public Drawable loadIcon() {
final ResolveInfo resolveInfo = mServiceInfo.getResolveInfo();
return (resolveInfo.getIconResource() == 0)
? getContext().getDrawable(R.drawable.ic_accessibility_generic)
: resolveInfo.loadIcon(getContext().getPackageManager());
}
@Override
public String getKey() {
return mServiceInfo.getComponentName().flattenToString();
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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 androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
/**
* A custom {@link android.widget.TextView} preference that removes the title and summary
* restriction from platform {@link Preference} implementation and the icon location is kept as
* gravity top instead of center.
*/
public class StaticTextPreference extends Preference {
StaticTextPreference(Context context) {
super(context);
setLayoutResource(R.layout.preference_static_text);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
}
}

View File

@@ -16,7 +16,7 @@
package com.android.settings.accessibility;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static com.android.settings.accessibility.AccessibilityStatsLogUtils.logAccessibilityServiceEnabled;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
@@ -24,7 +24,8 @@ import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContentResolver;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -40,25 +41,27 @@ import android.view.MenuInflater;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
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.ToggleSwitch;
import com.android.settings.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
import com.android.settingslib.accessibility.AccessibilityUtils;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class ToggleAccessibilityServicePreferenceFragment
extends ToggleFeaturePreferenceFragment implements View.OnClickListener {
private static final int DIALOG_ID_ENABLE_WARNING = 1;
private static final int DIALOG_ID_DISABLE_WARNING = 2;
private static final int DIALOG_ID_LAUNCH_ACCESSIBILITY_TUTORIAL = 3;
/** Fragment for providing toggle bar and basic accessibility service setup. */
public class ToggleAccessibilityServicePreferenceFragment extends
ToggleFeaturePreferenceFragment {
public static final int ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION = 1;
private LockPatternUtils mLockPatternUtils;
private AtomicBoolean mIsDialogShown = new AtomicBoolean(/* initialValue= */ false);
private static final String EMPTY_STRING = "";
private final SettingsContentObserver mSettingsContentObserver =
new SettingsContentObserver(new Handler()) {
@@ -68,8 +71,6 @@ public class ToggleAccessibilityServicePreferenceFragment
}
};
private ComponentName mComponentName;
private Dialog mDialog;
@Override
@@ -87,38 +88,33 @@ public class ToggleAccessibilityServicePreferenceFragment
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLockPatternUtils = new LockPatternUtils(getActivity());
mLockPatternUtils = new LockPatternUtils(getPrefContext());
}
@Override
public void onResume() {
mSettingsContentObserver.register(getContentResolver());
updateSwitchBarToggleSwitch();
super.onResume();
}
@Override
public void onPause() {
mSettingsContentObserver.unregister(getContentResolver());
super.onPause();
updateSwitchBarToggleSwitch();
mSettingsContentObserver.register(getContentResolver());
}
@Override
public void onPreferenceToggled(String preferenceKey, boolean enabled) {
ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
AccessibilityUtils.setAccessibilityServiceState(getActivity(), toggledService, enabled);
logAccessibilityServiceEnabled(toggledService, enabled);
AccessibilityUtils.setAccessibilityServiceState(getPrefContext(), toggledService, enabled);
}
// IMPORTANT: Refresh the info since there are dynamically changing
// capabilities. For
// example, before JellyBean MR2 the user was granting the explore by touch
// one.
private AccessibilityServiceInfo getAccessibilityServiceInfo() {
List<AccessibilityServiceInfo> serviceInfos = AccessibilityManager.getInstance(
getActivity()).getInstalledAccessibilityServiceList();
final int serviceInfoCount = serviceInfos.size();
for (int i = 0; i < serviceInfoCount; i++) {
AccessibilityServiceInfo serviceInfo = serviceInfos.get(i);
AccessibilityServiceInfo getAccessibilityServiceInfo() {
final List<AccessibilityServiceInfo> infos = AccessibilityManager.getInstance(
getPrefContext()).getInstalledAccessibilityServiceList();
for (int i = 0, count = infos.size(); i < count; i++) {
AccessibilityServiceInfo serviceInfo = infos.get(i);
ResolveInfo resolveInfo = serviceInfo.getResolveInfo();
if (mComponentName.getPackageName().equals(resolveInfo.serviceInfo.packageName)
&& mComponentName.getClassName().equals(resolveInfo.serviceInfo.name)) {
@@ -131,36 +127,48 @@ public class ToggleAccessibilityServicePreferenceFragment
@Override
public Dialog onCreateDialog(int dialogId) {
switch (dialogId) {
case DIALOG_ID_ENABLE_WARNING: {
case DialogEnums.ENABLE_WARNING_FROM_TOGGLE: {
final AccessibilityServiceInfo info = getAccessibilityServiceInfo();
if (info == null) {
return null;
}
mDialog = AccessibilityServiceWarning
.createCapabilitiesDialog(getActivity(), info, this);
.createCapabilitiesDialog(getPrefContext(), info,
this::onDialogButtonFromEnableToggleClicked);
break;
}
case DIALOG_ID_DISABLE_WARNING: {
AccessibilityServiceInfo info = getAccessibilityServiceInfo();
case DialogEnums.ENABLE_WARNING_FROM_SHORTCUT_TOGGLE: {
final AccessibilityServiceInfo info = getAccessibilityServiceInfo();
if (info == null) {
return null;
}
mDialog = AccessibilityServiceWarning
.createDisableDialog(getActivity(), info, this);
.createCapabilitiesDialog(getPrefContext(), info,
this::onDialogButtonFromShortcutToggleClicked);
break;
}
case DIALOG_ID_LAUNCH_ACCESSIBILITY_TUTORIAL: {
if (isGestureNavigateEnabled()) {
mDialog = AccessibilityGestureNavigationTutorial
.showGestureNavigationTutorialDialog(getActivity());
} else {
mDialog = AccessibilityGestureNavigationTutorial
.showAccessibilityButtonTutorialDialog(getActivity());
case DialogEnums.ENABLE_WARNING_FROM_SHORTCUT: {
final AccessibilityServiceInfo info = getAccessibilityServiceInfo();
if (info == null) {
return null;
}
mDialog = AccessibilityServiceWarning
.createCapabilitiesDialog(getPrefContext(), info,
this::onDialogButtonFromShortcutClicked);
break;
}
case DialogEnums.DISABLE_WARNING_FROM_TOGGLE: {
final AccessibilityServiceInfo info = getAccessibilityServiceInfo();
if (info == null) {
return null;
}
mDialog = AccessibilityServiceWarning
.createDisableDialog(getPrefContext(), info,
this::onDialogButtonFromDisableToggleClicked);
break;
}
default: {
throw new IllegalArgumentException();
mDialog = super.onCreateDialog(dialogId);
}
}
return mDialog;
@@ -168,17 +176,42 @@ public class ToggleAccessibilityServicePreferenceFragment
@Override
public int getDialogMetricsCategory(int dialogId) {
if (dialogId == DIALOG_ID_ENABLE_WARNING) {
return SettingsEnums.DIALOG_ACCESSIBILITY_SERVICE_ENABLE;
} else {
return SettingsEnums.DIALOG_ACCESSIBILITY_SERVICE_DISABLE;
switch (dialogId) {
case DialogEnums.ENABLE_WARNING_FROM_TOGGLE:
case DialogEnums.ENABLE_WARNING_FROM_SHORTCUT:
case DialogEnums.ENABLE_WARNING_FROM_SHORTCUT_TOGGLE:
return SettingsEnums.DIALOG_ACCESSIBILITY_SERVICE_ENABLE;
case DialogEnums.DISABLE_WARNING_FROM_TOGGLE:
return SettingsEnums.DIALOG_ACCESSIBILITY_SERVICE_DISABLE;
case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
return SettingsEnums.DIALOG_ACCESSIBILITY_TUTORIAL;
default:
return super.getDialogMetricsCategory(dialogId);
}
}
@Override
int getUserShortcutTypes() {
return AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(),
mComponentName);
}
@Override
protected void updateToggleServiceTitle(SwitchPreference switchPreference) {
final AccessibilityServiceInfo info = getAccessibilityServiceInfo();
final String switchBarText = (info == null) ? "" :
getString(R.string.accessibility_service_master_switch_title,
info.getResolveInfo().loadLabel(getPackageManager()));
switchPreference.setTitle(switchBarText);
}
private void updateSwitchBarToggleSwitch() {
final boolean checked = AccessibilityUtils.getEnabledServicesFromSettings(getActivity())
final boolean checked = AccessibilityUtils.getEnabledServicesFromSettings(getPrefContext())
.contains(mComponentName);
mSwitchBar.setCheckedInternal(checked);
if (mToggleServiceDividerSwitchPreference.isChecked() == checked) {
return;
}
mToggleServiceDividerSwitchPreference.setChecked(checked);
}
/**
@@ -195,7 +228,7 @@ public class ToggleAccessibilityServicePreferenceFragment
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION) {
if (resultCode == Activity.RESULT_OK) {
handleConfirmServiceEnabled(true);
handleConfirmServiceEnabled(/* confirmed= */ true);
// The user confirmed that they accept weaker encryption when
// enabling the accessibility service, so change encryption.
// Since we came here asynchronously, check encryption again.
@@ -205,46 +238,14 @@ public class ToggleAccessibilityServicePreferenceFragment
Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, 0);
}
} else {
handleConfirmServiceEnabled(false);
handleConfirmServiceEnabled(/* confirmed= */ false);
}
}
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.permission_enable_allow_button) {
if (isFullDiskEncrypted()) {
String title = createConfirmCredentialReasonMessage();
Intent intent = ConfirmDeviceCredentialActivity.createIntent(title, null);
startActivityForResult(intent,
ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION);
} else {
handleConfirmServiceEnabled(true);
if (isServiceSupportAccessibilityButton()) {
showDialog(DIALOG_ID_LAUNCH_ACCESSIBILITY_TUTORIAL);
}
}
} else if (view.getId() == R.id.permission_enable_deny_button) {
handleConfirmServiceEnabled(false);
} else if (view.getId() == R.id.permission_disable_stop_button) {
handleConfirmServiceEnabled(false);
} else if (view.getId() == R.id.permission_disable_cancel_button) {
handleConfirmServiceEnabled(true);
} else {
throw new IllegalArgumentException();
}
mDialog.dismiss();
}
private boolean isGestureNavigateEnabled() {
return getContext().getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode)
== NAV_BAR_MODE_GESTURAL;
}
private boolean isServiceSupportAccessibilityButton() {
final AccessibilityManager ams = (AccessibilityManager) getContext().getSystemService(
Context.ACCESSIBILITY_SERVICE);
final AccessibilityManager ams = getPrefContext().getSystemService(
AccessibilityManager.class);
final List<AccessibilityServiceInfo> services = ams.getInstalledAccessibilityServiceList();
for (AccessibilityServiceInfo info : services) {
@@ -261,7 +262,7 @@ public class ToggleAccessibilityServicePreferenceFragment
}
private void handleConfirmServiceEnabled(boolean confirmed) {
mSwitchBar.setCheckedInternal(confirmed);
mToggleServiceDividerSwitchPreference.setChecked(confirmed);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, confirmed);
onPreferenceToggled(mPreferenceKey, confirmed);
}
@@ -284,23 +285,37 @@ public class ToggleAccessibilityServicePreferenceFragment
}
@Override
protected void onInstallSwitchBarToggleSwitch() {
super.onInstallSwitchBarToggleSwitch();
mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
@Override
public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
if (checked) {
mSwitchBar.setCheckedInternal(false);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, false);
showDialog(DIALOG_ID_ENABLE_WARNING);
} else {
mSwitchBar.setCheckedInternal(true);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, true);
showDialog(DIALOG_ID_DISABLE_WARNING);
}
return true;
protected void onInstallSwitchPreferenceToggleSwitch() {
super.onInstallSwitchPreferenceToggleSwitch();
mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener(this::onPreferenceClick);
}
@Override
public void onToggleClicked(ShortcutPreference preference) {
final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
if (preference.isChecked()) {
if (!mToggleServiceDividerSwitchPreference.isChecked()) {
preference.setChecked(false);
showPopupDialog(DialogEnums.ENABLE_WARNING_FROM_SHORTCUT_TOGGLE);
} else {
AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), shortcutTypes,
mComponentName);
showPopupDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
}
});
} else {
AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), shortcutTypes,
mComponentName);
}
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
@Override
public void onSettingsClicked(ShortcutPreference preference) {
super.onSettingsClicked(preference);
final boolean isServiceOnOrShortcutAdded = mShortcutPreference.isChecked()
|| mToggleServiceDividerSwitchPreference.isChecked();
showPopupDialog(isServiceOnOrShortcutAdded ? DialogEnums.EDIT_SHORTCUT
: DialogEnums.ENABLE_WARNING_FROM_SHORTCUT);
}
@Override
@@ -321,5 +336,149 @@ public class ToggleAccessibilityServicePreferenceFragment
}
mComponentName = arguments.getParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME);
// 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();
// Get Accessibility service name.
mPackageName = getAccessibilityServiceInfo().getResolveInfo().loadLabel(
getPackageManager());
}
private void onDialogButtonFromDisableToggleClicked(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
handleConfirmServiceEnabled(/* confirmed= */ false);
break;
case DialogInterface.BUTTON_NEGATIVE:
handleConfirmServiceEnabled(/* confirmed= */ true);
break;
default:
throw new IllegalArgumentException("Unexpected button identifier");
}
}
private void onDialogButtonFromEnableToggleClicked(View view) {
final int viewId = view.getId();
if (viewId == R.id.permission_enable_allow_button) {
onAllowButtonFromEnableToggleClicked();
} else if (viewId == R.id.permission_enable_deny_button) {
onDenyButtonFromEnableToggleClicked();
} else {
throw new IllegalArgumentException("Unexpected view id");
}
}
private void onAllowButtonFromEnableToggleClicked() {
if (isFullDiskEncrypted()) {
final String title = createConfirmCredentialReasonMessage();
final Intent intent = ConfirmDeviceCredentialActivity.createIntent(title, /* details= */
null);
startActivityForResult(intent,
ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION);
} else {
handleConfirmServiceEnabled(/* confirmed= */ true);
if (isServiceSupportAccessibilityButton()) {
mIsDialogShown.set(false);
showPopupDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
}
}
mDialog.dismiss();
}
private void onDenyButtonFromEnableToggleClicked() {
handleConfirmServiceEnabled(/* confirmed= */ false);
mDialog.dismiss();
}
void onDialogButtonFromShortcutToggleClicked(View view) {
final int viewId = view.getId();
if (viewId == R.id.permission_enable_allow_button) {
onAllowButtonFromShortcutToggleClicked();
} else if (viewId == R.id.permission_enable_deny_button) {
onDenyButtonFromShortcutToggleClicked();
} else {
throw new IllegalArgumentException("Unexpected view id");
}
}
private void onAllowButtonFromShortcutToggleClicked() {
mShortcutPreference.setChecked(true);
final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), shortcutTypes, mComponentName);
mIsDialogShown.set(false);
showPopupDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
mDialog.dismiss();
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
private void onDenyButtonFromShortcutToggleClicked() {
mShortcutPreference.setChecked(false);
mDialog.dismiss();
}
void onDialogButtonFromShortcutClicked(View view) {
final int viewId = view.getId();
if (viewId == R.id.permission_enable_allow_button) {
onAllowButtonFromShortcutClicked();
} else if (viewId == R.id.permission_enable_deny_button) {
onDenyButtonFromShortcutClicked();
} else {
throw new IllegalArgumentException("Unexpected view id");
}
}
private void onAllowButtonFromShortcutClicked() {
mIsDialogShown.set(false);
showPopupDialog(DialogEnums.EDIT_SHORTCUT);
mDialog.dismiss();
}
private void onDenyButtonFromShortcutClicked() {
mDialog.dismiss();
}
private boolean onPreferenceClick(Preference preference) {
boolean checked = ((DividerSwitchPreference) preference).isChecked();
if (checked) {
mToggleServiceDividerSwitchPreference.setChecked(false);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED,
/* disableService */ false);
if (!mShortcutPreference.isChecked()) {
showPopupDialog(DialogEnums.ENABLE_WARNING_FROM_TOGGLE);
} else {
handleConfirmServiceEnabled(/* confirmed= */ true);
if (isServiceSupportAccessibilityButton()) {
showPopupDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
}
}
} else {
mToggleServiceDividerSwitchPreference.setChecked(true);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED,
/* enableService */ true);
showDialog(DialogEnums.DISABLE_WARNING_FROM_TOGGLE);
}
return true;
}
private void showPopupDialog(int dialogId) {
if (mIsDialogShown.compareAndSet(/* expect= */ false, /* update= */ true)) {
showDialog(dialogId);
setOnDismissListener(
dialog -> mIsDialogShown.compareAndSet(/* expect= */ true, /* update= */
false));
}
}
}

View File

@@ -0,0 +1,217 @@
/*
* 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 static android.content.Context.MODE_PRIVATE;
import static com.android.settings.accessibility.ToggleAutoclickPreferenceController.KEY_DELAY_MODE;
import static com.android.settings.accessibility.ToggleAutoclickPreferenceFragment.Quantity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.provider.Settings;
import android.view.accessibility.AccessibilityManager;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
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 com.android.settingslib.widget.LayoutPreference;
/**
* Controller class that controls accessibility autoclick seekbar settings.
*/
public class ToggleAutoclickCustomSeekbarController extends BasePreferenceController
implements LifecycleObserver, OnResume, OnPause,
SharedPreferences.OnSharedPreferenceChangeListener {
private static final String CONTROL_AUTOCLICK_DELAY_SECURE =
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY;
private static final String KEY_CUSTOM_DELAY_VALUE = "custom_delay_value";
// Min allowed autoclick delay value.
static final int MIN_AUTOCLICK_DELAY_MS = 200;
// Max allowed autoclick delay value.
static final int MAX_AUTOCLICK_DELAY_MS = 1000;
// Allowed autoclick delay values are discrete.
// This is the difference between two allowed values.
private static final int AUTOCLICK_DELAY_STEP = 100;
private final SharedPreferences mSharedPreferences;
private final ContentResolver mContentResolver;
private ImageView mShorter;
private ImageView mLonger;
private SeekBar mSeekBar;
private TextView mDelayLabel;
private final SeekBar.OnSeekBarChangeListener mSeekBarChangeListener =
new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
updateCustomDelayValue(seekBarProgressToDelay(progress));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// Nothing to do.
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// Nothing to do.
}
};
public ToggleAutoclickCustomSeekbarController(Context context, String preferenceKey) {
super(context, preferenceKey);
mSharedPreferences = context.getSharedPreferences(context.getPackageName(), MODE_PRIVATE);
mContentResolver = context.getContentResolver();
}
public ToggleAutoclickCustomSeekbarController(Context context, Lifecycle lifecycle,
String preferenceKey) {
this(context, preferenceKey);
if (lifecycle != null) {
lifecycle.addObserver(this);
}
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void onResume() {
if (mSharedPreferences != null) {
mSharedPreferences.registerOnSharedPreferenceChangeListener(this);
}
}
@Override
public void onPause() {
if (mSharedPreferences != null) {
mSharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
}
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final LayoutPreference preference = screen.findPreference(getPreferenceKey());
if (isAvailable()) {
int delayMillis = getSharedPreferenceForDelayValue();
// Initialize seek bar preference. Sets seek bar size to the number of possible delay
// values.
mSeekBar = preference.findViewById(R.id.autoclick_delay);
mSeekBar.setMax(delayToSeekBarProgress(MAX_AUTOCLICK_DELAY_MS));
mSeekBar.setProgress(delayToSeekBarProgress(delayMillis));
mSeekBar.setOnSeekBarChangeListener(mSeekBarChangeListener);
mDelayLabel = preference.findViewById(R.id.current_label);
mDelayLabel.setText(delayTimeToString(delayMillis));
mShorter = preference.findViewById(R.id.shorter);
mShorter.setOnClickListener(v -> {
minusDelayByImageView();
});
mLonger = preference.findViewById(R.id.longer);
mLonger.setOnClickListener(v -> {
plusDelayByImageView();
});
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (KEY_DELAY_MODE.equals(key)) {
final int delayMillis = getSharedPreferenceForDelayValue();
updateCustomDelayValue(delayMillis);
}
}
/** Converts seek bar preference progress value to autoclick delay associated with it. */
private int seekBarProgressToDelay(int progress) {
return progress * AUTOCLICK_DELAY_STEP + MIN_AUTOCLICK_DELAY_MS;
}
/**
* Converts autoclick delay value to seek bar preference progress values that represents said
* delay.
*/
private int delayToSeekBarProgress(int delayMillis) {
return (delayMillis - MIN_AUTOCLICK_DELAY_MS) / AUTOCLICK_DELAY_STEP;
}
private int getSharedPreferenceForDelayValue() {
final int delayMillis = Settings.Secure.getInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
AccessibilityManager.AUTOCLICK_DELAY_DEFAULT);
return mSharedPreferences.getInt(KEY_CUSTOM_DELAY_VALUE, delayMillis);
}
private void putSecureInt(String name, int value) {
Settings.Secure.putInt(mContentResolver, name, value);
}
private void updateCustomDelayValue(int delayMillis) {
putSecureInt(CONTROL_AUTOCLICK_DELAY_SECURE, delayMillis);
mSharedPreferences.edit().putInt(KEY_CUSTOM_DELAY_VALUE, delayMillis).apply();
mSeekBar.setProgress(delayToSeekBarProgress(delayMillis));
mDelayLabel.setText(delayTimeToString(delayMillis));
}
private void minusDelayByImageView() {
final int delayMillis = getSharedPreferenceForDelayValue();
if (delayMillis > MIN_AUTOCLICK_DELAY_MS) {
updateCustomDelayValue(delayMillis - AUTOCLICK_DELAY_STEP);
}
}
private void plusDelayByImageView() {
final int delayMillis = getSharedPreferenceForDelayValue();
if (delayMillis < MAX_AUTOCLICK_DELAY_MS) {
updateCustomDelayValue(delayMillis + AUTOCLICK_DELAY_STEP);
}
}
private CharSequence delayTimeToString(int delayMillis) {
final int quantity = (delayMillis == 1000) ? Quantity.ONE : Quantity.FEW;
final float delaySecond = (float) delayMillis / 1000;
// Only show integer when delay time is 1.
final String decimalFormat = (delaySecond == 1) ? "%.0f" : "%.1f";
return mContext.getResources().getQuantityString(
R.plurals.accessibilty_autoclick_delay_unit_second,
quantity, String.format(decimalFormat, delaySecond));
}
}

View File

@@ -0,0 +1,192 @@
/*
* 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 static android.content.Context.MODE_PRIVATE;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.provider.Settings;
import android.util.ArrayMap;
import androidx.lifecycle.LifecycleObserver;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.widget.LayoutPreference;
import com.android.settingslib.widget.RadioButtonPreference;
import java.util.Map;
/**
* Controller class that controls accessibility autoclick settings.
*/
public class ToggleAutoclickPreferenceController extends BasePreferenceController implements
LifecycleObserver, RadioButtonPreference.OnClickListener, PreferenceControllerMixin {
private static final String CONTROL_AUTOCLICK_DELAY_SECURE =
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY;
private 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;
// Pair the preference key and autoclick mode value.
private final Map<String, Integer> mAccessibilityAutoclickKeyToValueMap = new ArrayMap<>();
private SharedPreferences mSharedPreferences;
private final ContentResolver mContentResolver;
private final Resources mResources;
private OnChangeListener mOnChangeListener;
private RadioButtonPreference mDelayModePref;
/**
* Seek bar preference for autoclick delay value. The seek bar has values between 0 and
* number of possible discrete autoclick delay values. These will have to be converted to actual
* delay values before saving them in settings.
*/
private LayoutPreference mSeekBerPreference;
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();
}
public ToggleAutoclickPreferenceController(Context context, Lifecycle lifecycle,
String preferenceKey) {
super(context, preferenceKey);
mSharedPreferences = context.getSharedPreferences(context.getPackageName(), MODE_PRIVATE);
mContentResolver = context.getContentResolver();
mResources = context.getResources();
setAutoclickModeToKeyMap();
if (lifecycle != null) {
lifecycle.addObserver(this);
}
}
public void setOnChangeListener(OnChangeListener listener) {
mOnChangeListener = listener;
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mDelayModePref = (RadioButtonPreference)
screen.findPreference(getPreferenceKey());
mDelayModePref.setOnClickListener(this);
mSeekBerPreference = (LayoutPreference) screen.findPreference(KEY_AUTOCLICK_CUSTOM_SEEKBAR);
updateState((Preference) mDelayModePref);
}
@Override
public void onRadioButtonClicked(RadioButtonPreference preference) {
final int value = mAccessibilityAutoclickKeyToValueMap.get(mPreferenceKey);
handleRadioButtonPreferenceChange(value);
if (mOnChangeListener != null) {
mOnChangeListener.onCheckedChanged(mDelayModePref);
}
}
private void updatePreferenceCheckedState(int mode) {
if (mCurrentUiAutoClickMode == mode) {
mDelayModePref.setChecked(true);
}
}
private void updatePreferenceVisibleState(int mode) {
mSeekBerPreference.setVisible(mCurrentUiAutoClickMode == mode);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
final boolean enabled = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, 0) == 1;
mCurrentUiAutoClickMode =
enabled ? getSharedPreferenceForAutoClickMode() : AUTOCLICK_OFF_MODE;
// Reset RadioButton.
mDelayModePref.setChecked(false);
final int mode = mAccessibilityAutoclickKeyToValueMap.get(mDelayModePref.getKey());
updatePreferenceCheckedState(mode);
updatePreferenceVisibleState(mode);
}
/** Listener interface handles checked event. */
public interface OnChangeListener {
/**
* A hook that is called when preference checked.
*/
void onCheckedChanged(Preference preference);
}
private void setAutoclickModeToKeyMap() {
final String[] autoclickKeys = mResources.getStringArray(
R.array.accessibility_autoclick_control_selector_keys);
final int[] autoclickValues = mResources.getIntArray(
R.array.accessibility_autoclick_selector_values);
final int autoclickValueCount = autoclickValues.length;
for (int i = 0; i < autoclickValueCount; i++) {
mAccessibilityAutoclickKeyToValueMap.put(autoclickKeys[i], autoclickValues[i]);
}
}
private void handleRadioButtonPreferenceChange(int preference) {
putSecureInt(Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
(preference != AUTOCLICK_OFF_MODE) ? /* enabled */ 1 : /* disabled */ 0);
mSharedPreferences.edit().putInt(KEY_DELAY_MODE, preference).apply();
if (preference != AUTOCLICK_CUSTOM_MODE) {
putSecureInt(CONTROL_AUTOCLICK_DELAY_SECURE, preference);
}
}
private void putSecureInt(String name, int value) {
Settings.Secure.putInt(mContentResolver, name, value);
}
private int getSharedPreferenceForAutoClickMode() {
return mSharedPreferences.getInt(KEY_DELAY_MODE, AUTOCLICK_CUSTOM_MODE);
}
}

View File

@@ -16,24 +16,26 @@
package com.android.settings.accessibility;
import static com.android.settings.accessibility.ToggleAutoclickCustomSeekbarController.MAX_AUTOCLICK_DELAY_MS;
import static com.android.settings.accessibility.ToggleAutoclickCustomSeekbarController.MIN_AUTOCLICK_DELAY_MS;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.provider.SearchIndexableResource;
import android.provider.Settings;
import android.view.accessibility.AccessibilityManager;
import android.widget.Switch;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settings.widget.SeekBarPreference;
import com.android.settings.widget.SwitchBar;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.List;
@@ -41,70 +43,66 @@ import java.util.List;
* Fragment for preference screen for settings related to Automatically click after mouse stops
* feature.
*/
@SearchIndexable
public class ToggleAutoclickPreferenceFragment extends ToggleFeaturePreferenceFragment
implements SwitchBar.OnSwitchChangeListener, Preference.OnPreferenceChangeListener {
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class ToggleAutoclickPreferenceFragment extends DashboardFragment
implements ToggleAutoclickPreferenceController.OnChangeListener {
/** Min allowed autoclick delay value. */
private static final int MIN_AUTOCLICK_DELAY = 200;
/** Max allowed autoclick delay value. */
private static final int MAX_AUTOCLICK_DELAY = 1000;
/**
* Allowed autoclick delay values are discrete. This is the difference between two allowed
* values.
*/
private static final int AUTOCLICK_DELAY_STEP = 100;
private static final String TAG = "AutoclickPrefFragment";
private static final List<AbstractPreferenceController> sControllers = new ArrayList<>();
@Retention(SOURCE)
@IntDef({
Quantity.OTHER,
Quantity.ONE,
Quantity.FEW
})
@interface Quantity {
int OTHER = 0;
int ONE = 1;
int FEW = 3;
}
/**
* Resource ids from which autoclick preference summaries should be derived. The strings have
* placeholder for integer delay value.
*/
private static final int[] mAutoclickPreferenceSummaries = {
R.plurals.accessibilty_autoclick_preference_subtitle_extremely_short_delay,
R.plurals.accessibilty_autoclick_preference_subtitle_very_short_delay,
private static final int[] AUTOCLICK_PREFERENCE_SUMMARIES = {
R.plurals.accessibilty_autoclick_preference_subtitle_short_delay,
R.plurals.accessibilty_autoclick_preference_subtitle_long_delay,
R.plurals.accessibilty_autoclick_preference_subtitle_very_long_delay
R.plurals.accessibilty_autoclick_preference_subtitle_medium_delay,
R.plurals.accessibilty_autoclick_preference_subtitle_long_delay
};
/**
* Seek bar preference for autoclick delay value. The seek bar has values between 0 and
* number of possible discrete autoclick delay values. These will have to be converted to actual
* delay values before saving them in settings.
*/
private SeekBarPreference mDelay;
/**
* Gets string that should be used as a autoclick preference summary for provided autoclick
* delay.
*
* @param resources Resources from which string should be retrieved.
* @param delay Delay for whose value summary should be retrieved.
* @param delayMillis Delay for whose value summary should be retrieved.
*/
static CharSequence getAutoclickPreferenceSummary(Resources resources, int delay) {
int summaryIndex = getAutoclickPreferenceSummaryIndex(delay);
return resources.getQuantityString(
mAutoclickPreferenceSummaries[summaryIndex], delay, delay);
static CharSequence getAutoclickPreferenceSummary(Resources resources, int delayMillis) {
final int summaryIndex = getAutoclickPreferenceSummaryIndex(delayMillis);
final int quantity = (delayMillis == 1000) ? Quantity.ONE : Quantity.FEW;
final float delaySecond = (float) delayMillis / 1000;
// Only show integer when delay time is 1.
final String decimalFormat = (delaySecond == 1) ? "%.0f" : "%.1f";
return resources.getQuantityString(AUTOCLICK_PREFERENCE_SUMMARIES[summaryIndex],
quantity, String.format(decimalFormat, delaySecond));
}
/**
* Finds index of the summary that should be used for the provided autoclick delay.
*/
private static int getAutoclickPreferenceSummaryIndex(int delay) {
if (delay <= MIN_AUTOCLICK_DELAY) {
if (delay <= MIN_AUTOCLICK_DELAY_MS) {
return 0;
}
if (delay >= MAX_AUTOCLICK_DELAY) {
return mAutoclickPreferenceSummaries.length - 1;
if (delay >= MAX_AUTOCLICK_DELAY_MS) {
return AUTOCLICK_PREFERENCE_SUMMARIES.length - 1;
}
int rangeSize = (MAX_AUTOCLICK_DELAY - MIN_AUTOCLICK_DELAY) /
(mAutoclickPreferenceSummaries.length - 1);
return (delay - MIN_AUTOCLICK_DELAY) / rangeSize;
}
@Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
Settings.Secure.putInt(getContentResolver(), preferenceKey, enabled ? 1 : 0);
mDelay.setEnabled(enabled);
int delayRange = MAX_AUTOCLICK_DELAY_MS - MIN_AUTOCLICK_DELAY_MS;
int rangeSize = (delayRange) / (AUTOCLICK_PREFERENCE_SUMMARIES.length - 1);
return (delay - MIN_AUTOCLICK_DELAY_MS) / rangeSize;
}
@Override
@@ -117,88 +115,68 @@ public class ToggleAutoclickPreferenceFragment extends ToggleFeaturePreferenceFr
return R.string.help_url_autoclick;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_autoclick_settings;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public void onResume() {
super.onResume();
int delay = Settings.Secure.getInt(
getContentResolver(), Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
AccessibilityManager.AUTOCLICK_DELAY_DEFAULT);
// Initialize seek bar preference. Sets seek bar size to the number of possible delay
// values.
mDelay = (SeekBarPreference) findPreference("autoclick_delay");
mDelay.setMax(delayToSeekBarProgress(MAX_AUTOCLICK_DELAY));
mDelay.setProgress(delayToSeekBarProgress(delay));
mDelay.setOnPreferenceChangeListener(this);
mFooterPreferenceMixin.createFooterPreference()
.setTitle(R.string.accessibility_autoclick_description);
for (AbstractPreferenceController controller : sControllers) {
((ToggleAutoclickPreferenceController) controller).setOnChangeListener(this);
}
}
@Override
protected void onInstallSwitchBarToggleSwitch() {
super.onInstallSwitchBarToggleSwitch();
public void onPause() {
super.onPause();
int value = Settings.Secure.getInt(getContentResolver(),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, 0);
mSwitchBar.setCheckedInternal(value == 1);
mSwitchBar.addOnSwitchChangeListener(this);
mDelay.setEnabled(value == 1);
for (AbstractPreferenceController controller : sControllers) {
((ToggleAutoclickPreferenceController) controller).setOnChangeListener(null);
}
}
@Override
protected void onRemoveSwitchBarToggleSwitch() {
super.onRemoveSwitchBarToggleSwitch();
mSwitchBar.removeOnSwitchChangeListener(this);
public void onCheckedChanged(Preference preference) {
for (AbstractPreferenceController controller : sControllers) {
controller.updateState(preference);
}
}
@Override
public void onSwitchChanged(Switch switchView, boolean isChecked) {
onPreferenceToggled(Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, isChecked);
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
return buildPreferenceControllers(context, getSettingsLifecycle());
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == mDelay && newValue instanceof Integer) {
Settings.Secure.putInt(getContentResolver(),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
seekBarProgressToDelay((int)newValue));
return true;
}
return false;
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
Lifecycle lifecycle) {
Resources resources = context.getResources();
String[] autoclickKeys = resources.getStringArray(
R.array.accessibility_autoclick_control_selector_keys);
final int length = autoclickKeys.length;
for (int i = 0; i < length; i++) {
sControllers.add(new ToggleAutoclickPreferenceController(
context, lifecycle, autoclickKeys[i]));
}
return sControllers;
}
/**
* Converts seek bar preference progress value to autoclick delay associated with it.
*/
private int seekBarProgressToDelay(int progress) {
return progress * AUTOCLICK_DELAY_STEP + MIN_AUTOCLICK_DELAY;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_autoclick_settings) {
/**
* Converts autoclick delay value to seek bar preference progress values that represents said
* delay.
*/
private int delayToSeekBarProgress(int delay) {
return (delay - MIN_AUTOCLICK_DELAY) / AUTOCLICK_DELAY_STEP;
}
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
boolean enabled) {
final ArrayList<SearchIndexableResource> result = new ArrayList<>();
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.accessibility_autoclick_settings;
result.add(sir);
return result;
public List<AbstractPreferenceController> createPreferenceControllers(
Context context) {
return buildPreferenceControllers(context, null);
}
};
}

View File

@@ -0,0 +1,144 @@
/*
* 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.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
import static com.android.settings.accessibility.AccessibilityStatsLogUtils.logAccessibilityServiceEnabled;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import android.app.settings.SettingsEnums;
import android.content.ContentResolver;
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.SwitchPreference;
import com.android.settings.R;
import java.util.ArrayList;
import java.util.List;
/** Settings page for color inversion. */
public class ToggleColorInversionPreferenceFragment extends ToggleFeaturePreferenceFragment {
private static final String ENABLED = Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED;
private final Handler mHandler = new Handler();
private SettingsContentObserver mSettingsContentObserver;
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_COLOR_INVERSION_SETTINGS;
}
@Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
logAccessibilityServiceEnabled(mComponentName, enabled);
Settings.Secure.putInt(getContentResolver(), ENABLED, enabled ? ON : OFF);
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_color_inversion_settings;
}
@Override
protected void onRemoveSwitchPreferenceToggleSwitch() {
super.onRemoveSwitchPreferenceToggleSwitch();
mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener(null);
}
@Override
protected void updateToggleServiceTitle(SwitchPreference 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) {
mComponentName = COLOR_INVERSION_COMPONENT_NAME;
mPackageName = getText(R.string.accessibility_display_inversion_preference_title);
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))
.build();
final List<String> enableServiceFeatureKeys = new ArrayList<>(/* initialCapacity= */ 1);
enableServiceFeatureKeys.add(ENABLED);
mSettingsContentObserver = new SettingsContentObserver(mHandler, enableServiceFeatureKeys) {
@Override
public void onChange(boolean selfChange, Uri uri) {
updateSwitchBarToggleSwitch();
}
};
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onResume() {
super.onResume();
updateSwitchBarToggleSwitch();
mSettingsContentObserver.register(getContentResolver());
}
@Override
public void onPause() {
mSettingsContentObserver.unregister(getContentResolver());
super.onPause();
}
@Override
public int getHelpResource() {
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() {
final boolean checked = Settings.Secure.getInt(getContentResolver(), ENABLED, OFF) == ON;
if (mToggleServiceDividerSwitchPreference.isChecked() == checked) {
return;
}
mToggleServiceDividerSwitchPreference.setChecked(checked);
}
}

View File

@@ -16,35 +16,140 @@
package com.android.settings.accessibility;
import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
import static com.android.settings.accessibility.AccessibilityStatsLogUtils.logAccessibilityServiceEnabled;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.hardware.display.ColorDisplayManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.provider.SearchIndexableResource;
import android.os.Handler;
import android.provider.Settings;
import android.view.accessibility.AccessibilityManager;
import android.widget.Switch;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.preference.ListPreference;
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.search.Indexable;
import com.android.settings.widget.SwitchBar;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.List;
@SearchIndexable
public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceFragment
implements Preference.OnPreferenceChangeListener, SwitchBar.OnSwitchChangeListener {
private static final String ENABLED = Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED;
private static final String TYPE = Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER;
private static final int DEFAULT_TYPE = AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY;
/** Settings for daltonizer. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public final class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceFragment
implements DaltonizerRadioButtonPreferenceController.OnChangeListener {
private ListPreference mType;
private static final String ENABLED = Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED;
private static final String KEY_PREVIEW = "daltonizer_preview";
private static final String KEY_CATEGORY_MODE = "daltonizer_mode_category";
private static final List<AbstractPreferenceController> sControllers = new ArrayList<>();
private final Handler mHandler = new Handler();
private SettingsContentObserver mSettingsContentObserver;
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
Lifecycle lifecycle) {
if (sControllers.size() == 0) {
final Resources resources = context.getResources();
final String[] daltonizerKeys = resources.getStringArray(
R.array.daltonizer_mode_keys);
for (int i = 0; i < daltonizerKeys.length; i++) {
sControllers.add(new DaltonizerRadioButtonPreferenceController(
context, lifecycle, daltonizerKeys[i]));
}
}
return sControllers;
}
@Override
public void onCheckedChanged(Preference preference) {
for (AbstractPreferenceController controller : sControllers) {
controller.updateState(preference);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mComponentName = DALTONIZER_COMPONENT_NAME;
mPackageName = getText(R.string.accessibility_display_daltonizer_preference_title);
mHtmlDescription = getText(R.string.accessibility_display_daltonizer_preference_subtitle);
final List<String> enableServiceFeatureKeys = new ArrayList<>(/* initialCapacity= */ 1);
enableServiceFeatureKeys.add(ENABLED);
mSettingsContentObserver = new SettingsContentObserver(mHandler, enableServiceFeatureKeys) {
@Override
public void onChange(boolean selfChange, Uri uri) {
updateSwitchBarToggleSwitch();
}
};
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
updatePreferenceOrder();
}
/** Customizes the order by preference key. */
private List<String> getPreferenceOrderList() {
List<String> 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);
return lists;
}
private void updatePreferenceOrder() {
List<String> 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();
updateSwitchBarToggleSwitch();
mSettingsContentObserver.register(getContentResolver());
for (AbstractPreferenceController controller :
buildPreferenceControllers(getPrefContext(), getSettingsLifecycle())) {
((DaltonizerRadioButtonPreferenceController) controller).setOnChangeListener(this);
((DaltonizerRadioButtonPreferenceController) controller).displayPreference(
getPreferenceScreen());
}
}
@Override
public void onPause() {
mSettingsContentObserver.unregister(getContentResolver());
for (AbstractPreferenceController controller :
buildPreferenceControllers(getPrefContext(), getSettingsLifecycle())) {
((DaltonizerRadioButtonPreferenceController) controller).setOnChangeListener(null);
}
super.onPause();
}
@Override
public int getMetricsCategory() {
@@ -56,19 +161,6 @@ public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceF
return R.string.help_url_color_correction;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mType = (ListPreference) findPreference("type");
if (!ColorDisplayManager.isColorTransformAccelerated(getActivity())) {
mFooterPreferenceMixin.createFooterPreference().setTitle(
R.string.accessibility_display_daltonizer_preference_subtitle);
}
initPreferences();
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_daltonizer_settings;
@@ -76,70 +168,52 @@ public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceF
@Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
Settings.Secure.putInt(getContentResolver(), ENABLED, enabled ? 1 : 0);
logAccessibilityServiceEnabled(mComponentName, enabled);
Settings.Secure.putInt(getContentResolver(), ENABLED, enabled ? ON : OFF);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == mType) {
Settings.Secure.putInt(getContentResolver(), TYPE, Integer.parseInt((String) newValue));
preference.setSummary("%s");
protected void onRemoveSwitchPreferenceToggleSwitch() {
super.onRemoveSwitchPreferenceToggleSwitch();
mToggleServiceDividerSwitchPreference.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);
}
@Override
int getUserShortcutTypes() {
return AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(),
mComponentName);
}
private void updateSwitchBarToggleSwitch() {
final boolean checked = Settings.Secure.getInt(getContentResolver(), ENABLED, OFF) == ON;
if (mToggleServiceDividerSwitchPreference.isChecked() == checked) {
return;
}
return true;
mToggleServiceDividerSwitchPreference.setChecked(checked);
}
@Override
protected void onInstallSwitchBarToggleSwitch() {
super.onInstallSwitchBarToggleSwitch();
mSwitchBar.setCheckedInternal(
Settings.Secure.getInt(getContentResolver(), ENABLED, 0) == 1);
mSwitchBar.addOnSwitchChangeListener(this);
}
@Override
protected void onRemoveSwitchBarToggleSwitch() {
super.onRemoveSwitchBarToggleSwitch();
mSwitchBar.removeOnSwitchChangeListener(this);
}
@Override
protected void updateSwitchBarText(SwitchBar switchBar) {
switchBar.setSwitchBarText(R.string.accessibility_daltonizer_master_switch_title,
R.string.accessibility_daltonizer_master_switch_title);
}
private void initPreferences() {
final String value = Integer.toString(
Settings.Secure.getInt(getContentResolver(), TYPE, DEFAULT_TYPE));
mType.setValue(value);
mType.setOnPreferenceChangeListener(this);
final int index = mType.findIndexOfValue(value);
if (index < 0) {
// We're using a mode controlled by developer preferences.
mType.setSummary(getString(R.string.daltonizer_type_overridden,
getString(R.string.simulate_color_space)));
}
}
@Override
public void onSwitchChanged(Switch switchView, boolean isChecked) {
onPreferenceToggled(mPreferenceKey, isChecked);
}
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
boolean enabled) {
final ArrayList<SearchIndexableResource> result = new ArrayList<>();
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.accessibility_daltonizer_settings;
result.add(sir);
return result;
}
};
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_daltonizer_settings);
}

View File

@@ -16,60 +16,219 @@
package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.getScreenHeightPixels;
import static com.android.settings.accessibility.AccessibilityUtil.getScreenWidthPixels;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.icu.text.CaseMap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.Html;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
import android.widget.CheckBox;
import android.widget.ImageView;
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.AccessibilityUtil.UserShortcutType;
import com.android.settings.widget.SwitchBar;
import com.android.settings.widget.ToggleSwitch;
import com.android.settingslib.accessibility.AccessibilityUtils;
import com.android.settingslib.widget.FooterPreference;
public abstract class ToggleFeaturePreferenceFragment extends SettingsPreferenceFragment {
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;
protected SwitchBar mSwitchBar;
protected ToggleSwitch mToggleSwitch;
/**
* Base class for accessibility fragments with toggle, shortcut, some helper functions
* and dialog management.
*/
public abstract class ToggleFeaturePreferenceFragment extends SettingsPreferenceFragment
implements ShortcutPreference.OnClickCallback {
protected DividerSwitchPreference mToggleServiceDividerSwitchPreference;
protected ShortcutPreference mShortcutPreference;
protected Preference mSettingsPreference;
protected String mPreferenceKey;
protected CharSequence mSettingsTitle;
protected Intent mSettingsIntent;
// The mComponentName maybe null, such as Magnify
protected ComponentName mComponentName;
protected CharSequence mPackageName;
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";
private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
private static final String EXTRA_SHORTCUT_TYPE = "shortcut_type";
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
private int mUserShortcutTypes = UserShortcutType.EMPTY;
private CheckBox mSoftwareTypeCheckBox;
private CheckBox mHardwareTypeCheckBox;
private SettingsContentObserver mSettingsContentObserver;
// For html description of accessibility service, must follow the rule, such as
// <img src="R.drawable.fileName"/>, a11y settings will get the resources successfully.
private static final String IMG_PREFIX = "R.drawable.";
private ImageView mImageGetterCacheView;
private final Html.ImageGetter mImageGetter = (String str) -> {
if (str != null && str.startsWith(IMG_PREFIX)) {
final String fileName = str.substring(IMG_PREFIX.length());
return getDrawableFromUri(Uri.parse(
ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+ mComponentName.getPackageName() + "/" + DRAWABLE_FOLDER + "/"
+ fileName));
}
return null;
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupDefaultShortcutIfNecessary(getPrefContext());
final int resId = getPreferenceScreenResId();
if (resId <= 0) {
PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
getActivity());
getPrefContext());
setPreferenceScreen(preferenceScreen);
}
final List<String> 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) {
mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
removeDialog(DialogEnums.EDIT_SHORTCUT);
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
};
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
SettingsActivity activity = (SettingsActivity) getActivity();
mSwitchBar = activity.getSwitchBar();
updateSwitchBarText(mSwitchBar);
mToggleSwitch = mSwitchBar.getSwitch();
final SettingsActivity activity = (SettingsActivity) getActivity();
final SwitchBar switchBar = activity.getSwitchBar();
switchBar.hide();
// Need to be called as early as possible. Protected variables will be assigned here.
onProcessArguments(getArguments());
// Show the "Settings" menu as if it were a preference screen
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) {
PreferenceScreen preferenceScreen = getPreferenceScreen();
Preference settingsPref = new Preference(preferenceScreen.getContext());
settingsPref.setTitle(mSettingsTitle);
settingsPref.setIconSpaceReserved(true);
settingsPref.setIntent(mSettingsIntent);
preferenceScreen.addPreference(settingsPref);
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);
}
}
@@ -79,38 +238,153 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
installActionBarToggleSwitch();
}
@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) {
outState.putInt(EXTRA_SHORTCUT_TYPE, mUserShortcutTypesCache);
super.onSaveInstanceState(outState);
}
@Override
public Dialog onCreateDialog(int dialogId) {
Dialog dialog;
switch (dialogId) {
case DialogEnums.EDIT_SHORTCUT:
final CharSequence dialogTitle = getPrefContext().getString(
R.string.accessibility_shortcut_title, mPackageName);
dialog = AccessibilityEditDialogUtils.showEditShortcutDialog(
getPrefContext(), dialogTitle, this::callOnAlertDialogCheckboxClicked);
initializeDialogCheckBox(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;
}
}
/** 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 void onDestroyView() {
super.onDestroyView();
removeActionBarToggleSwitch();
}
protected void updateSwitchBarText(SwitchBar switchBar) {
// Implement this to provide meaningful text in switch bar
switchBar.setSwitchBarText(R.string.accessibility_service_master_switch_title,
R.string.accessibility_service_master_switch_title);
/**
* 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 abstract void onPreferenceToggled(String preferenceKey, boolean enabled);
protected void onInstallSwitchBarToggleSwitch() {
protected void onInstallSwitchPreferenceToggleSwitch() {
// Implement this to set a checked listener.
}
protected void onRemoveSwitchBarToggleSwitch() {
protected void onRemoveSwitchPreferenceToggleSwitch() {
// Implement this to reset a checked listener.
}
private void installActionBarToggleSwitch() {
mSwitchBar.show();
onInstallSwitchBarToggleSwitch();
onInstallSwitchPreferenceToggleSwitch();
}
private void removeActionBarToggleSwitch() {
mToggleSwitch.setOnBeforeCheckedChangeListener(null);
onRemoveSwitchBarToggleSwitch();
mSwitchBar.hide();
mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener(null);
onRemoveSwitchPreferenceToggleSwitch();
}
public void setTitle(String title) {
@@ -121,12 +395,6 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
// Key.
mPreferenceKey = arguments.getString(AccessibilitySettings.EXTRA_PREFERENCE_KEY);
// Enabled.
if (arguments.containsKey(AccessibilitySettings.EXTRA_CHECKED)) {
final boolean enabled = arguments.getBoolean(AccessibilitySettings.EXTRA_CHECKED);
mSwitchBar.setCheckedInternal(enabled);
}
// Title.
if (arguments.containsKey(AccessibilitySettings.EXTRA_RESOLVE_INFO)) {
ResolveInfo info = arguments.getParcelable(AccessibilitySettings.EXTRA_RESOLVE_INFO);
@@ -136,13 +404,345 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
}
// Summary.
if (arguments.containsKey(AccessibilitySettings.EXTRA_SUMMARY_RES)) {
final int summary = arguments.getInt(AccessibilitySettings.EXTRA_SUMMARY_RES);
mFooterPreferenceMixin.createFooterPreference().setTitle(summary);
} else if (arguments.containsKey(AccessibilitySettings.EXTRA_SUMMARY)) {
final CharSequence summary = arguments.getCharSequence(
AccessibilitySettings.EXTRA_SUMMARY);
mFooterPreferenceMixin.createFooterPreference().setTitle(summary);
if (arguments.containsKey(AccessibilitySettings.EXTRA_SUMMARY)) {
mDescription = arguments.getCharSequence(AccessibilitySettings.EXTRA_SUMMARY);
}
// Settings html description.
if (arguments.containsKey(AccessibilitySettings.EXTRA_HTML_DESCRIPTION)) {
mHtmlDescription = arguments.getCharSequence(
AccessibilitySettings.EXTRA_HTML_DESCRIPTION);
}
}
private Drawable getDrawableFromUri(Uri imageUri) {
if (mImageGetterCacheView == null) {
mImageGetterCacheView = new ImageView(getPrefContext());
}
mImageGetterCacheView.setAdjustViewBounds(true);
mImageGetterCacheView.setImageURI(imageUri);
if (mImageGetterCacheView.getDrawable() == null) {
return null;
}
final Drawable drawable =
mImageGetterCacheView.getDrawable().mutate().getConstantState().newDrawable();
mImageGetterCacheView.setImageURI(null);
final int imageWidth = drawable.getIntrinsicWidth();
final int imageHeight = drawable.getIntrinsicHeight();
final int screenHalfHeight = getScreenHeightPixels(getPrefContext()) / /* half */ 2;
if ((imageWidth > getScreenWidthPixels(getPrefContext()))
|| (imageHeight > screenHalfHeight)) {
return null;
}
drawable.setBounds(/* left= */0, /* top= */0, drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight());
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 String mComponentName;
private int mType;
AccessibilityUserShortcutType(String componentName, int type) {
this.mComponentName = componentName;
this.mType = type;
}
AccessibilityUserShortcutType(String flattenedString) {
sStringColonSplitter.setString(flattenedString);
if (sStringColonSplitter.hasNext()) {
this.mComponentName = sStringColonSplitter.next();
this.mType = Integer.parseInt(sStringColonSplitter.next());
}
}
String getComponentName() {
return mComponentName;
}
void setComponentName(String componentName) {
this.mComponentName = componentName;
}
int getType() {
return mType;
}
void setType(int type) {
this.mType = type;
}
String flattenToString() {
final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR));
joiner.add(mComponentName);
joiner.add(String.valueOf(mType));
return joiner.toString();
}
}
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) {
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);
updateAlertDialogCheckState();
}
private void updateAlertDialogCheckState() {
if (mUserShortcutTypesCache != UserShortcutType.EMPTY) {
updateCheckStatus(mSoftwareTypeCheckBox, UserShortcutType.SOFTWARE);
updateCheckStatus(mHardwareTypeCheckBox, UserShortcutType.HARDWARE);
}
}
private void updateCheckStatus(CheckBox checkBox, @UserShortcutType int type) {
checkBox.setChecked((mUserShortcutTypesCache & type) == type);
}
private void updateUserShortcutType(boolean saveChanges) {
mUserShortcutTypesCache = UserShortcutType.EMPTY;
if (mSoftwareTypeCheckBox.isChecked()) {
mUserShortcutTypesCache |= UserShortcutType.SOFTWARE;
}
if (mHardwareTypeCheckBox.isChecked()) {
mUserShortcutTypesCache |= UserShortcutType.HARDWARE;
}
if (saveChanges) {
final boolean isChanged = (mUserShortcutTypesCache != UserShortcutType.EMPTY);
if (isChanged) {
setUserShortcutType(getPrefContext(), mUserShortcutTypesCache);
}
mUserShortcutTypes = mUserShortcutTypesCache;
}
}
private void setUserShortcutType(Context context, int type) {
if (mComponentName == null) {
return;
}
Set<String> info = SharedPreferenceUtils.getUserShortcutTypes(context);
final String componentName = mComponentName.flattenToString();
if (info.isEmpty()) {
info = new HashSet<>();
} else {
final Set<String> filtered = info.stream()
.filter(str -> str.contains(componentName))
.collect(Collectors.toSet());
info.removeAll(filtered);
}
final AccessibilityUserShortcutType shortcut = new AccessibilityUserShortcutType(
componentName, type);
info.add(shortcut.flattenToString());
SharedPreferenceUtils.setUserShortcutType(context, info);
}
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 = 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);
List<CharSequence> list = new ArrayList<>();
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
list.add(softwareTitle);
}
if ((shortcutTypes & UserShortcutType.HARDWARE) == 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);
}
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<String> info = SharedPreferenceUtils.getUserShortcutTypes(context);
final String componentName = mComponentName.flattenToString();
final Set<String> 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();
}
/**
* 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 (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()));
}
protected void updateShortcutPreferenceData() {
if (mComponentName == null) {
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);
}
}
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);
mShortcutPreference.setChecked(
AccessibilityUtil.hasValuesInSettings(getPrefContext(), shortcutTypes,
mComponentName));
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
private String getShortcutPreferenceKey() {
return KEY_SHORTCUT_PREFERENCE;
}
@Override
public void onToggleClicked(ShortcutPreference preference) {
if (mComponentName == null) {
return;
}
final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
if (preference.isChecked()) {
AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), shortcutTypes,
mComponentName);
showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
} else {
AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), shortcutTypes,
mComponentName);
}
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
@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());
}
/**
* 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;
String targetString = Settings.Secure.getString(context.getContentResolver(), targetKey);
if (!TextUtils.isEmpty(targetString)) {
// The shortcut setting has been set
return;
}
// AccessibilityManager#getAccessibilityShortcutTargets may not return correct shortcut
// targets during boot. Needs to read settings directly here.
targetString = AccessibilityUtils.getShortcutTargetServiceComponentNameString(context,
UserHandle.myUserId());
if (TextUtils.isEmpty(targetString)) {
// No configurable default accessibility service
return;
}
// Only fallback to default accessibility service when setting is never updated.
final ComponentName shortcutName = ComponentName.unflattenFromString(targetString);
if (shortcutName != null) {
Settings.Secure.putString(context.getContentResolver(), targetKey,
shortcutName.flattenToString());
}
}
}

View File

@@ -16,173 +16,287 @@
package com.android.settings.accessibility;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
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.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.content.DialogInterface;
import android.icu.text.CaseMap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.view.Display;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.RelativeLayout.LayoutParams;
import android.widget.Switch;
import android.widget.VideoView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
import android.widget.CheckBox;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
import androidx.appcompat.app.AlertDialog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.widget.SwitchBar;
import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
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 implements SwitchBar.OnSwitchChangeListener {
ToggleFeaturePreferenceFragment {
private static final int DIALOG_ID_GESTURE_NAVIGATION_TUTORIAL = 1;
private static final String EXTRA_SHORTCUT_TYPE = "shortcut_type";
private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
private int mUserShortcutType = UserShortcutType.EMPTY;
private CheckBox mSoftwareTypeCheckBox;
private CheckBox mHardwareTypeCheckBox;
private CheckBox mTripleTapTypeCheckBox;
private Dialog mDialog;
protected class VideoPreference extends Preference {
private ImageView mVideoBackgroundView;
private OnGlobalLayoutListener mLayoutListener;
public VideoPreference(Context context) {
super(context);
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
Resources res = getPrefContext().getResources();
final int backgroundAssetWidth = res.getDimensionPixelSize(
R.dimen.screen_magnification_video_background_width);
final int videoAssetWidth = res
.getDimensionPixelSize(R.dimen.screen_magnification_video_width);
final int videoAssetHeight = res
.getDimensionPixelSize(R.dimen.screen_magnification_video_height);
final int videoAssetMarginTop = res.getDimensionPixelSize(
R.dimen.screen_magnification_video_margin_top);
view.setDividerAllowedAbove(false);
view.setDividerAllowedBelow(false);
mVideoBackgroundView = (ImageView) view.findViewById(R.id.video_background);
final VideoView videoView = (VideoView) view.findViewById(R.id.video);
// Loop the video.
videoView.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
mediaPlayer.setLooping(true);
}
});
// Make sure the VideoView does not request audio focus.
videoView.setAudioFocusRequest(AudioManager.AUDIOFOCUS_NONE);
// Resolve and set the video content
Bundle args = getArguments();
if ((args != null) && args.containsKey(
AccessibilitySettings.EXTRA_VIDEO_RAW_RESOURCE_ID)) {
videoView.setVideoURI(Uri.parse(String.format("%s://%s/%s",
ContentResolver.SCHEME_ANDROID_RESOURCE,
getPrefContext().getPackageName(),
args.getInt(AccessibilitySettings.EXTRA_VIDEO_RAW_RESOURCE_ID))));
}
// Make sure video controls (e.g. for pausing) are not displayed.
videoView.setMediaController(null);
// LayoutListener for adjusting the position of the VideoView on the background image.
mLayoutListener = new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
final int backgroundViewWidth = mVideoBackgroundView.getWidth();
LayoutParams videoLp = (LayoutParams) videoView.getLayoutParams();
videoLp.width = videoAssetWidth * backgroundViewWidth / backgroundAssetWidth;
videoLp.height = videoAssetHeight * backgroundViewWidth / backgroundAssetWidth;
videoLp.setMargins(0,
videoAssetMarginTop * backgroundViewWidth / backgroundAssetWidth, 0, 0);
videoView.setLayoutParams(videoLp);
videoView.invalidate();
videoView.start();
}
};
mVideoBackgroundView.getViewTreeObserver().addOnGlobalLayoutListener(mLayoutListener);
}
@Override
protected void onPrepareForRemoval() {
mVideoBackgroundView.getViewTreeObserver()
.removeOnGlobalLayoutListener(mLayoutListener);
}
}
protected VideoPreference mVideoPreference;
protected Preference mConfigWarningPreference;
private boolean mLaunchFromSuw = false;
private boolean mInitialSetting = false;
// 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);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.accessibility_screen_magnification_title);
}
mVideoPreference = new VideoPreference(getPrefContext());
mVideoPreference.setSelectable(false);
mVideoPreference.setPersistent(false);
mVideoPreference.setLayoutResource(R.layout.magnification_video_preference);
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
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))
.build();
mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
removeDialog(DialogEnums.EDIT_SHORTCUT);
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
};
return super.onCreateView(inflater, container, savedInstanceState);
}
mConfigWarningPreference = new Preference(getPrefContext());
mConfigWarningPreference.setSelectable(false);
mConfigWarningPreference.setPersistent(false);
mConfigWarningPreference.setVisible(false);
mConfigWarningPreference.setIcon(R.drawable.ic_warning_24dp);
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
initShortcutPreference();
final PreferenceScreen preferenceScreen = getPreferenceManager().getPreferenceScreen();
preferenceScreen.setOrderingAsAdded(false);
mVideoPreference.setOrder(0);
mConfigWarningPreference.setOrder(2);
preferenceScreen.addPreference(mVideoPreference);
preferenceScreen.addPreference(mConfigWarningPreference);
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();
VideoView videoView = (VideoView) getView().findViewById(R.id.video);
if (videoView != null) {
videoView.start();
}
final AccessibilityManager am = getPrefContext().getSystemService(
AccessibilityManager.class);
am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
updateConfigurationWarningIfNeeded();
updateShortcutPreferenceData();
updateShortcutPreference();
}
@Override
public void onPause() {
final AccessibilityManager am = getPrefContext().getSystemService(
AccessibilityManager.class);
am.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
super.onPause();
}
@Override
public Dialog onCreateDialog(int dialogId) {
if (dialogId == DIALOG_ID_GESTURE_NAVIGATION_TUTORIAL) {
if (isGestureNavigateEnabled()) {
mDialog = AccessibilityGestureNavigationTutorial
.showGestureNavigationTutorialDialog(getActivity());
} else {
mDialog = AccessibilityGestureNavigationTutorial
.showAccessibilityButtonTutorialDialog(getActivity());
}
final AlertDialog dialog;
switch (dialogId) {
case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
return AccessibilityGestureNavigationTutorial
.showGestureNavigationTutorialDialog(getPrefContext());
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);
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);
});
}
private void initializeDialogCheckBox(AlertDialog 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);
final View dialogTripleTapView = dialog.findViewById(R.id.triple_tap_shortcut);
mTripleTapTypeCheckBox = dialogTripleTapView.findViewById(R.id.checkbox);
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);
}
}
private void updateCheckStatus(CheckBox checkBox, @UserShortcutType int type) {
checkBox.setChecked((mUserShortcutTypesCache & type) == type);
}
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;
}
return mDialog;
if (saveChanges) {
final boolean isChanged = (mUserShortcutTypesCache != UserShortcutType.EMPTY);
if (isChanged) {
setUserShortcutType(getPrefContext(), mUserShortcutTypesCache);
}
mUserShortcutType = mUserShortcutTypesCache;
}
}
private void setUserShortcutType(Context context, int type) {
Set<String> info = SharedPreferenceUtils.getUserShortcutTypes(context);
if (info.isEmpty()) {
info = new HashSet<>();
} else {
final Set<String> 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);
}
@Override
protected CharSequence getShortcutTypeSummary(Context context) {
if (!mShortcutPreference.isChecked()) {
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);
List<CharSequence> list = new ArrayList<>();
if ((shortcutType & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
list.add(softwareTitle);
}
if ((shortcutType & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE) {
final CharSequence hardwareTitle = context.getText(
R.string.accessibility_shortcut_hardware_keyword);
list.add(hardwareTitle);
}
if ((shortcutType & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP) {
final CharSequence tripleTapTitle = context.getText(
R.string.accessibility_shortcut_triple_tap_keyword);
list.add(tripleTapTitle);
}
// Show software shortcut if first time to use.
if (list.isEmpty()) {
list.add(softwareTitle);
}
final String joinStrings = TextUtils.join(/* delimiter= */", ", list);
return CaseMap.toTitle().wholeString().noLowercase().apply(Locale.getDefault(), /* iter= */
null, joinStrings);
}
@Override
protected int getUserShortcutTypes(Context context, @UserShortcutType int defaultValue) {
final Set<String> info = SharedPreferenceUtils.getUserShortcutTypes(context);
final Set<String> filtered = info.stream().filter(
str -> str.contains(MAGNIFICATION_CONTROLLER_NAME)).collect(
Collectors.toSet());
if (filtered.isEmpty()) {
return defaultValue;
}
final String str = (String) filtered.toArray()[0];
final AccessibilityUserShortcutType shortcut = new AccessibilityUserShortcutType(str);
return shortcut.getType();
}
@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()));
}
@Override
@@ -193,12 +307,21 @@ public class ToggleScreenMagnificationPreferenceFragment extends
@Override
public int getDialogMetricsCategory(int dialogId) {
return SettingsEnums.ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION;
switch (dialogId) {
case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION;
case DialogEnums.ACCESSIBILITY_BUTTON_TUTORIAL:
return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_ACCESSIBILITY_BUTTON;
case DialogEnums.MAGNIFICATION_EDIT_SHORTCUT:
return SettingsEnums.DIALOG_MAGNIFICATION_EDIT_SHORTCUT;
default:
return super.getDialogMetricsCategory(dialogId);
}
}
@Override
public void onSwitchChanged(Switch switchView, boolean isChecked) {
onPreferenceToggled(mPreferenceKey, isChecked);
int getUserShortcutTypes() {
return getUserShortcutTypeFromSettings(getPrefContext());
}
@Override
@@ -206,79 +329,225 @@ public class ToggleScreenMagnificationPreferenceFragment extends
if (enabled && TextUtils.equals(
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
preferenceKey)) {
showDialog(DIALOG_ID_GESTURE_NAVIGATION_TUTORIAL);
showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
}
MagnificationPreferenceFragment.setChecked(getContentResolver(), preferenceKey, enabled);
updateConfigurationWarningIfNeeded();
}
@Override
protected void onInstallSwitchBarToggleSwitch() {
super.onInstallSwitchBarToggleSwitch();
mSwitchBar.setCheckedInternal(
MagnificationPreferenceFragment.isChecked(getContentResolver(), mPreferenceKey));
mSwitchBar.addOnSwitchChangeListener(this);
protected void onInstallSwitchPreferenceToggleSwitch() {
super.onInstallSwitchPreferenceToggleSwitch();
mToggleServiceDividerSwitchPreference.setVisible(false);
}
@Override
protected void onRemoveSwitchBarToggleSwitch() {
super.onRemoveSwitchBarToggleSwitch();
mSwitchBar.removeOnSwitchChangeListener(this);
public void onToggleClicked(ShortcutPreference preference) {
final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
if (preference.isChecked()) {
optInAllMagnificationValuesToSettings(getPrefContext(), shortcutTypes);
showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
} else {
optOutAllMagnificationValuesFromSettings(getPrefContext(), shortcutTypes);
}
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
@Override
protected void onProcessArguments(Bundle arguments) {
super.onProcessArguments(arguments);
if (arguments == null) {
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);
}
}
private void initShortcutPreference() {
mShortcutPreference = new ShortcutPreference(getPrefContext(), null);
mShortcutPreference.setPersistent(false);
mShortcutPreference.setKey(KEY_SHORTCUT_PREFERENCE);
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
mShortcutPreference.setOnClickCallback(this);
final CharSequence title = getString(R.string.accessibility_shortcut_title, mPackageName);
mShortcutPreference.setTitle(title);
}
@Override
protected void updateShortcutPreference() {
final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
mShortcutPreference.setChecked(
hasMagnificationValuesInSettings(getPrefContext(), shortcutTypes));
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
@VisibleForTesting
static void optInAllMagnificationValuesToSettings(Context context, int shortcutTypes) {
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
optInMagnificationValueToSettings(context, UserShortcutType.SOFTWARE);
}
if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
optInMagnificationValueToSettings(context, UserShortcutType.HARDWARE);
}
if (((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP)) {
optInMagnificationValueToSettings(context, UserShortcutType.TRIPLETAP);
}
}
private static void optInMagnificationValueToSettings(Context context,
@UserShortcutType int shortcutType) {
if (shortcutType == UserShortcutType.TRIPLETAP) {
Settings.Secure.putInt(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, ON);
return;
}
if (arguments.containsKey(AccessibilitySettings.EXTRA_VIDEO_RAW_RESOURCE_ID)) {
mVideoPreference.setVisible(true);
final int resId = arguments.getInt(
AccessibilitySettings.EXTRA_VIDEO_RAW_RESOURCE_ID);
} else {
mVideoPreference.setVisible(false);
if (hasMagnificationValueInSettings(context, shortcutType)) {
return;
}
if (arguments.containsKey(AccessibilitySettings.EXTRA_LAUNCHED_FROM_SUW)) {
mLaunchFromSuw = arguments.getBoolean(AccessibilitySettings.EXTRA_LAUNCHED_FROM_SUW);
final String targetKey = AccessibilityUtil.convertKeyFromSettings(shortcutType);
final String targetString = Settings.Secure.getString(context.getContentResolver(),
targetKey);
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(context.getContentResolver(), targetKey, joiner.toString());
}
@VisibleForTesting
static void optOutAllMagnificationValuesFromSettings(Context context,
int shortcutTypes) {
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
optOutMagnificationValueFromSettings(context, UserShortcutType.SOFTWARE);
}
if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
optOutMagnificationValueFromSettings(context, UserShortcutType.HARDWARE);
}
if (((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP)) {
optOutMagnificationValueFromSettings(context, UserShortcutType.TRIPLETAP);
}
}
private static void optOutMagnificationValueFromSettings(Context context,
@UserShortcutType int shortcutType) {
if (shortcutType == UserShortcutType.TRIPLETAP) {
Settings.Secure.putInt(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF);
return;
}
if (arguments.containsKey(AccessibilitySettings.EXTRA_CHECKED)) {
mInitialSetting = arguments.getBoolean(AccessibilitySettings.EXTRA_CHECKED);
final String targetKey = AccessibilityUtil.convertKeyFromSettings(shortcutType);
final String targetString = Settings.Secure.getString(context.getContentResolver(),
targetKey);
if (TextUtils.isEmpty(targetString)) {
return;
}
if (arguments.containsKey(AccessibilitySettings.EXTRA_TITLE_RES)) {
final int titleRes = arguments.getInt(AccessibilitySettings.EXTRA_TITLE_RES);
if (titleRes > 0) {
getActivity().setTitle(titleRes);
final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR));
sStringColonSplitter.setString(targetString);
while (sStringColonSplitter.hasNext()) {
final String name = sStringColonSplitter.next();
if (TextUtils.isEmpty(name) || MAGNIFICATION_CONTROLLER_NAME.equals(name)) {
continue;
}
joiner.add(name);
}
Settings.Secure.putString(context.getContentResolver(), targetKey, joiner.toString());
}
@VisibleForTesting
static boolean hasMagnificationValuesInSettings(Context context, int shortcutTypes) {
boolean exist = false;
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
exist = hasMagnificationValueInSettings(context, UserShortcutType.SOFTWARE);
}
if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
exist |= hasMagnificationValueInSettings(context, UserShortcutType.HARDWARE);
}
if (((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP)) {
exist |= hasMagnificationValueInSettings(context, UserShortcutType.TRIPLETAP);
}
return exist;
}
private static boolean hasMagnificationValueInSettings(Context context,
@UserShortcutType int shortcutType) {
if (shortcutType == UserShortcutType.TRIPLETAP) {
return Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF) == ON;
}
final String targetKey = AccessibilityUtil.convertKeyFromSettings(shortcutType);
final String targetString = Settings.Secure.getString(context.getContentResolver(),
targetKey);
if (TextUtils.isEmpty(targetString)) {
return false;
}
sStringColonSplitter.setString(targetString);
while (sStringColonSplitter.hasNext()) {
final String name = sStringColonSplitter.next();
if (MAGNIFICATION_CONTROLLER_NAME.equals(name)) {
return true;
}
}
return false;
}
private boolean isGestureNavigateEnabled() {
return getContext().getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode)
== NAV_BAR_MODE_GESTURAL;
private boolean isWindowMagnification(Context context) {
final int mode = Settings.Secure.getIntForUser(
context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
context.getContentResolver().getUserId());
return mode == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
}
private void updateConfigurationWarningIfNeeded() {
final CharSequence warningMessage =
MagnificationPreferenceFragment.getConfigurationWarningStringForSecureSettingsKey(
mPreferenceKey, getPrefContext());
if (warningMessage != null) {
mConfigWarningPreference.setSummary(warningMessage);
private static int getUserShortcutTypeFromSettings(Context context) {
int shortcutTypes = UserShortcutType.EMPTY;
if (hasMagnificationValuesInSettings(context, UserShortcutType.SOFTWARE)) {
shortcutTypes |= UserShortcutType.SOFTWARE;
}
mConfigWarningPreference.setVisible(warningMessage != null);
if (hasMagnificationValuesInSettings(context, UserShortcutType.HARDWARE)) {
shortcutTypes |= UserShortcutType.HARDWARE;
}
if (hasMagnificationValuesInSettings(context, UserShortcutType.TRIPLETAP)) {
shortcutTypes |= UserShortcutType.TRIPLETAP;
}
return shortcutTypes;
}
private static int getScreenWidth(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return size.x;
/**
* Gets the service summary of magnification.
*
* @param context The current context.
*/
public static CharSequence getServiceSummary(Context context) {
// Get the user shortcut type from settings provider.
final int uerShortcutType = getUserShortcutTypeFromSettings(context);
return (uerShortcutType != AccessibilityUtil.UserShortcutType.EMPTY)
? context.getText(R.string.accessibility_summary_shortcut_enabled)
: context.getText(R.string.accessibility_summary_shortcut_disabled);
}
}

View File

@@ -32,11 +32,12 @@ 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 (mToggleSwitch.isChecked() != args.getBoolean(AccessibilitySettings.EXTRA_CHECKED)) {
if (mToggleServiceDividerSwitchPreference.isChecked() != args.getBoolean(
AccessibilitySettings.EXTRA_CHECKED)) {
// TODO: Distinguish between magnification modes
mMetricsFeatureProvider.action(getContext(),
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION,
mToggleSwitch.isChecked());
mToggleServiceDividerSwitchPreference.isChecked());
}
}
super.onStop();

View File

@@ -18,6 +18,7 @@ package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import android.os.Bundle;
import android.view.View;
public class ToggleScreenReaderPreferenceFragmentForSetupWizard
extends ToggleAccessibilityServicePreferenceFragment {
@@ -25,9 +26,9 @@ public class ToggleScreenReaderPreferenceFragmentForSetupWizard
private boolean mToggleSwitchWasInitiallyChecked;
@Override
protected void onProcessArguments(Bundle arguments) {
super.onProcessArguments(arguments);
mToggleSwitchWasInitiallyChecked = mToggleSwitch.isChecked();
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mToggleSwitchWasInitiallyChecked = mToggleServiceDividerSwitchPreference.isChecked();
}
@Override
@@ -38,12 +39,11 @@ public class ToggleScreenReaderPreferenceFragmentForSetupWizard
@Override
public void onStop() {
// Log the final choice in value if it's different from the previous value.
if (mToggleSwitch.isChecked() != mToggleSwitchWasInitiallyChecked) {
if (mToggleServiceDividerSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
mMetricsFeatureProvider.action(getContext(),
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SCREEN_READER, mToggleSwitch.isChecked());
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SCREEN_READER,
mToggleServiceDividerSwitchPreference.isChecked());
}
super.onStop();
}
}

View File

@@ -18,16 +18,17 @@ package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import android.os.Bundle;
import android.view.View;
public class ToggleSelectToSpeakPreferenceFragmentForSetupWizard
extends ToggleAccessibilityServicePreferenceFragment {
extends InvisibleToggleAccessibilityServicePreferenceFragment {
private boolean mToggleSwitchWasInitiallyChecked;
@Override
protected void onProcessArguments(Bundle arguments) {
super.onProcessArguments(arguments);
mToggleSwitchWasInitiallyChecked = mToggleSwitch.isChecked();
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mToggleSwitchWasInitiallyChecked = mToggleServiceDividerSwitchPreference.isChecked();
}
@Override
@@ -38,13 +39,12 @@ public class ToggleSelectToSpeakPreferenceFragmentForSetupWizard
@Override
public void onStop() {
// Log the final choice in value if it's different from the previous value.
if (mToggleSwitch.isChecked() != mToggleSwitchWasInitiallyChecked) {
if (mToggleServiceDividerSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
mMetricsFeatureProvider.action(getContext(),
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SELECT_TO_SPEAK,
mToggleSwitch.isChecked());
mToggleServiceDividerSwitchPreference.isChecked());
}
super.onStop();
}
}

View File

@@ -31,8 +31,7 @@ public class TopLevelAccessibilityPreferenceController extends BasePreferenceCon
@Override
public int getAvailabilityStatus() {
return mContext.getResources().getBoolean(R.bool.config_show_top_level_accessibility)
? AVAILABLE_UNSEARCHABLE
: UNSUPPORTED_ON_DEVICE;
? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.os.Vibrator;
import android.provider.Settings;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
public class VibrationPreferenceController extends BasePreferenceController {
private final Vibrator mVibrator;
public VibrationPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mVibrator = mContext.getSystemService(Vibrator.class);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public CharSequence getSummary() {
int ringIntensity = Settings.System.getInt(mContext.getContentResolver(),
Settings.System.RING_VIBRATION_INTENSITY,
mVibrator.getDefaultRingVibrationIntensity());
if (Settings.System.getInt(mContext.getContentResolver(),
Settings.System.VIBRATE_WHEN_RINGING, 0) == 0
&& !AccessibilitySettings.isRampingRingerEnabled(mContext)) {
ringIntensity = Vibrator.VIBRATION_INTENSITY_OFF;
}
final CharSequence ringIntensityString =
VibrationIntensityPreferenceController.getIntensityString(mContext, ringIntensity);
final int notificationIntensity = Settings.System.getInt(mContext.getContentResolver(),
Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
mVibrator.getDefaultNotificationVibrationIntensity());
final CharSequence notificationIntensityString =
VibrationIntensityPreferenceController.getIntensityString(mContext,
notificationIntensity);
int touchIntensity = Settings.System.getInt(mContext.getContentResolver(),
Settings.System.HAPTIC_FEEDBACK_INTENSITY,
mVibrator.getDefaultHapticFeedbackIntensity());
if (Settings.System.getInt(mContext.getContentResolver(),
Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) == 0) {
touchIntensity = Vibrator.VIBRATION_INTENSITY_OFF;
}
final CharSequence touchIntensityString =
VibrationIntensityPreferenceController.getIntensityString(mContext, touchIntensity);
if (ringIntensity == touchIntensity && ringIntensity == notificationIntensity) {
return ringIntensityString;
} else {
return mContext.getString(R.string.accessibility_vibration_summary, ringIntensityString,
notificationIntensityString, touchIntensityString);
}
}
}

View File

@@ -17,21 +17,14 @@
package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.provider.SearchIndexableResource;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.List;
/**
* Accessibility settings for the vibration.
*/
@SearchIndexable
/** Accessibility settings for the vibration. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class VibrationSettings extends DashboardFragment {
private static final String TAG = "VibrationSettings";
@@ -41,6 +34,11 @@ public class VibrationSettings extends DashboardFragment {
return SettingsEnums.ACCESSIBILITY_VIBRATION;
}
@Override
public int getHelpResource() {
return R.string.help_uri_accessibility_vibration;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_vibration_settings;
@@ -51,16 +49,6 @@ public class VibrationSettings extends DashboardFragment {
return TAG;
}
public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
boolean enabled) {
List<SearchIndexableResource> indexables = new ArrayList<>();
SearchIndexableResource indexable = new SearchIndexableResource(context);
indexable.xmlResId = R.xml.accessibility_vibration_settings;
indexables.add(indexable);
return indexables;
}
};
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_vibration_settings);
}

View File

@@ -0,0 +1,76 @@
/*
* 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.AccessibilityUtil.UserShortcutType;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.os.Bundle;
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.
*
* <p>The child {@link ToggleAccessibilityServicePreferenceFragment} shows the actual UI for
* providing basic accessibility service setup.
*
* <p>For accessibility services that target SDK <= Q.
*/
public class VolumeShortcutToggleAccessibilityServicePreferenceFragment extends
ToggleAccessibilityServicePreferenceFragment {
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final CharSequence hardwareTitle = getPrefContext().getText(
R.string.accessibility_shortcut_edit_dialog_title_hardware);
mShortcutPreference.setSummary(hardwareTitle);
mShortcutPreference.setSettingsEditable(false);
setAllowedPreferredShortcutType(UserShortcutType.HARDWARE);
}
@Override
int getUserShortcutTypes() {
int shortcutTypes = super.getUserShortcutTypes();
final boolean isServiceOn =
getArguments().getBoolean(AccessibilitySettings.EXTRA_CHECKED);
final AccessibilityServiceInfo info = getAccessibilityServiceInfo();
final boolean hasRequestAccessibilityButtonFlag =
(info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
if (hasRequestAccessibilityButtonFlag && isServiceOn) {
shortcutTypes |= UserShortcutType.SOFTWARE;
} else {
shortcutTypes &= (~UserShortcutType.SOFTWARE);
}
return shortcutTypes;
}
private void setAllowedPreferredShortcutType(int type) {
final AccessibilityUserShortcutType shortcut = new AccessibilityUserShortcutType(
mComponentName.flattenToString(), type);
SharedPreferenceUtils.setUserShortcutType(getPrefContext(),
ImmutableSet.of(shortcut.flattenToString()));
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.os.Bundle;
import android.view.View;
/** For accessibility services that target SDK <= Q in setup wizard. */
public class VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard
extends VolumeShortcutToggleAccessibilityServicePreferenceFragment {
private boolean mToggleSwitchWasInitiallyChecked;
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mToggleSwitchWasInitiallyChecked = mToggleServiceDividerSwitchPreference.isChecked();
}
@Override
public int getMetricsCategory() {
return SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SCREEN_READER;
}
@Override
public void onStop() {
// Log the final choice in value if it's different from the previous value.
if (mToggleServiceDividerSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
mMetricsFeatureProvider.action(getContext(),
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SCREEN_READER,
mToggleServiceDividerSwitchPreference.isChecked());
}
super.onStop();
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.os.Bundle;
import android.view.View;
/** For accessibility services that target SDK <= Q in setup wizard. */
public class VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard
extends VolumeShortcutToggleAccessibilityServicePreferenceFragment {
private boolean mToggleSwitchWasInitiallyChecked;
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mToggleSwitchWasInitiallyChecked = mToggleServiceDividerSwitchPreference.isChecked();
}
@Override
public int getMetricsCategory() {
return SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SCREEN_READER;
}
@Override
public void onStop() {
// Log the final choice in value if it's different from the previous value.
if (mToggleServiceDividerSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
mMetricsFeatureProvider.action(getContext(),
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SELECT_TO_SPEAK,
mToggleServiceDividerSwitchPreference.isChecked());
}
super.onStop();
}
}

View File

@@ -17,22 +17,27 @@ package com.android.settings.accounts;
import static android.provider.Settings.EXTRA_AUTHORITIES;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.provider.SearchIndexableResource;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.users.AutoSyncDataPreferenceController;
import com.android.settings.users.AutoSyncPersonalDataPreferenceController;
import com.android.settings.users.AutoSyncWorkDataPreferenceController;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.search.SearchIndexableRaw;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@SearchIndexable
@@ -72,7 +77,8 @@ public class AccountDashboardFragment extends DashboardFragment {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
final AccountPreferenceController accountPrefController =
new AccountPreferenceController(context, parent, authorities);
new AccountPreferenceController(context, parent, authorities,
ProfileSelectFragment.ProfileType.ALL);
if (parent != null) {
parent.getSettingsLifecycle().addObserver(accountPrefController);
}
@@ -83,15 +89,8 @@ public class AccountDashboardFragment extends DashboardFragment {
return controllers;
}
public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(
Context context, boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.accounts_dashboard_settings;
return Arrays.asList(sir);
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accounts_dashboard_settings) {
@Override
public List<AbstractPreferenceController> createPreferenceControllers(
@@ -99,5 +98,30 @@ public class AccountDashboardFragment extends DashboardFragment {
return buildPreferenceControllers(
context, null /* parent */, null /* authorities*/);
}
@Override
public List<SearchIndexableRaw> getDynamicRawDataToIndex(Context context,
boolean enabled) {
final List<SearchIndexableRaw> indexRaws = new ArrayList<>();
final UserManager userManager = (UserManager) context.getSystemService(
Context.USER_SERVICE);
final List<UserInfo> profiles = userManager.getProfiles(UserHandle.myUserId());
for (final UserInfo userInfo : profiles) {
if (userInfo.isManagedProfile()) {
return indexRaws;
}
}
final AccountManager accountManager = AccountManager.get(context);
final Account[] accounts = accountManager.getAccounts();
for (Account account : accounts) {
final SearchIndexableRaw raw = new SearchIndexableRaw(context);
raw.key = AccountTypePreference.buildKey(account);
raw.title = account.name;
indexRaws.add(raw);
}
return indexRaws;
}
};
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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.accounts;
import static android.provider.Settings.EXTRA_AUTHORITIES;
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.users.AutoSyncDataPreferenceController;
import com.android.settings.users.AutoSyncPersonalDataPreferenceController;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
/**
* Account Setting page for personal profile.
*/
public class AccountPersonalDashboardFragment extends DashboardFragment {
private static final String TAG = "AccountPersonalFrag";
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCOUNT;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accounts_personal_dashboard_settings;
}
@Override
public int getHelpResource() {
return R.string.help_url_user_and_account_dashboard;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
final String[] authorities = getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
return buildPreferenceControllers(context, this /* parent */, authorities);
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
SettingsPreferenceFragment parent, String[] authorities) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
final AccountPreferenceController accountPrefController =
new AccountPreferenceController(context, parent, authorities,
ProfileSelectFragment.ProfileType.PERSONAL);
if (parent != null) {
parent.getSettingsLifecycle().addObserver(accountPrefController);
}
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
// public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
// new BaseSearchIndexProvider(R.xml.accounts_personal_dashboard_settings) {
//
// @Override
// public List<AbstractPreferenceController> createPreferenceControllers(
// Context context) {
// return buildPreferenceControllers(
// context, null /* parent */, null /* authorities*/);
// }
// };
}

View File

@@ -125,13 +125,13 @@ public class AccountPreference extends Preference {
res = R.drawable.ic_settings_sync;
break;
case SYNC_DISABLED:
res = R.drawable.ic_sync_grey_holo;
res = R.drawable.ic_settings_sync_disabled;
break;
case SYNC_ERROR:
res = R.drawable.ic_sync_red_holo;
res = R.drawable.ic_settings_sync_failed;
break;
default:
res = R.drawable.ic_sync_red_holo;
res = R.drawable.ic_settings_sync_failed;
Log.e(TAG, "Unknown sync status: " + status);
}
return res;

View File

@@ -53,8 +53,8 @@ import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.SearchIndexableRaw;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.accounts.AuthenticatorHelper;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -62,6 +62,7 @@ import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
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.search.SearchIndexableRaw;
import java.util.ArrayList;
import java.util.Collections;
@@ -79,17 +80,22 @@ public class AccountPreferenceController extends AbstractPreferenceController
private static final int ORDER_NEXT_TO_LAST = 1001;
private static final int ORDER_NEXT_TO_NEXT_TO_LAST = 1000;
private static final String PREF_KEY_ADD_ACCOUNT = "add_account";
private static final String PREF_KEY_REMOVE_PROFILE = "remove_profile";
private static final String PREF_KEY_WORK_PROFILE_SETTING = "work_profile_setting";
private UserManager mUm;
private SparseArray<ProfileData> mProfiles = new SparseArray<ProfileData>();
private ManagedProfileBroadcastReceiver mManagedProfileBroadcastReceiver
= new ManagedProfileBroadcastReceiver();
private ManagedProfileBroadcastReceiver mManagedProfileBroadcastReceiver =
new ManagedProfileBroadcastReceiver();
private Preference mProfileNotAvailablePreference;
private String[] mAuthorities;
private int mAuthoritiesCount = 0;
private SettingsPreferenceFragment mParent;
private SettingsPreferenceFragment mFragment;
private int mAccountProfileOrder = ORDER_ACCOUNT_PROFILES;
private AccountRestrictionHelper mHelper;
private MetricsFeatureProvider mMetricsFeatureProvider;
private @ProfileSelectFragment.ProfileType int mType;
/**
* Holds data related to the accounts belonging to one profile.
@@ -130,23 +136,25 @@ public class AccountPreferenceController extends AbstractPreferenceController
}
public AccountPreferenceController(Context context, SettingsPreferenceFragment parent,
String[] authorities) {
this(context, parent, authorities, new AccountRestrictionHelper(context));
String[] authorities, @ProfileSelectFragment.ProfileType int type) {
this(context, parent, authorities, new AccountRestrictionHelper(context), type);
}
@VisibleForTesting
AccountPreferenceController(Context context, SettingsPreferenceFragment parent,
String[] authorities, AccountRestrictionHelper helper) {
String[] authorities, AccountRestrictionHelper helper,
@ProfileSelectFragment.ProfileType int type) {
super(context);
mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
mAuthorities = authorities;
mParent = parent;
mFragment = parent;
if (mAuthorities != null) {
mAuthoritiesCount = mAuthorities.length;
}
final FeatureFactory featureFactory = FeatureFactory.getFactory(mContext);
mMetricsFeatureProvider = featureFactory.getMetricsFeatureProvider();
mHelper = helper;
mType = type;
}
@Override
@@ -166,7 +174,7 @@ public class AccountPreferenceController extends AbstractPreferenceController
}
@Override
public void updateRawDataToIndex(List<SearchIndexableRaw> rawData) {
public void updateDynamicRawDataToIndex(List<SearchIndexableRaw> rawData) {
if (!isAvailable()) {
return;
}
@@ -174,29 +182,21 @@ public class AccountPreferenceController extends AbstractPreferenceController
final String screenTitle = res.getString(R.string.account_settings_title);
List<UserInfo> profiles = mUm.getProfiles(UserHandle.myUserId());
final int profilesCount = profiles.size();
for (int i = 0; i < profilesCount; i++) {
UserInfo userInfo = profiles.get(i);
if (userInfo.isEnabled()) {
if (!mHelper.hasBaseUserRestriction(DISALLOW_MODIFY_ACCOUNTS, userInfo.id)) {
SearchIndexableRaw data = new SearchIndexableRaw(mContext);
data.title = res.getString(R.string.add_account_label);
data.screenTitle = screenTitle;
rawData.add(data);
}
if (userInfo.isManagedProfile()) {
if (!mHelper.hasBaseUserRestriction(DISALLOW_REMOVE_MANAGED_PROFILE,
for (final UserInfo userInfo : profiles) {
if (userInfo.isEnabled() && userInfo.isManagedProfile()) {
if (!mHelper.hasBaseUserRestriction(DISALLOW_REMOVE_MANAGED_PROFILE,
UserHandle.myUserId())) {
SearchIndexableRaw data = new SearchIndexableRaw(mContext);
data.title = res.getString(R.string.remove_managed_profile_label);
data.screenTitle = screenTitle;
rawData.add(data);
}
SearchIndexableRaw data = new SearchIndexableRaw(mContext);
data.title = res.getString(R.string.managed_profile_settings_title);
final SearchIndexableRaw data = new SearchIndexableRaw(mContext);
data.key = PREF_KEY_REMOVE_PROFILE;
data.title = res.getString(R.string.remove_managed_profile_label);
data.screenTitle = screenTitle;
rawData.add(data);
}
final SearchIndexableRaw data = new SearchIndexableRaw(mContext);
data.key = PREF_KEY_WORK_PROFILE_SETTING;
data.title = res.getString(R.string.managed_profile_settings_title);
data.screenTitle = screenTitle;
rawData.add(data);
}
}
}
@@ -226,11 +226,13 @@ public class AccountPreferenceController extends AbstractPreferenceController
@Override
public boolean onPreferenceClick(Preference preference) {
final int metricsCategory = mFragment.getMetricsCategory();
// Check the preference
final int count = mProfiles.size();
for (int i = 0; i < count; i++) {
ProfileData profileData = mProfiles.valueAt(i);
if (preference == profileData.addAccountPreference) {
mMetricsFeatureProvider.logClickedPreference(preference, metricsCategory);
Intent intent = new Intent(ACTION_ADD_ACCOUNT);
intent.putExtra(EXTRA_USER, profileData.userInfo.getUserHandle());
intent.putExtra(EXTRA_AUTHORITIES, mAuthorities);
@@ -238,16 +240,18 @@ public class AccountPreferenceController extends AbstractPreferenceController
return true;
}
if (preference == profileData.removeWorkProfilePreference) {
mMetricsFeatureProvider.logClickedPreference(preference, metricsCategory);
final int userId = profileData.userInfo.id;
RemoveUserFragment.newInstance(userId).show(mParent.getFragmentManager(),
RemoveUserFragment.newInstance(userId).show(mFragment.getFragmentManager(),
"removeUser");
return true;
}
if (preference == profileData.managedProfilePreference) {
mMetricsFeatureProvider.logClickedPreference(preference, metricsCategory);
Bundle arguments = new Bundle();
arguments.putParcelable(Intent.EXTRA_USER, profileData.userInfo.getUserHandle());
new SubSettingLauncher(mContext)
.setSourceMetricsCategory(mParent.getMetricsCategory())
.setSourceMetricsCategory(metricsCategory)
.setDestination(ManagedProfileSettings.class.getName())
.setTitleRes(R.string.managed_profile_settings_title)
.setArguments(arguments)
@@ -277,7 +281,13 @@ public class AccountPreferenceController extends AbstractPreferenceController
List<UserInfo> profiles = mUm.getProfiles(UserHandle.myUserId());
final int profilesCount = profiles.size();
for (int i = 0; i < profilesCount; i++) {
updateProfileUi(profiles.get(i));
if (profiles.get(i).isManagedProfile()
&& (mType & ProfileSelectFragment.ProfileType.WORK) != 0) {
updateProfileUi(profiles.get(i));
} else if (!profiles.get(i).isManagedProfile()
&& (mType & ProfileSelectFragment.ProfileType.PERSONAL) != 0) {
updateProfileUi(profiles.get(i));
}
}
}
cleanUpPreferences();
@@ -292,7 +302,7 @@ public class AccountPreferenceController extends AbstractPreferenceController
}
private void updateProfileUi(final UserInfo userInfo) {
if (mParent.getPreferenceManager() == null) {
if (mFragment.getPreferenceManager() == null) {
return;
}
final ProfileData data = mProfiles.get(userInfo.id);
@@ -302,7 +312,7 @@ public class AccountPreferenceController extends AbstractPreferenceController
if (userInfo.isEnabled()) {
// recreate the authentication helper to refresh the list of enabled accounts
data.authenticatorHelper =
new AuthenticatorHelper(mContext, userInfo.getUserHandle(), this);
new AuthenticatorHelper(mContext, userInfo.getUserHandle(), this);
}
return;
}
@@ -310,29 +320,34 @@ public class AccountPreferenceController extends AbstractPreferenceController
final ProfileData profileData = new ProfileData();
profileData.userInfo = userInfo;
AccessiblePreferenceCategory preferenceGroup =
mHelper.createAccessiblePreferenceCategory(mParent.getPreferenceManager().getContext());
mHelper.createAccessiblePreferenceCategory(
mFragment.getPreferenceManager().getContext());
preferenceGroup.setOrder(mAccountProfileOrder++);
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));
mContext.getString(R.string.account_settings));
} else if (userInfo.isManagedProfile()) {
preferenceGroup.setTitle(R.string.category_work);
String workGroupSummary = getWorkGroupSummary(context, userInfo);
preferenceGroup.setSummary(workGroupSummary);
preferenceGroup.setContentDescription(
mContext.getString(R.string.accessibility_category_work, workGroupSummary));
if (mType == ProfileSelectFragment.ProfileType.ALL) {
preferenceGroup.setTitle(R.string.category_work);
final String workGroupSummary = getWorkGroupSummary(context, userInfo);
preferenceGroup.setSummary(workGroupSummary);
preferenceGroup.setContentDescription(
mContext.getString(R.string.accessibility_category_work, workGroupSummary));
}
profileData.removeWorkProfilePreference = newRemoveWorkProfilePreference();
mHelper.enforceRestrictionOnPreference(profileData.removeWorkProfilePreference,
DISALLOW_REMOVE_MANAGED_PROFILE, UserHandle.myUserId());
DISALLOW_REMOVE_MANAGED_PROFILE, UserHandle.myUserId());
profileData.managedProfilePreference = newManagedProfileSettings();
} else {
preferenceGroup.setTitle(R.string.category_personal);
preferenceGroup.setContentDescription(
mContext.getString(R.string.accessibility_category_personal));
if (mType == ProfileSelectFragment.ProfileType.ALL) {
preferenceGroup.setTitle(R.string.category_personal);
preferenceGroup.setContentDescription(
mContext.getString(R.string.accessibility_category_personal));
}
}
final PreferenceScreen screen = mParent.getPreferenceScreen();
final PreferenceScreen screen = mFragment.getPreferenceScreen();
if (screen != null) {
screen.addPreference(preferenceGroup);
}
@@ -342,14 +357,15 @@ public class AccountPreferenceController extends AbstractPreferenceController
userInfo.getUserHandle(), this);
profileData.addAccountPreference = newAddAccountPreference();
mHelper.enforceRestrictionOnPreference(profileData.addAccountPreference,
DISALLOW_MODIFY_ACCOUNTS, userInfo.id);
DISALLOW_MODIFY_ACCOUNTS, userInfo.id);
}
mProfiles.put(userInfo.id, profileData);
}
private RestrictedPreference newAddAccountPreference() {
RestrictedPreference preference =
new RestrictedPreference(mParent.getPreferenceManager().getContext());
new RestrictedPreference(mFragment.getPreferenceManager().getContext());
preference.setKey(PREF_KEY_ADD_ACCOUNT);
preference.setTitle(R.string.add_account_label);
preference.setIcon(R.drawable.ic_add_24dp);
preference.setOnPreferenceClickListener(this);
@@ -359,7 +375,8 @@ public class AccountPreferenceController extends AbstractPreferenceController
private RestrictedPreference newRemoveWorkProfilePreference() {
RestrictedPreference preference = new RestrictedPreference(
mParent.getPreferenceManager().getContext());
mFragment.getPreferenceManager().getContext());
preference.setKey(PREF_KEY_REMOVE_PROFILE);
preference.setTitle(R.string.remove_managed_profile_label);
preference.setIcon(R.drawable.ic_delete);
preference.setOnPreferenceClickListener(this);
@@ -369,7 +386,8 @@ public class AccountPreferenceController extends AbstractPreferenceController
private Preference newManagedProfileSettings() {
Preference preference = new Preference(mParent.getPreferenceManager().getContext());
Preference preference = new Preference(mFragment.getPreferenceManager().getContext());
preference.setKey(PREF_KEY_WORK_PROFILE_SETTING);
preference.setTitle(R.string.managed_profile_settings_title);
preference.setIcon(R.drawable.ic_settings_24dp);
preference.setOnPreferenceClickListener(this);
@@ -388,12 +406,12 @@ public class AccountPreferenceController extends AbstractPreferenceController
}
void cleanUpPreferences() {
PreferenceScreen screen = mParent.getPreferenceScreen();
PreferenceScreen screen = mFragment.getPreferenceScreen();
if (screen == null) {
return;
}
final int count = mProfiles.size();
for (int i = count-1; i >= 0; i--) {
for (int i = count - 1; i >= 0; i--) {
final ProfileData data = mProfiles.valueAt(i);
if (data.pendingRemoval) {
screen.removePreference(data.preferenceGroup);
@@ -423,7 +441,7 @@ public class AccountPreferenceController extends AbstractPreferenceController
}
private void updateAccountTypes(ProfileData profileData) {
if (mParent.getPreferenceManager() == null
if (mFragment.getPreferenceManager() == null
|| profileData.preferenceGroup.getPreferenceManager() == null) {
// This could happen if activity is finishing
return;
@@ -449,7 +467,7 @@ public class AccountPreferenceController extends AbstractPreferenceController
}
for (String key : preferenceToRemove.keySet()) {
profileData.preferenceGroup.removePreference(
profileData.accountPreferences.get(key));
profileData.accountPreferences.get(key));
profileData.accountPreferences.remove(key);
}
} else {
@@ -457,7 +475,7 @@ public class AccountPreferenceController extends AbstractPreferenceController
// Put a label instead of the accounts list
if (mProfileNotAvailablePreference == null) {
mProfileNotAvailablePreference =
new Preference(mParent.getPreferenceManager().getContext());
new Preference(mFragment.getPreferenceManager().getContext());
}
mProfileNotAvailablePreference.setEnabled(false);
mProfileNotAvailablePreference.setIcon(R.drawable.empty_icon);
@@ -496,7 +514,7 @@ public class AccountPreferenceController extends AbstractPreferenceController
final Account[] accounts = AccountManager.get(mContext)
.getAccountsByTypeAsUser(accountType, userHandle);
final Drawable icon = helper.getDrawableForType(mContext, accountType);
final Context prefContext = mParent.getPreferenceManager().getContext();
final Context prefContext = mFragment.getPreferenceManager().getContext();
// Add a preference row for each individual account
for (Account account : accounts) {
@@ -507,26 +525,26 @@ public class AccountPreferenceController extends AbstractPreferenceController
continue;
}
final ArrayList<String> auths =
helper.getAuthoritiesForAccountType(account.type);
helper.getAuthoritiesForAccountType(account.type);
if (!AccountRestrictionHelper.showAccount(mAuthorities, auths)) {
continue;
}
final Bundle fragmentArguments = new Bundle();
fragmentArguments.putParcelable(AccountDetailDashboardFragment.KEY_ACCOUNT,
account);
account);
fragmentArguments.putParcelable(AccountDetailDashboardFragment.KEY_USER_HANDLE,
userHandle);
userHandle);
fragmentArguments.putString(AccountDetailDashboardFragment.KEY_ACCOUNT_TYPE,
accountType);
accountType);
fragmentArguments.putString(AccountDetailDashboardFragment.KEY_ACCOUNT_LABEL,
label.toString());
label.toString());
fragmentArguments.putInt(AccountDetailDashboardFragment.KEY_ACCOUNT_TITLE_RES,
titleResId);
titleResId);
fragmentArguments.putParcelable(EXTRA_USER, userHandle);
accountTypePreferences.add(new AccountTypePreference(
prefContext, mMetricsFeatureProvider.getMetricsCategory(mParent),
account, titleResPackageName, titleResId, label,
AccountDetailDashboardFragment.class.getName(), fragmentArguments, icon));
prefContext, mMetricsFeatureProvider.getMetricsCategory(mFragment),
account, titleResPackageName, titleResId, label,
AccountDetailDashboardFragment.class.getName(), fragmentArguments, icon));
}
helper.preloadDrawableForType(mContext, accountType);
}
@@ -536,7 +554,7 @@ public class AccountPreferenceController extends AbstractPreferenceController
public int compare(AccountTypePreference t1, AccountTypePreference t2) {
int result = t1.getSummary().toString().compareTo(t2.getSummary().toString());
return result != 0
? result : t1.getTitle().toString().compareTo(t2.getTitle().toString());
? result : t1.getTitle().toString().compareTo(t2.getTitle().toString());
}
});
return accountTypePreferences;
@@ -575,11 +593,15 @@ public class AccountPreferenceController extends AbstractPreferenceController
Log.v(TAG, "Received broadcast: " + action);
if (action.equals(Intent.ACTION_MANAGED_PROFILE_REMOVED)
|| action.equals(Intent.ACTION_MANAGED_PROFILE_ADDED)) {
// Clean old state
stopListeningToAccountUpdates();
// Build new state
updateUi();
listenToAccountUpdates();
if (mFragment instanceof AccountWorkProfileDashboardFragment) {
mFragment.getActivity().finish();
} else {
// Clean old state
stopListeningToAccountUpdates();
// Build new state
updateUi();
listenToAccountUpdates();
}
return;
}
Log.w(TAG, "Cannot handle received broadcast: " + intent.getAction());

View File

@@ -15,8 +15,17 @@
*/
package com.android.settings.accounts;
import static android.os.UserManager.DISALLOW_REMOVE_MANAGED_PROFILE;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
import com.android.settings.AccessiblePreferenceCategory;
import com.android.settingslib.RestrictedLockUtilsInternal;
@@ -44,7 +53,12 @@ public class AccountRestrictionHelper {
return;
}
if (hasBaseUserRestriction(userRestriction, userId)) {
preference.setEnabled(false);
if (userRestriction.equals(DISALLOW_REMOVE_MANAGED_PROFILE)
&& isOrganizationOwnedDevice()) {
preference.setDisabledByAdmin(getEnforcedAdmin(userRestriction, userId));
} else {
preference.setEnabled(false);
}
} else {
preference.checkRestrictionAndSetDisabled(userRestriction, userId);
}
@@ -55,6 +69,41 @@ public class AccountRestrictionHelper {
userId);
}
private boolean isOrganizationOwnedDevice() {
final DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
Context.DEVICE_POLICY_SERVICE);
if (dpm == null) {
return false;
}
return dpm.isOrganizationOwnedDeviceWithManagedProfile();
}
private EnforcedAdmin getEnforcedAdmin(String userRestriction, int userId) {
final DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
Context.DEVICE_POLICY_SERVICE);
if (dpm == null) {
return null;
}
final int managedUsedId = getManagedUserId(userId);
ComponentName adminComponent = dpm.getProfileOwnerAsUser(managedUsedId);
if (adminComponent != null) {
return new EnforcedAdmin(adminComponent, userRestriction,
UserHandle.of(managedUsedId));
}
return null;
}
private int getManagedUserId(int userId) {
final UserManager um = UserManager.get(mContext);
for (UserInfo ui : um.getProfiles(userId)) {
if (ui.id == userId || !ui.isManagedProfile()) {
continue;
}
return ui.id;
}
return -1;
}
public AccessiblePreferenceCategory createAccessiblePreferenceCategory(Context context) {
return new AccessiblePreferenceCategory(context);
}

View File

@@ -66,8 +66,8 @@ public class AccountSyncPreferenceController extends AbstractPreferenceControlle
new SubSettingLauncher(mContext)
.setDestination(AccountSyncSettings.class.getName())
.setArguments(args)
.setSourceMetricsCategory( SettingsEnums.ACCOUNT)
.setTitleRes( R.string.account_sync_title)
.setSourceMetricsCategory(SettingsEnums.ACCOUNT)
.setTitleRes(R.string.account_sync_title)
.launch();
return true;

View File

@@ -42,17 +42,20 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.widget.FooterPreference;
import com.google.android.collect.Lists;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
public class AccountSyncSettings extends AccountPreferenceBase {
@@ -61,9 +64,11 @@ public class AccountSyncSettings extends AccountPreferenceBase {
private static final int MENU_SYNC_NOW_ID = Menu.FIRST;
private static final int MENU_SYNC_CANCEL_ID = Menu.FIRST + 1;
private static final int CANT_DO_ONETIME_SYNC_DIALOG = 102;
private static final String UID_REQUEST_KEY = "uid_request_code";
private Account mAccount;
private ArrayList<SyncAdapterType> mInvisibleAdapters = Lists.newArrayList();
private HashMap<Integer, Integer> mUidRequestCodeMap = new HashMap<>();
@Override
public Dialog onCreateDialog(final int id) {
@@ -101,6 +106,14 @@ public class AccountSyncSettings extends AccountPreferenceBase {
setAccessibilityTitle();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (!mUidRequestCodeMap.isEmpty()) {
outState.putSerializable(UID_REQUEST_KEY, mUidRequestCodeMap);
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
@@ -130,6 +143,10 @@ public class AccountSyncSettings extends AccountPreferenceBase {
.done(activity, getPrefContext());
pref.setOrder(0);
getPreferenceScreen().addPreference(pref);
if (savedInstanceState != null && savedInstanceState.containsKey(UID_REQUEST_KEY)) {
mUidRequestCodeMap = (HashMap<Integer, Integer>) savedInstanceState.getSerializable(
UID_REQUEST_KEY);
}
}
private void setAccessibilityTitle() {
@@ -187,11 +204,9 @@ public class AccountSyncSettings extends AccountPreferenceBase {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
MenuItem syncNow = menu.add(0, MENU_SYNC_NOW_ID, 0,
getString(R.string.sync_menu_sync_now))
.setIcon(R.drawable.ic_menu_refresh_holo_dark);
getString(R.string.sync_menu_sync_now));
MenuItem syncCancel = menu.add(0, MENU_SYNC_CANCEL_ID, 0,
getString(R.string.sync_menu_sync_cancel))
.setIcon(com.android.internal.R.drawable.ic_menu_close_clear_cancel);
getString(R.string.sync_menu_sync_cancel));
syncNow.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
MenuItem.SHOW_AS_ACTION_WITH_TEXT);
@@ -207,7 +222,7 @@ public class AccountSyncSettings extends AccountPreferenceBase {
// Note that this also counts accounts that are not currently displayed
boolean syncActive = !ContentResolver.getCurrentSyncsAsUser(
mUserHandle.getIdentifier()).isEmpty();
menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive);
menu.findItem(MENU_SYNC_NOW_ID).setVisible(!syncActive).setEnabled(enabledSyncNowMenu());
menu.findItem(MENU_SYNC_CANCEL_ID).setVisible(syncActive);
}
@@ -227,13 +242,12 @@ public class AccountSyncSettings extends AccountPreferenceBase {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
final int uid = requestCode;
final int count = getPreferenceScreen().getPreferenceCount();
for (int i = 0; i < count; i++) {
Preference preference = getPreferenceScreen().getPreference(i);
if (preference instanceof SyncStateSwitchPreference) {
SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) preference;
if (syncPref.getUid() == uid) {
if (getRequestCodeByUid(syncPref.getUid()) == requestCode) {
onPreferenceTreeClick(syncPref);
return;
}
@@ -314,7 +328,9 @@ public class AccountSyncSettings extends AccountPreferenceBase {
mAccount, packageName, mUserHandle);
if (intent != null) {
try {
startIntentSenderForResult(intent, uid, null, 0, 0, 0, null);
final int requestCode = addUidAndGenerateRequestCode(uid);
startIntentSenderForResult(intent, requestCode, null /* fillInIntent */, 0, 0,
0, null /* options */);
return true;
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Error requesting account access", e);
@@ -458,8 +474,8 @@ public class AccountSyncSettings extends AccountPreferenceBase {
syncPref.setChecked(oneTimeSyncMode || syncEnabled);
}
if (syncIsFailing) {
mFooterPreferenceMixin.createFooterPreference()
.setTitle(R.string.sync_is_failing);
getPreferenceScreen().addPreference(new FooterPreference.Builder(
getActivity()).setTitle(R.string.sync_is_failing).build());
}
}
@@ -546,10 +562,43 @@ public class AccountSyncSettings extends AccountPreferenceBase {
return R.string.help_url_accounts;
}
@VisibleForTesting
boolean enabledSyncNowMenu() {
boolean enabled = false;
for (int i = 0, count = getPreferenceScreen().getPreferenceCount(); i < count; i++) {
final Preference pref = getPreferenceScreen().getPreference(i);
if (!(pref instanceof SyncStateSwitchPreference)) {
continue;
}
final SyncStateSwitchPreference syncPref = (SyncStateSwitchPreference) pref;
if (syncPref.isChecked()) {
enabled = true;
break;
}
}
return enabled;
}
private static String formatSyncDate(Context context, Date date) {
return DateUtils.formatDateTime(context, date.getTime(),
DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_SHOW_YEAR
| DateUtils.FORMAT_SHOW_TIME);
}
private int addUidAndGenerateRequestCode(int uid) {
if (mUidRequestCodeMap.containsKey(uid)) {
return mUidRequestCodeMap.get(uid);
}
final int requestCode = mUidRequestCodeMap.size() + 1;
mUidRequestCodeMap.put(uid, requestCode);
return requestCode;
}
private int getRequestCodeByUid(int uid) {
if (!mUidRequestCodeMap.containsKey(uid)) {
return -1;
}
return mUidRequestCodeMap.get(uid);
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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.accounts;
import static android.provider.Settings.EXTRA_AUTHORITIES;
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.users.AutoSyncDataPreferenceController;
import com.android.settings.users.AutoSyncWorkDataPreferenceController;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
/**
* Account Setting page for work profile.
*/
public class AccountWorkProfileDashboardFragment extends DashboardFragment {
private static final String TAG = "AccountWorkProfileFrag";
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCOUNT_WORK;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accounts_work_dashboard_settings;
}
@Override
public int getHelpResource() {
return R.string.help_url_user_and_account_dashboard;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
final String[] authorities = getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
return buildPreferenceControllers(context, this /* parent */, authorities);
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
SettingsPreferenceFragment parent, String[] authorities) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
final AccountPreferenceController accountPrefController =
new AccountPreferenceController(context, parent, authorities,
ProfileSelectFragment.ProfileType.WORK);
if (parent != null) {
parent.getSettingsLifecycle().addObserver(accountPrefController);
}
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
// public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
// new BaseSearchIndexProvider(R.xml.accounts_work_dashboard_settings) {
//
// @Override
// public List<AbstractPreferenceController> createPreferenceControllers(
// Context context) {
// return buildPreferenceControllers(
// context, null /* parent */, null /* authorities*/);
// }
// };
}

View File

@@ -100,8 +100,9 @@ public class AddAccountSettings extends Activity {
addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS,
Utils.hasMultipleUsers(AddAccountSettings.this));
addAccountOptions.putParcelable(EXTRA_USER, mUserHandle);
intent.putExtras(addAccountOptions);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtras(addAccountOptions)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivityForResultAsUser(intent, ADD_ACCOUNT_REQUEST, mUserHandle);
} else {
setResult(RESULT_OK);

Some files were not shown because too many files have changed in this diff Show More