Merge "Merge sc-dev-plus-aosp-without-vendor@7634622" into stage-aosp-master

This commit is contained in:
Xin Li
2021-08-17 18:14:14 +00:00
committed by Android (Google) Code Review
2494 changed files with 218431 additions and 77065 deletions

View File

@@ -16,8 +16,6 @@
package com.android.settings;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
@@ -73,8 +71,6 @@ public class ActivityPicker extends AlertActivity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addPrivateFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
final Intent intent = getIntent();

View File

@@ -192,3 +192,4 @@ public class AirplaneModeEnabler extends GlobalSettingsChangeListener {
return WirelessUtils.isAirplaneModeOn(mContext);
}
}

View File

@@ -49,8 +49,8 @@ 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.widget.MainSwitchBarController;
import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settings.wifi.tether.WifiTetherApBandPreferenceController;
import com.android.settings.wifi.tether.WifiTetherAutoOffPreferenceController;
import com.android.settings.wifi.tether.WifiTetherBasePreferenceController;
@@ -227,15 +227,15 @@ public class AllInOneTetherSettings extends RestrictedDashboardFragment
adapter.getProfileProxy(activity.getApplicationContext(), mProfileServiceListener,
BluetoothProfile.PAN);
}
final SwitchBar switchBar = activity.getSwitchBar();
final SettingsMainSwitchBar mainSwitch = activity.getSwitchBar();
mTetherEnabler = new TetherEnabler(activity,
new SwitchBarController(switchBar), mBluetoothPan);
new MainSwitchBarController(mainSwitch), 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();
mainSwitch.show();
}
@Override
@@ -304,12 +304,12 @@ public class AllInOneTetherSettings extends RestrictedDashboardFragment
}
@Override
public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) {
public void onAllowlistStatusChanged(int uid, boolean isAllowlisted) {
// Do nothing
}
@Override
public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) {
public void onDenylistStatusChanged(int uid, boolean isDenylisted) {
// Do nothing
}
@@ -355,7 +355,7 @@ public class AllInOneTetherSettings extends RestrictedDashboardFragment
@Override
public void onTetherConfigUpdated(AbstractPreferenceController controller) {
final SoftApConfiguration config = buildNewConfig();
mPasswordPreferenceController.updateVisibility(config.getSecurityType());
mPasswordPreferenceController.setSecurityType(config.getSecurityType());
mWifiManager.setSoftApConfiguration(config);
if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) {

View File

@@ -16,6 +16,8 @@
package com.android.settings;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
@@ -91,6 +93,7 @@ public class AllowBindAppWidgetActivity extends AlertActivity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addPrivateFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
setResult(RESULT_CANCELED); // By default, set the result to cancelled
Intent intent = getIntent();
CharSequence label = "";

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings;
import android.os.AsyncTask;
import androidx.annotation.Nullable;
import com.android.settingslib.utils.ThreadUtils;
import java.util.concurrent.Future;
/** A {@link SidecarFragment} which uses an {@link AsyncTask} to perform background work. */
public abstract class AsyncTaskSidecar<Param, Result> extends SidecarFragment {
private Future<Result> mAsyncTask;
@Override
public void onDestroy() {
if (mAsyncTask != null) {
mAsyncTask.cancel(true /* mayInterruptIfRunning */);
}
super.onDestroy();
}
/**
* Executes the background task.
*
* @param param parameters passed in from {@link #run}
*/
protected abstract Result doInBackground(@Nullable Param param);
/** Handles the background task's result. */
protected void onPostExecute(Result result) {}
/** Runs the sidecar and sets the state to RUNNING. */
public void run(@Nullable final Param param) {
setState(State.RUNNING, Substate.UNUSED);
if (mAsyncTask != null) {
mAsyncTask.cancel(true /* mayInterruptIfRunning */);
}
mAsyncTask =
ThreadUtils.postOnBackgroundThread(
() -> {
Result result = doInBackground(param);
ThreadUtils.postOnMainThread(() -> onPostExecute(result));
});
}
}

View File

@@ -37,6 +37,7 @@ public class CustomListPreference extends ListPreference {
public CustomListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setSingleLineTitle(true);
}
public CustomListPreference(Context context, AttributeSet attrs, int defStyleAttr,

View File

@@ -24,13 +24,10 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.display.BrightnessLevelPreferenceController;
import com.android.settings.display.CameraGesturePreferenceController;
import com.android.settings.display.LiftToWakePreferenceController;
import com.android.settings.display.NightDisplayPreferenceController;
import com.android.settings.display.NightModePreferenceController;
import com.android.settings.display.ScreenSaverPreferenceController;
import com.android.settings.display.ShowOperatorNamePreferenceController;
import com.android.settings.display.TapToWakePreferenceController;
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.settingslib.core.AbstractPreferenceController;
@@ -44,8 +41,6 @@ import java.util.List;
public class DisplaySettings extends DashboardFragment {
private static final String TAG = "DisplaySettings";
private static final String KEY_SCREEN_TIMEOUT = "screen_timeout";
@Override
public int getMetricsCategory() {
return SettingsEnums.DISPLAY;
@@ -81,11 +76,8 @@ public class DisplaySettings extends DashboardFragment {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new CameraGesturePreferenceController(context));
controllers.add(new LiftToWakePreferenceController(context));
controllers.add(new NightDisplayPreferenceController(context));
controllers.add(new NightModePreferenceController(context));
controllers.add(new ScreenSaverPreferenceController(context));
controllers.add(new TapToWakePreferenceController(context));
controllers.add(new TimeoutPreferenceController(context, KEY_SCREEN_TIMEOUT));
controllers.add(new VrDisplayPreferenceController(context));
controllers.add(new ShowOperatorNamePreferenceController(context));
controllers.add(new ThemePreferenceController(context));

View File

@@ -62,8 +62,8 @@ public class EncryptionInterstitial extends SettingsActivity {
@Override
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
resid = SetupWizardUtils.getTheme(getIntent());
super.onApplyThemeResource(theme, resid, first);
final int new_resid = SetupWizardUtils.getTheme(this, getIntent());
super.onApplyThemeResource(theme, new_resid, first);
}
@Override
@@ -111,6 +111,8 @@ public class EncryptionInterstitial extends SettingsActivity {
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
final boolean forFace = getActivity().getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
final boolean forBiometrics = getActivity().getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
Intent intent = getActivity().getIntent();
mRequestedPasswordQuality = intent.getIntExtra(EXTRA_PASSWORD_QUALITY, 0);
mUnlockMethodIntent = intent.getParcelableExtra(EXTRA_UNLOCK_METHOD_INTENT);
@@ -121,6 +123,8 @@ public class EncryptionInterstitial extends SettingsActivity {
R.string.encryption_interstitial_message_pattern_for_fingerprint :
forFace ?
R.string.encryption_interstitial_message_pattern_for_face :
forBiometrics ?
R.string.encryption_interstitial_message_pattern_for_biometrics :
R.string.encryption_interstitial_message_pattern;
break;
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
@@ -129,6 +133,8 @@ public class EncryptionInterstitial extends SettingsActivity {
R.string.encryption_interstitial_message_pin_for_fingerprint :
forFace ?
R.string.encryption_interstitial_message_pin_for_face :
forBiometrics ?
R.string.encryption_interstitial_message_pin_for_biometrics :
R.string.encryption_interstitial_message_pin;
break;
default:
@@ -136,6 +142,8 @@ public class EncryptionInterstitial extends SettingsActivity {
R.string.encryption_interstitial_message_password_for_fingerprint :
forFace ?
R.string.encryption_interstitial_message_password_for_face :
forBiometrics ?
R.string.encryption_interstitial_message_password_for_biometrics :
R.string.encryption_interstitial_message_password;
break;
}

View File

@@ -31,7 +31,6 @@ import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
@@ -166,12 +165,6 @@ public class FallbackHome extends Activity {
.addCategory(Intent.CATEGORY_HOME);
final ResolveInfo homeInfo = getPackageManager().resolveActivity(homeIntent, 0);
if (Objects.equals(getPackageName(), homeInfo.activityInfo.packageName)) {
if (UserManager.isSplitSystemUser()
&& UserHandle.myUserId() == UserHandle.USER_SYSTEM) {
// This avoids the situation where the system user has no home activity after
// SUW and this activity continues to throw out warnings. See b/28870689.
return;
}
Log.d(TAG, "User unlocked but no home; let's hope someone enables one soon?");
mHandler.sendEmptyMessageDelayed(0, 500);
} else {

View File

@@ -56,6 +56,7 @@ import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
import com.android.settings.network.ProxySubscriptionManager;
import com.android.settings.network.SubscriptionUtil;
import java.util.ArrayList;
import java.util.List;
@@ -310,7 +311,8 @@ public class IccLockSettings extends SettingsPreferenceFragment
mTabHost.addTab(buildTabSpec(tag,
String.valueOf(subInfo == null
? getContext().getString(R.string.sim_editor_title, slot + 1)
: subInfo.getDisplayName())));
: SubscriptionUtil.getUniqueSubscriptionDisplayName(
subInfo, getContext()))));
}
mTabHost.setCurrentTabByTag(getTagForSlotId(mSlotId));

View File

@@ -59,11 +59,11 @@ import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.ConfirmLockPattern;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
import com.google.android.setupcompat.template.FooterBarMixin;
@@ -83,16 +83,16 @@ import java.util.List;
*
* This is the initial screen.
*/
public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutListener {
private static final String TAG = "MasterClear";
public class MainClear extends InstrumentedFragment implements OnGlobalLayoutListener {
private static final String TAG = "MainClear";
@VisibleForTesting
static final int KEYGUARD_REQUEST = 55;
@VisibleForTesting
static final int CREDENTIAL_CONFIRM_REQUEST = 56;
private static final String KEY_SHOW_ESIM_RESET_CHECKBOX
= "masterclear.allow_retain_esim_profiles_after_fdr";
private static final String KEY_SHOW_ESIM_RESET_CHECKBOX =
"masterclear.allow_retain_esim_profiles_after_fdr";
static final String ERASE_EXTERNAL_EXTRA = "erase_sd";
static final String ERASE_ESIMS_EXTRA = "erase_esim";
@@ -139,8 +139,11 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL
*/
private boolean runKeyguardConfirmation(int request) {
Resources res = getActivity().getResources();
return new ChooseLockSettingsHelper(getActivity(), this).launchConfirmationActivity(
request, res.getText(R.string.master_clear_short_title));
final ChooseLockSettingsHelper.Builder builder =
new ChooseLockSettingsHelper.Builder(getActivity(), this);
return builder.setRequestCode(request)
.setTitle(res.getText(R.string.main_clear_short_title))
.show();
}
@VisibleForTesting
@@ -184,12 +187,15 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL
final Bundle args = new Bundle();
args.putBoolean(ERASE_EXTERNAL_EXTRA, mExternalStorage.isChecked());
args.putBoolean(ERASE_ESIMS_EXTRA, mEsimStorage.isChecked());
new SubSettingLauncher(getContext())
.setDestination(MasterClearConfirm.class.getName())
.setArguments(args)
.setTitleRes(R.string.master_clear_confirm_title)
.setSourceMetricsCategory(getMetricsCategory())
.launch();
final Intent intent = new Intent();
intent.setClass(getContext(),
com.android.settings.Settings.FactoryResetConfirmActivity.class);
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, MainClearConfirm.class.getName());
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID,
R.string.main_clear_confirm_title);
intent.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, getMetricsCategory());
getContext().startActivity(intent);
}
@VisibleForTesting
@@ -293,7 +299,7 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL
if (mScrollView != null) {
mScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
mScrollView = mContentView.findViewById(R.id.master_clear_scrollview);
mScrollView = mContentView.findViewById(R.id.main_clear_scrollview);
/*
* If the external storage is emulated, it will be erased with a factory
@@ -351,9 +357,9 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL
final UserManager um = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
loadAccountList(um);
final StringBuffer contentDescription = new StringBuffer();
final View masterClearContainer = mContentView.findViewById(R.id.master_clear_container);
getContentDescription(masterClearContainer, contentDescription);
masterClearContainer.setContentDescription(contentDescription);
final View mainClearContainer = mContentView.findViewById(R.id.main_clear_container);
getContentDescription(mainClearContainer, contentDescription);
mainClearContainer.setContentDescription(contentDescription);
// Set the status of initiateButton based on scrollview
mScrollView.setOnScrollChangeListener(new OnScrollChangeListener() {
@@ -421,7 +427,7 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
mixin.setPrimaryButton(
new FooterButton.Builder(getActivity())
.setText(R.string.master_clear_button_text)
.setText(R.string.main_clear_button_text)
.setListener(mInitiateListener)
.setButtonType(ButtonType.OTHER)
.setTheme(R.style.SudGlifButton_Primary)
@@ -472,15 +478,15 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL
final int profileId = userInfo.id;
final UserHandle userHandle = new UserHandle(profileId);
Account[] accounts = mgr.getAccountsAsUser(profileId);
final int N = accounts.length;
if (N == 0) {
final int accountLength = accounts.length;
if (accountLength == 0) {
continue;
}
accountsCount += N;
accountsCount += accountLength;
AuthenticatorDescription[] descs = AccountManager.get(context)
.getAuthenticatorTypesAsUser(profileId);
final int M = descs.length;
final int descLength = descs.length;
if (profilesSize > 1) {
View titleView = Utils.inflateCategoryHeader(inflater, contents);
@@ -490,10 +496,10 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL
contents.addView(titleView);
}
for (int i = 0; i < N; i++) {
for (int i = 0; i < accountLength; i++) {
Account account = accounts[i];
AuthenticatorDescription desc = null;
for (int j = 0; j < M; j++) {
for (int j = 0; j < descLength; j++) {
if (account.type.equals(descs[j].type)) {
desc = descs[j];
break;
@@ -521,7 +527,7 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL
icon = context.getPackageManager().getDefaultActivityIcon();
}
View child = inflater.inflate(R.layout.master_clear_account, contents, false);
View child = inflater.inflate(R.layout.main_clear_account, contents, false);
((ImageView) child.findViewById(android.R.id.icon)).setImageDrawable(icon);
((TextView) child.findViewById(android.R.id.title)).setText(account.name);
contents.addView(child);
@@ -549,7 +555,7 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL
.hasBaseUserRestriction(context, UserManager.DISALLOW_FACTORY_RESET,
UserHandle.myUserId());
if (disallow && !Utils.isDemoUser(context)) {
return inflater.inflate(R.layout.master_clear_disallowed_screen, null);
return inflater.inflate(R.layout.main_clear_disallowed_screen, null);
} else if (admin != null) {
new ActionDisabledByAdminDialogHelper(getActivity())
.prepareDialogBuilder(UserManager.DISALLOW_FACTORY_RESET, admin)
@@ -558,7 +564,7 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL
return new View(getContext());
}
mContentView = inflater.inflate(R.layout.master_clear, null);
mContentView = inflater.inflate(R.layout.main_clear, null);
establishInitialState();
return mContentView;

View File

@@ -21,26 +21,20 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
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.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.oemlock.OemLockManager;
import android.service.persistentdata.PersistentDataBlockManager;
import android.telephony.TelephonyManager;
import android.telephony.UiccSlotInfo;
import android.telephony.euicc.EuiccManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -60,8 +54,6 @@ import com.google.android.setupcompat.template.FooterButton.ButtonType;
import com.google.android.setupcompat.util.WizardManagerHelper;
import com.google.android.setupdesign.GlifLayout;
import java.util.Arrays;
/**
* Confirm and execute a reset of the device to a clean "just out of the box"
* state. Multiple confirmations are required: first, a general "are you sure
@@ -72,8 +64,8 @@ import java.util.Arrays;
*
* This is the confirmation screen.
*/
public class MasterClearConfirm extends InstrumentedFragment {
private final static String TAG = "MasterClearConfirm";
public class MainClearConfirm extends InstrumentedFragment {
private static final String TAG = "MainClearConfirm";
@VisibleForTesting View mContentView;
private boolean mEraseSdCard;
@@ -91,89 +83,6 @@ public class MasterClearConfirm extends InstrumentedFragment {
return;
}
// If the eSIM slot is in an error state, display a dialog to warn users that their eSIM
// profiles may not be fully deleted during FDR.
if (shouldShowEsimEraseFailureDialog()) {
Log.e(TAG, "eUICC card is in an error state. Display a dialog to warn the user.");
showEsimErrorDialog();
return;
}
performFactoryReset();
}
/**
* Returns true if the user choose to erase eSIM profile but the eUICC card is in an error
* state.
*/
private boolean shouldShowEsimEraseFailureDialog() {
EuiccManager euiccManager = getActivity().getSystemService(EuiccManager.class);
TelephonyManager telephonyManager =
getActivity().getSystemService(TelephonyManager.class);
if (euiccManager == null || !euiccManager.isEnabled()) {
Log.i(
TAG,
"eSIM manager is disabled. No need to check eSIM slot before FDR.");
return false;
}
if (!mEraseEsims) {
Log.i(
TAG,
"eSIM does not need to be reset. No need to check eSIM slot before FDR.");
return false;
}
UiccSlotInfo[] slotInfos = telephonyManager.getUiccSlotsInfo();
if (slotInfos == null) {
Log.i(TAG, "Unable to get UICC slots.");
return false;
}
// If getIsEuicc() returns false for an eSIM slot, it means the eSIM is in the error
// state.
return Arrays.stream(slotInfos).anyMatch(
slot -> slot != null && !slot.isRemovable() && !slot.getIsEuicc());
}
private void showEsimErrorDialog() {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.fdr_esim_failure_title)
.setMessage(R.string.fdr_esim_failure_text)
.setNeutralButton(R.string.dlg_cancel,
(DialogInterface.OnClickListener) (dialog, which) -> {
dialog.dismiss();
})
.setNegativeButton(R.string.fdr_esim_failure_reboot_btn,
(DialogInterface.OnClickListener) (dialog, which) -> {
dialog.dismiss();
PowerManager pm = (PowerManager) getActivity()
.getSystemService(Context.POWER_SERVICE);
pm.reboot(null);
})
.setPositiveButton(R.string.lockpassword_continue_label,
(DialogInterface.OnClickListener) (dialog, which) -> {
dialog.dismiss();
showContinueFdrDialog();
})
.show();
}
private void showContinueFdrDialog() {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.fdr_continue_title)
.setMessage(R.string.fdr_continue_text)
.setNegativeButton(R.string.dlg_cancel,
(DialogInterface.OnClickListener) (dialog, which) -> {
dialog.dismiss();
})
.setPositiveButton(R.string.fdr_continue_btn,
(DialogInterface.OnClickListener) (dialog, which) -> {
dialog.dismiss();
performFactoryReset();
})
.show();
}
private void performFactoryReset() {
final PersistentDataBlockManager pdbManager = (PersistentDataBlockManager)
getActivity().getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
@@ -194,7 +103,7 @@ public class MasterClearConfirm extends InstrumentedFragment {
mProgressDialog.hide();
if (getActivity() != null) {
getActivity().setRequestedOrientation(mOldOrientation);
doMasterClear();
doMainClear();
}
}
@@ -212,7 +121,7 @@ public class MasterClearConfirm extends InstrumentedFragment {
}
}.execute();
} else {
doMasterClear();
doMainClear();
}
}
@@ -221,9 +130,9 @@ public class MasterClearConfirm extends InstrumentedFragment {
progressDialog.setIndeterminate(true);
progressDialog.setCancelable(false);
progressDialog.setTitle(
getActivity().getString(R.string.master_clear_progress_title));
getActivity().getString(R.string.main_clear_progress_title));
progressDialog.setMessage(
getActivity().getString(R.string.master_clear_progress_text));
getActivity().getString(R.string.main_clear_progress_text));
return progressDialog;
}
};
@@ -271,11 +180,11 @@ public class MasterClearConfirm extends InstrumentedFragment {
return !WizardManagerHelper.isDeviceProvisioned(getActivity());
}
private void doMasterClear() {
private void doMainClear() {
Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
intent.setPackage("android");
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_REASON, "MasterClearConfirm");
intent.putExtra(Intent.EXTRA_REASON, "MainClearConfirm");
intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, mEraseSdCard);
intent.putExtra(Intent.EXTRA_WIPE_ESIMS, mEraseEsims);
getActivity().sendBroadcast(intent);
@@ -291,7 +200,7 @@ public class MasterClearConfirm extends InstrumentedFragment {
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
mixin.setPrimaryButton(
new FooterButton.Builder(getActivity())
.setText(R.string.master_clear_button_text)
.setText(R.string.main_clear_button_text)
.setListener(mFinalClickListener)
.setButtonType(ButtonType.OTHER)
.setTheme(R.style.SudGlifButton_Primary)
@@ -321,7 +230,7 @@ public class MasterClearConfirm extends InstrumentedFragment {
getActivity(), UserManager.DISALLOW_FACTORY_RESET, UserHandle.myUserId());
if (RestrictedLockUtilsInternal.hasBaseUserRestriction(getActivity(),
UserManager.DISALLOW_FACTORY_RESET, UserHandle.myUserId())) {
return inflater.inflate(R.layout.master_clear_disallowed_screen, null);
return inflater.inflate(R.layout.main_clear_disallowed_screen, null);
} else if (admin != null) {
new ActionDisabledByAdminDialogHelper(getActivity())
.prepareDialogBuilder(UserManager.DISALLOW_FACTORY_RESET, admin)
@@ -329,7 +238,7 @@ public class MasterClearConfirm extends InstrumentedFragment {
.show();
return new View(getActivity());
}
mContentView = inflater.inflate(R.layout.master_clear_confirm, null);
mContentView = inflater.inflate(R.layout.main_clear_confirm, null);
setUpActionBarAndTitle();
establishFinalConfirmationState();
setAccessibilityTitle();
@@ -351,7 +260,7 @@ public class MasterClearConfirm extends InstrumentedFragment {
void setSubtitle() {
if (mEraseEsims) {
((TextView) mContentView.findViewById(R.id.sud_layout_description))
.setText(R.string.master_clear_final_desc_esim);
.setText(R.string.main_clear_final_desc_esim);
}
}
@@ -361,9 +270,9 @@ public class MasterClearConfirm extends InstrumentedFragment {
Bundle args = getArguments();
mEraseSdCard = args != null
&& args.getBoolean(MasterClear.ERASE_EXTERNAL_EXTRA);
&& args.getBoolean(MainClear.ERASE_EXTERNAL_EXTRA);
mEraseEsims = args != null
&& args.getBoolean(MasterClear.ERASE_ESIMS_EXTRA);
&& args.getBoolean(MainClear.ERASE_ESIMS_EXTRA);
}
@Override

View File

@@ -16,6 +16,8 @@
package com.android.settings;
import static android.view.HapticFeedbackConstants.CLOCK_TICK;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -37,6 +39,7 @@ public class PointerSpeedPreference extends SeekBarDialogPreference implements
private boolean mRestoredOldState;
private boolean mTouchInProgress;
private int mLastProgress = -1;
private ContentObserver mSpeedObserver = new ContentObserver(new Handler()) {
@Override
@@ -70,12 +73,17 @@ public class PointerSpeedPreference extends SeekBarDialogPreference implements
mOldSpeed = mIm.getPointerSpeed(getContext());
mSeekBar.setProgress(mOldSpeed - InputManager.MIN_POINTER_SPEED);
mSeekBar.setOnSeekBarChangeListener(this);
mSeekBar.setContentDescription(getTitle());
}
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
if (!mTouchInProgress) {
mIm.tryPointerSpeed(progress + InputManager.MIN_POINTER_SPEED);
}
if (progress != mLastProgress) {
seekBar.performHapticFeedback(CLOCK_TICK);
mLastProgress = progress;
}
}
public void onStartTrackingTouch(SeekBar seekBar) {

View File

@@ -62,7 +62,8 @@ public class RegulatoryInfoDisplayActivity extends Activity implements
super.onCreate(savedInstanceState);
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setTitle(R.string.regulatory_labels)
.setOnDismissListener(this);
.setOnDismissListener(this)
.setPositiveButton(android.R.string.ok, null /* onClickListener */);
boolean regulatoryInfoDrawableExists = false;

View File

@@ -32,6 +32,7 @@ import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.euicc.EuiccManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
@@ -46,6 +47,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.ConfirmLockPattern;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
@@ -53,7 +55,9 @@ import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
/**
* Confirm and execute a reset of the device's network settings to a clean "just out of the box"
@@ -92,8 +96,11 @@ public class ResetNetwork extends InstrumentedFragment {
*/
private boolean runKeyguardConfirmation(int request) {
Resources res = getActivity().getResources();
return new ChooseLockSettingsHelper(getActivity(), this).launchConfirmationActivity(
request, res.getText(R.string.reset_network_title));
final ChooseLockSettingsHelper.Builder builder =
new ChooseLockSettingsHelper.Builder(getActivity(), this);
return builder.setRequestCode(request)
.setTitle(res.getText(R.string.reset_network_title))
.show();
}
@Override
@@ -109,7 +116,7 @@ public class ResetNetwork extends InstrumentedFragment {
if (resultCode == Activity.RESULT_OK) {
showFinalConfirmation();
} else {
establishInitialState();
establishInitialState(getActiveSubscriptionInfoList());
}
}
@@ -122,7 +129,7 @@ public class ResetNetwork extends InstrumentedFragment {
args.putInt(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
subscription.getSubscriptionId());
}
args.putBoolean(MasterClear.ERASE_ESIMS_EXTRA,
args.putBoolean(MainClear.ERASE_ESIMS_EXTRA,
mEsimContainer.getVisibility() == View.VISIBLE && mEsimCheckbox.isChecked());
new SubSettingLauncher(getContext())
.setDestination(ResetNetworkConfirm.class.getName())
@@ -158,14 +165,15 @@ public class ResetNetwork extends InstrumentedFragment {
* inflate each view, caching all of the widget pointers we'll need at the
* time, then simply reuse the inflated views directly whenever we need
* to change contents.
*
* @param subscriptionsList is a list of SubscriptionInfo(s) which allow user to select from
*/
private void establishInitialState() {
private void establishInitialState(List<SubscriptionInfo> subscriptionsList) {
mSubscriptionSpinner = (Spinner) mContentView.findViewById(R.id.reset_network_subscription);
mEsimContainer = mContentView.findViewById(R.id.erase_esim_container);
mEsimCheckbox = mContentView.findViewById(R.id.erase_esim);
mSubscriptions = SubscriptionManager.from(getActivity())
.getActiveSubscriptionInfoList();
mSubscriptions = subscriptionsList;
if (mSubscriptions != null && mSubscriptions.size() > 0) {
// Get the default subscription in the order of data, voice, sms, first up.
int defaultSubscription = SubscriptionManager.getDefaultDataSubscriptionId();
@@ -187,7 +195,8 @@ public class ResetNetwork extends InstrumentedFragment {
// Set the first selected value to the default
selectedIndex = subscriptionNames.size();
}
String name = record.getDisplayName().toString();
String name = SubscriptionUtil.getUniqueSubscriptionDisplayName(
record, getContext()).toString();
if (TextUtils.isEmpty(name)) {
name = record.getNumber();
}
@@ -228,6 +237,31 @@ public class ResetNetwork extends InstrumentedFragment {
}
}
private List<SubscriptionInfo> getActiveSubscriptionInfoList() {
SubscriptionManager mgr = getActivity().getSystemService(SubscriptionManager.class);
if (mgr == null) {
Log.w(TAG, "No SubscriptionManager");
return Collections.emptyList();
}
return Optional.ofNullable(mgr.getActiveSubscriptionInfoList())
.orElse(Collections.emptyList());
}
@Override
public void onResume() {
super.onResume();
// update options if subcription has been changed
List<SubscriptionInfo> updatedSubscriptions = getActiveSubscriptionInfoList();
if ((mSubscriptions != null)
&& (mSubscriptions.size() == updatedSubscriptions.size())
&& mSubscriptions.containsAll(updatedSubscriptions)) {
return;
}
Log.d(TAG, "subcription list changed");
establishInitialState(updatedSubscriptions);
}
private boolean showEuiccSettings(Context context) {
EuiccManager euiccManager =
(EuiccManager) context.getSystemService(Context.EUICC_SERVICE);
@@ -258,7 +292,7 @@ public class ResetNetwork extends InstrumentedFragment {
mContentView = inflater.inflate(R.layout.reset_network, null);
establishInitialState();
establishInitialState(getActiveSubscriptionInfoList());
return mContentView;
}

View File

@@ -33,11 +33,14 @@ import android.net.wifi.WifiManager;
import android.net.wifi.p2p.WifiP2pManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Looper;
import android.os.RecoverySystem;
import android.os.UserHandle;
import android.os.UserManager;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -50,7 +53,7 @@ import androidx.appcompat.app.AlertDialog;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper;
import com.android.settings.network.ApnSettings;
import com.android.settings.network.apn.ApnSettings;
import com.android.settingslib.RestrictedLockUtilsInternal;
/**
@@ -64,6 +67,7 @@ import com.android.settingslib.RestrictedLockUtilsInternal;
* This is the confirmation screen.
*/
public class ResetNetworkConfirm extends InstrumentedFragment {
private static final String TAG = "ResetNetworkConfirm";
@VisibleForTesting View mContentView;
@VisibleForTesting boolean mEraseEsim;
@@ -72,12 +76,15 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private ProgressDialog mProgressDialog;
private AlertDialog mAlertDialog;
private OnSubscriptionsChangedListener mSubscriptionsChangedListener;
/**
* Async task used to do all reset task. If error happens during
* erasing eSIM profiles or timeout, an error msg is shown.
*/
private class ResetNetworkTask extends AsyncTask<Void, Void, Boolean> {
private static final String TAG = "ResetNetworkTask";
private final Context mContext;
private final String mPackageName;
@@ -136,6 +143,8 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
}
restoreDefaultApn(mContext);
Log.d(TAG, "network factoryReset complete. succeeded: "
+ String.valueOf(isResetSucceed));
return isResetSucceed;
}
@@ -168,6 +177,18 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
return;
}
// abandon execution if subscription no longer active
if (mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
SubscriptionManager mgr = getSubscriptionManager();
// always remove listener
stopMonitorSubscriptionChange(mgr);
if (!isSubscriptionRemainActive(mgr, mSubId)) {
Log.w(TAG, "subId " + mSubId + " disappear when confirm");
mActivity.finish();
return;
}
}
mProgressDialog = getProgressDialog(mActivity);
mProgressDialog.show();
@@ -195,7 +216,7 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
progressDialog.setIndeterminate(true);
progressDialog.setCancelable(false);
progressDialog.setMessage(
context.getString(R.string.master_clear_progress_text));
context.getString(R.string.main_clear_progress_text));
return progressDialog;
}
@@ -258,10 +279,60 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
if (args != null) {
mSubId = args.getInt(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mEraseEsim = args.getBoolean(MasterClear.ERASE_ESIMS_EXTRA);
mEraseEsim = args.getBoolean(MainClear.ERASE_ESIMS_EXTRA);
}
mActivity = getActivity();
if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
return;
}
// close confirmation dialog when reset specific subscription
// but removed priori to the confirmation button been pressed
startMonitorSubscriptionChange(getSubscriptionManager());
}
private SubscriptionManager getSubscriptionManager() {
SubscriptionManager mgr = mActivity.getSystemService(SubscriptionManager.class);
if (mgr == null) {
Log.w(TAG, "No SubscriptionManager");
}
return mgr;
}
private void startMonitorSubscriptionChange(SubscriptionManager mgr) {
if (mgr == null) {
return;
}
// update monitor listener
mSubscriptionsChangedListener = new OnSubscriptionsChangedListener(
Looper.getMainLooper()) {
@Override
public void onSubscriptionsChanged() {
SubscriptionManager mgr = getSubscriptionManager();
if (isSubscriptionRemainActive(mgr, mSubId)) {
return;
}
// close UI if subscription no longer active
Log.w(TAG, "subId " + mSubId + " no longer active.");
stopMonitorSubscriptionChange(mgr);
mActivity.finish();
}
};
mgr.addOnSubscriptionsChangedListener(
mActivity.getMainExecutor(), mSubscriptionsChangedListener);
}
private boolean isSubscriptionRemainActive(SubscriptionManager mgr, int subscriptionId) {
return (mgr == null) ? false : (mgr.getActiveSubscriptionInfo(subscriptionId) != null);
}
private void stopMonitorSubscriptionChange(SubscriptionManager mgr) {
if ((mgr == null) || (mSubscriptionsChangedListener == null)) {
return;
}
mgr.removeOnSubscriptionsChangedListener(mSubscriptionsChangedListener);
mSubscriptionsChangedListener = null;
}
@Override
@@ -276,6 +347,7 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
if (mAlertDialog != null) {
mAlertDialog.dismiss();
}
stopMonitorSubscriptionChange(getSubscriptionManager());
super.onDestroy();
}

View File

@@ -19,10 +19,16 @@ package com.android.settings;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.FeatureFlagUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.core.FeatureFlags;
import com.android.settings.enterprise.EnterprisePrivacySettings;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.security.SecuritySettingsFeatureProvider;
import com.google.android.setupdesign.util.ThemeHelper;
/**
* Top-level Settings activity
@@ -37,6 +43,8 @@ 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 CombinedBiometricSettingsActivity extends SettingsActivity { /* empty */ }
public static class CombinedBiometricProfileSettingsActivity extends SettingsActivity { /* empty */ }
public static class TetherSettingsActivity extends SettingsActivity {
// TODO(b/147675042): Clean the override up when we enable the new Fragment persistently.
@Override
@@ -78,10 +86,10 @@ public class Settings extends SettingsActivity {
public static class DataSaverSummaryActivity extends SettingsActivity{ /* empty */ }
public static class DateTimeSettingsActivity extends SettingsActivity { /* empty */ }
public static class PrivateVolumeForgetActivity extends SettingsActivity { /* empty */ }
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 NetworkProviderSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiP2pSettingsActivity extends SettingsActivity { /* empty */ }
public static class AvailableVirtualKeyboardActivity extends SettingsActivity { /* empty */ }
public static class KeyboardLayoutPickerActivity extends SettingsActivity { /* empty */ }
@@ -95,6 +103,7 @@ public class Settings extends SettingsActivity {
public static class DisplaySettingsActivity extends SettingsActivity { /* empty */ }
public static class NightDisplaySettingsActivity extends SettingsActivity { /* empty */ }
public static class NightDisplaySuggestionActivity extends NightDisplaySettingsActivity { /* empty */ }
public static class SmartAutoRotateSettingsActivity extends SettingsActivity { /* empty */ }
public static class MyDeviceInfoActivity extends SettingsActivity { /* empty */ }
public static class ModuleLicensesActivity extends SettingsActivity { /* empty */ }
public static class ApplicationSettingsActivity extends SettingsActivity { /* empty */ }
@@ -110,14 +119,79 @@ public class Settings extends SettingsActivity {
public static class AccessibilityInversionSettingsActivity extends SettingsActivity { /* empty */ }
public static class AccessibilityContrastSettingsActivity extends SettingsActivity { /* empty */ }
public static class AccessibilityDaltonizerSettingsActivity extends SettingsActivity { /* empty */ }
public static class SecurityDashboardActivity extends SettingsActivity { /* empty */ }
/**
* Activity for lockscreen settings.
*/
public static class LockScreenSettingsActivity extends SettingsActivity { /* empty */ }
/**
* Activity for Reduce Bright Colors.
*/
public static class ReduceBrightColorsSettingsActivity extends SettingsActivity { /* empty */ }
/** Activity for the security dashboard. */
public static class SecurityDashboardActivity extends SettingsActivity {
/** Whether the given fragment is allowed. */
@VisibleForTesting
@Override
public boolean isValidFragment(String fragmentName) {
return super.isValidFragment(fragmentName)
|| (fragmentName != null
&& TextUtils.equals(fragmentName, getAlternativeFragmentName()));
}
@Override
public String getInitialFragmentName(Intent intent) {
final String alternativeFragmentName = getAlternativeFragmentName();
if (alternativeFragmentName != null) {
return alternativeFragmentName;
}
return super.getInitialFragmentName(intent);
}
private String getAlternativeFragmentName() {
String alternativeFragmentClassname = null;
final SecuritySettingsFeatureProvider securitySettingsFeatureProvider =
FeatureFactory.getFactory(this).getSecuritySettingsFeatureProvider();
if (securitySettingsFeatureProvider.hasAlternativeSecuritySettingsFragment()) {
alternativeFragmentClassname = securitySettingsFeatureProvider
.getAlternativeSecuritySettingsFragmentClassname();
}
return alternativeFragmentClassname;
}
}
public static class UsageAccessSettingsActivity extends SettingsActivity { /* empty */ }
public static class AppUsageAccessSettingsActivity extends SettingsActivity { /* empty */ }
public static class LocationSettingsActivity extends SettingsActivity { /* empty */ }
public static class ScanningSettingsActivity extends SettingsActivity { /* empty */ }
public static class PrivacyDashboardActivity extends SettingsActivity { /* empty */ }
public static class PrivacySettingsActivity extends SettingsActivity { /* empty */ }
public static class FactoryResetActivity extends SettingsActivity { /* empty */ }
public static class FactoryResetActivity extends SettingsActivity {
@Override
protected void onCreate(Bundle savedState) {
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
ThemeHelper.trySetDynamicColor(this);
super.onCreate(savedState);
}
@Override
protected boolean isToolbarEnabled() {
return false;
}
}
public static class FactoryResetConfirmActivity extends SettingsActivity {
@Override
protected void onCreate(Bundle savedState) {
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
ThemeHelper.trySetDynamicColor(this);
super.onCreate(savedState);
}
@Override
protected boolean isToolbarEnabled() {
return false;
}
}
public static class RunningServicesActivity extends SettingsActivity { /* empty */ }
public static class BatterySaverSettingsActivity extends SettingsActivity { /* empty */ }
public static class BatterySaverScheduleSettingsActivity extends SettingsActivity { /* empty */ }
@@ -168,10 +242,6 @@ public class Settings extends SettingsActivity {
public static class ManageDomainUrlsActivity extends SettingsActivity { /* empty */ }
public static class AutomaticStorageManagerSettingsActivity extends SettingsActivity { /* empty */ }
public static class GamesStorageActivity extends SettingsActivity { /* empty */ }
public static class MoviesStorageActivity extends SettingsActivity { /* empty */ }
public static class PhotosStorageActivity extends SettingsActivity {
/* empty */
}
public static class GestureNavigationSettingsActivity extends SettingsActivity { /* empty */ }
public static class InteractAcrossProfilesSettingsActivity extends SettingsActivity {
/* empty */
@@ -187,6 +257,8 @@ public class Settings extends SettingsActivity {
public static class OverlaySettingsActivity extends SettingsActivity { /* empty */ }
public static class ManageExternalStorageActivity extends SettingsActivity { /* empty */ }
public static class AppManageExternalStorageActivity extends SettingsActivity { /* empty */ }
public static class MediaManagementAppsActivity extends SettingsActivity { /* empty */ }
public static class AppMediaManagementAppsActivity extends SettingsActivity { /* empty */ }
public static class WriteSettingsActivity extends SettingsActivity { /* empty */ }
public static class ChangeWifiStateActivity extends SettingsActivity { /* empty */ }
public static class AppDrawOverlaySettingsActivity extends SettingsActivity { /* empty */ }
@@ -198,6 +270,10 @@ public class Settings extends SettingsActivity {
public static class ManagedProfileSettingsActivity extends SettingsActivity { /* empty */ }
public static class DeletionHelperActivity extends SettingsActivity { /* empty */ }
/** Actviity to manage apps with {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM} */
public static class AlarmsAndRemindersActivity extends SettingsActivity {/* empty */ }
/** App specific version of {@link AlarmsAndRemindersActivity} */
public static class AlarmsAndRemindersAppActivity extends SettingsActivity {/* empty */ }
public static class ApnEditorActivity extends SettingsActivity { /* empty */ }
public static class ChooseAccountActivity extends SettingsActivity { /* empty */ }
@@ -209,7 +285,11 @@ public class Settings extends SettingsActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!EnterprisePrivacySettings.isPageEnabled(this)) {
if (FeatureFactory.getFactory(this)
.getEnterprisePrivacyFeatureProvider(this)
.showParentalControls()) {
finish();
} else if (!EnterprisePrivacySettings.isPageEnabled(this)) {
finish();
}
}
@@ -219,9 +299,7 @@ public class Settings extends SettingsActivity {
public static class BluetoothDeviceDetailActivity extends SettingsActivity { /* empty */ }
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.
*/
@@ -231,7 +309,6 @@ public class Settings extends SettingsActivity {
public static class NetworkDashboardActivity extends SettingsActivity {}
public static class ConnectedDeviceDashboardActivity extends SettingsActivity {}
public static class PowerUsageSummaryActivity extends SettingsActivity { /* empty */ }
public static class AppAndNotificationDashboardActivity extends SettingsActivity {}
public static class StorageDashboardActivity extends SettingsActivity {}
public static class AccountDashboardActivity extends SettingsActivity {}
public static class SystemDashboardActivity extends SettingsActivity {}
@@ -241,4 +318,8 @@ public class Settings extends SettingsActivity {
*/
public static class MediaControlsSettingsActivity extends SettingsActivity {}
/**
* Activity for AppDashboard.
*/
public static class AppDashboardActivity extends SettingsActivity {}
}

View File

@@ -61,7 +61,7 @@ import com.android.settings.dashboard.DashboardFeatureProvider;
import com.android.settings.homepage.TopLevelSettings;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.wfd.WifiDisplaySettings;
import com.android.settings.widget.SwitchBar;
import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.core.instrumentation.SharedPreferencesLogger;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
@@ -172,7 +172,7 @@ public class SettingsActivity extends SettingsBaseActivity
}
};
private SwitchBar mSwitchBar;
private SettingsMainSwitchBar mMainSwitch;
private Button mNextButton;
@@ -181,8 +181,8 @@ public class SettingsActivity extends SettingsBaseActivity
private DashboardFeatureProvider mDashboardFeatureProvider;
public SwitchBar getSwitchBar() {
return mSwitchBar;
public SettingsMainSwitchBar getSwitchBar() {
return mMainSwitch;
}
@Override
@@ -215,7 +215,7 @@ public class SettingsActivity extends SettingsBaseActivity
private String getMetricsTag() {
String tag = null;
if (getIntent() != null && getIntent().hasExtra(EXTRA_SHOW_FRAGMENT)) {
tag = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
tag = getInitialFragmentName(getIntent());
}
if (TextUtils.isEmpty(tag)) {
Log.w(LOG_TAG, "MetricsTag is invalid " + tag);
@@ -246,7 +246,7 @@ public class SettingsActivity extends SettingsBaseActivity
}
// Getting Intent properties can only be done after the super.onCreate(...)
final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
final String initialFragmentName = getInitialFragmentName(intent);
// This is a "Sub Settings" when:
// - this is a real SubSettings
@@ -290,9 +290,10 @@ public class SettingsActivity extends SettingsBaseActivity
actionBar.setHomeButtonEnabled(!isInSetupWizard);
actionBar.setDisplayShowTitleEnabled(true);
}
mSwitchBar = findViewById(R.id.switch_bar);
if (mSwitchBar != null) {
mSwitchBar.setMetricsTag(getMetricsTag());
mMainSwitch = findViewById(R.id.switch_bar);
if (mMainSwitch != null) {
mMainSwitch.setMetricsTag(getMetricsTag());
mMainSwitch.setTranslationZ(findViewById(R.id.main_content).getTranslationZ() + 1);
}
// see if we should show Back/Next buttons
@@ -346,6 +347,12 @@ public class SettingsActivity extends SettingsBaseActivity
}
}
/** Returns the initial fragment name that the activity will launch. */
@VisibleForTesting
public String getInitialFragmentName(Intent intent) {
return intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
}
@Override
protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
theme.applyStyle(R.style.SetupWizardPartnerResource, true);
@@ -567,7 +574,7 @@ public class SettingsActivity extends SettingsBaseActivity
/**
* Switch to a specific Fragment with taking care of validation, Title and BackStack
*/
private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
private void switchToFragment(String fragmentName, Bundle args, boolean validate,
int titleResId, CharSequence title) {
Log.d(LOG_TAG, "Switching to fragment " + fragmentName);
if (validate && !isValidFragment(fragmentName)) {
@@ -575,6 +582,9 @@ public class SettingsActivity extends SettingsBaseActivity
+ fragmentName);
}
Fragment f = Utils.getTargetFragment(this, fragmentName, args);
if (f == null) {
return;
}
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.main_content, f);
if (titleResId > 0) {
@@ -585,7 +595,6 @@ public class SettingsActivity extends SettingsBaseActivity
transaction.commitAllowingStateLoss();
getSupportFragmentManager().executePendingTransactions();
Log.d(LOG_TAG, "Executed frag manager pendingTransactions");
return f;
}
private void updateTilesList() {
@@ -678,7 +687,7 @@ public class SettingsActivity extends SettingsBaseActivity
if (somethingChanged) {
Log.d(LOG_TAG, "Enabled state changed for some tiles, reloading all categories "
+ changedList.toString());
updateCategories();
mCategoryMixin.updateCategories();
} else {
Log.d(LOG_TAG, "No enabled state changed, skipping updateCategory call");
}

View File

@@ -111,9 +111,13 @@ public class SettingsDumpService extends Service {
for (SubscriptionInfo info : manager.getAvailableSubscriptionInfoList()) {
telephonyManager = telephonyManager
.createForSubscriptionId(info.getSubscriptionId());
NetworkTemplate carrier = NetworkTemplate.buildTemplateCarrierMetered(
telephonyManager.getSubscriberId());
final JSONObject usage = dumpDataUsage(carrier, controller);
String subscriberId = telephonyManager.getSubscriberId();
// The null subscriberId means that no any mobile/carrier network will be matched.
// Using old API: buildTemplateMobileAll for the null subscriberId to avoid NPE.
NetworkTemplate template = subscriberId != null
? NetworkTemplate.buildTemplateCarrierMetered(subscriberId)
: NetworkTemplate.buildTemplateMobileAll(subscriberId);
final JSONObject usage = dumpDataUsage(template, controller);
usage.put("subId", info.getSubscriptionId());
array.put(usage);
}

View File

@@ -39,6 +39,7 @@ import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.settings.Settings.CreateShortcutActivity;
import com.android.settingslib.utils.ThreadUtils;
import java.util.ArrayList;
import java.util.List;
@@ -62,7 +63,7 @@ public class SettingsInitialize extends BroadcastReceiver {
final PackageManager pm = context.getPackageManager();
managedProfileSetup(context, pm, broadcast, userInfo);
webviewSettingSetup(context, pm, userInfo);
refreshExistingShortcuts(context);
ThreadUtils.postOnBackgroundThread(() -> refreshExistingShortcuts(context));
}
private void managedProfileSetup(Context context, final PackageManager pm, Intent broadcast,
@@ -142,5 +143,4 @@ public class SettingsInitialize extends BroadcastReceiver {
}
shortcutManager.updateShortcuts(updates);
}
}

View File

@@ -44,8 +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.actionbar.SearchMenuController;
import com.android.settings.support.actionbar.HelpMenuController;
import com.android.settings.support.actionbar.HelpResourceProvider;
import com.android.settings.widget.HighlightablePreferenceGroupAdapter;
import com.android.settings.widget.LoadingViewController;
@@ -55,6 +53,8 @@ import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.search.Indexable;
import com.android.settingslib.widget.LayoutPreference;
import com.google.android.material.appbar.AppBarLayout;
import java.util.UUID;
/**
@@ -110,9 +110,8 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
@VisibleForTesting
ViewGroup mPinnedHeaderFrameLayout;
private AppBarLayout mAppBarLayout;
private LayoutPreference mHeader;
private View mEmptyView;
private LinearLayoutManager mLayoutManager;
private ArrayMap<String, Preference> mPreferenceCache;
@@ -126,8 +125,6 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
SearchMenuController.init(this /* host */);
HelpMenuController.init(this /* host */);
if (icicle != null) {
mPreferenceHighlighted = icicle.getBoolean(SAVE_HIGHLIGHTED_KEY);
@@ -140,6 +137,7 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
Bundle savedInstanceState) {
final View root = super.onCreateView(inflater, container, savedInstanceState);
mPinnedHeaderFrameLayout = root.findViewById(R.id.pinned_header);
mAppBarLayout = getActivity().findViewById(R.id.app_bar);
return root;
}
@@ -245,7 +243,7 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
return;
}
if (mAdapter != null) {
mAdapter.requestHighlight(getView(), getListView());
mAdapter.requestHighlight(getView(), getListView(), mAppBarLayout);
}
}
@@ -438,6 +436,13 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
return getActivity().getSystemService(name);
}
/**
* Returns the specified system service from the owning Activity.
*/
protected <T> T getSystemService(final Class<T> serviceClass) {
return getActivity().getSystemService(serviceClass);
}
/**
* Returns the PackageManager from the owning Activity.
*/

View File

@@ -19,6 +19,7 @@ package com.android.settings;
import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_FIRST_RUN;
import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_SETUP_FLOW;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.sysprop.SetupWizardProperties;
@@ -39,25 +40,39 @@ public class SetupWizardUtils {
return theme;
}
public static int getTheme(Intent intent) {
public static int getTheme(Context context, Intent intent) {
String theme = getThemeString(intent);
// TODO(yukl): Move to ThemeResolver and add any additional required attributes in
// onApplyThemeResource using Theme overlays
if (theme != null) {
if (WizardManagerHelper.isAnySetupWizard(intent)) {
switch (theme) {
case ThemeHelper.THEME_GLIF_V3_LIGHT:
return R.style.GlifV3Theme_Light;
case ThemeHelper.THEME_GLIF_V3:
return R.style.GlifV3Theme;
case ThemeHelper.THEME_GLIF_V2_LIGHT:
return R.style.GlifV2Theme_Light;
case ThemeHelper.THEME_GLIF_V2:
return R.style.GlifV2Theme;
case ThemeHelper.THEME_GLIF_LIGHT:
return R.style.GlifTheme_Light;
case ThemeHelper.THEME_GLIF:
return R.style.GlifTheme;
if (ThemeHelper.isSetupWizardDayNightEnabled(context)) {
switch (theme) {
case ThemeHelper.THEME_GLIF_V3_LIGHT:
case ThemeHelper.THEME_GLIF_V3:
return R.style.GlifV3Theme_DayNight;
case ThemeHelper.THEME_GLIF_V2_LIGHT:
case ThemeHelper.THEME_GLIF_V2:
return R.style.GlifV2Theme_DayNight;
case ThemeHelper.THEME_GLIF_LIGHT:
case ThemeHelper.THEME_GLIF:
return R.style.GlifTheme_DayNight;
}
} else {
switch (theme) {
case ThemeHelper.THEME_GLIF_V3_LIGHT:
return R.style.GlifV3Theme_Light;
case ThemeHelper.THEME_GLIF_V3:
return R.style.GlifV3Theme;
case ThemeHelper.THEME_GLIF_V2_LIGHT:
return R.style.GlifV2Theme_Light;
case ThemeHelper.THEME_GLIF_V2:
return R.style.GlifV2Theme;
case ThemeHelper.THEME_GLIF_LIGHT:
return R.style.GlifTheme_Light;
case ThemeHelper.THEME_GLIF:
return R.style.GlifTheme;
}
}
} else {
switch (theme) {
@@ -76,17 +91,30 @@ public class SetupWizardUtils {
return R.style.GlifTheme;
}
public static int getTransparentTheme(Intent intent) {
final int suwTheme = getTheme(intent);
int transparentTheme = R.style.GlifV2Theme_Light_Transparent;
if (suwTheme == R.style.GlifV3Theme) {
transparentTheme = R.style.GlifV3Theme_Transparent;
public static int getTransparentTheme(Context context, Intent intent) {
int transparentTheme;
final int suwTheme = getTheme(context, intent);
if (ThemeHelper.isSetupWizardDayNightEnabled(context)) {
transparentTheme = R.style.GlifV2Theme_DayNight_Transparent;
} else {
transparentTheme = R.style.GlifV2Theme_Light_Transparent;
}
if (suwTheme == R.style.GlifV3Theme_DayNight) {
transparentTheme = R.style.GlifV3Theme_DayNight_Transparent;
} else if (suwTheme == R.style.GlifV3Theme_Light) {
transparentTheme = R.style.GlifV3Theme_Light_Transparent;
} else if (suwTheme == R.style.GlifV2Theme) {
transparentTheme = R.style.GlifV2Theme_Transparent;
} else if (suwTheme == R.style.GlifV2Theme_DayNight) {
transparentTheme = R.style.GlifV2Theme_DayNight_Transparent;
} else if (suwTheme == R.style.GlifV2Theme_Light) {
transparentTheme = R.style.GlifV2Theme_Light_Transparent;
} else if (suwTheme == R.style.GlifTheme_DayNight) {
transparentTheme = R.style.SetupWizardTheme_DayNight_Transparent;
} else if (suwTheme == R.style.GlifTheme_Light) {
transparentTheme = R.style.SetupWizardTheme_Light_Transparent;
} else if (suwTheme == R.style.GlifV3Theme) {
transparentTheme = R.style.GlifV3Theme_Transparent;
} else if (suwTheme == R.style.GlifV2Theme) {
transparentTheme = R.style.GlifV2Theme_Transparent;
} else if (suwTheme == R.style.GlifTheme) {
transparentTheme = R.style.SetupWizardTheme_Transparent;
}

View File

@@ -0,0 +1,364 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.CallSuper;
import androidx.annotation.IntDef;
import com.android.settingslib.utils.ThreadUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* A headless fragment encapsulating a long-running action such as a network RPC surviving rotation.
*
* <p>Subclasses should implement their own state machine, updating the state on each state change
* via {@link #setState(int, int)}. They can define their own states, however, it is suggested that
* the pre-defined {@link @State} constants are used and customizations are implemented via
* substates. Custom states must be outside the range of pre-defined states.
*
* <p>It is safe to update the state at any time, but state updates must originate from the main
* thread.
*
* <p>A listener can be attached that receives state updates while it's registered. Note that state
* change events can occur at any point in time and hence a registered listener should unregister if
* it cannot act upon the state change (typically a non-resumed fragment).
*
* <p>Listeners can receive state changes for the same state/substate combination, so listeners
* should make sure to be idempotent during state change events.
*
* <p>If a SidecarFragment is only relevant during the lifetime of another fragment (for example, a
* sidecar performing a details request for a DetailsFragment), that fragment needs to become the
* managing fragment of the sidecar.
*
* <h2>Managing fragment responsibilities</h2>
*
* <ol>
* <li>Instantiates the sidecar fragment when necessary, preferably in {@link #onStart}.
* <li>Removes the sidecar fragment when it's no longer used or when itself is removed. Removal of
* the managing fragment can be detected by checking {@link #isRemoving} in {@link #onStop}.
* <br>
* <li>Registers as a listener in {@link #onResume()}, unregisters in {@link #onPause()}.
* <li>Starts the long-running operation by calling into the sidecar.
* <li>Receives state updates via {@link Listener#onStateChange(SidecarFragment)} and updates the
* UI accordingly.
* </ol>
*
* <h2>Managing fragment example</h2>
*
* <pre>
* public class MainFragment implements SidecarFragment.Listener {
* private static final String TAG_SOME_SIDECAR = ...;
* private static final String KEY_SOME_SIDECAR_STATE = ...;
*
* private SomeSidecarFragment mSidecar;
*
* &#064;Override
* public void onStart() {
* super.onStart();
* Bundle args = ...; // optional args
* mSidecar = SidecarFragment.get(getFragmentManager(), TAG_SOME_SIDECAR,
* SidecarFragment.class, args);
* }
*
* &#064;Override
* public void onResume() {
* mSomeSidecar.addListener(this);
* }
*
* &#064;Override
* public void onPause() {
* mSomeSidecar.removeListener(this):
* }
* }
* </pre>
*/
public class SidecarFragment extends Fragment {
private static final String TAG = "SidecarFragment";
/**
* Get an instance of this sidecar.
*
* <p>Will return the existing instance if one is already present. Note that the args will not
* be used in this situation, so args must be constant for any particular fragment manager and
* tag.
*/
@SuppressWarnings("unchecked")
protected static <T extends SidecarFragment> T get(
FragmentManager fm, String tag, Class<T> clazz, Bundle args) {
T fragment = (T) fm.findFragmentByTag(tag);
if (fragment == null) {
try {
fragment = clazz.newInstance();
} catch (java.lang.InstantiationException e) {
throw new InstantiationException("Unable to create fragment", e);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Unable to create fragment", e);
}
if (args != null) {
fragment.setArguments(args);
}
fm.beginTransaction().add(fragment, tag).commit();
// No real harm in doing this here - get() should generally only be called from onCreate
// which is on the main thread - and it allows us to start running the sidecar on this
// instance immediately rather than having to wait until the transaction commits.
fm.executePendingTransactions();
}
return fragment;
}
/** State definitions. @see {@link #getState} */
@Retention(RetentionPolicy.SOURCE)
@IntDef({State.INIT, State.RUNNING, State.SUCCESS, State.ERROR})
public @interface State {
/** Initial idling state. */
int INIT = 0;
/** The long-running operation is in progress. */
int RUNNING = 1;
/** The long-running operation has succeeded. */
int SUCCESS = 2;
/** The long-running operation has failed. */
int ERROR = 3;
}
/** Substate definitions. @see {@link #getSubstate} */
@Retention(RetentionPolicy.SOURCE)
@IntDef({
Substate.UNUSED,
Substate.RUNNING_BIND_SERVICE,
Substate.RUNNING_GET_ACTIVATION_CODE,
})
public @interface Substate {
// Unknown/unused substate.
int UNUSED = 0;
int RUNNING_BIND_SERVICE = 1;
int RUNNING_GET_ACTIVATION_CODE = 2;
// Future tags: 3+
}
/** **************************************** */
private Set<Listener> mListeners = new CopyOnWriteArraySet<>();
// Used to track whether onCreate has been called yet.
private boolean mCreated;
@State private int mState;
@Substate private int mSubstate;
/** A listener receiving state change events. */
public interface Listener {
/**
* Called upon any state or substate change.
*
* <p>The new state can be queried through {@link #getState} and {@link #getSubstate}.
*
* <p>Called from the main thread.
*
* @param fragment the SidecarFragment that changed its state
*/
void onStateChange(SidecarFragment fragment);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mCreated = true;
setState(State.INIT, Substate.UNUSED);
}
@Override
public void onDestroy() {
mCreated = false;
super.onDestroy();
}
/**
* Registers a listener that will receive subsequent state changes.
*
* <p>A {@link Listener#onStateChange(SidecarFragment)} event is fired as part of this call
* unless {@link #onCreate} has not yet been called (which means that it's unsafe to access this
* fragment as it has not been setup or restored completely). In that case, the future call to
* onCreate will trigger onStateChange on registered listener.
*
* <p>Must be called from the main thread.
*
* @param listener a listener, or null for unregistering the current listener
*/
public void addListener(Listener listener) {
ThreadUtils.ensureMainThread();
mListeners.add(listener);
if (mCreated) {
notifyListener(listener);
}
}
/**
* Removes a previously registered listener.
*
* @return {@code true} if the listener was removed, {@code false} if there was no such listener
* registered.
*/
public boolean removeListener(Listener listener) {
ThreadUtils.ensureMainThread();
return mListeners.remove(listener);
}
/** Returns the current state. */
@State
public int getState() {
return mState;
}
/** Returns the current substate. */
@Substate
public int getSubstate() {
return mSubstate;
}
/**
* Resets the sidecar to its initial state.
*
* <p>Implementers can override this method to perform additional reset tasks, but must call the
* super method.
*/
@CallSuper
public void reset() {
setState(State.INIT, Substate.UNUSED);
}
/**
* Updates the state and substate and notifies the registered listener.
*
* <p>Must be called from the main thread.
*
* @param state the state to transition to
* @param substate the substate to transition to
*/
protected void setState(@State int state, @Substate int substate) {
ThreadUtils.ensureMainThread();
mState = state;
mSubstate = substate;
notifyAllListeners();
printState();
}
private void notifyAllListeners() {
for (Listener listener : mListeners) {
notifyListener(listener);
}
}
private void notifyListener(Listener listener) {
listener.onStateChange(this);
}
/** Prints the state of the sidecar. */
public void printState() {
StringBuilder sb =
new StringBuilder("SidecarFragment.setState(): Sidecar Class: ")
.append(getClass().getCanonicalName());
sb.append(", State: ");
switch (mState) {
case SidecarFragment.State.INIT:
sb.append("State.INIT");
break;
case SidecarFragment.State.RUNNING:
sb.append("State.RUNNING");
break;
case SidecarFragment.State.SUCCESS:
sb.append("State.SUCCESS");
break;
case SidecarFragment.State.ERROR:
sb.append("State.ERROR");
break;
default:
sb.append(mState);
break;
}
switch (mSubstate) {
case SidecarFragment.Substate.UNUSED:
sb.append(", Substate.UNUSED");
break;
default:
sb.append(", ").append(mSubstate);
break;
}
Log.v(TAG, sb.toString());
}
@Override
public String toString() {
return String.format(
Locale.US,
"SidecarFragment[mState=%d, mSubstate=%d]: %s",
mState,
mSubstate,
super.toString());
}
/** The State of the sidecar status. */
public static final class States {
public static final States SUCCESS = States.create(State.SUCCESS, Substate.UNUSED);
public static final States ERROR = States.create(State.ERROR, Substate.UNUSED);
@State public final int state;
@Substate public final int substate;
/** Creates a new sidecar state. */
public static States create(@State int state, @Substate int substate) {
return new States(state, substate);
}
public States(@State int state, @Substate int substate) {
this.state = state;
this.substate = substate;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof States)) {
return false;
}
States other = (States) o;
return this.state == other.state && this.substate == other.substate;
}
@Override
public int hashCode() {
return state * 31 + substate;
}
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings;
import android.content.BroadcastReceiver;
@@ -15,7 +31,8 @@ public class TestingSettingsBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(TelephonyManager.ACTION_SECRET_CODE)) {
if (intent != null && intent.getAction() != null
&& intent.getAction().equals(TelephonyManager.ACTION_SECRET_CODE)) {
Intent i = new Intent(Intent.ACTION_MAIN);
i.setClass(context, TestingSettingsActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

View File

@@ -20,6 +20,8 @@ import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
import static android.net.ConnectivityManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static com.android.settingslib.RestrictedLockUtilsInternal.checkIfUsbDataSignalingIsDisabled;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
@@ -38,10 +40,12 @@ import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.SearchIndexableResource;
import android.text.TextUtils;
import android.util.FeatureFlagUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -51,6 +55,7 @@ import com.android.settings.core.FeatureFlags;
import com.android.settings.datausage.DataSaverBackend;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.wifi.tether.WifiTetherPreferenceController;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.TetherUtil;
import com.android.settingslib.search.SearchIndexable;
@@ -78,15 +83,12 @@ public class TetherSettings extends RestrictedSettingsFragment
private static final String KEY_ENABLE_ETHERNET_TETHERING = "enable_ethernet_tethering";
private static final String KEY_DATA_SAVER_FOOTER = "disabled_on_data_saver";
@VisibleForTesting
static final String KEY_TETHER_PREFS_FOOTER = "tether_prefs_footer";
@VisibleForTesting
static final String BLUETOOTH_TETHERING_STATE_CHANGED =
"android.bluetooth.pan.profile.action.TETHERING_STATE_CHANGED";
static final String KEY_TETHER_PREFS_TOP_INTRO = "tether_prefs_top_intro";
private static final String TAG = "TetheringSettings";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private SwitchPreference mUsbTether;
private RestrictedSwitchPreference mUsbTether;
private SwitchPreference mBluetoothTether;
@@ -94,7 +96,6 @@ public class TetherSettings extends RestrictedSettingsFragment
private BroadcastReceiver mTetherChangeReceiver;
private String[] mUsbRegexs;
private String[] mBluetoothRegexs;
private String mEthernetRegex;
private AtomicReference<BluetoothPan> mBluetoothPan = new AtomicReference<>();
@@ -103,7 +104,6 @@ public class TetherSettings extends RestrictedSettingsFragment
private OnStartTetheringCallback mStartTetheringCallback;
private ConnectivityManager mCm;
private EthernetManager mEm;
private TetheringManager mTm;
private TetheringEventCallback mTetheringEventCallback;
private EthernetListener mEthernetListener;
@@ -119,6 +119,13 @@ public class TetherSettings extends RestrictedSettingsFragment
private boolean mDataSaverEnabled;
private Preference mDataSaverFooter;
@VisibleForTesting
String[] mUsbRegexs;
@VisibleForTesting
Context mContext;
@VisibleForTesting
TetheringManager mTm;
@Override
public int getMetricsCategory() {
return SettingsEnums.TETHER;
@@ -140,7 +147,8 @@ public class TetherSettings extends RestrictedSettingsFragment
super.onCreate(icicle);
addPreferencesFromResource(R.xml.tether_prefs);
mDataSaverBackend = new DataSaverBackend(getContext());
mContext = getContext();
mDataSaverBackend = new DataSaverBackend(mContext);
mDataSaverEnabled = mDataSaverBackend.isDataSaverEnabled();
mDataSaverFooter = findPreference(KEY_DATA_SAVER_FOOTER);
@@ -159,7 +167,7 @@ public class TetherSettings extends RestrictedSettingsFragment
}
setupTetherPreference();
setFooterPreferenceTitle();
setTopIntroPreferenceTitle();
mDataSaverBackend.addListener(this);
@@ -169,7 +177,7 @@ public class TetherSettings extends RestrictedSettingsFragment
mUsbRegexs = mTm.getTetherableUsbRegexs();
mBluetoothRegexs = mTm.getTetherableBluetoothRegexs();
mEthernetRegex = getContext().getResources().getString(
mEthernetRegex = mContext.getResources().getString(
com.android.internal.R.string.config_ethernet_iface_regex);
final boolean usbAvailable = mUsbRegexs.length != 0;
@@ -212,7 +220,7 @@ public class TetherSettings extends RestrictedSettingsFragment
@VisibleForTesting
void setupTetherPreference() {
mUsbTether = (SwitchPreference) findPreference(KEY_USB_TETHER_SETTINGS);
mUsbTether = (RestrictedSwitchPreference) findPreference(KEY_USB_TETHER_SETTINGS);
mBluetoothTether = (SwitchPreference) findPreference(KEY_ENABLE_BLUETOOTH_TETHERING);
mEthernetTether = (SwitchPreference) findPreference(KEY_ENABLE_ETHERNET_TETHERING);
}
@@ -227,22 +235,21 @@ public class TetherSettings extends RestrictedSettingsFragment
}
@Override
public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) {
public void onAllowlistStatusChanged(int uid, boolean isAllowlisted) {
}
@Override
public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) {
public void onDenylistStatusChanged(int uid, boolean isDenylisted) {
}
@VisibleForTesting
void setFooterPreferenceTitle() {
final Preference footerPreference = findPreference(KEY_TETHER_PREFS_FOOTER);
final WifiManager wifiManager =
(WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
void setTopIntroPreferenceTitle() {
final Preference topIntroPreference = findPreference(KEY_TETHER_PREFS_TOP_INTRO);
final WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
if (wifiManager.isStaApConcurrencySupported()) {
footerPreference.setTitle(R.string.tethering_footer_info_sta_ap_concurrency);
topIntroPreference.setTitle(R.string.tethering_footer_info_sta_ap_concurrency);
} else {
footerPreference.setTitle(R.string.tethering_footer_info);
topIntroPreference.setTitle(R.string.tethering_footer_info);
}
}
@@ -250,27 +257,32 @@ public class TetherSettings extends RestrictedSettingsFragment
@Override
public void onReceive(Context content, Intent intent) {
String action = intent.getAction();
// TODO: stop using ACTION_TETHER_STATE_CHANGED and use mTetheringEventCallback instead.
if (DEBUG) {
Log.d(TAG, "onReceive() action : " + action);
}
// TODO(b/194961339): Stop using ACTION_TETHER_STATE_CHANGED and use
// mTetheringEventCallback instead.
if (action.equals(TetheringManager.ACTION_TETHER_STATE_CHANGED)) {
// TODO - this should understand the interface types
ArrayList<String> available = intent.getStringArrayListExtra(
TetheringManager.EXTRA_AVAILABLE_TETHER);
ArrayList<String> active = intent.getStringArrayListExtra(
TetheringManager.EXTRA_ACTIVE_TETHER);
ArrayList<String> errored = intent.getStringArrayListExtra(
TetheringManager.EXTRA_ERRORED_TETHER);
updateState(available.toArray(new String[available.size()]),
active.toArray(new String[active.size()]),
errored.toArray(new String[errored.size()]));
updateBluetoothState();
updateEthernetState(available.toArray(new String[available.size()]),
active.toArray(new String[active.size()]));
} else if (action.equals(Intent.ACTION_MEDIA_SHARED)) {
mMassStorageActive = true;
updateState();
updateBluetoothAndEthernetState();
updateUsbPreference();
} else if (action.equals(Intent.ACTION_MEDIA_UNSHARED)) {
mMassStorageActive = false;
updateState();
updateBluetoothAndEthernetState();
updateUsbPreference();
} else if (action.equals(UsbManager.ACTION_USB_STATE)) {
mUsbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
updateState();
updateBluetoothAndEthernetState();
updateUsbPreference();
} else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
if (mBluetoothEnableForTether) {
switch (intent
@@ -289,9 +301,9 @@ public class TetherSettings extends RestrictedSettingsFragment
// ignore transition states
}
}
updateState();
} else if (action.equals(BLUETOOTH_TETHERING_STATE_CHANGED)) {
updateState();
updateBluetoothAndEthernetState();
} else if (action.equals(BluetoothPan.ACTION_TETHERING_STATE_CHANGED)) {
updateBluetoothAndEthernetState();
}
}
}
@@ -320,7 +332,8 @@ public class TetherSettings extends RestrictedSettingsFragment
if (mEm != null)
mEm.addListener(mEthernetListener);
updateState();
updateUsbState();
updateBluetoothAndEthernetState();
}
@Override
@@ -360,64 +373,66 @@ public class TetherSettings extends RestrictedSettingsFragment
filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BLUETOOTH_TETHERING_STATE_CHANGED);
filter.addAction(BluetoothPan.ACTION_TETHERING_STATE_CHANGED);
activity.registerReceiver(mTetherChangeReceiver, filter);
if (intent != null) mTetherChangeReceiver.onReceive(activity, intent);
}
private void updateState() {
final TetheringManager tm = getContext().getSystemService(TetheringManager.class);
final String[] available = tm.getTetherableIfaces();
final String[] tethered = tm.getTetheredIfaces();
final String[] errored = tm.getTetheringErroredIfaces();
updateState(available, tethered, errored);
// TODO(b/194961339): Separate the updateBluetoothAndEthernetState() to two methods,
// updateBluetoothAndEthernetState() and updateBluetoothAndEthernetPreference().
// Because we should update the state when only receiving tethering
// state changes and update preference when usb or media share changed.
private void updateBluetoothAndEthernetState() {
String[] tethered = mTm.getTetheredIfaces();
updateBluetoothAndEthernetState(tethered);
}
private void updateState(String[] available, String[] tethered,
String[] errored) {
updateUsbState(available, tethered, errored);
private void updateBluetoothAndEthernetState(String[] tethered) {
String[] available = mTm.getTetherableIfaces();
updateBluetoothState();
updateEthernetState(available, tethered);
}
private void updateUsbState() {
String[] tethered = mTm.getTetheredIfaces();
updateUsbState(tethered);
}
@VisibleForTesting
void updateUsbState(String[] available, String[] tethered,
String[] errored) {
boolean usbAvailable = mUsbConnected && !mMassStorageActive;
int usbError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
for (String s : available) {
for (String regex : mUsbRegexs) {
if (s.matches(regex)) {
if (usbError == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
usbError = mTm.getLastTetherError(s);
}
}
}
}
void updateUsbState(String[] tethered) {
boolean usbTethered = false;
for (String s : tethered) {
for (String regex : mUsbRegexs) {
if (s.matches(regex)) usbTethered = true;
}
}
boolean usbErrored = false;
for (String s: errored) {
for (String regex : mUsbRegexs) {
if (s.matches(regex)) usbErrored = true;
}
if (DEBUG) {
Log.d(TAG, "updateUsbState() mUsbConnected : " + mUsbConnected
+ ", mMassStorageActive : " + mMassStorageActive
+ ", usbTethered : " + usbTethered);
}
if (usbTethered) {
mUsbTether.setEnabled(!mDataSaverEnabled);
mUsbTether.setChecked(true);
} else if (usbAvailable) {
mUsbTether.setEnabled(!mDataSaverEnabled);
mUsbTether.setDisabledByAdmin(
checkIfUsbDataSignalingIsDisabled(mContext, UserHandle.myUserId()));
} else {
mUsbTether.setChecked(false);
updateUsbPreference();
}
}
private void updateUsbPreference() {
boolean usbAvailable = mUsbConnected && !mMassStorageActive;
if (usbAvailable) {
mUsbTether.setEnabled(!mDataSaverEnabled);
} else {
mUsbTether.setEnabled(false);
mUsbTether.setChecked(false);
}
mUsbTether.setDisabledByAdmin(
checkIfUsbDataSignalingIsDisabled(mContext, UserHandle.myUserId()));
}
@VisibleForTesting
@@ -437,7 +452,11 @@ public class TetherSettings extends RestrictedSettingsFragment
private void updateBluetoothState() {
final int btState = getBluetoothState();
if (DEBUG) {
Log.d(TAG, "updateBluetoothState() btState : " + btState);
}
if (btState == BluetoothAdapter.ERROR) {
Log.w(TAG, "updateBluetoothState() Bluetooth state is error!");
return;
}
@@ -458,7 +477,6 @@ public class TetherSettings extends RestrictedSettingsFragment
@VisibleForTesting
void updateEthernetState(String[] available, String[] tethered) {
boolean isAvailable = false;
boolean isTethered = false;
@@ -470,6 +488,11 @@ public class TetherSettings extends RestrictedSettingsFragment
if (s.matches(mEthernetRegex)) isTethered = true;
}
if (DEBUG) {
Log.d(TAG, "updateEthernetState() isAvailable : " + isAvailable
+ ", isTethered : " + isTethered);
}
if (isTethered) {
mEthernetTether.setEnabled(!mDataSaverEnabled);
mEthernetTether.setChecked(true);
@@ -606,7 +629,7 @@ public class TetherSettings extends RestrictedSettingsFragment
private void update() {
TetherSettings settings = mTetherSettings.get();
if (settings != null) {
settings.updateState();
settings.updateBluetoothAndEthernetState();
}
}
}
@@ -614,13 +637,16 @@ public class TetherSettings extends RestrictedSettingsFragment
private final class TetheringEventCallback implements TetheringManager.TetheringEventCallback {
@Override
public void onTetheredInterfacesChanged(List<String> interfaces) {
updateState();
Log.d(TAG, "onTetheredInterfacesChanged() interfaces : " + interfaces.toString());
String[] tethered = interfaces.toArray(new String[interfaces.size()]);
updateUsbState(tethered);
updateBluetoothAndEthernetState(tethered);
}
}
private final class EthernetListener implements EthernetManager.Listener {
public void onAvailabilityChanged(String iface, boolean isAvailable) {
mHandler.post(TetherSettings.this::updateState);
mHandler.post(() -> updateBluetoothAndEthernetState());
}
}
}

View File

@@ -18,10 +18,6 @@ package com.android.settings;
import static android.content.Intent.EXTRA_USER;
import static android.content.Intent.EXTRA_USER_ID;
import static android.media.MediaRoute2Info.TYPE_GROUP;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
@@ -52,13 +48,12 @@ import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -90,6 +85,7 @@ import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.TtsSpan;
import android.util.ArraySet;
import android.util.FeatureFlagUtils;
import android.util.IconDrawableFactory;
import android.util.Log;
import android.view.LayoutInflater;
@@ -99,6 +95,7 @@ import android.widget.EditText;
import android.widget.ListView;
import android.widget.TabWidget;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.core.graphics.drawable.IconCompat;
@@ -116,6 +113,7 @@ import com.android.settings.dashboard.profileselector.ProfileFragmentBridge;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.widget.ActionBarShadowController;
import com.android.settingslib.widget.AdaptiveIcon;
import java.util.Iterator;
import java.util.List;
@@ -147,6 +145,24 @@ public final class Utils extends com.android.settingslib.Utils {
*/
public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
/**
* Whether to show location indicators.
*/
public static final String PROPERTY_LOCATION_INDICATORS_ENABLED = "location_indicators_enabled";
/**
* Whether to show location indicator settings in developer options.
*/
public static final String PROPERTY_LOCATION_INDICATOR_SETTINGS_ENABLED =
"location_indicator_settings_enabled";
/** Whether or not app hibernation is enabled on the device **/
public static final String PROPERTY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled";
/** Whether or not app hibernation targets apps that target a pre-S SDK **/
public static final String PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS =
"app_hibernation_targets_pre_s_apps";
/**
* Finds a matching activity for a preference's intent. If a matching
* activity is not found, it will remove the preference.
@@ -452,6 +468,19 @@ public final class Utils extends com.android.settingslib.Utils {
return UserHandle.USER_NULL;
}
/** Returns user ID of current user, throws IllegalStateException if it's not available. */
public static int getCurrentUserId(UserManager userManager, boolean isWorkProfile)
throws IllegalStateException {
if (isWorkProfile) {
final UserHandle managedUserHandle = getManagedProfile(userManager);
if (managedUserHandle == null) {
throw new IllegalStateException("Work profile user ID is not available.");
}
return managedUserHandle.getIdentifier();
}
return UserHandle.myUserId();
}
/**
* Returns the target user for a Settings activity.
* <p>
@@ -540,7 +569,7 @@ public final class Utils extends com.android.settingslib.Utils {
* @return UserInfo of the user or null for non-existent user.
*/
public static UserInfo getExistingUser(UserManager userManager, UserHandle checkUser) {
final List<UserInfo> users = userManager.getUsers(true /* excludeDying */);
final List<UserInfo> users = userManager.getAliveUsers();
final int checkUserId = checkUser.getIdentifier();
for (UserInfo user : users) {
if (user.id == checkUserId) {
@@ -649,7 +678,7 @@ public final class Utils extends com.android.settingslib.Utils {
*
* @param isInternal indicating if the caller is "internal" to the system,
* meaning we're willing to trust extras like
* {@link ChooseLockSettingsHelper#EXTRA_ALLOW_ANY_USER}.
* {@link ChooseLockSettingsHelper#EXTRA_KEY_ALLOW_ANY_USER}.
* @throws SecurityException if the given userId does not belong to the
* current user group.
*/
@@ -658,7 +687,7 @@ public final class Utils extends com.android.settingslib.Utils {
return getCredentialOwnerUserId(context);
}
final boolean allowAnyUser = isInternal
&& bundle.getBoolean(ChooseLockSettingsHelper.EXTRA_ALLOW_ANY_USER, false);
&& bundle.getBoolean(ChooseLockSettingsHelper.EXTRA_KEY_ALLOW_ANY_USER, false);
final int userId = bundle.getInt(Intent.EXTRA_USER_ID, UserHandle.myUserId());
if (userId == LockPatternUtils.USER_FRP) {
return allowAnyUser ? userId : enforceSystemUser(context, userId);
@@ -832,6 +861,13 @@ public final class Utils extends com.android.settingslib.Utils {
return faceManager != null && faceManager.isHardwareDetected();
}
/**
* Return true if the device supports multiple biometrics authentications.
*/
public static boolean isMultipleBiometricsSupported(Context context) {
return hasFingerprintHardware(context) && hasFaceHardware(context);
}
/**
* Launches an intent which may optionally have a user id defined.
* @param fragment Fragment to use to launch the activity.
@@ -951,15 +987,36 @@ public final class Utils extends com.android.settingslib.Utils {
}
/**
* Sets the preference icon with a drawable that is scaled down to to avoid crashing Settings if
* it's too big.
* Gets the adaptive icon with a drawable that wrapped with an adaptive background using {@code
* backgroundColor} if it is not a {@link AdaptiveIconDrawable}
*
* If the given {@code icon} is too big, it will be auto scaled down to to avoid crashing
* Settings.
*/
public static void setSafeIcon(Preference pref, Drawable icon) {
public static Drawable getAdaptiveIcon(Context context, Drawable icon,
@ColorInt int backgroundColor) {
Drawable adaptiveIcon = getSafeIcon(icon);
if (!(adaptiveIcon instanceof AdaptiveIconDrawable)) {
adaptiveIcon = new AdaptiveIcon(context, adaptiveIcon);
((AdaptiveIcon) adaptiveIcon).setBackgroundColor(backgroundColor);
}
return adaptiveIcon;
}
/**
* Gets the icon with a drawable that is scaled down to to avoid crashing Settings if it's too
* big and not a {@link VectorDrawable}.
*/
public static Drawable getSafeIcon(Drawable icon) {
Drawable safeIcon = icon;
if ((icon != null) && !(icon instanceof VectorDrawable)) {
safeIcon = getSafeDrawable(icon, 500, 500);
}
pref.setIcon(safeIcon);
return safeIcon;
}
/**
@@ -969,7 +1026,7 @@ public final class Utils extends com.android.settingslib.Utils {
* @param maxWidth maximum width, in pixels.
* @param maxHeight maximum height, in pixels.
*/
public static Drawable getSafeDrawable(Drawable original, int maxWidth, int maxHeight) {
private static Drawable getSafeDrawable(Drawable original, int maxWidth, int maxHeight) {
final int actualWidth = original.getMinimumWidth();
final int actualHeight = original.getMinimumHeight();
@@ -1101,13 +1158,17 @@ public final class Utils extends com.android.settingslib.Utils {
== ProfileSelectFragment.ProfileType.PERSONAL : false;
final boolean isWork = args != null ? args.getInt(ProfileSelectFragment.EXTRA_PROFILE)
== ProfileSelectFragment.ProfileType.WORK : false;
if (activity.getSystemService(UserManager.class).getUserProfiles().size() > 1
&& ProfileFragmentBridge.FRAGMENT_MAP.get(fragmentName) != null
&& !isWork && !isPersonal) {
f = Fragment.instantiate(activity, ProfileFragmentBridge.FRAGMENT_MAP.get(fragmentName),
args);
} else {
f = Fragment.instantiate(activity, fragmentName, args);
try {
if (activity.getSystemService(UserManager.class).getUserProfiles().size() > 1
&& ProfileFragmentBridge.FRAGMENT_MAP.get(fragmentName) != null
&& !isWork && !isPersonal) {
f = Fragment.instantiate(activity,
ProfileFragmentBridge.FRAGMENT_MAP.get(fragmentName), args);
} else {
f = Fragment.instantiate(activity, fragmentName, args);
}
} catch (Exception e) {
Log.e(TAG, "Unable to get target fragment", e);
}
return f;
}
@@ -1154,29 +1215,14 @@ public final class Utils extends com.android.settingslib.Utils {
}
/**
* Returns {@code true} if needed to disable media output, otherwise returns {@code false}.
* Returns the color of homepage preference icons.
*/
public static boolean isMediaOutputDisabled(
MediaRouter2Manager router2Manager, String packageName) {
boolean isMediaOutputDisabled = false;
if (!TextUtils.isEmpty(packageName)) {
final List<MediaRoute2Info> infos = router2Manager.getAvailableRoutes(packageName);
if (infos.size() == 1) {
final MediaRoute2Info info = infos.get(0);
final int deviceType = info.getType();
switch (deviceType) {
case TYPE_UNKNOWN:
case TYPE_REMOTE_TV:
case TYPE_REMOTE_SPEAKER:
case TYPE_GROUP:
isMediaOutputDisabled = true;
break;
default:
isMediaOutputDisabled = false;
break;
}
}
}
return isMediaOutputDisabled;
@ColorInt
public static int getHomepageIconColor(Context context) {
return getColorAttrDefaultColor(context, android.R.attr.textColorSecondary);
}
public static boolean isProviderModelEnabled(Context context) {
return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
/**
* Preference controller for accessibility button footer.
*/
public class AccessibilityButtonFooterPreferenceController extends
AccessibilityFooterPreferenceController {
public AccessibilityButtonFooterPreferenceController(Context context, String key) {
super(context, key);
}
@Override
protected String getLabelName() {
return mContext.getString(R.string.accessibility_button_title);
}
@Override
public void displayPreference(PreferenceScreen screen) {
// Need to update footerPreference's data before super.displayPreference(), then it will use
// data to update related property of footerPreference.
if (AccessibilityUtil.isGestureNavigateEnabled(mContext)) {
final AccessibilityFooterPreference footerPreference =
screen.findPreference(getPreferenceKey());
footerPreference.setTitle(
mContext.getString(R.string.accessibility_button_gesture_description));
}
super.displayPreference(screen);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
/** Settings fragment containing accessibility button properties. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class AccessibilityButtonFragment extends DashboardFragment {
private static final String TAG = "AccessibilityButtonFragment";
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_button_settings;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_BUTTON_SETTINGS;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_button_settings);
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.provider.Settings;
import android.util.ArrayMap;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.google.common.primitives.Ints;
/** Preference controller that controls the preferred location in accessibility button page. */
public class AccessibilityButtonLocationPreferenceController extends BasePreferenceController
implements Preference.OnPreferenceChangeListener {
private final ArrayMap<String, String> mValueTitleMap = new ArrayMap<>();
private int mDefaultLocation;
public AccessibilityButtonLocationPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
initValueTitleMap();
}
@Override
public int getAvailabilityStatus() {
return AccessibilityUtil.isGestureNavigateEnabled(mContext)
? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final ListPreference listPreference = (ListPreference) preference;
final Integer value = Ints.tryParse((String) newValue);
if (value != null) {
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, value);
updateState(listPreference);
}
return true;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
final ListPreference listPreference = (ListPreference) preference;
listPreference.setValue(getCurrentAccessibilityButtonMode());
}
private String getCurrentAccessibilityButtonMode() {
final int mode = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, mDefaultLocation);
return String.valueOf(mode);
}
private void initValueTitleMap() {
if (mValueTitleMap.size() == 0) {
final String[] values = mContext.getResources().getStringArray(
R.array.accessibility_button_location_selector_values);
final String[] titles = mContext.getResources().getStringArray(
R.array.accessibility_button_location_selector_titles);
final int mapSize = values.length;
mDefaultLocation = Integer.parseInt(values[0]);
for (int i = 0; i < mapSize; i++) {
mValueTitleMap.put(values[i], titles[i]);
}
}
}
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.widget.ImageView;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
import com.android.settingslib.widget.LayoutPreference;
/** Preference controller that controls the preview effect in accessibility button page. */
public class AccessibilityButtonPreviewPreferenceController extends BasePreferenceController
implements LifecycleObserver, OnResume, OnPause {
private static final int SMALL_SIZE = 0;
private static final float DEFAULT_OPACITY = 0.55f;
private static final int DEFAULT_SIZE = 0;
private final ContentResolver mContentResolver;
@VisibleForTesting
final ContentObserver mContentObserver;
private FloatingMenuLayerDrawable mFloatingMenuPreviewDrawable;
@VisibleForTesting
ImageView mPreview;
public AccessibilityButtonPreviewPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mContentResolver = context.getContentResolver();
mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
updatePreviewPreference();
}
};
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final LayoutPreference preference = screen.findPreference(getPreferenceKey());
mPreview = preference.findViewById(R.id.preview_image);
updatePreviewPreference();
}
@Override
public void onResume() {
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_MODE),
/* notifyForDescendants= */ false, mContentObserver);
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
/* notifyForDescendants= */ false, mContentObserver);
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY),
/* notifyForDescendants= */ false, mContentObserver);
}
@Override
public void onPause() {
mContentResolver.unregisterContentObserver(mContentObserver);
}
private void updatePreviewPreference() {
if (AccessibilityUtil.isFloatingMenuEnabled(mContext)) {
final int size = Settings.Secure.getInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, DEFAULT_SIZE);
final int opacity = (int) (Settings.Secure.getFloat(mContentResolver,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, DEFAULT_OPACITY) * 100);
final int floatingMenuIconId = (size == SMALL_SIZE)
? R.drawable.accessibility_button_preview_small_floating_menu
: R.drawable.accessibility_button_preview_large_floating_menu;
mPreview.setImageDrawable(getFloatingMenuPreviewDrawable(floatingMenuIconId, opacity));
// Only change opacity(alpha) would not invoke redraw view, need to invalidate manually.
mPreview.invalidate();
} else {
mPreview.setImageDrawable(
mContext.getDrawable(R.drawable.accessibility_button_navigation));
}
}
private Drawable getFloatingMenuPreviewDrawable(int resId, int opacity) {
if (mFloatingMenuPreviewDrawable == null) {
mFloatingMenuPreviewDrawable = FloatingMenuLayerDrawable.createLayerDrawable(
mContext, resId, opacity);
} else {
mFloatingMenuPreviewDrawable.updateLayerDrawable(mContext, resId, opacity);
}
return mFloatingMenuPreviewDrawable;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import com.android.settings.R;
/**
* Preference controller for accessibility control timeout footer.
*/
public class AccessibilityControlTimeoutFooterPreferenceController extends
AccessibilityFooterPreferenceController {
public AccessibilityControlTimeoutFooterPreferenceController(Context context, String key) {
super(context, key);
}
@Override
protected String getLabelName() {
return mContext.getString(R.string.accessibility_setting_item_control_timeout_title);
}
@Override
protected int getHelpResource() {
return R.string.help_url_timeout;
}
}

View File

@@ -16,6 +16,9 @@
package com.android.settings.accessibility;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_BUTTON_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
@@ -30,6 +33,9 @@ import android.text.TextUtils;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
@@ -37,6 +43,7 @@ import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.accessibility.AccessibilityUtils;
import java.util.List;
import java.util.Objects;
import java.util.Set;
public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment {
@@ -61,44 +68,83 @@ public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment {
return;
}
// In case the A11yServiceInfo doesn't exist, go to ally services list.
final ComponentName componentName = ComponentName.unflattenFromString(extraComponentName);
if (openSystemAccessibilitySettingsAndFinish(componentName)) {
return;
}
if (openAccessibilityDetailsSettingsAndFinish(componentName)) {
return;
}
// Fall back to open accessibility services list.
openAccessibilitySettingsAndFinish();
}
private boolean openSystemAccessibilitySettingsAndFinish(
@Nullable ComponentName componentName) {
final LaunchFragmentArguments launchArguments =
getSystemAccessibilitySettingsLaunchArguments(componentName);
if (launchArguments == null) {
return false;
}
openSubSettings(launchArguments.mDestination, launchArguments.mArguments);
finish();
return true;
}
@Nullable
private LaunchFragmentArguments getSystemAccessibilitySettingsLaunchArguments(
@Nullable ComponentName componentName) {
if (MAGNIFICATION_COMPONENT_NAME.equals(componentName)) {
final String destination = ToggleScreenMagnificationPreferenceFragment.class.getName();
final Bundle arguments = new Bundle();
MagnificationGesturesPreferenceController.populateMagnificationGesturesPreferenceExtras(
arguments, getContext());
return new LaunchFragmentArguments(destination, arguments);
}
if (ACCESSIBILITY_BUTTON_COMPONENT_NAME.equals(componentName)) {
final String destination = AccessibilityButtonFragment.class.getName();
return new LaunchFragmentArguments(destination, /* arguments= */ null);
}
return null;
}
private void openAccessibilitySettingsAndFinish() {
openSubSettings(AccessibilitySettings.class.getName(), /* arguments= */ null);
finish();
}
private boolean openAccessibilityDetailsSettingsAndFinish(
@Nullable ComponentName componentName) {
// In case the A11yServiceInfo doesn't exist, go to ally services list.
final AccessibilityServiceInfo info = getAccessibilityServiceInfo(componentName);
if (info == null) {
Log.w(TAG, "Open accessibility services list due to invalid component name.");
openAccessibilitySettingsAndFinish();
return;
Log.w(TAG, "openAccessibilityDetailsSettingsAndFinish : invalid component name.");
return false;
}
// In case this accessibility service isn't permitted, go to a11y services list.
if (!isServiceAllowed(componentName.getPackageName())) {
Log.w(TAG,
"Open accessibility services list due to target accessibility service is "
"openAccessibilityDetailsSettingsAndFinish: target accessibility service is"
+ "prohibited by Device Admin.");
openAccessibilitySettingsAndFinish();
return;
return false;
}
openAccessibilityDetailsSettingsAndFinish(buildArguments(info));
}
@VisibleForTesting
void openAccessibilitySettingsAndFinish() {
new SubSettingLauncher(getActivity())
.setDestination(AccessibilitySettings.class.getName())
.setSourceMetricsCategory(getMetricsCategory())
.launch();
openSubSettings(ToggleAccessibilityServicePreferenceFragment.class.getName(),
buildArguments(info));
finish();
return true;
}
@VisibleForTesting
void openAccessibilityDetailsSettingsAndFinish(Bundle arguments) {
private void openSubSettings(@NonNull String destination, @Nullable Bundle arguments) {
new SubSettingLauncher(getActivity())
.setDestination(ToggleAccessibilityServicePreferenceFragment.class.getName())
.setDestination(destination)
.setSourceMetricsCategory(getMetricsCategory())
.setArguments(arguments)
.launch();
finish();
}
@VisibleForTesting
@@ -175,4 +221,13 @@ public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment {
}
activity.finish();
}
private static class LaunchFragmentArguments {
final String mDestination;
final Bundle mArguments;
LaunchFragmentArguments(@NonNull String destination, @Nullable Bundle arguments) {
mDestination = Objects.requireNonNull(destination);
mArguments = arguments;
}
}
}

View File

@@ -0,0 +1,515 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import static com.android.settings.accessibility.ItemInfoArrayAdapter.ItemInfo;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.icu.text.MessageFormat;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.ImageSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.utils.AnnotationSpan;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* Utility class for creating the edit dialog.
*/
public class AccessibilityDialogUtils {
/** Denotes the dialog emuns for show dialog. */
@Retention(RetentionPolicy.SOURCE)
public @interface DialogEnums {
/** OPEN: Settings > Accessibility > Any toggle service > Shortcut > Settings. */
int EDIT_SHORTCUT = 1;
/** OPEN: Settings > Accessibility > Magnification > Shortcut > Settings. */
int MAGNIFICATION_EDIT_SHORTCUT = 1001;
/**
* OPEN: Settings > Accessibility > Downloaded toggle service > Toggle use service to
* enable service.
*/
int ENABLE_WARNING_FROM_TOGGLE = 1002;
/** OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox. */
int ENABLE_WARNING_FROM_SHORTCUT = 1003;
/**
* OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox
* toggle.
*/
int ENABLE_WARNING_FROM_SHORTCUT_TOGGLE = 1004;
/**
* OPEN: Settings > Accessibility > Downloaded toggle service > Toggle use service to
* disable service.
*/
int DISABLE_WARNING_FROM_TOGGLE = 1005;
/**
* OPEN: Settings > Accessibility > Magnification > Toggle user service in button
* navigation.
*/
int ACCESSIBILITY_BUTTON_TUTORIAL = 1006;
/**
* OPEN: Settings > Accessibility > Magnification > Toggle user service in gesture
* navigation.
*/
int GESTURE_NAVIGATION_TUTORIAL = 1007;
/**
* OPEN: Settings > Accessibility > Downloaded toggle service > Toggle user service > Show
* launch tutorial.
*/
int LAUNCH_ACCESSIBILITY_TUTORIAL = 1008;
}
/**
* IntDef enum for dialog type that indicates different dialog for user to choose the shortcut
* type.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
DialogType.EDIT_SHORTCUT_GENERIC,
DialogType.EDIT_SHORTCUT_GENERIC_SUW,
DialogType.EDIT_SHORTCUT_MAGNIFICATION,
DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW,
DialogType.EDIT_MAGNIFICATION_SWITCH_SHORTCUT,
})
public @interface DialogType {
int EDIT_SHORTCUT_GENERIC = 0;
int EDIT_SHORTCUT_GENERIC_SUW = 1;
int EDIT_SHORTCUT_MAGNIFICATION = 2;
int EDIT_SHORTCUT_MAGNIFICATION_SUW = 3;
int EDIT_MAGNIFICATION_SWITCH_SHORTCUT = 4;
}
/**
* Method to show the edit shortcut dialog.
*
* @param context A valid context
* @param dialogType The type of edit shortcut dialog
* @param dialogTitle The title of edit shortcut dialog
* @param listener The listener to determine the action of edit shortcut dialog
* @return A edit shortcut dialog for showing
*/
public static AlertDialog showEditShortcutDialog(Context context, int dialogType,
CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
final AlertDialog alertDialog = createDialog(context, dialogType, dialogTitle, listener);
alertDialog.show();
setScrollIndicators(alertDialog);
return alertDialog;
}
/**
* Method to show the magnification edit shortcut dialog in Magnification.
*
* @param context A valid context
* @param positiveBtnListener The positive button listener
* @return A magnification edit shortcut dialog in Magnification
*/
public static Dialog createMagnificationSwitchShortcutDialog(Context context,
CustomButtonsClickListener positiveBtnListener) {
final View contentView = createSwitchShortcutDialogContentView(context);
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setView(contentView)
.setTitle(context.getString(
R.string.accessibility_magnification_switch_shortcut_title))
.create();
setCustomButtonsClickListener(alertDialog, contentView,
positiveBtnListener, /* negativeBtnListener= */ null);
setScrollIndicators(contentView);
return alertDialog;
}
private static AlertDialog createDialog(Context context, int dialogType,
CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setView(createEditDialogContentView(context, dialogType))
.setTitle(dialogTitle)
.setPositiveButton(R.string.save, listener)
.setNegativeButton(R.string.cancel,
(DialogInterface dialog, int which) -> dialog.dismiss())
.create();
return alertDialog;
}
/**
* Sets the scroll indicators for dialog view. The indicators appears while content view is
* out of vision for vertical scrolling.
*/
private static void setScrollIndicators(AlertDialog dialog) {
final ScrollView scrollView = dialog.findViewById(R.id.container_layout);
setScrollIndicators(scrollView);
}
/**
* Sets the scroll indicators for dialog view. The indicators appear while content view is
* out of vision for vertical scrolling.
*
* @param view The view contains customized dialog content. Usually it is {@link ScrollView} or
* {@link AbsListView}
*/
private static void setScrollIndicators(@NonNull View view) {
view.setScrollIndicators(
View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM,
View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
}
interface CustomButtonsClickListener {
void onClick(@CustomButton int which);
}
/**
* Annotation for customized dialog button type.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
CustomButton.POSITIVE,
CustomButton.NEGATIVE,
})
public @interface CustomButton {
int POSITIVE = 1;
int NEGATIVE = 2;
}
private static void setCustomButtonsClickListener(Dialog dialog, View contentView,
CustomButtonsClickListener positiveBtnListener,
CustomButtonsClickListener negativeBtnListener) {
final Button positiveButton = contentView.findViewById(
R.id.custom_positive_button);
final Button negativeButton = contentView.findViewById(
R.id.custom_negative_button);
if (positiveButton != null) {
positiveButton.setOnClickListener(v -> {
if (positiveBtnListener != null) {
positiveBtnListener.onClick(CustomButton.POSITIVE);
}
dialog.dismiss();
});
}
if (negativeButton != null) {
negativeButton.setOnClickListener(v -> {
if (negativeBtnListener != null) {
negativeBtnListener.onClick(CustomButton.NEGATIVE);
}
dialog.dismiss();
});
}
}
private static View createSwitchShortcutDialogContentView(Context context) {
return createEditDialogContentView(context, DialogType.EDIT_MAGNIFICATION_SWITCH_SHORTCUT);
}
/**
* Get a content View for the edit shortcut dialog.
*
* @param context A valid context
* @param dialogType The type of edit shortcut dialog
* @return A content view suitable for viewing
*/
private static View createEditDialogContentView(Context context, int dialogType) {
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View contentView = null;
switch (dialogType) {
case DialogType.EDIT_SHORTCUT_GENERIC:
contentView = inflater.inflate(
R.layout.accessibility_edit_shortcut, null);
initSoftwareShortcut(context, contentView);
initHardwareShortcut(context, contentView);
break;
case DialogType.EDIT_SHORTCUT_GENERIC_SUW:
contentView = inflater.inflate(
R.layout.accessibility_edit_shortcut, null);
initSoftwareShortcutForSUW(context, contentView);
initHardwareShortcut(context, contentView);
break;
case DialogType.EDIT_SHORTCUT_MAGNIFICATION:
contentView = inflater.inflate(
R.layout.accessibility_edit_shortcut_magnification, null);
initSoftwareShortcut(context, contentView);
initHardwareShortcut(context, contentView);
initMagnifyShortcut(context, contentView);
initAdvancedWidget(contentView);
break;
case DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW:
contentView = inflater.inflate(
R.layout.accessibility_edit_shortcut_magnification, null);
initSoftwareShortcutForSUW(context, contentView);
initHardwareShortcut(context, contentView);
initMagnifyShortcut(context, contentView);
initAdvancedWidget(contentView);
break;
case DialogType.EDIT_MAGNIFICATION_SWITCH_SHORTCUT:
contentView = inflater.inflate(
R.layout.accessibility_edit_magnification_shortcut, null);
final ImageView image = contentView.findViewById(R.id.image);
image.setImageResource(retrieveSoftwareShortcutImageResId(context));
break;
default:
throw new IllegalArgumentException();
}
return contentView;
}
private static void setupShortcutWidget(View view, CharSequence titleText,
CharSequence summaryText, int imageResId) {
final CheckBox checkBox = view.findViewById(R.id.checkbox);
checkBox.setText(titleText);
final TextView summary = view.findViewById(R.id.summary);
if (TextUtils.isEmpty(summaryText)) {
summary.setVisibility(View.GONE);
} else {
summary.setText(summaryText);
summary.setMovementMethod(LinkMovementMethod.getInstance());
summary.setFocusable(false);
}
final ImageView image = view.findViewById(R.id.image);
image.setImageResource(imageResId);
}
private static void initSoftwareShortcutForSUW(Context context, View view) {
final View dialogView = view.findViewById(R.id.software_shortcut);
final CharSequence title = context.getText(
R.string.accessibility_shortcut_edit_dialog_title_software);
final TextView summary = dialogView.findViewById(R.id.summary);
final int lineHeight = summary.getLineHeight();
setupShortcutWidget(dialogView, title,
retrieveSoftwareShortcutSummaryForSUW(context, lineHeight),
retrieveSoftwareShortcutImageResId(context));
}
private static void initSoftwareShortcut(Context context, View view) {
final View dialogView = view.findViewById(R.id.software_shortcut);
final CharSequence title = context.getText(
R.string.accessibility_shortcut_edit_dialog_title_software);
final TextView summary = dialogView.findViewById(R.id.summary);
final int lineHeight = summary.getLineHeight();
setupShortcutWidget(dialogView, title,
retrieveSoftwareShortcutSummary(context, lineHeight),
retrieveSoftwareShortcutImageResId(context));
}
private static void initHardwareShortcut(Context context, View view) {
final View dialogView = view.findViewById(R.id.hardware_shortcut);
final CharSequence title = context.getText(
R.string.accessibility_shortcut_edit_dialog_title_hardware);
final CharSequence summary = context.getText(
R.string.accessibility_shortcut_edit_dialog_summary_hardware);
setupShortcutWidget(dialogView, title, summary,
R.drawable.accessibility_shortcut_type_hardware);
// TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted.
}
private static void initMagnifyShortcut(Context context, View view) {
final View dialogView = view.findViewById(R.id.triple_tap_shortcut);
final CharSequence title = context.getText(
R.string.accessibility_shortcut_edit_dialog_title_triple_tap);
String summary = context.getString(
R.string.accessibility_shortcut_edit_dialog_summary_triple_tap);
// Format the number '3' in the summary.
final Object[] arguments = {3};
summary = MessageFormat.format(summary, arguments);
setupShortcutWidget(dialogView, title, summary,
R.drawable.accessibility_shortcut_type_triple_tap);
// TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted.
}
private static void initAdvancedWidget(View view) {
final LinearLayout advanced = view.findViewById(R.id.advanced_shortcut);
final View tripleTap = view.findViewById(R.id.triple_tap_shortcut);
advanced.setOnClickListener((View v) -> {
advanced.setVisibility(View.GONE);
tripleTap.setVisibility(View.VISIBLE);
});
}
private static CharSequence retrieveSoftwareShortcutSummaryForSUW(Context context,
int lineHeight) {
final SpannableStringBuilder sb = new SpannableStringBuilder();
if (!AccessibilityUtil.isFloatingMenuEnabled(context)) {
sb.append(getSummaryStringWithIcon(context, lineHeight));
}
return sb;
}
private static CharSequence retrieveSoftwareShortcutSummary(Context context, int lineHeight) {
final SpannableStringBuilder sb = new SpannableStringBuilder();
if (!AccessibilityUtil.isFloatingMenuEnabled(context)) {
sb.append(getSummaryStringWithIcon(context, lineHeight));
sb.append("\n\n");
}
sb.append(getCustomizeAccessibilityButtonLink(context));
return sb;
}
private static int retrieveSoftwareShortcutImageResId(Context context) {
return AccessibilityUtil.isFloatingMenuEnabled(context)
? R.drawable.accessibility_shortcut_type_software_floating
: R.drawable.accessibility_shortcut_type_software;
}
private static CharSequence getCustomizeAccessibilityButtonLink(Context context) {
final View.OnClickListener linkListener = v -> new SubSettingLauncher(context)
.setDestination(AccessibilityButtonFragment.class.getName())
.setSourceMetricsCategory(
SettingsEnums.SWITCH_SHORTCUT_DIALOG_ACCESSIBILITY_BUTTON_SETTINGS)
.launch();
final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo(
AnnotationSpan.LinkInfo.DEFAULT_ANNOTATION, linkListener);
return AnnotationSpan.linkify(context.getText(
R.string.accessibility_shortcut_edit_dialog_summary_software_floating), linkInfo);
}
private static SpannableString getSummaryStringWithIcon(Context context, int lineHeight) {
final String summary = context
.getString(R.string.accessibility_shortcut_edit_dialog_summary_software);
final SpannableString spannableMessage = SpannableString.valueOf(summary);
// Icon
final int indexIconStart = summary.indexOf("%s");
final int indexIconEnd = indexIconStart + 2;
final Drawable icon = context.getDrawable(R.drawable.ic_accessibility_new);
final ImageSpan imageSpan = new ImageSpan(icon);
imageSpan.setContentDescription("");
icon.setBounds(0, 0, lineHeight, lineHeight);
spannableMessage.setSpan(
imageSpan, indexIconStart, indexIconEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannableMessage;
}
/**
* Returns the color associated with the specified attribute in the context's theme.
*/
@ColorInt
private static int getThemeAttrColor(final Context context, final int attributeColor) {
final int colorResId = getAttrResourceId(context, attributeColor);
return ContextCompat.getColor(context, colorResId);
}
/**
* Returns the identifier of the resolved resource assigned to the given attribute.
*/
private static int getAttrResourceId(final Context context, final int attributeColor) {
final int[] attrs = {attributeColor};
final TypedArray typedArray = context.obtainStyledAttributes(attrs);
final int colorResId = typedArray.getResourceId(0, 0);
typedArray.recycle();
return colorResId;
}
/**
* Creates a dialog with the given view.
*
* @param context A valid context
* @param dialogTitle The title of the dialog
* @param customView The customized view
* @param listener This listener will be invoked when the positive button in the dialog is
* clicked
* @return the {@link Dialog} with the given view
*/
public static Dialog createCustomDialog(Context context, CharSequence dialogTitle,
View customView, DialogInterface.OnClickListener listener) {
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setView(customView)
.setTitle(dialogTitle)
.setCancelable(true)
.setPositiveButton(R.string.save, listener)
.setNegativeButton(R.string.cancel, null)
.create();
if (customView instanceof ScrollView || customView instanceof AbsListView) {
setScrollIndicators(customView);
}
return alertDialog;
}
/**
* Creates a single choice {@link ListView} with given {@link ItemInfo} list.
*
* @param context A context.
* @param itemInfoList A {@link ItemInfo} list.
* @param itemListener The listener will be invoked when the item is clicked.
*/
@NonNull
public static ListView createSingleChoiceListView(@NonNull Context context,
@NonNull List<? extends ItemInfo> itemInfoList,
@Nullable AdapterView.OnItemClickListener itemListener) {
final ListView list = new ListView(context);
// Set an id to save its state.
list.setId(android.R.id.list);
list.setDivider(/* divider= */ null);
list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
final ItemInfoArrayAdapter
adapter = new ItemInfoArrayAdapter(context, itemInfoList);
list.setAdapter(adapter);
list.setOnItemClickListener(itemListener);
return list;
}
}

View File

@@ -1,327 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import com.android.settings.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Utility class for creating the edit dialog.
*/
public class AccessibilityEditDialogUtils {
/**
* IntDef enum for dialog type that indicates different dialog for user to choose the shortcut
* type.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
DialogType.EDIT_SHORTCUT_GENERIC,
DialogType.EDIT_SHORTCUT_MAGNIFICATION,
DialogType.EDIT_MAGNIFICATION_MODE,
})
private @interface DialogType {
int EDIT_SHORTCUT_GENERIC = 0;
int EDIT_SHORTCUT_MAGNIFICATION = 1;
int EDIT_MAGNIFICATION_MODE = 2;
}
/**
* Method to show the edit shortcut dialog.
*
* @param context A valid context
* @param dialogTitle The title of edit shortcut dialog
* @param listener The listener to determine the action of edit shortcut dialog
* @return A edit shortcut dialog for showing
*/
public static AlertDialog showEditShortcutDialog(Context context, CharSequence dialogTitle,
DialogInterface.OnClickListener listener) {
final AlertDialog alertDialog = createDialog(context, DialogType.EDIT_SHORTCUT_GENERIC,
dialogTitle, listener);
alertDialog.show();
setScrollIndicators(alertDialog);
return alertDialog;
}
/**
* Method to show the edit shortcut dialog in Magnification.
*
* @param context A valid context
* @param dialogTitle The title of edit shortcut dialog
* @param listener The listener to determine the action of edit shortcut dialog
* @return A edit shortcut dialog for showing in Magnification
*/
public static AlertDialog showMagnificationEditShortcutDialog(Context context,
CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
final AlertDialog alertDialog = createDialog(context,
DialogType.EDIT_SHORTCUT_MAGNIFICATION, dialogTitle, listener);
alertDialog.show();
setScrollIndicators(alertDialog);
return alertDialog;
}
/**
* Method to show the magnification mode dialog in Magnification.
*
* @param context A valid context
* @param dialogTitle The title of magnify mode dialog
* @param listener The listener to determine the action of magnify mode dialog
* @return A magnification mode dialog in Magnification
*/
public static AlertDialog showMagnificationModeDialog(Context context,
CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
final AlertDialog alertDialog = createDialog(context,
DialogType.EDIT_MAGNIFICATION_MODE, dialogTitle, listener);
alertDialog.show();
setScrollIndicators(alertDialog);
return alertDialog;
}
private static AlertDialog createDialog(Context context, int dialogType,
CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setView(createEditDialogContentView(context, dialogType))
.setTitle(dialogTitle)
.setPositiveButton(R.string.save, listener)
.setNegativeButton(R.string.cancel,
(DialogInterface dialog, int which) -> dialog.dismiss())
.create();
return alertDialog;
}
/**
* Sets the scroll indicators for dialog view. The indicators appears while content view is
* out of vision for vertical scrolling.
*/
private static void setScrollIndicators(AlertDialog dialog) {
final ScrollView scrollView = dialog.findViewById(R.id.container_layout);
scrollView.setScrollIndicators(
View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM,
View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
}
/**
* Get a content View for the edit shortcut dialog.
*
* @param context A valid context
* @param dialogType The type of edit shortcut dialog
* @return A content view suitable for viewing
*/
private static View createEditDialogContentView(Context context, int dialogType) {
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View contentView = null;
switch (dialogType) {
case DialogType.EDIT_SHORTCUT_GENERIC:
contentView = inflater.inflate(
R.layout.accessibility_edit_shortcut, null);
initSoftwareShortcut(context, contentView);
initHardwareShortcut(context, contentView);
break;
case DialogType.EDIT_SHORTCUT_MAGNIFICATION:
contentView = inflater.inflate(
R.layout.accessibility_edit_shortcut_magnification, null);
initSoftwareShortcut(context, contentView);
initHardwareShortcut(context, contentView);
initMagnifyShortcut(context, contentView);
initAdvancedWidget(contentView);
break;
case DialogType.EDIT_MAGNIFICATION_MODE:
contentView = inflater.inflate(
R.layout.accessibility_edit_magnification_mode, null);
initMagnifyFullScreen(context, contentView);
initMagnifyWindowScreen(context, contentView);
break;
default:
throw new IllegalArgumentException();
}
return contentView;
}
private static void initMagnifyFullScreen(Context context, View view) {
final View dialogView = view.findViewById(R.id.magnify_full_screen);
final CharSequence title = context.getText(
R.string.accessibility_magnification_area_settings_full_screen);
// TODO(b/146019459): Use vector drawable instead of temporal png file to avoid distorted.
setupShortcutWidget(dialogView, title, R.drawable.accessibility_magnification_full_screen);
}
private static void initMagnifyWindowScreen(Context context, View view) {
final View dialogView = view.findViewById(R.id.magnify_window_screen);
final CharSequence title = context.getText(
R.string.accessibility_magnification_area_settings_window_screen);
// TODO(b/146019459): Use vector drawable instead of temporal png file to avoid distorted.
setupShortcutWidget(dialogView, title,
R.drawable.accessibility_magnification_window_screen);
}
private static void setupShortcutWidget(View view, CharSequence titleText, int imageResId) {
setupShortcutWidget(view, titleText, null, imageResId);
}
private static void setupShortcutWidget(View view, CharSequence titleText,
CharSequence summaryText, int imageResId) {
final CheckBox checkBox = view.findViewById(R.id.checkbox);
checkBox.setText(titleText);
final TextView summary = view.findViewById(R.id.summary);
if (TextUtils.isEmpty(summaryText)) {
summary.setVisibility(View.GONE);
} else {
summary.setText(summaryText);
}
final ImageView image = view.findViewById(R.id.image);
image.setImageResource(imageResId);
}
private static void initSoftwareShortcut(Context context, View view) {
final View dialogView = view.findViewById(R.id.software_shortcut);
final TextView summary = dialogView.findViewById(R.id.summary);
final int lineHeight = summary.getLineHeight();
setupShortcutWidget(dialogView, retrieveTitle(context),
retrieveSummary(context, lineHeight), retrieveImageResId(context));
}
private static void initHardwareShortcut(Context context, View view) {
final View dialogView = view.findViewById(R.id.hardware_shortcut);
final CharSequence title = context.getText(
R.string.accessibility_shortcut_edit_dialog_title_hardware);
final CharSequence summary = context.getText(
R.string.accessibility_shortcut_edit_dialog_summary_hardware);
setupShortcutWidget(dialogView, title, summary,
R.drawable.accessibility_shortcut_type_hardware);
// TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted.
}
private static void initMagnifyShortcut(Context context, View view) {
final View dialogView = view.findViewById(R.id.triple_tap_shortcut);
final CharSequence title = context.getText(
R.string.accessibility_shortcut_edit_dialog_title_triple_tap);
final CharSequence summary = context.getText(
R.string.accessibility_shortcut_edit_dialog_summary_triple_tap);
setupShortcutWidget(dialogView, title, summary,
R.drawable.accessibility_shortcut_type_triple_tap);
// TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted.
}
private static void initAdvancedWidget(View view) {
final LinearLayout advanced = view.findViewById(R.id.advanced_shortcut);
final View tripleTap = view.findViewById(R.id.triple_tap_shortcut);
advanced.setOnClickListener((View v) -> {
advanced.setVisibility(View.GONE);
tripleTap.setVisibility(View.VISIBLE);
});
}
private static CharSequence retrieveTitle(Context context) {
int resId = R.string.accessibility_shortcut_edit_dialog_title_software;
if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
resId = AccessibilityUtil.isTouchExploreEnabled(context)
? R.string.accessibility_shortcut_edit_dialog_title_software_gesture_talkback
: R.string.accessibility_shortcut_edit_dialog_title_software_gesture;
}
return context.getText(resId);
}
private static CharSequence retrieveSummary(Context context, int lineHeight) {
if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
final int resId = AccessibilityUtil.isTouchExploreEnabled(context)
? R.string.accessibility_shortcut_edit_dialog_summary_software_gesture_talkback
: R.string.accessibility_shortcut_edit_dialog_summary_software_gesture;
return context.getText(resId);
}
return getSummaryStringWithIcon(context, lineHeight);
}
private static int retrieveImageResId(Context context) {
// TODO(b/142531156): Use vector drawable instead of temporal png file to avoid distorted.
int resId = R.drawable.accessibility_shortcut_type_software;
if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
resId = AccessibilityUtil.isTouchExploreEnabled(context)
? R.drawable.accessibility_shortcut_type_software_gesture_talkback
: R.drawable.accessibility_shortcut_type_software_gesture;
}
return resId;
}
private static SpannableString getSummaryStringWithIcon(Context context, int lineHeight) {
final String summary = context
.getString(R.string.accessibility_shortcut_edit_dialog_summary_software);
final SpannableString spannableMessage = SpannableString.valueOf(summary);
// Icon
final int indexIconStart = summary.indexOf("%s");
final int indexIconEnd = indexIconStart + 2;
final Drawable icon = context.getDrawable(R.drawable.ic_accessibility_new);
final ImageSpan imageSpan = new ImageSpan(icon);
imageSpan.setContentDescription("");
icon.setBounds(0, 0, lineHeight, lineHeight);
spannableMessage.setSpan(
imageSpan, indexIconStart, indexIconEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannableMessage;
}
/**
* Returns the color associated with the specified attribute in the context's theme.
*/
@ColorInt
private static int getThemeAttrColor(final Context context, final int attributeColor) {
final int colorResId = getAttrResourceId(context, attributeColor);
return ContextCompat.getColor(context, colorResId);
}
/**
* Returns the identifier of the resolved resource assigned to the given attribute.
*/
private static int getAttrResourceId(final Context context, final int attributeColor) {
final int[] attrs = {attributeColor};
final TypedArray typedArray = context.obtainStyledAttributes(attrs);
final int colorResId = typedArray.getResourceId(0, 0);
typedArray.recycle();
return colorResId;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.widget.FooterPreference;
/**
* A custom preference acting as footer of a page. Disables the movement method by default.
*/
public final class AccessibilityFooterPreference extends FooterPreference {
private boolean mLinkEnabled;
public AccessibilityFooterPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AccessibilityFooterPreference(Context context) {
super(context);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final TextView title = holder.itemView.findViewById(android.R.id.title);
if (mLinkEnabled) {
// When a TextView has a movement method, it will set the view to clickable. This makes
// View.onTouchEvent always return true and consumes the touch event, essentially
// nullifying any return values of MovementMethod.onTouchEvent.
// To still allow propagating touch events to the parent when this view doesn't have
// links, we only set the movement method here if the text contains links.
title.setMovementMethod(LinkMovementMethod.getInstance());
} else {
title.setMovementMethod(/* movement= */ null);
}
}
/**
* Sets the title field supports movement method.
*/
public void setLinkEnabled(boolean enabled) {
if (mLinkEnabled != enabled) {
mLinkEnabled = enabled;
notifyChanged();
}
}
/**
* Returns true if the title field supports movement method.
*/
public boolean isLinkEnabled() {
return mLinkEnabled;
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.content.Intent;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.HelpUtils;
/**
* Base class for accessibility preference footer.
*/
public abstract class AccessibilityFooterPreferenceController extends BasePreferenceController {
public AccessibilityFooterPreferenceController(Context context, String key) {
super(context, key);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final AccessibilityFooterPreference footerPreference =
screen.findPreference(getPreferenceKey());
updateFooterPreferences(footerPreference);
}
/**
* Override this if showing a help item in the footer bar, by returning the resource id.
*
* @return the resource id for the help url
*/
protected int getHelpResource() {
return 0;
}
/** Returns the accessibility feature name. */
protected abstract String getLabelName();
private void updateFooterPreferences(AccessibilityFooterPreference footerPreference) {
final StringBuffer sb = new StringBuffer();
sb.append(mContext.getString(
R.string.accessibility_introduction_title, getLabelName()))
.append("\n\n")
.append(footerPreference.getTitle());
footerPreference.setContentDescription(sb);
if (getHelpResource() != 0) {
footerPreference.setLearnMoreAction(view -> {
final Intent helpIntent = HelpUtils.getHelpIntent(
mContext, mContext.getString(getHelpResource()),
mContext.getClass().getName());
view.startActivityForResult(helpIntent, 0);
});
final String learnMoreContentDescription = mContext.getString(
R.string.footer_learn_more_content_description, getLabelName());
footerPreference.setLearnMoreContentDescription(learnMoreContentDescription);
}
}
}

View File

@@ -24,12 +24,10 @@ import static com.android.settings.accessibility.AccessibilityUtil.UserShortcutT
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ImageSpan;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.TextureView;
@@ -49,11 +47,11 @@ import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.util.Preconditions;
import androidx.core.widget.TextViewCompat;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.android.settings.R;
import com.android.settings.Utils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -310,30 +308,23 @@ public final class AccessibilityGestureNavigationTutorial {
}
private static View makeTitleView(Context context) {
final String familyName =
context.getString(
com.android.internal.R.string.config_headlineFontFamilyMedium);
final TextView textView = new TextView(context);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, /* size= */ 20);
textView.setTextColor(Utils.getColorAttr(context, android.R.attr.textColorPrimary));
// Sets the text color, size, style, hint color, and highlight color from the specified
// TextAppearance resource.
TextViewCompat.setTextAppearance(textView, R.style.AccessibilityDialogTitle);
textView.setGravity(Gravity.CENTER);
textView.setTypeface(Typeface.create(familyName, Typeface.NORMAL));
return textView;
}
private static View makeInstructionView(Context context) {
final TextView textView = new TextView(context);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, /* size= */ 16);
textView.setTextColor(Utils.getColorAttr(context, android.R.attr.textColorPrimary));
textView.setTypeface(
Typeface.create(/* familyName= */ "sans-serif", Typeface.NORMAL));
TextViewCompat.setTextAppearance(textView, R.style.AccessibilityDialogDescription);
return textView;
}
private static TutorialPage createSoftwareTutorialPage(@NonNull Context context) {
final CharSequence title = getSoftwareTitle(context);
final CharSequence title = context.getText(
R.string.accessibility_tutorial_dialog_title_button);
final ImageView image = createSoftwareImage(context);
final CharSequence instruction = getSoftwareInstruction(context);
final ImageView indicatorIcon =
@@ -390,44 +381,19 @@ public final class AccessibilityGestureNavigationTutorial {
return tutorialPages;
}
private static CharSequence getSoftwareTitle(Context context) {
final boolean isGestureNavigationEnabled =
AccessibilityUtil.isGestureNavigateEnabled(context);
final int resId = isGestureNavigationEnabled
? R.string.accessibility_tutorial_dialog_title_gesture
: R.string.accessibility_tutorial_dialog_title_button;
return context.getText(resId);
}
private static ImageView createSoftwareImage(Context context) {
int resId = R.drawable.accessibility_shortcut_type_software;
if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
resId = AccessibilityUtil.isTouchExploreEnabled(context)
? R.drawable.accessibility_shortcut_type_software_gesture_talkback
: R.drawable.accessibility_shortcut_type_software_gesture;
}
final int resId = AccessibilityUtil.isFloatingMenuEnabled(context)
? R.drawable.accessibility_shortcut_type_software_floating
: R.drawable.accessibility_shortcut_type_software;
return createImageView(context, resId);
}
private static CharSequence getSoftwareInstruction(Context context) {
final boolean isGestureNavigateEnabled =
AccessibilityUtil.isGestureNavigateEnabled(context);
final boolean isTouchExploreEnabled = AccessibilityUtil.isTouchExploreEnabled(context);
int resId = R.string.accessibility_tutorial_dialog_message_button;
if (isGestureNavigateEnabled) {
resId = isTouchExploreEnabled
? R.string.accessibility_tutorial_dialog_message_gesture_talkback
: R.string.accessibility_tutorial_dialog_message_gesture;
}
CharSequence text = context.getText(resId);
if (resId == R.string.accessibility_tutorial_dialog_message_button) {
text = getSoftwareInstructionWithIcon(context, text);
}
return text;
return AccessibilityUtil.isFloatingMenuEnabled(context)
? context.getText(R.string.accessibility_tutorial_dialog_message_floating_button)
: getSoftwareInstructionWithIcon(context,
context.getText(R.string.accessibility_tutorial_dialog_message_button));
}
private static CharSequence getSoftwareInstructionWithIcon(Context context, CharSequence text) {

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import com.android.settingslib.search.SearchIndexableRaw;
import java.util.List;
/**
* Provider for Accessibility Search related features.
*/
public interface AccessibilitySearchFeatureProvider {
/**
* Returns a list of raw data for indexing. See {@link SearchIndexableRaw}
*
* @param context a valid context {@link Context} instance
* @return a list of {@link SearchIndexableRaw} references. Can be null.
*/
List<SearchIndexableRaw> getSearchIndexableRawData(Context context);
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import com.android.settingslib.search.SearchIndexableRaw;
import java.util.List;
/**
* Provider implementation for Accessibility Search related features.
*/
public class AccessibilitySearchFeatureProviderImpl implements AccessibilitySearchFeatureProvider {
@Override
public List<SearchIndexableRaw> getSearchIndexableRawData(Context context) {
return null;
}
}

View File

@@ -35,6 +35,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
@@ -60,11 +61,19 @@ public class AccessibilityServiceWarning {
return false;
};
/**
* The interface to execute the uninstallation action.
*/
interface UninstallActionPerformer {
void uninstallPackage();
}
/** Returns a {@link Dialog} to be shown to confirm that they want to enable a service. */
public static Dialog createCapabilitiesDialog(Context context,
AccessibilityServiceInfo info, View.OnClickListener listener) {
public static Dialog createCapabilitiesDialog(@NonNull Context context,
@NonNull AccessibilityServiceInfo info, @NonNull View.OnClickListener listener,
@NonNull UninstallActionPerformer performer) {
final AlertDialog ad = new AlertDialog.Builder(context)
.setView(createEnableDialogContentView(context, info, listener))
.setView(createEnableDialogContentView(context, info, listener, performer))
.create();
Window window = ad.getWindow();
@@ -88,7 +97,8 @@ public class AccessibilityServiceWarning {
}
private static View createEnableDialogContentView(Context context,
AccessibilityServiceInfo info, View.OnClickListener listener) {
@NonNull AccessibilityServiceInfo info, View.OnClickListener listener,
UninstallActionPerformer performer) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
@@ -129,6 +139,14 @@ public class AccessibilityServiceWarning {
permissionAllowButton.setOnTouchListener(filterTouchListener);
permissionDenyButton.setOnClickListener(listener);
final Button uninstallButton = content.findViewById(
R.id.permission_enable_uninstall_button);
// Shows an uninstall button to help users quickly remove the non-system App due to the
// required permissions.
if (!AccessibilityUtil.isSystemApp(info)) {
uninstallButton.setVisibility(View.VISIBLE);
uninstallButton.setOnClickListener(v -> performer.uninstallPackage());
}
return content;
}

View File

@@ -16,7 +16,7 @@
package com.android.settings.accessibility;
import static com.android.settingslib.TwoTargetPreference.ICON_SIZE_MEDIUM;
import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityShortcutInfo;
@@ -28,8 +28,8 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.hardware.display.ColorDisplayManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -43,7 +43,6 @@ import androidx.annotation.VisibleForTesting;
import androidx.core.content.ContextCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.SwitchPreference;
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.content.PackageMonitor;
@@ -51,13 +50,14 @@ import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.display.DarkUIPreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.accessibility.AccessibilityUtils;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.search.SearchIndexableRaw;
import java.util.ArrayList;
import java.util.Collection;
@@ -76,28 +76,17 @@ public class AccessibilitySettings extends DashboardFragment {
// Preference categories
private static final String CATEGORY_SCREEN_READER = "screen_reader_category";
private static final String CATEGORY_AUDIO_AND_CAPTIONS = "audio_and_captions_category";
private static final String CATEGORY_CAPTIONS = "captions_category";
private static final String CATEGORY_AUDIO = "audio_category";
private static final String CATEGORY_DISPLAY = "display_category";
private static final String CATEGORY_INTERACTION_CONTROL = "interaction_control_category";
private static final String CATEGORY_EXPERIMENTAL = "experimental_category";
private static final String CATEGORY_DOWNLOADED_SERVICES = "user_installed_services_category";
private static final String[] CATEGORIES = new String[] {
CATEGORY_SCREEN_READER, CATEGORY_AUDIO_AND_CAPTIONS, CATEGORY_DISPLAY,
CATEGORY_INTERACTION_CONTROL, CATEGORY_EXPERIMENTAL, CATEGORY_DOWNLOADED_SERVICES
private static final String[] CATEGORIES = new String[]{
CATEGORY_SCREEN_READER, CATEGORY_CAPTIONS, CATEGORY_AUDIO, CATEGORY_DISPLAY,
CATEGORY_INTERACTION_CONTROL, CATEGORY_DOWNLOADED_SERVICES
};
// Preferences
private static final String TOGGLE_INVERSION_PREFERENCE =
"toggle_inversion_preference";
private static final String TOGGLE_LARGE_POINTER_ICON =
"toggle_large_pointer_icon";
private static final String TOGGLE_DISABLE_ANIMATIONS = "toggle_disable_animations";
private static final String DISPLAY_MAGNIFICATION_PREFERENCE_SCREEN =
"magnification_preference_screen";
private static final String DISPLAY_DALTONIZER_PREFERENCE_SCREEN =
"daltonizer_preference";
// Extras passed to sub-fragments.
static final String EXTRA_PREFERENCE_KEY = "preference_key";
static final String EXTRA_CHECKED = "checked";
@@ -125,7 +114,7 @@ public class AccessibilitySettings extends DashboardFragment {
@Override
public void run() {
if (getActivity() != null) {
updateServicePreferences();
onContentChanged();
}
}
};
@@ -156,7 +145,8 @@ public class AccessibilitySettings extends DashboardFragment {
}
};
private final SettingsContentObserver mSettingsContentObserver;
@VisibleForTesting
final SettingsContentObserver mSettingsContentObserver;
private final Map<String, PreferenceCategory> mCategoryToPrefCategoryMap =
new ArrayMap<>();
@@ -165,22 +155,8 @@ public class AccessibilitySettings extends DashboardFragment {
private final Map<ComponentName, PreferenceCategory> mPreBundledServiceComponentToCategoryMap =
new ArrayMap<>();
private SwitchPreference mToggleLargePointerIconPreference;
private SwitchPreference mToggleDisableAnimationsPreference;
private Preference mDisplayMagnificationPreferenceScreen;
private Preference mDisplayDaltonizerPreferenceScreen;
private Preference mToggleInversionPreference;
/**
* Check if the color transforms are color accelerated. Some transforms are experimental only
* on non-accelerated platforms due to the performance implications.
*
* @param context The current context
*/
public static boolean isColorTransformAccelerated(Context context) {
return context.getResources()
.getBoolean(com.android.internal.R.bool.config_setColorTransformAccelerated);
}
private boolean mNeedPreferencesUpdate = false;
private boolean mIsForeground = true;
public AccessibilitySettings() {
// Observe changes to anything that the shortcut can toggle, so we can reflect updates
@@ -197,7 +173,7 @@ public class AccessibilitySettings extends DashboardFragment {
mSettingsContentObserver = new SettingsContentObserver(mHandler, shortcutFeatureKeys) {
@Override
public void onChange(boolean selfChange, Uri uri) {
updateAllPreferences();
onContentChanged();
}
};
}
@@ -212,36 +188,43 @@ public class AccessibilitySettings extends DashboardFragment {
return R.string.help_uri_accessibility;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
initializeAllPreferences();
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
use(DarkUIPreferenceController.class).setParentFragment(this);
use(AccessibilityHearingAidPreferenceController.class)
.setFragmentManager(getFragmentManager());
}
@Override
public void onStart() {
super.onStart();
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
initializeAllPreferences();
updateAllPreferences();
registerContentMonitors();
}
mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
mSettingsContentObserver.register(getContentResolver());
@Override
public void onStart() {
if (mNeedPreferencesUpdate) {
updateAllPreferences();
mNeedPreferencesUpdate = false;
}
mIsForeground = true;
super.onStart();
}
@Override
public void onStop() {
mSettingsPackageMonitor.unregister();
mSettingsContentObserver.unregister(getContentResolver());
mIsForeground = false;
super.onStop();
}
@Override
public void onDestroy() {
unregisterContentMonitors();
super.onDestroy();
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_settings;
@@ -255,8 +238,8 @@ public class AccessibilitySettings extends DashboardFragment {
/**
* Returns the summary for the current state of this accessibilityService.
*
* @param context A valid context
* @param info The accessibilityService's info
* @param context A valid context
* @param info The accessibilityService's info
* @param serviceEnabled Whether the accessibility service is enabled.
* @return The service summary
*/
@@ -295,8 +278,8 @@ public class AccessibilitySettings extends DashboardFragment {
/**
* Returns the description for the current state of this accessibilityService.
*
* @param context A valid context
* @param info The accessibilityService's info
* @param context A valid context
* @param info The accessibilityService's info
* @param serviceEnabled Whether the accessibility service is enabled.
* @return The service description
*/
@@ -315,33 +298,43 @@ public class AccessibilitySettings extends DashboardFragment {
context.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, 0) == 1;
}
@VisibleForTesting
void onContentChanged() {
// If the fragment is visible then update preferences immediately, else set the flag then
// wait for the fragment to show up to update preferences.
if (mIsForeground) {
updateAllPreferences();
} else {
mNeedPreferencesUpdate = true;
}
}
private void initializeAllPreferences() {
for (int i = 0; i < CATEGORIES.length; i++) {
PreferenceCategory prefCategory = findPreference(CATEGORIES[i]);
mCategoryToPrefCategoryMap.put(CATEGORIES[i], prefCategory);
}
// Display inversion.
mToggleInversionPreference = findPreference(TOGGLE_INVERSION_PREFERENCE);
// Large pointer icon.
mToggleLargePointerIconPreference = findPreference(TOGGLE_LARGE_POINTER_ICON);
mToggleDisableAnimationsPreference = findPreference(TOGGLE_DISABLE_ANIMATIONS);
// Display magnification.
mDisplayMagnificationPreferenceScreen = findPreference(
DISPLAY_MAGNIFICATION_PREFERENCE_SCREEN);
// Display color adjustments.
mDisplayDaltonizerPreferenceScreen = findPreference(DISPLAY_DALTONIZER_PREFERENCE_SCREEN);
}
private void updateAllPreferences() {
@VisibleForTesting
void updateAllPreferences() {
updateSystemPreferences();
updateServicePreferences();
}
private void registerContentMonitors() {
final Context context = getActivity();
mSettingsPackageMonitor.register(context, context.getMainLooper(), /* externalStorage= */
false);
mSettingsContentObserver.register(getContentResolver());
}
private void unregisterContentMonitors() {
mSettingsPackageMonitor.unregister();
mSettingsContentObserver.unregister(getContentResolver());
}
protected void updateServicePreferences() {
// Since services category is auto generated we have to do a pass
// to generate it since services can come and go and then based on
@@ -356,8 +349,10 @@ public class AccessibilitySettings extends DashboardFragment {
initializePreBundledServicesMapFromArray(CATEGORY_SCREEN_READER,
R.array.config_preinstalled_screen_reader_services);
initializePreBundledServicesMapFromArray(CATEGORY_AUDIO_AND_CAPTIONS,
R.array.config_preinstalled_audio_and_caption_services);
initializePreBundledServicesMapFromArray(CATEGORY_CAPTIONS,
R.array.config_preinstalled_captions_services);
initializePreBundledServicesMapFromArray(CATEGORY_AUDIO,
R.array.config_preinstalled_audio_services);
initializePreBundledServicesMapFromArray(CATEGORY_DISPLAY,
R.array.config_preinstalled_display_services);
initializePreBundledServicesMapFromArray(CATEGORY_INTERACTION_CONTROL,
@@ -384,13 +379,15 @@ public class AccessibilitySettings extends DashboardFragment {
// Update the order of all the category according to the order defined in xml file.
updateCategoryOrderFromArray(CATEGORY_SCREEN_READER,
R.array.config_order_screen_reader_services);
updateCategoryOrderFromArray(CATEGORY_AUDIO_AND_CAPTIONS,
R.array.config_order_audio_and_caption_services);
R.array.config_order_screen_reader_services);
updateCategoryOrderFromArray(CATEGORY_CAPTIONS,
R.array.config_order_captions_services);
updateCategoryOrderFromArray(CATEGORY_AUDIO,
R.array.config_order_audio_services);
updateCategoryOrderFromArray(CATEGORY_INTERACTION_CONTROL,
R.array.config_order_interaction_control_services);
R.array.config_order_interaction_control_services);
updateCategoryOrderFromArray(CATEGORY_DISPLAY,
R.array.config_order_display_services);
R.array.config_order_display_services);
// Need to check each time when updateServicePreferences() called.
if (downloadedServicesCategory.getPreferenceCount() == 0) {
@@ -398,6 +395,9 @@ public class AccessibilitySettings extends DashboardFragment {
} else {
getPreferenceScreen().addPreference(downloadedServicesCategory);
}
// Hide screen reader category if it is empty.
updatePreferenceCategoryVisibility(CATEGORY_SCREEN_READER);
}
private List<RestrictedPreference> getInstalledAccessibilityList(Context context) {
@@ -460,7 +460,7 @@ public class AccessibilitySettings extends DashboardFragment {
* key with the string array of preference order which is defined in the xml.
*
* @param categoryKey The key of the category need to update the order
* @param key The key of the string array which defines the order of category
* @param key The key of the string array which defines the order of category
*/
private void updateCategoryOrderFromArray(String categoryKey, int key) {
String[] services = getResources().getStringArray(key);
@@ -478,37 +478,33 @@ public class AccessibilitySettings extends DashboardFragment {
}
}
/**
* Updates the visibility of a category according to its child preference count.
*
* @param categoryKey The key of the category which needs to check
*/
private void updatePreferenceCategoryVisibility(String categoryKey) {
final PreferenceCategory category = mCategoryToPrefCategoryMap.get(categoryKey);
category.setVisible(category.getPreferenceCount() != 0);
}
/**
* Updates preferences related to system configurations.
*/
protected void updateSystemPreferences() {
// Move color inversion and color correction preferences to Display category if device
// supports HWC hardware-accelerated color transform.
if (ColorDisplayManager.isColorTransformAccelerated(getContext())) {
PreferenceCategory experimentalCategory =
mCategoryToPrefCategoryMap.get(CATEGORY_EXPERIMENTAL);
PreferenceCategory displayCategory =
mCategoryToPrefCategoryMap.get(CATEGORY_DISPLAY);
experimentalCategory.removePreference(mToggleInversionPreference);
experimentalCategory.removePreference(mDisplayDaltonizerPreferenceScreen);
mDisplayMagnificationPreferenceScreen.setSummary(
ToggleScreenMagnificationPreferenceFragment.getServiceSummary(getContext()));
mDisplayDaltonizerPreferenceScreen.setOrder(
mDisplayMagnificationPreferenceScreen.getOrder() + 1);
mDisplayDaltonizerPreferenceScreen.setSummary(AccessibilityUtil.getSummary(
getContext(), Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED));
mToggleInversionPreference.setOrder(
mDisplayDaltonizerPreferenceScreen.getOrder() + 1);
mToggleLargePointerIconPreference.setOrder(
mToggleInversionPreference.getOrder() + 1);
mToggleDisableAnimationsPreference.setOrder(
mToggleLargePointerIconPreference.getOrder() + 1);
mToggleInversionPreference.setSummary(AccessibilityUtil.getSummary(
getContext(), Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED));
displayCategory.addPreference(mToggleInversionPreference);
displayCategory.addPreference(mDisplayDaltonizerPreferenceScreen);
}
// Do nothing.
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_settings);
new BaseSearchIndexProvider(R.xml.accessibility_settings) {
@Override
public List<SearchIndexableRaw> getRawDataToIndex(Context context,
boolean enabled) {
return FeatureFactory.getFactory(context)
.getAccessibilitySearchFeatureProvider().getSearchIndexableRawData(
context);
}
};
/**
* This class helps setup RestrictedPreference.
@@ -532,6 +528,7 @@ public class AccessibilitySettings extends DashboardFragment {
* installed accessibility services
* @return The list of {@link RestrictedPreference}
*/
@VisibleForTesting
List<RestrictedPreference> createAccessibilityServicePreferenceList(
List<AccessibilityServiceInfo> installedServices) {
@@ -572,7 +569,6 @@ public class AccessibilitySettings extends DashboardFragment {
setRestrictedPreferenceEnabled(preference, packageName, serviceAllowed,
serviceEnabled);
final String prefKey = preference.getKey();
final int imageRes = info.getAnimatedImageRes();
final CharSequence description = getServiceDescription(mContext, info,
@@ -597,6 +593,7 @@ public class AccessibilitySettings extends DashboardFragment {
* installed accessibility shortcuts
* @return The list of {@link RestrictedPreference}
*/
@VisibleForTesting
List<RestrictedPreference> createAccessibilityActivityPreferenceList(
List<AccessibilityShortcutInfo> installedShortcuts) {
final Set<ComponentName> enabledServices =
@@ -676,7 +673,7 @@ public class AccessibilitySettings extends DashboardFragment {
preference.setKey(key);
preference.setTitle(title);
preference.setSummary(summary);
Utils.setSafeIcon(preference, icon);
preference.setIcon(Utils.getAdaptiveIcon(mContext, icon, Color.WHITE));
preference.setFragment(fragment);
preference.setIconSize(ICON_SIZE_MEDIUM);
preference.setPersistent(false); // Disable SharedPreferences.

View File

@@ -16,26 +16,33 @@
package com.android.settings.accessibility;
import static com.android.settings.Utils.getAdaptiveIcon;
import static com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE;
import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ServiceInfo;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.widget.LinearLayout;
import androidx.preference.Preference;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settingslib.RestrictedPreference;
import com.google.android.setupdesign.GlifPreferenceLayout;
import com.google.android.setupdesign.util.ThemeHelper;
import java.util.List;
@@ -61,8 +68,8 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm
// Preference controls.
private Preference mDisplayMagnificationPreference;
private Preference mScreenReaderPreference;
private Preference mSelectToSpeakPreference;
private RestrictedPreference mScreenReaderPreference;
private RestrictedPreference mSelectToSpeakPreference;
@Override
public int getMetricsCategory() {
@@ -73,16 +80,23 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
GlifPreferenceLayout layout = (GlifPreferenceLayout) view;
final GlifPreferenceLayout layout = (GlifPreferenceLayout) view;
layout.setDividerInsets(Integer.MAX_VALUE, 0);
layout.setDescriptionText(R.string.vision_settings_description);
layout.setHeaderText(R.string.vision_settings_title);
layout.setIcon(getPrefContext().getDrawable(R.drawable.ic_accessibility_visibility));
if (ThemeHelper.shouldApplyExtendedPartnerConfig(getActivity())) {
final LinearLayout headerLayout = layout.findManagedViewById(R.id.sud_layout_header);
headerLayout.setPadding(0, headerLayout.getPaddingTop(), 0,
headerLayout.getPaddingBottom());
}
}
@Override
public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
final GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
return layout.onCreateRecyclerView(inflater, parent, savedInstanceState);
}
@@ -145,7 +159,7 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm
return null;
}
private void updateAccessibilityServicePreference(Preference preference,
private void updateAccessibilityServicePreference(RestrictedPreference preference,
String packageName, String serviceName, String targetFragment) {
final AccessibilityServiceInfo info = findService(packageName, serviceName);
if (info == null) {
@@ -153,24 +167,28 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm
return;
}
ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
String title = info.getResolveInfo().loadLabel(getPackageManager()).toString();
final ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
final Drawable icon = info.getResolveInfo().loadIcon(getPackageManager());
preference.setIcon(getAdaptiveIcon(getContext(), icon, Color.WHITE));
preference.setIconSize(ICON_SIZE_MEDIUM);
final String title = info.getResolveInfo().loadLabel(getPackageManager()).toString();
preference.setTitle(title);
ComponentName componentName = new ComponentName(serviceInfo.packageName, serviceInfo.name);
final ComponentName componentName =
new ComponentName(serviceInfo.packageName, serviceInfo.name);
preference.setKey(componentName.flattenToString());
if (AccessibilityUtil.getAccessibilityServiceFragmentType(info) == VOLUME_SHORTCUT_TOGGLE) {
preference.setFragment(targetFragment);
}
// Update the extras.
Bundle extras = preference.getExtras();
final Bundle extras = preference.getExtras();
extras.putParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME, componentName);
extras.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY,
preference.getKey());
extras.putString(AccessibilitySettings.EXTRA_TITLE, title);
String description = info.loadDescription(getPackageManager());
final String description = info.loadDescription(getPackageManager());
extras.putString(AccessibilitySettings.EXTRA_SUMMARY, description);
extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, info.getAnimatedImageRes());

View File

@@ -34,8 +34,10 @@ import com.android.settings.display.FontSizePreferenceFragmentForSetupWizard;
import com.android.settings.search.actionbar.SearchMenuController;
import com.android.settings.support.actionbar.HelpResourceProvider;
import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.transition.SettingsTransitionHelper;
import com.google.android.setupcompat.util.WizardManagerHelper;
import com.google.android.setupdesign.util.ThemeHelper;
public class AccessibilitySettingsForSetupWizardActivity extends SettingsActivity {
@@ -89,6 +91,9 @@ public class AccessibilitySettingsForSetupWizardActivity extends SettingsActivit
.setSourceMetricsCategory(caller instanceof Instrumentable
? ((Instrumentable) caller).getMetricsCategory()
: Instrumentable.METRICS_CATEGORY_UNKNOWN)
.setExtras(SetupWizardUtils.copyLifecycleExtra(getIntent().getExtras(),
new Bundle()))
.setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_FADE)
.launch();
return true;
}
@@ -96,11 +101,22 @@ public class AccessibilitySettingsForSetupWizardActivity extends SettingsActivit
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
applyTheme();
tryLaunchFontSizeSettings();
findViewById(R.id.content_parent).setFitsSystemWindows(false);
}
private void applyTheme() {
if (ThemeHelper.trySetDynamicColor(this)) {
final int appliedTheme = ThemeHelper.isSetupWizardDayNightEnabled(this)
? R.style.SudDynamicColorThemeSettings_SetupWizard_DayNight
: R.style.SudDynamicColorThemeSettings_SetupWizard;
setTheme(appliedTheme);
} else {
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
}
}
@VisibleForTesting
void tryLaunchFontSizeSettings() {
if (WizardManagerHelper.isAnySetupWizard(getIntent())
@@ -115,7 +131,8 @@ public class AccessibilitySettingsForSetupWizardActivity extends SettingsActivit
.setArguments(args)
.setSourceMetricsCategory(Instrumentable.METRICS_CATEGORY_UNKNOWN)
.setExtras(SetupWizardUtils.copyLifecycleExtra(getIntent().getExtras(),
new Bundle()));
new Bundle()))
.setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_FADE);
Log.d(LOG_TAG, "Launch font size settings");
subSettingLauncher.launch();

View File

@@ -0,0 +1,425 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_GENERAL_CATEGORY;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.icu.text.CaseMap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.widget.CheckBox;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.utils.LocaleUtils;
import com.google.android.setupcompat.util.WizardManagerHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* Base class for accessibility fragments shortcut functions and dialog management.
*/
public abstract class AccessibilityShortcutPreferenceFragment extends DashboardFragment
implements ShortcutPreference.OnClickCallback {
private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
protected static final String KEY_SAVED_USER_SHORTCUT_TYPE = "shortcut_type";
protected static final int NOT_SET = -1;
// Save user's shortcutType value when savedInstance has value (e.g. device rotated).
protected int mSavedCheckBoxValue = NOT_SET;
protected ShortcutPreference mShortcutPreference;
private AccessibilityManager.TouchExplorationStateChangeListener
mTouchExplorationStateChangeListener;
private SettingsContentObserver mSettingsContentObserver;
private CheckBox mSoftwareTypeCheckBox;
private CheckBox mHardwareTypeCheckBox;
/** Returns the accessibility component name. */
protected abstract ComponentName getComponentName();
/** Returns the accessibility feature name. */
protected abstract CharSequence getLabelName();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Restore the user shortcut type.
if (savedInstanceState != null && savedInstanceState.containsKey(
KEY_SAVED_USER_SHORTCUT_TYPE)) {
mSavedCheckBoxValue = savedInstanceState.getInt(KEY_SAVED_USER_SHORTCUT_TYPE, NOT_SET);
}
final int resId = getPreferenceScreenResId();
if (resId <= 0) {
final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
getPrefContext());
setPreferenceScreen(preferenceScreen);
}
if (showGeneralCategory()) {
initGeneralCategory();
}
final List<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) {
mShortcutPreference = new ShortcutPreference(getPrefContext(), /* attrs= */ null);
mShortcutPreference.setPersistent(false);
mShortcutPreference.setKey(getShortcutPreferenceKey());
mShortcutPreference.setOnClickCallback(this);
final CharSequence title = getString(R.string.accessibility_shortcut_title, getLabelName());
mShortcutPreference.setTitle(title);
getPreferenceScreen().addPreference(mShortcutPreference);
mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
removeDialog(DialogEnums.EDIT_SHORTCUT);
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
};
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onResume() {
super.onResume();
final AccessibilityManager am = getPrefContext().getSystemService(
AccessibilityManager.class);
am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
mSettingsContentObserver.register(getContentResolver());
updateShortcutPreferenceData();
updateShortcutPreference();
}
@Override
public void onPause() {
final AccessibilityManager am = getPrefContext().getSystemService(
AccessibilityManager.class);
am.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
mSettingsContentObserver.unregister(getContentResolver());
super.onPause();
}
@Override
public void onSaveInstanceState(Bundle outState) {
final int value = getShortcutTypeCheckBoxValue();
if (value != NOT_SET) {
outState.putInt(KEY_SAVED_USER_SHORTCUT_TYPE, value);
}
super.onSaveInstanceState(outState);
}
@Override
public Dialog onCreateDialog(int dialogId) {
final Dialog dialog;
switch (dialogId) {
case DialogEnums.EDIT_SHORTCUT:
final CharSequence dialogTitle = getPrefContext().getString(
R.string.accessibility_shortcut_title, getLabelName());
final int dialogType = WizardManagerHelper.isAnySetupWizard(getIntent())
? AccessibilityDialogUtils.DialogType.EDIT_SHORTCUT_GENERIC_SUW :
AccessibilityDialogUtils.DialogType.EDIT_SHORTCUT_GENERIC;
dialog = AccessibilityDialogUtils.showEditShortcutDialog(
getPrefContext(), dialogType, dialogTitle,
this::callOnAlertDialogCheckboxClicked);
setupEditShortcutDialog(dialog);
return dialog;
case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
dialog = AccessibilityGestureNavigationTutorial
.createAccessibilityTutorialDialog(getPrefContext(),
getUserShortcutTypes());
dialog.setCanceledOnTouchOutside(false);
return dialog;
default:
throw new IllegalArgumentException("Unsupported dialogId " + dialogId);
}
}
@Override
public int getDialogMetricsCategory(int dialogId) {
switch (dialogId) {
case DialogEnums.EDIT_SHORTCUT:
return SettingsEnums.DIALOG_ACCESSIBILITY_SERVICE_EDIT_SHORTCUT;
case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
return SettingsEnums.DIALOG_ACCESSIBILITY_TUTORIAL;
default:
return SettingsEnums.ACTION_UNKNOWN;
}
}
@Override
public void onSettingsClicked(ShortcutPreference preference) {
showDialog(DialogEnums.EDIT_SHORTCUT);
}
@Override
public void onToggleClicked(ShortcutPreference preference) {
if (getComponentName() == null) {
return;
}
final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(),
getComponentName().flattenToString(), AccessibilityUtil.UserShortcutType.SOFTWARE);
if (preference.isChecked()) {
AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), shortcutTypes,
getComponentName());
showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
} else {
AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), shortcutTypes,
getComponentName());
}
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
/**
* Overrides to return specific shortcut preference key
*
* @return String The specific shortcut preference key
*/
protected String getShortcutPreferenceKey() {
return KEY_SHORTCUT_PREFERENCE;
}
@VisibleForTesting
void setupEditShortcutDialog(Dialog dialog) {
final View dialogSoftwareView = dialog.findViewById(R.id.software_shortcut);
mSoftwareTypeCheckBox = dialogSoftwareView.findViewById(R.id.checkbox);
setDialogTextAreaClickListener(dialogSoftwareView, mSoftwareTypeCheckBox);
final View dialogHardwareView = dialog.findViewById(R.id.hardware_shortcut);
mHardwareTypeCheckBox = dialogHardwareView.findViewById(R.id.checkbox);
setDialogTextAreaClickListener(dialogHardwareView, mHardwareTypeCheckBox);
updateEditShortcutDialogCheckBox();
}
/**
* Returns accumulated {@link AccessibilityUtil.UserShortcutType} checkbox value or
* {@code NOT_SET} if checkboxes did not exist.
*/
protected int getShortcutTypeCheckBoxValue() {
if (mSoftwareTypeCheckBox == null || mHardwareTypeCheckBox == null) {
return NOT_SET;
}
int value = AccessibilityUtil.UserShortcutType.EMPTY;
if (mSoftwareTypeCheckBox.isChecked()) {
value |= AccessibilityUtil.UserShortcutType.SOFTWARE;
}
if (mHardwareTypeCheckBox.isChecked()) {
value |= AccessibilityUtil.UserShortcutType.HARDWARE;
}
return value;
}
/**
* Returns the shortcut type list which has been checked by user.
*/
protected int getUserShortcutTypes() {
return AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(),
getComponentName());
};
/**
* This method will be invoked when a button in the edit shortcut dialog is clicked.
*
* @param dialog The dialog that received the click
* @param which The button that was clicked
*/
protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
if (getComponentName() == null) {
return;
}
final int value = getShortcutTypeCheckBoxValue();
saveNonEmptyUserShortcutType(value);
AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), value, getComponentName());
AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), ~value, getComponentName());
mShortcutPreference.setChecked(value != AccessibilityUtil.UserShortcutType.EMPTY);
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
@VisibleForTesting
void initGeneralCategory() {
final PreferenceCategory generalCategory = new PreferenceCategory(getPrefContext());
generalCategory.setKey(KEY_GENERAL_CATEGORY);
generalCategory.setTitle(getGeneralCategoryDescription(null));
getPreferenceScreen().addPreference(generalCategory);
}
@VisibleForTesting
void saveNonEmptyUserShortcutType(int type) {
if (type == AccessibilityUtil.UserShortcutType.EMPTY) {
return;
}
final PreferredShortcut shortcut = new PreferredShortcut(
getComponentName().flattenToString(), type);
PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
}
/**
* Overrides to return customized description for general category above shortcut
*
* @return CharSequence The customized description for general category
*/
protected CharSequence getGeneralCategoryDescription(@Nullable CharSequence title) {
if (title == null || title.toString().isEmpty()) {
// Return default 'Options' string for category
return getContext().getString(R.string.accessibility_screen_option);
}
return title;
}
/**
* Overrides to determinate if showing additional category description above shortcut
*
* @return boolean true to show category, false otherwise.
*/
protected boolean showGeneralCategory() {
return false;
}
private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) {
final View dialogTextArea = dialogView.findViewById(R.id.container);
dialogTextArea.setOnClickListener(v -> checkBox.toggle());
}
protected CharSequence getShortcutTypeSummary(Context context) {
if (!mShortcutPreference.isSettingsEditable()) {
return context.getText(R.string.accessibility_shortcut_edit_dialog_title_hardware);
}
if (!mShortcutPreference.isChecked()) {
return context.getText(R.string.switch_off_text);
}
final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(context,
getComponentName().flattenToString(), AccessibilityUtil.UserShortcutType.SOFTWARE);
final List<CharSequence> list = new ArrayList<>();
final CharSequence softwareTitle = context.getText(
R.string.accessibility_shortcut_edit_summary_software);
if (hasShortcutType(shortcutTypes, AccessibilityUtil.UserShortcutType.SOFTWARE)) {
list.add(softwareTitle);
}
if (hasShortcutType(shortcutTypes, AccessibilityUtil.UserShortcutType.HARDWARE)) {
final CharSequence hardwareTitle = context.getText(
R.string.accessibility_shortcut_hardware_keyword);
list.add(hardwareTitle);
}
// Show software shortcut if first time to use.
if (list.isEmpty()) {
list.add(softwareTitle);
}
return CaseMap.toTitle().wholeString().noLowercase().apply(Locale.getDefault(), /* iter= */
null, LocaleUtils.getConcatenatedString(list));
}
private void updateEditShortcutDialogCheckBox() {
// If it is during onConfigChanged process then restore the value, or get the saved value
// when shortcutPreference is checked.
int value = restoreOnConfigChangedValue();
if (value == NOT_SET) {
final int lastNonEmptyUserShortcutType = PreferredShortcuts.retrieveUserShortcutType(
getPrefContext(), getComponentName().flattenToString(),
AccessibilityUtil.UserShortcutType.SOFTWARE);
value = mShortcutPreference.isChecked() ? lastNonEmptyUserShortcutType
: AccessibilityUtil.UserShortcutType.EMPTY;
}
mSoftwareTypeCheckBox.setChecked(
hasShortcutType(value, AccessibilityUtil.UserShortcutType.SOFTWARE));
mHardwareTypeCheckBox.setChecked(
hasShortcutType(value, AccessibilityUtil.UserShortcutType.HARDWARE));
}
private int restoreOnConfigChangedValue() {
final int savedValue = mSavedCheckBoxValue;
mSavedCheckBoxValue = NOT_SET;
return savedValue;
}
private boolean hasShortcutType(int value, @AccessibilityUtil.UserShortcutType int type) {
return (value & type) == type;
}
protected void updateShortcutPreferenceData() {
if (getComponentName() == null) {
return;
}
final int shortcutTypes = AccessibilityUtil.getUserShortcutTypesFromSettings(
getPrefContext(), getComponentName());
if (shortcutTypes != AccessibilityUtil.UserShortcutType.EMPTY) {
final PreferredShortcut shortcut = new PreferredShortcut(
getComponentName().flattenToString(), shortcutTypes);
PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
}
}
protected void updateShortcutPreference() {
if (getComponentName() == null) {
return;
}
final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(),
getComponentName().flattenToString(), AccessibilityUtil.UserShortcutType.SOFTWARE);
mShortcutPreference.setChecked(
AccessibilityUtil.hasValuesInSettings(getPrefContext(), shortcutTypes,
getComponentName()));
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
}

View File

@@ -16,6 +16,7 @@
package com.android.settings.accessibility;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -143,6 +144,13 @@ final class AccessibilityUtil {
== NAV_BAR_MODE_GESTURAL;
}
/** Determines if a accessibility floating menu is being used. */
public static boolean isFloatingMenuEnabled(Context context) {
return Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, /* def= */ -1)
== ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
}
/** Determines if a touch explore is being used. */
public static boolean isTouchExploreEnabled(Context context) {
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
@@ -381,4 +389,13 @@ final class AccessibilityUtil {
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenHeightDp,
resources.getDisplayMetrics()));
}
/**
* Indicates if the accessibility service belongs to a system App.
* @param info AccessibilityServiceInfo
* @return {@code true} if the App is a system App.
*/
public static boolean isSystemApp(@NonNull AccessibilityServiceInfo info) {
return info.getResolveInfo().serviceInfo.applicationInfo.isSystemApp();
}
}

View File

@@ -1,90 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.widget.ImageView;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
/**
* A custom {@link ImageView} preference for showing animated or static image, such as <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) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
/** Accessibility settings for audio adjustment. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class AudioAdjustmentFragment extends DashboardFragment {
private static final String TAG = "AudioAdjustmentFragment";
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_AUDIO_ADJUSTMENT;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_audio_adjustment;
}
@Override
protected String getLogTag() {
return TAG;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_audio_adjustment);
}

View File

@@ -16,6 +16,7 @@
package com.android.settings.accessibility;
import static android.view.HapticFeedbackConstants.CLOCK_TICK;
import static com.android.settings.Utils.isNightMode;
import android.content.Context;
@@ -44,6 +45,7 @@ public class BalanceSeekBar extends SeekBar {
private final Context mContext;
private final Object mListenerLock = new Object();
private OnSeekBarChangeListener mOnSeekBarChangeListener;
private int mLastProgress = -1;
private final OnSeekBarChangeListener mProxySeekBarListener = new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
@@ -73,6 +75,12 @@ public class BalanceSeekBar extends SeekBar {
progress = mCenter;
seekBar.setProgress(progress); // direct update (fromUser becomes false)
}
if (progress != mLastProgress) {
if (progress == mCenter || progress == getMin() || progress == getMax()) {
seekBar.performHapticFeedback(CLOCK_TICK);
}
mLastProgress = progress;
}
final float balance = (progress - mCenter) * 0.01f;
Settings.System.putFloatForUser(mContext.getContentResolver(),
Settings.System.MASTER_BALANCE, balance, UserHandle.USER_CURRENT);
@@ -115,6 +123,7 @@ public class BalanceSeekBar extends SeekBar {
res.getDimensionPixelSize(R.dimen.balance_seekbar_center_marker_width),
res.getDimensionPixelSize(R.dimen.balance_seekbar_center_marker_height));
mCenterMarkerPaint = new Paint();
// TODO use a more suitable colour?
mCenterMarkerPaint.setColor(isNightMode(context) ? Color.WHITE : Color.BLACK);
mCenterMarkerPaint.setStyle(Paint.Style.FILL);
// Remove the progress colour
@@ -151,10 +160,5 @@ public class BalanceSeekBar extends SeekBar {
canvas.restore();
super.onDraw(canvas);
}
@VisibleForTesting
OnSeekBarChangeListener getProxySeekBarListener() {
return mProxySeekBarListener;
}
}

View File

@@ -33,8 +33,8 @@ import androidx.preference.PreferenceCategory;
import com.android.internal.widget.SubtitleView;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.accessibility.ListDialogPreference.OnValueChangedListener;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.accessibility.AccessibilityUtils;
import com.android.settingslib.search.SearchIndexable;
@@ -46,8 +46,10 @@ import java.util.Locale;
/** Settings fragment containing font style of captioning properties. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class CaptionAppearanceFragment extends SettingsPreferenceFragment
public class CaptionAppearanceFragment extends DashboardFragment
implements OnPreferenceChangeListener, OnValueChangedListener {
private static final String TAG = "CaptionAppearanceFragment";
private static final String PREF_CAPTION_PREVIEW = "caption_preview";
private static final String PREF_BACKGROUND_COLOR = "captioning_background_color";
private static final String PREF_BACKGROUND_OPACITY = "captioning_background_opacity";
@@ -107,12 +109,11 @@ public class CaptionAppearanceFragment extends SettingsPreferenceFragment
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
super.onCreatePreferences(savedInstanceState, rootKey);
mCaptioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
addPreferencesFromResource(R.xml.captioning_appearance);
initializeAllPreferences();
updateAllPreferences();
refreshShowingCustom();
@@ -120,6 +121,16 @@ public class CaptionAppearanceFragment extends SettingsPreferenceFragment
refreshPreviewText();
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.captioning_appearance;
}
@Override
protected String getLogTag() {
return TAG;
}
private void refreshPreviewText() {
final Context context = getActivity();
if (context == null) {

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import com.android.settings.R;
/**
* Preference controller for caption footer.
*/
public class CaptionFooterPreferenceController extends AccessibilityFooterPreferenceController {
public CaptionFooterPreferenceController(Context context, String key) {
super(context, key);
}
@Override
protected String getLabelName() {
return mContext.getString(R.string.accessibility_captioning_title);
}
@Override
protected int getHelpResource() {
return R.string.help_url_caption;
}
}

View File

@@ -26,18 +26,19 @@ import android.view.accessibility.CaptioningManager;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
/** Settings fragment containing more options of captioning properties. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class CaptionMoreOptionsFragment extends SettingsPreferenceFragment
public class CaptionMoreOptionsFragment extends DashboardFragment
implements Preference.OnPreferenceChangeListener {
private static final String TAG = "CaptionMoreOptionsFragment";
private static final String PREF_LOCALE = "captioning_locale";
private CaptioningManager mCaptioningManager;
private LocalePreference mLocale;
@Override
@@ -46,17 +47,26 @@ public class CaptionMoreOptionsFragment extends SettingsPreferenceFragment
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
super.onCreatePreferences(savedInstanceState, rootKey);
mCaptioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
addPreferencesFromResource(R.xml.captioning_more_options);
initializeAllPreferences();
updateAllPreferences();
installUpdateListeners();
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.captioning_more_options;
}
@Override
protected String getLogTag() {
return TAG;
}
private void initializeAllPreferences() {
mLocale = (LocalePreference) findPreference(PREF_LOCALE);
}

View File

@@ -22,15 +22,17 @@ import android.content.Context;
import android.os.Bundle;
import android.provider.Settings;
import android.view.accessibility.CaptioningManager;
import android.widget.Switch;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.SettingsMainSwitchPreference;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.widget.OnMainSwitchChangeListener;
import com.google.common.primitives.Floats;
@@ -39,15 +41,17 @@ import java.util.List;
/** Settings fragment containing captioning properties. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class CaptionPropertiesFragment extends SettingsPreferenceFragment
implements OnPreferenceChangeListener {
public class CaptionPropertiesFragment extends DashboardFragment
implements OnPreferenceChangeListener, OnMainSwitchChangeListener {
private static final String TAG = "CaptionPropertiesFragment";
private static final String PREF_SWITCH = "captioning_preference_switch";
private static final String PREF_TEXT = "captioning_caption_appearance";
private static final String PREF_MORE = "captioning_more_options";
private CaptioningManager mCaptioningManager;
private SwitchPreference mSwitch;
private SettingsMainSwitchPreference mSwitch;
private Preference mTextAppearance;
private Preference mMoreOptions;
@@ -60,12 +64,11 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
super.onCreatePreferences(savedInstanceState, rootKey);
mCaptioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
addPreferencesFromResource(R.xml.captioning_settings);
initializeAllPreferences();
installUpdateListeners();
initFontSizeValuesArray();
@@ -77,8 +80,18 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
updateAllPreferences();
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.captioning_settings;
}
@Override
protected String getLogTag() {
return TAG;
}
private void initializeAllPreferences() {
mSwitch = (SwitchPreference) findPreference(PREF_SWITCH);
mSwitch = (SettingsMainSwitchPreference) findPreference(PREF_SWITCH);
mTextAppearance = (Preference) findPreference(PREF_TEXT);
mMoreOptions = (Preference) findPreference(PREF_MORE);
@@ -88,6 +101,8 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
private void installUpdateListeners() {
mSwitch.setOnPreferenceChangeListener(this);
mSwitch.addOnSwitchChangeListener(this);
}
private void initFontSizeValuesArray() {
@@ -133,4 +148,11 @@ public class CaptionPropertiesFragment extends SettingsPreferenceFragment
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.captioning_settings);
@Override
public void onSwitchChanged(Switch switchView, boolean isChecked) {
final ContentResolver cr = getActivity().getContentResolver();
Settings.Secure.putInt(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, isChecked ? 1 : 0);
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
/** Preference controller that controls the fade switch button in accessibility button page. */
public class FloatingMenuFadePreferenceController extends BasePreferenceController implements
Preference.OnPreferenceChangeListener, LifecycleObserver, OnResume, OnPause {
private static final int OFF = 0;
private static final int ON = 1;
private final ContentResolver mContentResolver;
@VisibleForTesting
final ContentObserver mContentObserver;
@VisibleForTesting
SwitchPreference mPreference;
public FloatingMenuFadePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mContentResolver = context.getContentResolver();
mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
updateAvailabilityStatus();
}
};
}
@Override
public int getAvailabilityStatus() {
return AccessibilityUtil.isFloatingMenuEnabled(mContext)
? AVAILABLE : DISABLED_DEPENDENT_SETTING;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean isEnabled = (boolean) newValue;
putFloatingMenuFadeValue(isEnabled);
return true;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
final SwitchPreference switchPreference = (SwitchPreference) preference;
switchPreference.setChecked(getFloatingMenuFadeValue() == ON);
}
@Override
public void onResume() {
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_BUTTON_MODE),
/* notifyForDescendants= */ false, mContentObserver);
}
@Override
public void onPause() {
mContentResolver.unregisterContentObserver(mContentObserver);
}
private void updateAvailabilityStatus() {
mPreference.setEnabled(AccessibilityUtil.isFloatingMenuEnabled(mContext));
}
private int getFloatingMenuFadeValue() {
return Settings.Secure.getInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, ON);
}
private void putFloatingMenuFadeValue(boolean isEnabled) {
Settings.Secure.putInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
isEnabled ? ON : OFF);
}
}

View File

@@ -0,0 +1,133 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import java.util.Objects;
/** LayerDrawable that contains device icon as background and floating menu icon as foreground. */
public class FloatingMenuLayerDrawable extends LayerDrawable {
private FloatingMenuLayerDrawableState mState;
/**
* Creates a new layer drawable with the list of specified layers.
*
* @param layers a list of drawables to use as layers in this new drawable,
* must be non-null
*/
private FloatingMenuLayerDrawable(@NonNull Drawable[] layers) {
super(layers);
}
/**
* Create the {@link LayerDrawable} that contains device icon as background and floating menu
* icon with given {@code opacity} value as foreground.
*
* @param context the valid context used to get the icon
* @param resId the resource ID of the floating menu icon
* @param opacity the opacity to apply to the given icon
* @return the drawable that combines the device icon and the floating menu icon
*/
public static FloatingMenuLayerDrawable createLayerDrawable(Context context, int resId,
int opacity) {
final Drawable bg = context.getDrawable(R.drawable.accessibility_button_preview_base);
final FloatingMenuLayerDrawable basicDrawable = new FloatingMenuLayerDrawable(
new Drawable[]{bg, null});
basicDrawable.updateLayerDrawable(context, resId, opacity);
return basicDrawable;
}
/**
* Update the drawable with given {@code resId} drawable and {@code opacity}(alpha)
* value at index 1 layer.
*
* @param context the valid context used to get the icon
* @param resId the resource ID of the floating menu icon
* @param opacity the opacity to apply to the given icon
*/
public void updateLayerDrawable(Context context, int resId, int opacity) {
final Drawable icon = context.getDrawable(resId);
icon.setAlpha(opacity);
this.setDrawable(/* index= */ 1, icon);
this.setConstantState(context, resId, opacity);
}
@Override
public ConstantState getConstantState() {
return mState;
}
/** Stores the constant state and data to the given drawable. */
private void setConstantState(Context context, int resId, int opacity) {
mState = new FloatingMenuLayerDrawableState(context, resId, opacity);
}
/** {@link ConstantState} to store the data of {@link FloatingMenuLayerDrawable}. */
@VisibleForTesting
static class FloatingMenuLayerDrawableState extends ConstantState {
private final Context mContext;
private final int mResId;
private final int mOpacity;
FloatingMenuLayerDrawableState(Context context, int resId, int opacity) {
mContext = context;
mResId = resId;
mOpacity = opacity;
}
@NonNull
@Override
public Drawable newDrawable() {
return createLayerDrawable(mContext, mResId, mOpacity);
}
@Override
public int getChangingConfigurations() {
return 0;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final FloatingMenuLayerDrawableState that = (FloatingMenuLayerDrawableState) o;
return mResId == that.mResId
&& mOpacity == that.mOpacity
&& Objects.equals(mContext, that.mContext);
}
@Override
public int hashCode() {
return Objects.hash(mContext, mResId, mOpacity);
}
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.util.ArrayMap;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
import com.google.common.primitives.Ints;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** Preference controller that controls the preferred size in accessibility button page. */
public class FloatingMenuSizePreferenceController extends BasePreferenceController
implements Preference.OnPreferenceChangeListener, LifecycleObserver, OnResume, OnPause {
private final ContentResolver mContentResolver;
@VisibleForTesting
final ContentObserver mContentObserver;
@VisibleForTesting
ListPreference mPreference;
private final ArrayMap<String, String> mValueTitleMap = new ArrayMap<>();
private int mDefaultSize;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
Size.SMALL,
Size.LARGE,
})
@VisibleForTesting
@interface Size {
int SMALL = 0;
int LARGE = 1;
}
public FloatingMenuSizePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mContentResolver = context.getContentResolver();
mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
updateAvailabilityStatus();
}
};
initValueTitleMap();
}
@Override
public int getAvailabilityStatus() {
return AccessibilityUtil.isFloatingMenuEnabled(mContext)
? AVAILABLE : DISABLED_DEPENDENT_SETTING;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final ListPreference listPreference = (ListPreference) preference;
final Integer value = Ints.tryParse((String) newValue);
if (value != null) {
putAccessibilityFloatingMenuSize(value);
updateState(listPreference);
}
return true;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
final ListPreference listPreference = (ListPreference) preference;
listPreference.setValue(String.valueOf(getAccessibilityFloatingMenuSize(mDefaultSize)));
}
@Override
public void onResume() {
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_BUTTON_MODE), /* notifyForDescendants= */
false, mContentObserver);
}
@Override
public void onPause() {
mContentResolver.unregisterContentObserver(mContentObserver);
}
private void updateAvailabilityStatus() {
mPreference.setEnabled(AccessibilityUtil.isFloatingMenuEnabled(mContext));
}
private void initValueTitleMap() {
if (mValueTitleMap.size() == 0) {
final String[] values = mContext.getResources().getStringArray(
R.array.accessibility_button_size_selector_values);
final String[] titles = mContext.getResources().getStringArray(
R.array.accessibility_button_size_selector_titles);
final int mapSize = values.length;
mDefaultSize = Integer.parseInt(values[0]);
for (int i = 0; i < mapSize; i++) {
mValueTitleMap.put(values[i], titles[i]);
}
}
}
@Size
private int getAccessibilityFloatingMenuSize(@Size int defaultValue) {
return Settings.Secure.getInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, defaultValue);
}
private void putAccessibilityFloatingMenuSize(@Size int value) {
Settings.Secure.putInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, value);
}
}

View File

@@ -0,0 +1,153 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import androidx.annotation.FloatRange;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
import com.android.settings.core.SliderPreferenceController;
import com.android.settings.widget.SeekBarPreference;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
/** Preference controller that controls the transparency seekbar in accessibility button page. */
public class FloatingMenuTransparencyPreferenceController extends SliderPreferenceController
implements LifecycleObserver, OnResume, OnPause {
@VisibleForTesting
@FloatRange(from = 0.0, to = 1.0)
static final float DEFAULT_TRANSPARENCY = 0.45f;
@VisibleForTesting
static final float MAXIMUM_TRANSPARENCY = 1.0f;
private static final int FADE_ENABLED = 1;
private static final float MIN_PROGRESS = 0f;
private static final float MAX_PROGRESS = 90f;
@VisibleForTesting
static final float PRECISION = 100f;
private final ContentResolver mContentResolver;
@VisibleForTesting
final ContentObserver mContentObserver;
@VisibleForTesting
SeekBarPreference mPreference;
public FloatingMenuTransparencyPreferenceController(Context context,
String preferenceKey) {
super(context, preferenceKey);
mContentResolver = context.getContentResolver();
mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
updateAvailabilityStatus();
}
};
}
@Override
public int getAvailabilityStatus() {
return AccessibilityUtil.isFloatingMenuEnabled(mContext)
? AVAILABLE : DISABLED_DEPENDENT_SETTING;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
mPreference.setContinuousUpdates(true);
mPreference.setMax(getMax());
mPreference.setMin(getMin());
mPreference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS);
updateState(mPreference);
}
@Override
public void onResume() {
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_BUTTON_MODE), /* notifyForDescendants= */
false, mContentObserver);
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED),
/* notifyForDescendants= */ false, mContentObserver);
}
@Override
public void onPause() {
mContentResolver.unregisterContentObserver(mContentObserver);
}
@Override
public int getSliderPosition() {
return convertTransparencyFloatToInt(getTransparency());
}
@Override
public boolean setSliderPosition(int position) {
final float opacityValue = MAXIMUM_TRANSPARENCY - convertTransparencyIntToFloat(position);
return Settings.Secure.putFloat(mContentResolver,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, opacityValue);
}
@Override
public int getMax() {
return (int) MAX_PROGRESS;
}
@Override
public int getMin() {
return (int) MIN_PROGRESS;
}
private void updateAvailabilityStatus() {
final boolean fadeEnabled = Settings.Secure.getInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, FADE_ENABLED)
== FADE_ENABLED;
mPreference.setEnabled(AccessibilityUtil.isFloatingMenuEnabled(mContext) && fadeEnabled);
}
private int convertTransparencyFloatToInt(float value) {
return Math.round(value * PRECISION);
}
private float convertTransparencyIntToFloat(int value) {
return (float) value / PRECISION;
}
private float getTransparency() {
float transparencyValue = MAXIMUM_TRANSPARENCY - (Settings.Secure.getFloat(mContentResolver,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, DEFAULT_TRANSPARENCY));
final float minValue = MIN_PROGRESS / PRECISION;
final float maxValue = MAX_PROGRESS / PRECISION;
return (transparencyValue < minValue || transparencyValue > maxValue)
? DEFAULT_TRANSPARENCY : transparencyValue;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,43 +14,37 @@
* limitations under the License.
*/
package com.android.settings.security;
package com.android.settings.accessibility;
import android.content.Context;
import android.os.UserHandle;
import android.graphics.fonts.FontStyle;
import android.provider.Settings;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.core.TogglePreferenceController;
public class LockdownButtonPreferenceController extends TogglePreferenceController {
/** PreferenceController for displaying all text in bold. */
public class FontWeightAdjustmentPreferenceController extends TogglePreferenceController {
static final int BOLD_TEXT_ADJUSTMENT =
FontStyle.FONT_WEIGHT_BOLD - FontStyle.FONT_WEIGHT_NORMAL;
private final LockPatternUtils mLockPatternUtils;
public LockdownButtonPreferenceController(Context context, String key) {
super(context, key);
mLockPatternUtils = new LockPatternUtils(context);
public FontWeightAdjustmentPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
if (mLockPatternUtils.isSecure(UserHandle.myUserId())) {
return AVAILABLE;
} else {
return DISABLED_FOR_USER;
}
return AVAILABLE;
}
@Override
public boolean isChecked() {
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0) != 0;
Settings.Secure.FONT_WEIGHT_ADJUSTMENT, 0) == BOLD_TEXT_ADJUSTMENT;
}
@Override
public boolean setChecked(boolean isChecked) {
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.LOCKDOWN_IN_POWER_MENU, isChecked ? 1 : 0);
return true;
return Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.FONT_WEIGHT_ADJUSTMENT, (isChecked ? BOLD_TEXT_ADJUSTMENT : 0));
}
}

View File

@@ -38,7 +38,7 @@ public class InvisibleToggleAccessibilityServicePreferenceFragment extends
@Override
protected void onInstallSwitchPreferenceToggleSwitch() {
super.onInstallSwitchPreferenceToggleSwitch();
mToggleServiceDividerSwitchPreference.setVisible(false);
mToggleServiceSwitchPreference.setVisible(false);
}
/**

View File

@@ -0,0 +1,91 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.R;
import java.util.List;
/**
* An {@link ArrayAdapter} to fill the information of {@link ItemInfo} in the item view. The item
* view must have textview to set the title.
*
* @param <T> the type of elements in the array, inherited from {@link ItemInfo}.
*/
public class ItemInfoArrayAdapter<T extends ItemInfoArrayAdapter.ItemInfo> extends ArrayAdapter<T> {
public ItemInfoArrayAdapter(@NonNull Context context, @NonNull List<T> items) {
super(context, R.layout.dialog_single_radio_choice_list_item, R.id.title, items);
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
final View root = super.getView(position, convertView, parent);
final ItemInfo item = getItem(position);
final TextView title = root.findViewById(R.id.title);
title.setText(item.mTitle);
final TextView summary = root.findViewById(R.id.summary);
if (!TextUtils.isEmpty(item.mSummary)) {
summary.setVisibility(View.VISIBLE);
summary.setText(item.mSummary);
} else {
summary.setVisibility(View.GONE);
}
final ImageView image = root.findViewById(R.id.image);
image.setImageResource(item.mDrawableId);
if (getContext().getResources().getConfiguration().getLayoutDirection()
== View.LAYOUT_DIRECTION_LTR) {
image.setScaleType(ImageView.ScaleType.FIT_START);
} else {
image.setScaleType(ImageView.ScaleType.FIT_END);
}
return root;
}
/**
* Presents a data structure shown in the item view.
*/
public static class ItemInfo {
@NonNull
public final CharSequence mTitle;
@Nullable
public final CharSequence mSummary;
@DrawableRes
public final int mDrawableId;
public ItemInfo(@NonNull CharSequence title, @Nullable CharSequence summary,
@DrawableRes int drawableId) {
mTitle = title;
mSummary = summary;
mDrawableId = drawableId;
}
}
}

View File

@@ -30,45 +30,41 @@ import android.os.Bundle;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.Nullable;
import androidx.preference.SwitchPreference;
import androidx.preference.Preference;
import com.android.settings.R;
import java.util.ArrayList;
import java.util.List;
/** Fragment for providing open activity button. */
public class LaunchAccessibilityActivityPreferenceFragment extends
ToggleFeaturePreferenceFragment {
public class LaunchAccessibilityActivityPreferenceFragment extends ToggleFeaturePreferenceFragment {
private static final String TAG = "LaunchA11yActivity";
private static final String EMPTY_STRING = "";
protected static final String KEY_LAUNCH_PREFERENCE = "launch_preference";
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = super.onCreateView(inflater, container, savedInstanceState);
mToggleServiceDividerSwitchPreference.setSwitchVisibility(View.GONE);
}
// Init new preference to replace the switch preference instead.
initLaunchPreference();
removePreference(KEY_USE_SERVICE_PREFERENCE);
return view;
};
@Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
logAccessibilityServiceEnabled(mComponentName, enabled);
launchShortcutTargetActivity(getPrefContext().getDisplayId(), mComponentName);
}
@Override
protected void onInstallSwitchPreferenceToggleSwitch() {
super.onInstallSwitchPreferenceToggleSwitch();
mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener((preference) -> {
final boolean checked = ((DividerSwitchPreference) preference).isChecked();
onPreferenceToggled(mPreferenceKey, checked);
return false;
});
// Do nothing.
}
@Override
@@ -82,10 +78,12 @@ public class LaunchAccessibilityActivityPreferenceFragment extends
// Settings animated image.
final int animatedImageRes = arguments.getInt(
AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES);
mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(mComponentName.getPackageName())
.appendPath(String.valueOf(animatedImageRes))
.build();
if (animatedImageRes > 0) {
mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(mComponentName.getPackageName())
.appendPath(String.valueOf(animatedImageRes))
.build();
}
// Settings html description.
mHtmlDescription = arguments.getCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION);
@@ -97,12 +95,6 @@ public class LaunchAccessibilityActivityPreferenceFragment extends
mSettingsTitle = (mSettingsIntent == null) ? null : settingsTitle;
}
@Override
public void onSettingsClicked(ShortcutPreference preference) {
super.onSettingsClicked(preference);
showDialog(DialogEnums.EDIT_SHORTCUT);
}
@Override
int getUserShortcutTypes() {
return AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(),
@@ -116,16 +108,6 @@ public class LaunchAccessibilityActivityPreferenceFragment extends
// accessibility service from this page.
}
@Override
protected void updateToggleServiceTitle(SwitchPreference switchPreference) {
final AccessibilityShortcutInfo info = getAccessibilityShortcutInfo();
final String switchBarText = (info == null) ? EMPTY_STRING : getString(
R.string.accessibility_service_master_open_title,
info.getActivityInfo().loadLabel(getPackageManager()));
switchPreference.setTitle(switchBarText);
}
// IMPORTANT: Refresh the info since there are dynamically changing capabilities.
private AccessibilityShortcutInfo getAccessibilityShortcutInfo() {
final List<AccessibilityShortcutInfo> infos = AccessibilityManager.getInstance(
@@ -143,6 +125,34 @@ public class LaunchAccessibilityActivityPreferenceFragment extends
return null;
}
/** Customizes the order by preference key. */
protected List<String> getPreferenceOrderList() {
final List<String> lists = new ArrayList<>();
lists.add(KEY_ANIMATED_IMAGE);
lists.add(KEY_LAUNCH_PREFERENCE);
lists.add(KEY_GENERAL_CATEGORY);
lists.add(KEY_HTML_DESCRIPTION_PREFERENCE);
return lists;
}
private void initLaunchPreference() {
final Preference launchPreference = new Preference(getPrefContext());
launchPreference.setKey(KEY_LAUNCH_PREFERENCE);
final AccessibilityShortcutInfo info = getAccessibilityShortcutInfo();
final String switchBarText = (info == null) ? EMPTY_STRING : getString(
R.string.accessibility_service_primary_open_title,
info.getActivityInfo().loadLabel(getPackageManager()));
launchPreference.setTitle(switchBarText);
launchPreference.setOnPreferenceClickListener(preference -> {
logAccessibilityServiceEnabled(mComponentName, /* enabled= */ true);
launchShortcutTargetActivity(getPrefContext().getDisplayId(), mComponentName);
return true;
});
getPreferenceScreen().addPreference(launchPreference);
}
private void launchShortcutTargetActivity(int displayId, ComponentName name) {
final Intent intent = new Intent();
final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();

View File

@@ -0,0 +1,105 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.ContentResolver;
import android.content.Context;
import android.provider.Settings;
import androidx.annotation.IntDef;
import com.android.settings.R;
import com.google.common.primitives.Ints;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** Class to provide magnification capabilities. */
public final class MagnificationCapabilities {
private static final String KEY_CAPABILITY =
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY;
/**
* Annotation for supported magnification mode.
*
* @see Settings.Secure#ACCESSIBILITY_MAGNIFICATION_CAPABILITY
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
MagnificationMode.NONE,
MagnificationMode.FULLSCREEN,
MagnificationMode.WINDOW,
MagnificationMode.ALL,
})
public @interface MagnificationMode {
int NONE = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
int FULLSCREEN = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
int WINDOW = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
int ALL = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
}
/**
* Gets the summary for the given {@code capabilities}.
*
* @param context A {@link Context}.
* @param capabilities Magnification capabilities {@link MagnificationMode}
* @return The summary text represents the given capabilities
*/
public static String getSummary(Context context, @MagnificationMode int capabilities) {
final String[] summaries = context.getResources().getStringArray(
R.array.magnification_mode_summaries);
final int[] values = context.getResources().getIntArray(
R.array.magnification_mode_values);
final int idx = Ints.indexOf(values, capabilities);
return summaries[idx == /* no index exist */ -1 ? 0 : idx];
}
/**
* Sets the magnification capabilities {@link MagnificationMode} to settings key. This
* overwrites any existing capabilities.
*
* @param context A {@link Context}.
* @param capabilities Magnification capabilities {@link MagnificationMode}
*/
public static void setCapabilities(Context context, @MagnificationMode int capabilities) {
final ContentResolver contentResolver = context.getContentResolver();
Settings.Secure.putIntForUser(contentResolver, KEY_CAPABILITY, capabilities,
contentResolver.getUserId());
}
/**
* Returns the magnification capabilities {@link MagnificationMode} from setting's key. May be
* default value {@link MagnificationMode#FULLSCREEN} if not set.
*
* @param context A {@link Context}.
* @return The magnification capabilities {@link MagnificationMode}
*/
@MagnificationMode
public static int getCapabilities(Context context) {
final ContentResolver contentResolver = context.getContentResolver();
return Settings.Secure.getIntForUser(contentResolver, KEY_CAPABILITY,
MagnificationMode.FULLSCREEN, contentResolver.getUserId());
}
private MagnificationCapabilities() {}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.os.UserHandle;
import android.provider.Settings;
import com.android.settings.core.TogglePreferenceController;
/** Controller that shows the magnification enable mode summary. */
public class MagnificationEnablePreferenceController extends TogglePreferenceController {
private static final String KEY_ENABLE = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE;
public MagnificationEnablePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public boolean isChecked() {
final int enableMode = Settings.Secure.getIntForUser(mContext.getContentResolver(),
KEY_ENABLE,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
UserHandle.USER_CURRENT);
return enableMode == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
}
@Override
public boolean setChecked(boolean isChecked) {
final int value = isChecked ? Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
: Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
return Settings.Secure.putIntForUser(mContext.getContentResolver(), KEY_ENABLE, value,
UserHandle.USER_CURRENT);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
}

View File

@@ -14,6 +14,7 @@
package com.android.settings.accessibility;
import android.content.Context;
import android.icu.text.MessageFormat;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -93,8 +94,12 @@ public class MagnificationGesturesPreferenceController extends TogglePreferenceC
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
extras.putInt(AccessibilitySettings.EXTRA_TITLE_RES,
R.string.accessibility_screen_magnification_gestures_title);
extras.putCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION,
context.getText(R.string.accessibility_screen_magnification_summary));
String summary = context.getString(R.string.accessibility_screen_magnification_summary);
final Object[] numberArguments = {1, 2, 3, 4, 5};
summary = MessageFormat.format(summary, numberArguments);
extras.putCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, summary);
extras.putInt(AccessibilitySettings.EXTRA_VIDEO_RAW_RESOURCE_ID,
R.raw.accessibility_screen_magnification);
}

View File

@@ -16,15 +16,87 @@
package com.android.settings.accessibility;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.settings.accessibility.AccessibilityDialogUtils.CustomButton;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.DialogCreatable;
import com.android.settings.R;
import com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnCreate;
import com.android.settingslib.core.lifecycle.events.OnResume;
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
/** Controller that shows the magnification area mode summary. */
public class MagnificationModePreferenceController extends BasePreferenceController {
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
/** Controller that shows the magnification area mode summary and the preference click behavior. */
public class MagnificationModePreferenceController extends BasePreferenceController implements
DialogCreatable, LifecycleObserver, OnCreate, OnResume, OnSaveInstanceState {
static final String PREF_KEY = "screen_magnification_mode";
private static final int DIALOG_ID_BASE = 10;
@VisibleForTesting
static final int DIALOG_MAGNIFICATION_MODE = DIALOG_ID_BASE + 1;
@VisibleForTesting
static final int DIALOG_MAGNIFICATION_SWITCH_SHORTCUT = DIALOG_ID_BASE + 2;
@VisibleForTesting
static final String EXTRA_MODE = "mode";
private static final String TAG = "MagnificationModePreferenceController";
private static final char COMPONENT_NAME_SEPARATOR = ':';
private DialogHelper mDialogHelper;
// The magnification mode in the dialog.
private int mMode = MagnificationMode.NONE;
private Preference mModePreference;
@VisibleForTesting
ListView mMagnificationModesListView;
private final List<MagnificationModeInfo> mModeInfos = new ArrayList<>();
public MagnificationModePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
initModeInfos();
}
private void initModeInfos() {
mModeInfos.add(new MagnificationModeInfo(mContext.getText(
R.string.accessibility_magnification_mode_dialog_option_full_screen), null,
R.drawable.ic_illustration_fullscreen, MagnificationMode.FULLSCREEN));
mModeInfos.add(new MagnificationModeInfo(
mContext.getText(R.string.accessibility_magnification_mode_dialog_option_window),
null, R.drawable.ic_illustration_window, MagnificationMode.WINDOW));
mModeInfos.add(new MagnificationModeInfo(
mContext.getText(R.string.accessibility_magnification_mode_dialog_option_switch),
mContext.getText(
R.string.accessibility_magnification_area_settings_mode_switch_summary),
R.drawable.ic_illustration_switch, MagnificationMode.ALL));
}
@Override
@@ -32,10 +104,197 @@ public class MagnificationModePreferenceController extends BasePreferenceControl
return AVAILABLE;
}
@Override
public CharSequence getSummary() {
return MagnificationSettingsFragment.getMagnificationCapabilitiesSummary(
mContext);
final int capabilities = MagnificationCapabilities.getCapabilities(mContext);
return MagnificationCapabilities.getSummary(mContext, capabilities);
}
@Override
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mMode = savedInstanceState.getInt(EXTRA_MODE, MagnificationMode.NONE);
}
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mModePreference = screen.findPreference(getPreferenceKey());
mModePreference.setOnPreferenceClickListener(preference -> {
mMode = MagnificationCapabilities.getCapabilities(mContext);
mDialogHelper.showDialog(DIALOG_MAGNIFICATION_MODE);
return true;
});
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(EXTRA_MODE, mMode);
}
/**
* Sets {@link DialogHelper} used to show the dialog.
*/
public void setDialogHelper(DialogHelper dialogHelper) {
mDialogHelper = dialogHelper;
mDialogHelper.setDialogDelegate(this);
}
@Override
public Dialog onCreateDialog(int dialogId) {
switch (dialogId) {
case DIALOG_MAGNIFICATION_MODE:
return createMagnificationModeDialog();
case DIALOG_MAGNIFICATION_SWITCH_SHORTCUT:
return createMagnificationShortCutConfirmDialog();
}
return null;
}
@Override
public int getDialogMetricsCategory(int dialogId) {
switch (dialogId) {
case DIALOG_MAGNIFICATION_MODE:
return SettingsEnums.DIALOG_MAGNIFICATION_CAPABILITY;
case DIALOG_MAGNIFICATION_SWITCH_SHORTCUT:
return SettingsEnums.DIALOG_MAGNIFICATION_SWITCH_SHORTCUT;
default:
return 0;
}
}
private Dialog createMagnificationModeDialog() {
mMagnificationModesListView = AccessibilityDialogUtils.createSingleChoiceListView(
mContext, mModeInfos, this::onMagnificationModeSelected);
final View headerView = LayoutInflater.from(mContext).inflate(
R.layout.accessibility_magnification_mode_header, mMagnificationModesListView,
false);
mMagnificationModesListView.addHeaderView(headerView, /* data= */ null, /* isSelectable= */
false);
mMagnificationModesListView.setItemChecked(computeSelectionIndex(), true);
final CharSequence title = mContext.getString(
R.string.accessibility_magnification_mode_dialog_title);
return AccessibilityDialogUtils.createCustomDialog(mContext, title,
mMagnificationModesListView, this::onMagnificationModeDialogPositiveButtonClicked);
}
private void onMagnificationModeDialogPositiveButtonClicked(DialogInterface dialogInterface,
int which) {
final int selectedIndex = mMagnificationModesListView.getCheckedItemPosition();
if (selectedIndex != AdapterView.INVALID_POSITION) {
final MagnificationModeInfo modeInfo =
(MagnificationModeInfo) mMagnificationModesListView.getItemAtPosition(
selectedIndex);
setMode(modeInfo.mMagnificationMode);
} else {
Log.w(TAG, "invalid index");
}
}
private void setMode(int mode) {
mMode = mode;
MagnificationCapabilities.setCapabilities(mContext, mMode);
mModePreference.setSummary(
MagnificationCapabilities.getSummary(mContext, mMode));
}
private void onMagnificationModeSelected(AdapterView<?> parent, View view, int position,
long id) {
final MagnificationModeInfo modeInfo =
(MagnificationModeInfo) mMagnificationModesListView.getItemAtPosition(
position);
if (modeInfo.mMagnificationMode == mMode) {
return;
}
mMode = modeInfo.mMagnificationMode;
if (isTripleTapEnabled(mContext) && mMode != MagnificationMode.FULLSCREEN) {
mDialogHelper.showDialog(DIALOG_MAGNIFICATION_SWITCH_SHORTCUT);
}
}
private int computeSelectionIndex() {
final int modesSize = mModeInfos.size();
for (int i = 0; i < modesSize; i++) {
if (mModeInfos.get(i).mMagnificationMode == mMode) {
return i + mMagnificationModesListView.getHeaderViewsCount();
}
}
Log.w(TAG, "computeSelectionIndex failed");
return 0;
}
@VisibleForTesting
static boolean isTripleTapEnabled(Context context) {
return Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF) == ON;
}
private Dialog createMagnificationShortCutConfirmDialog() {
return AccessibilityDialogUtils.createMagnificationSwitchShortcutDialog(mContext,
this::onSwitchShortcutDialogButtonClicked);
}
@VisibleForTesting
void onSwitchShortcutDialogButtonClicked(@CustomButton int which) {
optOutMagnificationFromTripleTap();
//TODO(b/147990389): Merge this function into AccessibilityUtils after the format of
// magnification target is changed to ComponentName.
optInMagnificationToAccessibilityButton();
}
private void optOutMagnificationFromTripleTap() {
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF);
}
private void optInMagnificationToAccessibilityButton() {
final String targetKey = Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
final String targetString = Settings.Secure.getString(mContext.getContentResolver(),
targetKey);
if (targetString != null && targetString.contains(MAGNIFICATION_CONTROLLER_NAME)) {
return;
}
final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR));
if (!TextUtils.isEmpty(targetString)) {
joiner.add(targetString);
}
joiner.add(MAGNIFICATION_CONTROLLER_NAME);
Settings.Secure.putString(mContext.getContentResolver(), targetKey,
joiner.toString());
}
// TODO(b/186731461): Remove it when this controller is used in DashBoardFragment only.
@Override
public void onResume() {
updateState(mModePreference);
}
/**
* An interface to help the delegate to show the dialog. It will be injected to the delegate.
*/
interface DialogHelper extends DialogCreatable {
void showDialog(int dialogId);
void setDialogDelegate(DialogCreatable delegate);
}
@VisibleForTesting
static class MagnificationModeInfo extends ItemInfoArrayAdapter.ItemInfo {
@MagnificationMode
public final int mMagnificationMode;
MagnificationModeInfo(@NonNull CharSequence title, @Nullable CharSequence summary,
@DrawableRes int drawableId, @MagnificationMode int magnificationMode) {
super(title, summary, drawableId);
mMagnificationMode = magnificationMode;
}
}
}

View File

@@ -19,62 +19,22 @@ package com.android.settings.accessibility;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.widget.CheckBox;
import androidx.annotation.IntDef;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import com.android.settings.DialogCreatable;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
import com.google.common.primitives.Ints;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** Settings page for magnification. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class MagnificationSettingsFragment extends DashboardFragment {
public class MagnificationSettingsFragment extends DashboardFragment implements
MagnificationModePreferenceController.DialogHelper {
private static final String TAG = "MagnificationSettingsFragment";
private static final String PREF_KEY_MODE = "magnification_mode";
// TODO(b/146019459): Use magnification_capability.
private static final String KEY_CAPABILITY = Settings.System.MASTER_MONO;
private static final int DIALOG_MAGNIFICATION_CAPABILITY = 1;
private static final String EXTRA_CAPABILITY = "capability";
private Preference mModePreference;
private int mCapabilities = MagnifyMode.NONE;
private CheckBox mMagnifyFullScreenCheckBox;
private CheckBox mMagnifyWindowCheckBox;
static String getMagnificationCapabilitiesSummary(Context context) {
final String[] magnificationModeSummaries = context.getResources().getStringArray(
R.array.magnification_mode_summaries);
final int[] magnificationModeValues = context.getResources().getIntArray(
R.array.magnification_mode_values);
final int capabilities = MagnificationSettingsFragment.getMagnificationCapabilities(
context);
private DialogCreatable mDialogDelegate;
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() {
@@ -82,30 +42,27 @@ public class MagnificationSettingsFragment extends DashboardFragment {
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(EXTRA_CAPABILITY, mCapabilities);
super.onSaveInstanceState(outState);
public void onAttach(Context context) {
super.onAttach(context);
use(MagnificationModePreferenceController.class).setDialogHelper(this);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
mCapabilities = savedInstanceState.getInt(EXTRA_CAPABILITY, MagnifyMode.NONE);
}
if (mCapabilities == MagnifyMode.NONE) {
mCapabilities = getMagnificationCapabilities(getPrefContext());
}
public void showDialog(int dialogId) {
super.showDialog(dialogId);
}
@Override
public void setDialogDelegate(DialogCreatable delegate) {
mDialogDelegate = delegate;
}
@Override
public int getDialogMetricsCategory(int dialogId) {
switch (dialogId) {
case DIALOG_MAGNIFICATION_CAPABILITY:
return SettingsEnums.DIALOG_MAGNIFICATION_CAPABILITY;
default:
return 0;
if (mDialogDelegate != null) {
return mDialogDelegate.getDialogMetricsCategory(dialogId);
}
return 0;
}
@Override
@@ -113,17 +70,6 @@ public class MagnificationSettingsFragment extends DashboardFragment {
return TAG;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mModePreference = findPreference(PREF_KEY_MODE);
mModePreference.setOnPreferenceClickListener(preference -> {
mCapabilities = getMagnificationCapabilities(getPrefContext());
showDialog(DIALOG_MAGNIFICATION_CAPABILITY);
return true;
});
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_magnification_service_settings;
@@ -131,104 +77,15 @@ public class MagnificationSettingsFragment extends DashboardFragment {
@Override
public Dialog onCreateDialog(int dialogId) {
if (dialogId == DIALOG_MAGNIFICATION_CAPABILITY) {
final String title = getPrefContext().getString(
R.string.accessibility_magnification_mode_title);
AlertDialog alertDialog = AccessibilityEditDialogUtils
.showMagnificationModeDialog(getPrefContext(), title,
this::callOnAlertDialogCheckboxClicked);
initializeDialogCheckBox(alertDialog);
return alertDialog;
if (mDialogDelegate != null) {
final Dialog dialog = mDialogDelegate.onCreateDialog(dialogId);
if (dialog != null) {
return dialog;
}
}
throw new IllegalArgumentException("Unsupported dialogId " + dialogId);
}
private void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
updateCapabilities(true);
mModePreference.setSummary(
getMagnificationCapabilitiesSummary(getPrefContext()));
}
private void initializeDialogCheckBox(AlertDialog dialog) {
final View dialogFullScreenView = dialog.findViewById(R.id.magnify_full_screen);
mMagnifyFullScreenCheckBox = dialogFullScreenView.findViewById(R.id.checkbox);
final View dialogWidowView = dialog.findViewById(R.id.magnify_window_screen);
mMagnifyWindowCheckBox = dialogWidowView.findViewById(R.id.checkbox);
updateAlertDialogCheckState();
updateAlertDialogEnableState();
}
private void updateAlertDialogCheckState() {
updateCheckStatus(mMagnifyWindowCheckBox, MagnifyMode.WINDOW);
updateCheckStatus(mMagnifyFullScreenCheckBox, MagnifyMode.FULLSCREEN);
}
private void updateCheckStatus(CheckBox checkBox, int mode) {
checkBox.setChecked((mode & mCapabilities) != 0);
checkBox.setOnClickListener(v -> {
updateCapabilities(false);
updateAlertDialogEnableState();
});
}
private void updateAlertDialogEnableState() {
if (mCapabilities != MagnifyMode.ALL) {
disableEnabledMagnificationModePreference();
} else {
enableAllPreference();
}
}
private void enableAllPreference() {
mMagnifyFullScreenCheckBox.setEnabled(true);
mMagnifyWindowCheckBox.setEnabled(true);
}
private void disableEnabledMagnificationModePreference() {
if (!mMagnifyFullScreenCheckBox.isChecked()) {
mMagnifyWindowCheckBox.setEnabled(false);
} else if (!mMagnifyWindowCheckBox.isChecked()) {
mMagnifyFullScreenCheckBox.setEnabled(false);
}
}
private void updateCapabilities(boolean saveToDB) {
int capabilities = 0;
capabilities |=
mMagnifyFullScreenCheckBox.isChecked() ? MagnifyMode.FULLSCREEN : 0;
capabilities |= mMagnifyWindowCheckBox.isChecked() ? MagnifyMode.WINDOW : 0;
mCapabilities = capabilities;
if (saveToDB) {
setMagnificationCapabilities(capabilities);
}
}
private void setSecureIntValue(String key, int value) {
Settings.Secure.putIntForUser(getPrefContext().getContentResolver(),
key, value, getPrefContext().getContentResolver().getUserId());
}
private void setMagnificationCapabilities(int capabilities) {
setSecureIntValue(KEY_CAPABILITY, capabilities);
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({
MagnifyMode.NONE,
MagnifyMode.FULLSCREEN,
MagnifyMode.WINDOW,
MagnifyMode.ALL,
})
private @interface MagnifyMode {
int NONE = 0;
int FULLSCREEN = 1;
int WINDOW = 2;
int ALL = 3;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_magnification_service_settings);
}

View File

@@ -16,23 +16,58 @@
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 static android.graphics.drawable.GradientDrawable.Orientation;
import static com.android.settings.accessibility.AccessibilityUtil.getScreenHeightPixels;
import static com.android.settings.accessibility.AccessibilityUtil.getScreenWidthPixels;
import static com.google.common.primitives.Ints.max;
import android.content.Context;
import android.graphics.Paint.FontMetrics;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
/** Preference that easier preview by matching name to color. */
public class PaletteListPreference extends Preference {
import com.google.common.primitives.Floats;
import com.google.common.primitives.Ints;
private ListView mListView;
private ViewTreeObserver.OnPreDrawListener mPreDrawListener;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/** Preference that easier preview by matching name to color. */
public final class PaletteListPreference extends Preference {
private final List<Integer> mGradientColors = new ArrayList<>();
private final List<Float> mGradientOffsets = new ArrayList<>();
@IntDef({
Position.START,
Position.CENTER,
Position.END,
})
@Retention(RetentionPolicy.SOURCE)
@interface Position {
int START = 0;
int CENTER = 1;
int END = 2;
}
/**
* Constructs a new PaletteListPreference with the given context's theme and the supplied
@@ -61,47 +96,94 @@ public class PaletteListPreference extends Preference {
public PaletteListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayoutResource(R.layout.daltonizer_preview);
initPreDrawListener();
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final View rootView = holder.itemView;
mListView = rootView.findViewById(R.id.palette_listView);
if (mPreDrawListener != null) {
mListView.getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
final ViewGroup paletteView = holder.itemView.findViewById(R.id.palette_view);
initPaletteAttributes(getContext());
initPaletteView(getContext(), paletteView);
}
private void initPaletteAttributes(Context context) {
final int defaultColor = context.getColor(R.color.palette_list_gradient_background);
mGradientColors.add(Position.START, defaultColor);
mGradientColors.add(Position.CENTER, defaultColor);
mGradientColors.add(Position.END, defaultColor);
mGradientOffsets.add(Position.START, /* element= */ 0.0f);
mGradientOffsets.add(Position.CENTER, /* element= */ 0.5f);
mGradientOffsets.add(Position.END, /* element= */ 1.0f);
}
private void initPaletteView(Context context, ViewGroup rootView) {
if (rootView.getChildCount() > 0) {
rootView.removeAllViews();
}
final List<Integer> paletteColors = getPaletteColors(context);
final List<String> paletteData = getPaletteData(context);
final float textPadding =
context.getResources().getDimension(R.dimen.accessibility_layout_margin_start_end);
final String maxLengthData =
Collections.max(paletteData, Comparator.comparing(String::length));
final int textWidth = getTextWidth(context, maxLengthData);
final float textBound = (textWidth + textPadding) / getScreenWidthPixels(context);
mGradientOffsets.set(Position.CENTER, textBound);
final int screenHalfHeight = getScreenHeightPixels(context) / 2;
final int paletteItemHeight =
max(screenHalfHeight / paletteData.size(), getTextLineHeight(context));
for (int i = 0; i < paletteData.size(); ++i) {
final TextView textView = new TextView(context);
textView.setText(paletteData.get(i));
textView.setHeight(paletteItemHeight);
textView.setPaddingRelative(Math.round(textPadding), 0, 0, 0);
textView.setGravity(Gravity.CENTER_VERTICAL);
textView.setBackground(createGradientDrawable(rootView, paletteColors.get(i)));
rootView.addView(textView);
}
}
private void initPreDrawListener() {
mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (mListView == null) {
return false;
}
private GradientDrawable createGradientDrawable(ViewGroup rootView, @ColorInt int color) {
mGradientColors.set(Position.END, color);
final int listViewHeight = mListView.getMeasuredHeight();
final int listViewWidth = mListView.getMeasuredWidth();
final GradientDrawable gradientDrawable = new GradientDrawable();
final Orientation orientation =
rootView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
? Orientation.RIGHT_LEFT
: Orientation.LEFT_RIGHT;
gradientDrawable.setOrientation(orientation);
gradientDrawable.setColors(Ints.toArray(mGradientColors), Floats.toArray(mGradientOffsets));
// Removes the callback after get result of measure view.
final ViewTreeObserver viewTreeObserver = mListView.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.removeOnPreDrawListener(this);
}
mPreDrawListener = null;
return gradientDrawable;
}
// 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);
private List<Integer> getPaletteColors(Context context) {
final int[] paletteResources =
context.getResources().getIntArray(R.array.setting_palette_colors);
return Arrays.stream(paletteResources).boxed().collect(Collectors.toList());
}
return true;
}
};
private List<String> getPaletteData(Context context) {
final String[] paletteResources =
context.getResources().getStringArray(R.array.setting_palette_data);
return Arrays.asList(paletteResources);
}
private int getTextWidth(Context context, String text) {
final TextView tempView = new TextView(context);
return Math.round(tempView.getPaint().measureText(text));
}
private int getTextLineHeight(Context context) {
final TextView tempView = new TextView(context);
final FontMetrics fontMetrics = tempView.getPaint().getFontMetrics();
return Math.round(fontMetrics.bottom - fontMetrics.top);
}
}

View File

@@ -1,301 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.GradientDrawable.Orientation;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Custom ListView {@link ListView} which displays palette to deploy the color code preview.
*
* <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,96 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.ComponentName;
import android.text.TextUtils;
import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
import com.google.common.base.Objects;
/**
* A data class for containing {@link ComponentName#flattenToString()} and
* {@link UserShortcutType}. Represents the preferred shortcuts of the service or activity.
*/
public class PreferredShortcut {
private static final char COMPONENT_NAME_SEPARATOR = ':';
private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
/**
* Creates a {@link PreferredShortcut} from a encoded string described in {@link #toString()}.
*
* @param preferredShortcutString A string conform to the format described in {@link
* #toString()}
* @return A {@link PreferredShortcut} with the specified value
* @throws IllegalArgumentException If preferredShortcutString does not conform to the format
* described in {@link #toString()}
*/
public static PreferredShortcut fromString(String preferredShortcutString) {
sStringColonSplitter.setString(preferredShortcutString);
if (sStringColonSplitter.hasNext()) {
final String componentName = sStringColonSplitter.next();
final int type = Integer.parseInt(sStringColonSplitter.next());
return new PreferredShortcut(componentName, type);
}
throw new IllegalArgumentException(
"Invalid PreferredShortcut string: " + preferredShortcutString);
}
/** The format of {@link ComponentName#flattenToString()} */
private String mComponentName;
/** The format of {@link UserShortcutType} */
private int mType;
public PreferredShortcut(String componentName, int type) {
mComponentName = componentName;
mType = type;
}
public String getComponentName() {
return mComponentName;
}
public int getType() {
return mType;
}
@Override
public String toString() {
return mComponentName + COMPONENT_NAME_SEPARATOR + mType;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PreferredShortcut that = (PreferredShortcut) o;
return mType == that.mType && Objects.equal(mComponentName, that.mComponentName);
}
@Override
public int hashCode() {
return Objects.hashCode(mComponentName, mType);
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
import java.util.HashSet;
import java.util.Set;
/** Static utility methods relating to {@link PreferredShortcut} */
public final class PreferredShortcuts {
private static final String ACCESSIBILITY_PERF = "accessibility_prefs";
private static final String USER_SHORTCUT_TYPE = "user_shortcut_type";
/**
* Retrieves {@link UserShortcutType} for the given {@code componentName} from
* SharedPreferences.
*
* @param context {@link Context} to access the {@link SharedPreferences}
* @param componentName Name of the service or activity, should be the format of {@link
* ComponentName#flattenToString()}.
* @param defaultType See {@link UserShortcutType}
* @return {@link UserShortcutType}
*/
public static int retrieveUserShortcutType(Context context, String componentName,
int defaultType) {
if (componentName == null) {
return defaultType;
}
// Create a mutable set to modify
final Set<String> info = new HashSet<>(getFromSharedPreferences(context));
info.removeIf(str -> !str.contains(componentName));
if (info.isEmpty()) {
return defaultType;
}
final String str = info.stream().findFirst().get();
final PreferredShortcut shortcut = PreferredShortcut.fromString(str);
return shortcut.getType();
}
/**
* Saves a {@link PreferredShortcut} which containing {@link ComponentName#flattenToString()}
* and {@link UserShortcutType} in SharedPreferences.
*
* @param context {@link Context} to access the {@link SharedPreferences}
* @param shortcut Contains {@link ComponentName#flattenToString()} and {@link UserShortcutType}
*/
public static void saveUserShortcutType(Context context, PreferredShortcut shortcut) {
final String componentName = shortcut.getComponentName();
if (componentName == null) {
return;
}
// Create a mutable set to modify
final Set<String> info = new HashSet<>(getFromSharedPreferences(context));
info.removeIf(str -> str.contains(componentName));
info.add(shortcut.toString());
saveToSharedPreferences(context, info);
}
/**
* Returns a immutable set of {@link PreferredShortcut#toString()} list from
* SharedPreferences.
*/
private static Set<String> getFromSharedPreferences(Context context) {
return getSharedPreferences(context).getStringSet(USER_SHORTCUT_TYPE, Set.of());
}
/** Sets a set of {@link PreferredShortcut#toString()} list into SharedPreferences. */
private static void saveToSharedPreferences(Context context, Set<String> data) {
SharedPreferences.Editor editor = getSharedPreferences(context).edit();
editor.putStringSet(USER_SHORTCUT_TYPE, data).apply();
}
private static SharedPreferences getSharedPreferences(Context context) {
return context.getSharedPreferences(ACCESSIBILITY_PERF, Context.MODE_PRIVATE);
}
private PreferredShortcuts() {}
}

View File

@@ -22,9 +22,12 @@ import android.provider.Settings;
import com.android.settings.core.TogglePreferenceController;
public class MasterMonoPreferenceController extends TogglePreferenceController {
/**
* A toggle preference controller for Primary Mono
*/
public class PrimaryMonoPreferenceController extends TogglePreferenceController {
public MasterMonoPreferenceController(Context context, String preferenceKey) {
public PrimaryMonoPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 The Android Open Source Project
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,27 +20,34 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.PersistableBundle;
import android.provider.Settings;
import android.telecom.TelecomManager;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.accessibility.rtt.TelecomUtil;
import com.android.settings.core.BasePreferenceController;
import java.util.List;
/** A controller to control the status for RTT setting in Accessibility screen.*/
/** A controller to control the status for RTT setting in Accessibility screen. */
public class RTTSettingPreferenceController extends BasePreferenceController {
private static final String DIALER_RTT_CONFIGURATION = "dialer_rtt_configuration";
private static final String TAG = "RTTSettingsCtr";
private static final String DIALER_RTT_CONFIGURATION = "dialer_rtt_configuration";
private final Context mContext;
private final PackageManager mPackageManager;
private final TelecomManager mTelecomManager;
private final CarrierConfigManager mCarrierConfigManager;
private final CharSequence[] mModes;
private final String mDialerPackage;
@@ -52,17 +59,17 @@ public class RTTSettingPreferenceController extends BasePreferenceController {
mContext = context;
mModes = mContext.getResources().getTextArray(R.array.rtt_setting_mode);
mDialerPackage = mContext.getString(R.string.config_rtt_setting_package_name);
mPackageManager = context.getPackageManager();
mTelecomManager = context.getSystemService(TelecomManager.class);
mPackageManager = mContext.getPackageManager();
mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
mRTTIntent = new Intent(context.getString(R.string.config_rtt_setting_intent_action));
Log.d(TAG, "init controller");
}
@Override
public int getAvailabilityStatus() {
final List<ResolveInfo> resolved =
mPackageManager.queryIntentActivities(mRTTIntent, 0 /* flags */);
return resolved != null && !resolved.isEmpty() && isDialerSupportRTTSetting()
return resolved != null && !resolved.isEmpty() && isRttSettingSupported()
? AVAILABLE
: UNSUPPORTED_ON_DEVICE;
}
@@ -77,12 +84,82 @@ public class RTTSettingPreferenceController extends BasePreferenceController {
@Override
public CharSequence getSummary() {
final int option = Settings.Secure.getInt(mContext.getContentResolver(),
DIALER_RTT_CONFIGURATION, 1 /* not visible */);
DIALER_RTT_CONFIGURATION, 0 /* Invalid value */);
Log.d(TAG, "DIALER_RTT_CONFIGURATION value = " + option);
return mModes[option];
}
@VisibleForTesting
boolean isDialerSupportRTTSetting() {
return TextUtils.equals(mTelecomManager.getDefaultDialerPackage(), mDialerPackage);
boolean isRttSettingSupported() {
Log.d(TAG, "isRttSettingSupported [start]");
if (!isDefaultDialerSupportedRTT(mContext)) {
Log.d(TAG, "Dialer doesn't support RTT.");
return false;
}
// At least one PhoneAccount must have both isRttSupported and
// ignore_rtt_mode_setting_bool being true
for (PhoneAccountHandle phoneAccountHandle :
TelecomUtil.getCallCapablePhoneAccounts(mContext)) {
final int subId =
TelecomUtil.getSubIdForPhoneAccountHandle(mContext, phoneAccountHandle);
Log.d(TAG, "subscription id for the device: " + subId);
final boolean isRttCallingSupported = isRttSupportedByTelecom(phoneAccountHandle);
Log.d(TAG, "rtt calling supported by telecom:: " + isRttCallingSupported);
if (isRttCallingSupported) {
PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(subId);
// If IGNORE_RTT_MODE_SETTING_BOOL=true, RTT visibility is not supported because
// this means we must use the legacy Telecom setting, which does not support RTT
// visibility.
if (carrierConfig != null
&& getBooleanCarrierConfig(
CarrierConfigManager.KEY_IGNORE_RTT_MODE_SETTING_BOOL)) {
Log.d(TAG, "RTT visibility setting is supported.");
return true;
}
Log.d(TAG, "IGNORE_RTT_MODE_SETTING_BOOL is false.");
}
}
Log.d(TAG, "isRttSettingSupported [Not support]");
return false;
}
private boolean isRttSupportedByTelecom(PhoneAccountHandle phoneAccountHandle) {
PhoneAccount phoneAccount =
TelecomUtil.getTelecomManager(mContext).getPhoneAccount(phoneAccountHandle);
if (phoneAccount != null && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
Log.d(TAG, "Phone account has RTT capability.");
return true;
}
return false;
}
/**
* Gets the boolean config from carrier config manager.
*
* @param key config key defined in CarrierConfigManager.
* @return boolean value of corresponding key.
*/
private boolean getBooleanCarrierConfig(String key) {
if (mCarrierConfigManager == null) {
// Return static default defined in CarrierConfigManager.
return CarrierConfigManager.getDefaultConfig().getBoolean(key);
}
// If an invalid subId is used, this bundle will contain default values.
final int subId = SubscriptionManager.getDefaultVoiceSubscriptionId();
final PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(subId);
return bundle != null
? bundle.getBoolean(key)
: CarrierConfigManager.getDefaultConfig().getBoolean(key);
}
/** Returns whether is a correct default dialer which supports RTT. */
private static boolean isDefaultDialerSupportedRTT(Context context) {
return TextUtils.equals(
context.getString(R.string.config_rtt_setting_package_name),
TelecomUtil.getTelecomManager(context).getDefaultDialerPackage());
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.hardware.display.ColorDisplayManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.core.SliderPreferenceController;
import com.android.settings.widget.SeekBarPreference;
/** PreferenceController for feature intensity. */
public class ReduceBrightColorsIntensityPreferenceController extends SliderPreferenceController {
private static final int INVERSE_PERCENTAGE_BASE = 100;
private final ColorDisplayManager mColorDisplayManager;
public ReduceBrightColorsIntensityPreferenceController(Context context, String key) {
super(context, key);
mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
}
@Override
public int getAvailabilityStatus() {
if (!ColorDisplayManager.isReduceBrightColorsAvailable(mContext)) {
return UNSUPPORTED_ON_DEVICE;
}
if (!mColorDisplayManager.isReduceBrightColorsActivated()) {
return DISABLED_DEPENDENT_SETTING;
}
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final SeekBarPreference preference = screen.findPreference(getPreferenceKey());
preference.setContinuousUpdates(true);
preference.setMax(getMax());
preference.setMin(getMin());
preference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS);
updateState(preference);
}
@Override
public final void updateState(Preference preference) {
super.updateState(preference);
preference.setEnabled(mColorDisplayManager.isReduceBrightColorsActivated());
}
@Override
public int getSliderPosition() {
return INVERSE_PERCENTAGE_BASE - mColorDisplayManager.getReduceBrightColorsStrength();
}
@Override
public boolean setSliderPosition(int position) {
return mColorDisplayManager.setReduceBrightColorsStrength(
INVERSE_PERCENTAGE_BASE - position);
}
@Override
public int getMax() {
return INVERSE_PERCENTAGE_BASE
- ColorDisplayManager.getMinimumReduceBrightColorsStrength(mContext);
}
@Override
public int getMin() {
return INVERSE_PERCENTAGE_BASE
- ColorDisplayManager.getMaximumReduceBrightColorsStrength(mContext);
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.hardware.display.ColorDisplayManager;
import android.provider.Settings;
import androidx.preference.Preference;
import com.android.settings.core.TogglePreferenceController;
/** PreferenceController for persisting feature activation state after a restart. */
public class ReduceBrightColorsPersistencePreferenceController extends TogglePreferenceController {
private final ColorDisplayManager mColorDisplayManager;
public ReduceBrightColorsPersistencePreferenceController(
Context context, String preferenceKey) {
super(context, preferenceKey);
mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
}
@Override
public int getAvailabilityStatus() {
if (!ColorDisplayManager.isReduceBrightColorsAvailable(mContext)) {
return UNSUPPORTED_ON_DEVICE;
}
if (!mColorDisplayManager.isReduceBrightColorsActivated()) {
return DISABLED_DEPENDENT_SETTING;
}
return AVAILABLE;
}
@Override
public boolean isChecked() {
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.REDUCE_BRIGHT_COLORS_PERSIST_ACROSS_REBOOTS, 0) == 1;
}
@Override
public boolean setChecked(boolean isChecked) {
return Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.REDUCE_BRIGHT_COLORS_PERSIST_ACROSS_REBOOTS, (isChecked ? 1 : 0));
}
@Override
public final void updateState(Preference preference) {
super.updateState(preference);
preference.setEnabled(mColorDisplayManager.isReduceBrightColorsActivated());
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.database.ContentObserver;
import android.hardware.display.ColorDisplayManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.widget.PrimarySwitchPreference;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
/** PreferenceController that shows the Reduce Bright Colors summary */
public class ReduceBrightColorsPreferenceController extends TogglePreferenceController
implements LifecycleObserver, OnStart, OnStop {
private ContentObserver mSettingsContentObserver;
private PrimarySwitchPreference mPreference;
private final Context mContext;
private final ColorDisplayManager mColorDisplayManager;
public ReduceBrightColorsPreferenceController(Context context,
String preferenceKey) {
super(context, preferenceKey);
mContext = context;
mSettingsContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())){
@Override
public void onChange(boolean selfChange, Uri uri) {
final String path = uri == null ? null : uri.getLastPathSegment();
if (TextUtils.equals(path, Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED)) {
updateState(mPreference);
}
}
};
mColorDisplayManager = mContext.getSystemService(ColorDisplayManager.class);
}
@Override
public boolean isChecked() {
return mColorDisplayManager.isReduceBrightColorsActivated();
}
@Override
public boolean setChecked(boolean isChecked) {
return mColorDisplayManager.setReduceBrightColorsActivated(isChecked);
}
@Override
public CharSequence getSummary() {
return mContext.getText(
R.string.reduce_bright_colors_preference_summary);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
refreshSummary(preference);
}
@Override
public int getAvailabilityStatus() {
return ColorDisplayManager.isReduceBrightColorsAvailable(mContext) ? AVAILABLE
: UNSUPPORTED_ON_DEVICE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
}
@Override
public void onStart() {
mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED),
false, mSettingsContentObserver, UserHandle.USER_CURRENT);
}
@Override
public void onStop() {
mContext.getContentResolver().unregisterContentObserver(mSettingsContentObserver);
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.content.SharedPreferences;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
/** Utility class for SharedPreferences. */
public final class SharedPreferenceUtils {
private static final String ACCESSIBILITY_PERF = "accessibility_prefs";
private static final String USER_SHORTCUT_TYPE = "user_shortcut_type";
private SharedPreferenceUtils() { }
private static SharedPreferences getSharedPreferences(Context context, String fileName) {
return context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
}
/** Returns a set of user shortcuts list to determine user preferred service shortcut. */
public static Set<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

@@ -62,8 +62,8 @@ public class ShortcutPreference extends Preference {
ShortcutPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setLayoutResource(R.layout.accessibility_shortcut_secondary_action);
setWidgetLayoutResource(R.layout.preference_widget_master_switch);
setIconSpaceReserved(true);
setWidgetLayoutResource(R.layout.preference_widget_primary_switch);
setIconSpaceReserved(false);
}
@Override

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
/** Accessibility settings for accessibility shortcuts. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class ShortcutsSettingsFragment extends DashboardFragment {
private static final String TAG = "ShortcutsSettingsFragment";
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_SHORTCUTS_SETTINGS;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_shortcuts_settings;
}
@Override
protected String getLogTag() {
return TAG;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_shortcuts_settings);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 The Android Open Source Project
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,10 +11,10 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
* limitations under the License.
*/
package com.android.settings.gestures;
package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
@@ -23,14 +23,20 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
@SearchIndexable
public class GlobalActionsPanelSettings extends DashboardFragment {
/** Accessibility settings for system controls. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class SystemControlsFragment extends DashboardFragment {
private static final String TAG = "GlobalActionsPanelSettings";
private static final String TAG = "SystemControlsFragment";
@Override
public int getMetricsCategory() {
return SettingsEnums.GLOBAL_ACTIONS_PANEL_SETTINGS;
return SettingsEnums.ACCESSIBILITY_SYSTEM_CONTROLS;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_system_controls;
}
@Override
@@ -38,11 +44,7 @@ public class GlobalActionsPanelSettings extends DashboardFragment {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.global_actions_panel_settings;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.global_actions_panel_settings);
new BaseSearchIndexProvider(R.xml.accessibility_system_controls);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 The Android Open Source Project
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.settings.gestures;
package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
@@ -23,14 +23,20 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
@SearchIndexable
public class DeviceControlsSettings extends DashboardFragment {
/** Accessibility settings for tap assistance. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class TapAssistanceFragment extends DashboardFragment {
private static final String TAG = "QuickControlsSettings";
private static final String TAG = "TapAssistanceFragment";
@Override
public int getMetricsCategory() {
return SettingsEnums.DEVICE_CONTROLS_SETTINGS;
return SettingsEnums.ACCESSIBILITY_TAP_ASSISTANCE;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_tap_assistance;
}
@Override
@@ -38,11 +44,7 @@ public class DeviceControlsSettings extends DashboardFragment {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.device_controls_settings;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.device_controls_settings);
new BaseSearchIndexProvider(R.xml.accessibility_tap_assistance);
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import android.hardware.display.ColorDisplayManager;
import android.os.Bundle;
import android.provider.Settings;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
/** Accessibility settings for text and display. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class TextAndDisplayFragment extends DashboardFragment {
private static final String TAG = "TextAndDisplayFragment";
private static final String CATEGORY_EXPERIMENTAL = "experimental_category";
// Preferences
private static final String DISPLAY_DALTONIZER_PREFERENCE_SCREEN = "daltonizer_preference";
private static final String TOGGLE_DISABLE_ANIMATIONS = "toggle_disable_animations";
private static final String TOGGLE_LARGE_POINTER_ICON = "toggle_large_pointer_icon";
private Preference mDisplayDaltonizerPreferenceScreen;
private SwitchPreference mToggleDisableAnimationsPreference;
private SwitchPreference mToggleLargePointerIconPreference;
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_TEXT_AND_DISPLAY;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
initializeAllPreferences();
updateSystemPreferences();
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_text_and_display;
}
@Override
protected String getLogTag() {
return TAG;
}
private void initializeAllPreferences() {
// Display color adjustments.
mDisplayDaltonizerPreferenceScreen = findPreference(DISPLAY_DALTONIZER_PREFERENCE_SCREEN);
// Disable animation.
mToggleDisableAnimationsPreference = findPreference(TOGGLE_DISABLE_ANIMATIONS);
// Large pointer icon.
mToggleLargePointerIconPreference = findPreference(TOGGLE_LARGE_POINTER_ICON);
}
/**
* Updates preferences related to system configurations.
*/
private void updateSystemPreferences() {
final PreferenceCategory experimentalCategory = getPreferenceScreen().findPreference(
CATEGORY_EXPERIMENTAL);
if (ColorDisplayManager.isColorTransformAccelerated(getContext())) {
mDisplayDaltonizerPreferenceScreen.setSummary(AccessibilityUtil.getSummary(
getContext(), Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED));
getPreferenceScreen().removePreference(experimentalCategory);
} else {
// Move following preferences to experimental category if device don't supports HWC
// hardware-accelerated color transform.
getPreferenceScreen().removePreference(mDisplayDaltonizerPreferenceScreen);
getPreferenceScreen().removePreference(mToggleDisableAnimationsPreference);
getPreferenceScreen().removePreference(mToggleLargePointerIconPreference);
experimentalCategory.addPreference(mDisplayDaltonizerPreferenceScreen);
experimentalCategory.addPreference(mToggleDisableAnimationsPreference);
experimentalCategory.addPreference(mToggleLargePointerIconPreference);
}
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_text_and_display);
}

View File

@@ -16,17 +16,23 @@
package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
import static com.android.settings.accessibility.AccessibilityStatsLogUtils.logAccessibilityServiceEnabled;
import static com.android.settings.accessibility.PreferredShortcuts.retrieveUserShortcutType;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.net.Uri;
@@ -36,18 +42,20 @@ import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.widget.Switch;
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
import androidx.annotation.Nullable;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settings.widget.SettingsMainSwitchPreference;
import com.android.settingslib.accessibility.AccessibilityUtils;
import java.util.List;
@@ -57,7 +65,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class ToggleAccessibilityServicePreferenceFragment extends
ToggleFeaturePreferenceFragment {
public static final int ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION = 1;
private static final String TAG = "ToggleAccessibilityServicePreferenceFragment";
private static final int ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION = 1;
private LockPatternUtils mLockPatternUtils;
private AtomicBoolean mIsDialogShown = new AtomicBoolean(/* initialValue= */ false);
@@ -72,6 +81,7 @@ public class ToggleAccessibilityServicePreferenceFragment extends
};
private Dialog mDialog;
private BroadcastReceiver mPackageRemovedReceiver;
@Override
public int getMetricsCategory() {
@@ -91,6 +101,17 @@ public class ToggleAccessibilityServicePreferenceFragment extends
mLockPatternUtils = new LockPatternUtils(getPrefContext());
}
@Override
public void onStart() {
super.onStart();
final AccessibilityServiceInfo serviceInfo = getAccessibilityServiceInfo();
if (serviceInfo == null) {
getActivity().finishAndRemoveTask();
} else if (!AccessibilityUtil.isSystemApp(serviceInfo)) {
registerPackageRemoveReceiver();
}
}
@Override
public void onResume() {
super.onResume();
@@ -109,6 +130,7 @@ public class ToggleAccessibilityServicePreferenceFragment extends
// capabilities. For
// example, before JellyBean MR2 the user was granting the explore by touch
// one.
@Nullable
AccessibilityServiceInfo getAccessibilityServiceInfo() {
final List<AccessibilityServiceInfo> infos = AccessibilityManager.getInstance(
getPrefContext()).getInstalledAccessibilityServiceList();
@@ -134,7 +156,8 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
mDialog = AccessibilityServiceWarning
.createCapabilitiesDialog(getPrefContext(), info,
this::onDialogButtonFromEnableToggleClicked);
this::onDialogButtonFromEnableToggleClicked,
this::onDialogButtonFromUninstallClicked);
break;
}
case DialogEnums.ENABLE_WARNING_FROM_SHORTCUT_TOGGLE: {
@@ -144,7 +167,8 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
mDialog = AccessibilityServiceWarning
.createCapabilitiesDialog(getPrefContext(), info,
this::onDialogButtonFromShortcutToggleClicked);
this::onDialogButtonFromShortcutToggleClicked,
this::onDialogButtonFromUninstallClicked);
break;
}
case DialogEnums.ENABLE_WARNING_FROM_SHORTCUT: {
@@ -154,7 +178,8 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
mDialog = AccessibilityServiceWarning
.createCapabilitiesDialog(getPrefContext(), info,
this::onDialogButtonFromShortcutClicked);
this::onDialogButtonFromShortcutClicked,
this::onDialogButtonFromUninstallClicked);
break;
}
case DialogEnums.DISABLE_WARNING_FROM_TOGGLE: {
@@ -197,21 +222,26 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
@Override
protected void updateToggleServiceTitle(SwitchPreference switchPreference) {
protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) {
final AccessibilityServiceInfo info = getAccessibilityServiceInfo();
final String switchBarText = (info == null) ? "" :
getString(R.string.accessibility_service_master_switch_title,
getString(R.string.accessibility_service_primary_switch_title,
info.getResolveInfo().loadLabel(getPackageManager()));
switchPreference.setTitle(switchBarText);
}
private void updateSwitchBarToggleSwitch() {
final boolean checked = AccessibilityUtils.getEnabledServicesFromSettings(getPrefContext())
.contains(mComponentName);
if (mToggleServiceDividerSwitchPreference.isChecked() == checked) {
@Override
protected void updateSwitchBarToggleSwitch() {
final boolean checked = isAccessibilityServiceEnabled();
if (mToggleServiceSwitchPreference.isChecked() == checked) {
return;
}
mToggleServiceDividerSwitchPreference.setChecked(checked);
mToggleServiceSwitchPreference.setChecked(checked);
}
private boolean isAccessibilityServiceEnabled() {
return AccessibilityUtils.getEnabledServicesFromSettings(getPrefContext())
.contains(mComponentName);
}
/**
@@ -243,6 +273,32 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
}
private void registerPackageRemoveReceiver() {
if (mPackageRemovedReceiver != null || getContext() == null) {
return;
}
mPackageRemovedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String packageName = intent.getData().getSchemeSpecificPart();
if (TextUtils.equals(mComponentName.getPackageName(), packageName)) {
getActivity().finishAndRemoveTask();
}
}
};
final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
getContext().registerReceiver(mPackageRemovedReceiver, filter);
}
private void unregisterPackageRemoveReceiver() {
if (mPackageRemovedReceiver == null || getContext() == null) {
return;
}
getContext().unregisterReceiver(mPackageRemovedReceiver);
mPackageRemovedReceiver = null;
}
private boolean isServiceSupportAccessibilityButton() {
final AccessibilityManager ams = getPrefContext().getSystemService(
AccessibilityManager.class);
@@ -262,7 +318,6 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
private void handleConfirmServiceEnabled(boolean confirmed) {
mToggleServiceDividerSwitchPreference.setChecked(confirmed);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, confirmed);
onPreferenceToggled(mPreferenceKey, confirmed);
}
@@ -285,16 +340,18 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
@Override
protected void onInstallSwitchPreferenceToggleSwitch() {
super.onInstallSwitchPreferenceToggleSwitch();
mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener(this::onPreferenceClick);
public void onSwitchChanged(Switch switchView, boolean isChecked) {
if (isChecked != isAccessibilityServiceEnabled()) {
onPreferenceClick(isChecked);
}
}
@Override
public void onToggleClicked(ShortcutPreference preference) {
final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
final int shortcutTypes = retrieveUserShortcutType(getPrefContext(),
mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
if (preference.isChecked()) {
if (!mToggleServiceDividerSwitchPreference.isChecked()) {
if (!mToggleServiceSwitchPreference.isChecked()) {
preference.setChecked(false);
showPopupDialog(DialogEnums.ENABLE_WARNING_FROM_SHORTCUT_TOGGLE);
} else {
@@ -311,9 +368,8 @@ public class ToggleAccessibilityServicePreferenceFragment extends
@Override
public void onSettingsClicked(ShortcutPreference preference) {
super.onSettingsClicked(preference);
final boolean isServiceOnOrShortcutAdded = mShortcutPreference.isChecked()
|| mToggleServiceDividerSwitchPreference.isChecked();
|| mToggleServiceSwitchPreference.isChecked();
showPopupDialog(isServiceOnOrShortcutAdded ? DialogEnums.EDIT_SHORTCUT
: DialogEnums.ENABLE_WARNING_FROM_SHORTCUT);
}
@@ -340,10 +396,12 @@ public class ToggleAccessibilityServicePreferenceFragment extends
// Settings animated image.
final int animatedImageRes = arguments.getInt(
AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES);
mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(mComponentName.getPackageName())
.appendPath(String.valueOf(animatedImageRes))
.build();
if (animatedImageRes > 0) {
mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(mComponentName.getPackageName())
.appendPath(String.valueOf(animatedImageRes))
.build();
}
// Get Accessibility service name.
mPackageName = getAccessibilityServiceInfo().getResolveInfo().loadLabel(
@@ -374,6 +432,35 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
}
private void onDialogButtonFromUninstallClicked() {
mDialog.dismiss();
final Intent uninstallIntent = createUninstallPackageActivityIntent();
if (uninstallIntent == null) {
return;
}
startActivity(uninstallIntent);
}
@Nullable
private Intent createUninstallPackageActivityIntent() {
final AccessibilityServiceInfo a11yServiceInfo = getAccessibilityServiceInfo();
if (a11yServiceInfo == null) {
Log.w(TAG, "createUnInstallIntent -- invalid a11yServiceInfo");
return null;
}
final ApplicationInfo appInfo =
a11yServiceInfo.getResolveInfo().serviceInfo.applicationInfo;
final Uri packageUri = Uri.parse("package:" + appInfo.packageName);
final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
return uninstallIntent;
}
@Override
public void onStop() {
super.onStop();
unregisterPackageRemoveReceiver();
}
private void onAllowButtonFromEnableToggleClicked() {
if (isFullDiskEncrypted()) {
final String title = createConfirmCredentialReasonMessage();
@@ -411,7 +498,8 @@ public class ToggleAccessibilityServicePreferenceFragment extends
private void onAllowButtonFromShortcutToggleClicked() {
mShortcutPreference.setChecked(true);
final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
final int shortcutTypes = retrieveUserShortcutType(getPrefContext(),
mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), shortcutTypes, mComponentName);
mIsDialogShown.set(false);
@@ -450,10 +538,9 @@ public class ToggleAccessibilityServicePreferenceFragment extends
mDialog.dismiss();
}
private boolean onPreferenceClick(Preference preference) {
boolean checked = ((DividerSwitchPreference) preference).isChecked();
if (checked) {
mToggleServiceDividerSwitchPreference.setChecked(false);
private boolean onPreferenceClick(boolean isChecked) {
if (isChecked) {
mToggleServiceSwitchPreference.setChecked(false);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED,
/* disableService */ false);
if (!mShortcutPreference.isChecked()) {
@@ -465,7 +552,7 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
}
} else {
mToggleServiceDividerSwitchPreference.setChecked(true);
mToggleServiceSwitchPreference.setChecked(true);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED,
/* enableService */ true);
showDialog(DialogEnums.DISABLE_WARNING_FROM_TOGGLE);

View File

@@ -30,6 +30,7 @@ import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
@@ -49,7 +50,9 @@ public class ToggleAutoclickCustomSeekbarController extends BasePreferenceContro
private static final String CONTROL_AUTOCLICK_DELAY_SECURE =
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY;
private static final String KEY_CUSTOM_DELAY_VALUE = "custom_delay_value";
@VisibleForTesting
static final String KEY_CUSTOM_DELAY_VALUE = "custom_delay_value";
// Min allowed autoclick delay value.
static final int MIN_AUTOCLICK_DELAY_MS = 200;
@@ -59,7 +62,8 @@ public class ToggleAutoclickCustomSeekbarController extends BasePreferenceContro
// Allowed autoclick delay values are discrete.
// This is the difference between two allowed values.
private static final int AUTOCLICK_DELAY_STEP = 100;
@VisibleForTesting
static final int AUTOCLICK_DELAY_STEP = 100;
private final SharedPreferences mSharedPreferences;
private final ContentResolver mContentResolver;
@@ -68,7 +72,8 @@ public class ToggleAutoclickCustomSeekbarController extends BasePreferenceContro
private SeekBar mSeekBar;
private TextView mDelayLabel;
private final SeekBar.OnSeekBarChangeListener mSeekBarChangeListener =
@VisibleForTesting
final SeekBar.OnSeekBarChangeListener mSeekBarChangeListener =
new SeekBar.OnSeekBarChangeListener() {
@Override

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import com.android.settings.R;
/**
* Preference controller for accessibility autoclick footer.
*/
public class ToggleAutoclickFooterPreferenceController extends
AccessibilityFooterPreferenceController {
public ToggleAutoclickFooterPreferenceController(Context context, String key) {
super(context, key);
}
@Override
protected String getLabelName() {
return mContext.getString(R.string.accessibility_autoclick_preference_title);
}
@Override
protected int getHelpResource() {
return R.string.help_url_autoclick;
}
}

View File

@@ -25,6 +25,7 @@ import android.content.res.Resources;
import android.provider.Settings;
import android.util.ArrayMap;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LifecycleObserver;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -44,16 +45,23 @@ import java.util.Map;
public class ToggleAutoclickPreferenceController extends BasePreferenceController implements
LifecycleObserver, RadioButtonPreference.OnClickListener, PreferenceControllerMixin {
private static final String CONTROL_AUTOCLICK_DELAY_SECURE =
@VisibleForTesting
static final String CONTROL_AUTOCLICK_DELAY_SECURE =
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY;
private static final String KEY_AUTOCLICK_CUSTOM_SEEKBAR = "autoclick_custom_seekbar";
@VisibleForTesting
static final String KEY_AUTOCLICK_CUSTOM_SEEKBAR = "autoclick_custom_seekbar";
static final String KEY_DELAY_MODE = "delay_mode";
private static final int AUTOCLICK_OFF_MODE = 0;
private static final int AUTOCLICK_CUSTOM_MODE = 2000;
@VisibleForTesting
static final int AUTOCLICK_OFF_MODE = 0;
@VisibleForTesting
static final int AUTOCLICK_CUSTOM_MODE = 2000;
// Pair the preference key and autoclick mode value.
private final Map<String, Integer> mAccessibilityAutoclickKeyToValueMap = new ArrayMap<>();
@VisibleForTesting
Map<String, Integer> mAccessibilityAutoclickKeyToValueMap = new ArrayMap<>();
private SharedPreferences mSharedPreferences;
private final ContentResolver mContentResolver;
@@ -70,13 +78,7 @@ public class ToggleAutoclickPreferenceController extends BasePreferenceControlle
private int mCurrentUiAutoClickMode;
public ToggleAutoclickPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mSharedPreferences = context.getSharedPreferences(context.getPackageName(), MODE_PRIVATE);
mContentResolver = context.getContentResolver();
mResources = context.getResources();
setAutoclickModeToKeyMap();
this(context, /* lifecycle= */ null, preferenceKey);
}
public ToggleAutoclickPreferenceController(Context context, Lifecycle lifecycle,

View File

@@ -31,15 +31,15 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.widget.SettingsMainSwitchPreference;
import java.util.ArrayList;
import java.util.List;
/** Settings page for color inversion. */
public class ToggleColorInversionPreferenceFragment extends ToggleFeaturePreferenceFragment {
public class ToggleColorInversionPreferenceFragment extends
ToggleFeaturePreferenceFragment {
private static final String ENABLED = Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED;
private final Handler mHandler = new Handler();
@@ -64,25 +64,14 @@ public class ToggleColorInversionPreferenceFragment extends ToggleFeaturePrefere
@Override
protected void onRemoveSwitchPreferenceToggleSwitch() {
super.onRemoveSwitchPreferenceToggleSwitch();
mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener(null);
mToggleServiceSwitchPreference.setOnPreferenceClickListener(null);
}
@Override
protected void updateToggleServiceTitle(SwitchPreference switchPreference) {
protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) {
switchPreference.setTitle(R.string.accessibility_display_inversion_switch_title);
}
@Override
protected void onInstallSwitchPreferenceToggleSwitch() {
super.onInstallSwitchPreferenceToggleSwitch();
updateSwitchBarToggleSwitch();
mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener((preference) -> {
boolean checked = ((SwitchPreference) preference).isChecked();
onPreferenceToggled(mPreferenceKey, checked);
return false;
});
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -91,7 +80,7 @@ public class ToggleColorInversionPreferenceFragment extends ToggleFeaturePrefere
mHtmlDescription = getText(R.string.accessibility_display_inversion_preference_subtitle);
mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(getPrefContext().getPackageName())
.appendPath(String.valueOf(R.drawable.accessibility_color_inversion_banner))
.appendPath(String.valueOf(R.raw.accessibility_color_inversion_banner))
.build();
final List<String> enableServiceFeatureKeys = new ArrayList<>(/* initialCapacity= */ 1);
enableServiceFeatureKeys.add(ENABLED);
@@ -122,23 +111,18 @@ public class ToggleColorInversionPreferenceFragment extends ToggleFeaturePrefere
return R.string.help_url_color_inversion;
}
@Override
public void onSettingsClicked(ShortcutPreference preference) {
super.onSettingsClicked(preference);
showDialog(DialogEnums.EDIT_SHORTCUT);
}
@Override
int getUserShortcutTypes() {
return AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(),
mComponentName);
}
private void updateSwitchBarToggleSwitch() {
@Override
protected void updateSwitchBarToggleSwitch() {
final boolean checked = Settings.Secure.getInt(getContentResolver(), ENABLED, OFF) == ON;
if (mToggleServiceDividerSwitchPreference.isChecked() == checked) {
if (mToggleServiceSwitchPreference.isChecked() == checked) {
return;
}
mToggleServiceDividerSwitchPreference.setChecked(checked);
mToggleServiceSwitchPreference.setChecked(checked);
}
}

View File

@@ -33,11 +33,10 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.SettingsMainSwitchPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
@@ -99,34 +98,19 @@ public final class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePrefe
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
updatePreferenceOrder();
}
/** Customizes the order by preference key. */
private List<String> getPreferenceOrderList() {
List<String> lists = new ArrayList<>();
protected List<String> getPreferenceOrderList() {
final 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);
lists.add(KEY_HTML_DESCRIPTION_PREFERENCE);
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();
@@ -175,29 +159,12 @@ public final class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePrefe
@Override
protected void onRemoveSwitchPreferenceToggleSwitch() {
super.onRemoveSwitchPreferenceToggleSwitch();
mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener(null);
mToggleServiceSwitchPreference.setOnPreferenceClickListener(null);
}
@Override
protected void updateToggleServiceTitle(SwitchPreference switchPreference) {
switchPreference.setTitle(R.string.accessibility_daltonizer_master_switch_title);
}
@Override
protected void onInstallSwitchPreferenceToggleSwitch() {
super.onInstallSwitchPreferenceToggleSwitch();
updateSwitchBarToggleSwitch();
mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener((preference) -> {
boolean checked = ((SwitchPreference) preference).isChecked();
onPreferenceToggled(mPreferenceKey, checked);
return false;
});
}
@Override
public void onSettingsClicked(ShortcutPreference preference) {
super.onSettingsClicked(preference);
showDialog(DialogEnums.EDIT_SHORTCUT);
protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) {
switchPreference.setTitle(R.string.accessibility_daltonizer_primary_switch_title);
}
@Override
@@ -206,12 +173,13 @@ public final class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePrefe
mComponentName);
}
private void updateSwitchBarToggleSwitch() {
@Override
protected void updateSwitchBarToggleSwitch() {
final boolean checked = Settings.Secure.getInt(getContentResolver(), ENABLED, OFF) == ON;
if (mToggleServiceDividerSwitchPreference.isChecked() == checked) {
if (mToggleServiceSwitchPreference.isChecked() == checked) {
return;
}
mToggleServiceDividerSwitchPreference.setChecked(checked);
mToggleServiceSwitchPreference.setChecked(checked);
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =

View File

@@ -16,8 +16,7 @@
package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.getScreenHeightPixels;
import static com.android.settings.accessibility.AccessibilityUtil.getScreenWidthPixels;
import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
@@ -43,38 +42,40 @@ import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.Switch;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.accessibility.AccessibilityDialogUtils.DialogType;
import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
import com.android.settings.widget.SwitchBar;
import com.android.settings.utils.LocaleUtils;
import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settings.widget.SettingsMainSwitchPreference;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.accessibility.AccessibilityUtils;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.IllustrationPreference;
import com.android.settingslib.widget.OnMainSwitchChangeListener;
import com.google.android.setupcompat.util.WizardManagerHelper;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Collectors;
/**
* Base class for accessibility fragments with toggle, shortcut, some helper functions
* and dialog management.
*/
public abstract class ToggleFeaturePreferenceFragment extends SettingsPreferenceFragment
implements ShortcutPreference.OnClickCallback {
implements ShortcutPreference.OnClickCallback, OnMainSwitchChangeListener {
protected DividerSwitchPreference mToggleServiceDividerSwitchPreference;
protected SettingsMainSwitchPreference mToggleServiceSwitchPreference;
protected ShortcutPreference mShortcutPreference;
protected Preference mSettingsPreference;
protected String mPreferenceKey;
@@ -87,19 +88,24 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
protected Uri mImageUri;
private CharSequence mDescription;
protected CharSequence mHtmlDescription;
// Used to restore the edit dialog status.
protected int mUserShortcutTypesCache = UserShortcutType.EMPTY;
private static final String DRAWABLE_FOLDER = "drawable";
protected static final String KEY_USE_SERVICE_PREFERENCE = "use_service";
protected static final String KEY_GENERAL_CATEGORY = "general_categories";
protected static final String KEY_INTRODUCTION_CATEGORY = "introduction_categories";
public static final String KEY_GENERAL_CATEGORY = "general_categories";
protected static final String KEY_HTML_DESCRIPTION_PREFERENCE = "html_description";
private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
private static final String EXTRA_SHORTCUT_TYPE = "shortcut_type";
protected static final String KEY_SAVED_USER_SHORTCUT_TYPE = "shortcut_type";
protected static final String KEY_ANIMATED_IMAGE = "animated_image";
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
private int mUserShortcutTypes = UserShortcutType.EMPTY;
private SettingsContentObserver mSettingsContentObserver;
private CheckBox mSoftwareTypeCheckBox;
private CheckBox mHardwareTypeCheckBox;
private SettingsContentObserver mSettingsContentObserver;
public static final int NOT_SET = -1;
// Save user's shortcutType value when savedInstance has value (e.g. device rotated).
protected int mSavedCheckBoxValue = NOT_SET;
// For html description of accessibility service, must follow the rule, such as
// <img src="R.drawable.fileName"/>, a11y settings will get the resources successfully.
@@ -121,10 +127,17 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Restore the user shortcut type.
if (savedInstanceState != null && savedInstanceState.containsKey(
KEY_SAVED_USER_SHORTCUT_TYPE)) {
mSavedCheckBoxValue = savedInstanceState.getInt(KEY_SAVED_USER_SHORTCUT_TYPE, NOT_SET);
}
setupDefaultShortcutIfNecessary(getPrefContext());
final int resId = getPreferenceScreenResId();
if (resId <= 0) {
PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
getPrefContext());
setPreferenceScreen(preferenceScreen);
}
@@ -144,6 +157,21 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Need to be called as early as possible. Protected variables will be assigned here.
onProcessArguments(getArguments());
initAnimatedImagePreference();
initToggleServiceSwitchPreference();
initGeneralCategory();
initShortcutPreference();
initSettingsPreference();
initHtmlTextPreference();
initFooterPreference();
installActionBarToggleSwitch();
updateToggleServiceTitle(mToggleServiceSwitchPreference);
mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
removeDialog(DialogEnums.EDIT_SHORTCUT);
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
@@ -156,91 +184,16 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
super.onViewCreated(view, savedInstanceState);
final SettingsActivity activity = (SettingsActivity) getActivity();
final SwitchBar switchBar = activity.getSwitchBar();
final SettingsMainSwitchBar switchBar = activity.getSwitchBar();
switchBar.hide();
// Need to be called as early as possible. Protected variables will be assigned here.
onProcessArguments(getArguments());
PreferenceScreen preferenceScreen = getPreferenceScreen();
if (mImageUri != null) {
final int screenHalfHeight = getScreenHeightPixels(getPrefContext()) / /* half */ 2;
final AnimatedImagePreference animatedImagePreference = new AnimatedImagePreference(
getPrefContext());
animatedImagePreference.setImageUri(mImageUri);
animatedImagePreference.setSelectable(false);
animatedImagePreference.setMaxHeight(screenHalfHeight);
preferenceScreen.addPreference(animatedImagePreference);
}
mToggleServiceDividerSwitchPreference = new DividerSwitchPreference(getPrefContext());
mToggleServiceDividerSwitchPreference.setKey(KEY_USE_SERVICE_PREFERENCE);
if (getArguments().containsKey(AccessibilitySettings.EXTRA_CHECKED)) {
final boolean enabled = getArguments().getBoolean(AccessibilitySettings.EXTRA_CHECKED);
mToggleServiceDividerSwitchPreference.setChecked(enabled);
}
preferenceScreen.addPreference(mToggleServiceDividerSwitchPreference);
updateToggleServiceTitle(mToggleServiceDividerSwitchPreference);
final PreferenceCategory groupCategory = new PreferenceCategory(getPrefContext());
groupCategory.setKey(KEY_GENERAL_CATEGORY);
groupCategory.setTitle(R.string.accessibility_screen_option);
preferenceScreen.addPreference(groupCategory);
initShortcutPreference(savedInstanceState);
groupCategory.addPreference(mShortcutPreference);
// Show the "Settings" menu as if it were a preference screen.
if (mSettingsTitle != null && mSettingsIntent != null) {
mSettingsPreference = new Preference(getPrefContext());
mSettingsPreference.setTitle(mSettingsTitle);
mSettingsPreference.setIconSpaceReserved(true);
mSettingsPreference.setIntent(mSettingsIntent);
}
// The downloaded app may not show Settings. The framework app has Settings.
if (mSettingsPreference != null) {
groupCategory.addPreference(mSettingsPreference);
}
if (!TextUtils.isEmpty(mHtmlDescription)) {
final PreferenceCategory introductionCategory = new PreferenceCategory(
getPrefContext());
final CharSequence title = getString(R.string.accessibility_introduction_title,
mPackageName);
introductionCategory.setKey(KEY_INTRODUCTION_CATEGORY);
introductionCategory.setTitle(title);
preferenceScreen.addPreference(introductionCategory);
final HtmlTextPreference htmlTextPreference = new HtmlTextPreference(getPrefContext());
htmlTextPreference.setSummary(mHtmlDescription);
htmlTextPreference.setImageGetter(mImageGetter);
htmlTextPreference.setSelectable(false);
introductionCategory.addPreference(htmlTextPreference);
}
if (!TextUtils.isEmpty(mDescription)) {
createFooterPreference(mDescription);
}
if (TextUtils.isEmpty(mHtmlDescription) && TextUtils.isEmpty(mDescription)) {
final CharSequence defaultDescription = getText(
R.string.accessibility_service_default_description);
createFooterPreference(defaultDescription);
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
installActionBarToggleSwitch();
updatePreferenceOrder();
}
@Override
public void onResume() {
super.onResume();
final AccessibilityManager am = getPrefContext().getSystemService(
AccessibilityManager.class);
am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
@@ -260,7 +213,10 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(EXTRA_SHORTCUT_TYPE, mUserShortcutTypesCache);
final int value = getShortcutTypeCheckBoxValue();
if (value != NOT_SET) {
outState.putInt(KEY_SAVED_USER_SHORTCUT_TYPE, value);
}
super.onSaveInstanceState(outState);
}
@@ -271,9 +227,12 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
case DialogEnums.EDIT_SHORTCUT:
final CharSequence dialogTitle = getPrefContext().getString(
R.string.accessibility_shortcut_title, mPackageName);
dialog = AccessibilityEditDialogUtils.showEditShortcutDialog(
getPrefContext(), dialogTitle, this::callOnAlertDialogCheckboxClicked);
initializeDialogCheckBox(dialog);
final int dialogType = WizardManagerHelper.isAnySetupWizard(getIntent())
? DialogType.EDIT_SHORTCUT_GENERIC_SUW : DialogType.EDIT_SHORTCUT_GENERIC;
dialog = AccessibilityDialogUtils.showEditShortcutDialog(
getPrefContext(), dialogType, dialogTitle,
this::callOnAlertDialogCheckboxClicked);
setupEditShortcutDialog(dialog);
return dialog;
case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
dialog = AccessibilityGestureNavigationTutorial
@@ -298,92 +257,58 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
}
}
/** Denotes the dialog emuns for show dialog */
@Retention(RetentionPolicy.SOURCE)
protected @interface DialogEnums {
/** OPEN: Settings > Accessibility > Any toggle service > Shortcut > Settings. */
int EDIT_SHORTCUT = 1;
/** OPEN: Settings > Accessibility > Magnification > Shortcut > Settings. */
int MAGNIFICATION_EDIT_SHORTCUT = 1001;
/**
* OPEN: Settings > Accessibility > Downloaded toggle service > Toggle use service to
* enable service.
*/
int ENABLE_WARNING_FROM_TOGGLE = 1002;
/** OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox. */
int ENABLE_WARNING_FROM_SHORTCUT = 1003;
/**
* OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox
* toggle.
*/
int ENABLE_WARNING_FROM_SHORTCUT_TOGGLE = 1004;
/**
* OPEN: Settings > Accessibility > Downloaded toggle service > Toggle use service to
* disable service.
*/
int DISABLE_WARNING_FROM_TOGGLE = 1005;
/**
* OPEN: Settings > Accessibility > Magnification > Toggle user service in button
* navigation.
*/
int ACCESSIBILITY_BUTTON_TUTORIAL = 1006;
/**
* OPEN: Settings > Accessibility > Magnification > Toggle user service in gesture
* navigation.
*/
int GESTURE_NAVIGATION_TUTORIAL = 1007;
/**
* OPEN: Settings > Accessibility > Downloaded toggle service > Toggle user service > Show
* launch tutorial.
*/
int LAUNCH_ACCESSIBILITY_TUTORIAL = 1008;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_SERVICE;
}
@Override
public int getHelpResource() {
return 0;
}
@Override
public void onDestroyView() {
super.onDestroyView();
removeActionBarToggleSwitch();
}
@Override
public void onSwitchChanged(Switch switchView, boolean isChecked) {
onPreferenceToggled(mPreferenceKey, isChecked);
}
/**
* Returns the shortcut type list which has been checked by user.
*/
abstract int getUserShortcutTypes();
protected void updateToggleServiceTitle(SwitchPreference switchPreference) {
switchPreference.setTitle(R.string.accessibility_service_master_switch_title);
protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) {
switchPreference.setTitle(R.string.accessibility_service_primary_switch_title);
}
protected abstract void onPreferenceToggled(String preferenceKey, boolean enabled);
protected void onInstallSwitchPreferenceToggleSwitch() {
// Implement this to set a checked listener.
updateSwitchBarToggleSwitch();
mToggleServiceSwitchPreference.addOnSwitchChangeListener(this);
}
protected void onRemoveSwitchPreferenceToggleSwitch() {
// Implement this to reset a checked listener.
}
protected void updateSwitchBarToggleSwitch() {
// Implement this to update the state of switch.
}
private void installActionBarToggleSwitch() {
onInstallSwitchPreferenceToggleSwitch();
}
private void removeActionBarToggleSwitch() {
mToggleServiceDividerSwitchPreference.setOnPreferenceClickListener(null);
mToggleServiceSwitchPreference.setOnPreferenceClickListener(null);
onRemoveSwitchPreferenceToggleSwitch();
}
@@ -415,6 +340,31 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
}
}
/** Customizes the order by preference key. */
protected List<String> getPreferenceOrderList() {
final List<String> lists = new ArrayList<>();
lists.add(KEY_ANIMATED_IMAGE);
lists.add(KEY_USE_SERVICE_PREFERENCE);
lists.add(KEY_GENERAL_CATEGORY);
lists.add(KEY_HTML_DESCRIPTION_PREFERENCE);
return lists;
}
private void updatePreferenceOrder() {
final List<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);
}
}
}
private Drawable getDrawableFromUri(Uri imageUri) {
if (mImageGetterCacheView == null) {
mImageGetterCacheView = new ImageView(getPrefContext());
@@ -432,8 +382,8 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
mImageGetterCacheView.setImageURI(null);
final int imageWidth = drawable.getIntrinsicWidth();
final int imageHeight = drawable.getIntrinsicHeight();
final int screenHalfHeight = getScreenHeightPixels(getPrefContext()) / /* half */ 2;
if ((imageWidth > getScreenWidthPixels(getPrefContext()))
final int screenHalfHeight = AccessibilityUtil.getScreenHeightPixels(getPrefContext()) / 2;
if ((imageWidth > AccessibilityUtil.getScreenWidthPixels(getPrefContext()))
|| (imageHeight > screenHalfHeight)) {
return null;
}
@@ -444,60 +394,161 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
return drawable;
}
static final class AccessibilityUserShortcutType {
private static final char COMPONENT_NAME_SEPARATOR = ':';
private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
private String mComponentName;
private int mType;
AccessibilityUserShortcutType(String componentName, int type) {
this.mComponentName = componentName;
this.mType = type;
private void initAnimatedImagePreference() {
if (mImageUri == null) {
return;
}
AccessibilityUserShortcutType(String flattenedString) {
sStringColonSplitter.setString(flattenedString);
if (sStringColonSplitter.hasNext()) {
this.mComponentName = sStringColonSplitter.next();
this.mType = Integer.parseInt(sStringColonSplitter.next());
}
final IllustrationPreference illustrationPreference =
new IllustrationPreference(getPrefContext());
illustrationPreference.setImageUri(mImageUri);
illustrationPreference.setSelectable(false);
illustrationPreference.setKey(KEY_ANIMATED_IMAGE);
getPreferenceScreen().addPreference(illustrationPreference);
}
private void initToggleServiceSwitchPreference() {
mToggleServiceSwitchPreference = new SettingsMainSwitchPreference(getPrefContext());
mToggleServiceSwitchPreference.setKey(KEY_USE_SERVICE_PREFERENCE);
if (getArguments().containsKey(AccessibilitySettings.EXTRA_CHECKED)) {
final boolean enabled = getArguments().getBoolean(AccessibilitySettings.EXTRA_CHECKED);
mToggleServiceSwitchPreference.setChecked(enabled);
}
String getComponentName() {
return mComponentName;
getPreferenceScreen().addPreference(mToggleServiceSwitchPreference);
}
private void initGeneralCategory() {
final PreferenceCategory generalCategory = new PreferenceCategory(getPrefContext());
generalCategory.setKey(KEY_GENERAL_CATEGORY);
generalCategory.setTitle(R.string.accessibility_screen_option);
getPreferenceScreen().addPreference(generalCategory);
}
protected void initShortcutPreference() {
// Initial the shortcut preference.
mShortcutPreference = new ShortcutPreference(getPrefContext(), /* attrs= */ null);
mShortcutPreference.setPersistent(false);
mShortcutPreference.setKey(getShortcutPreferenceKey());
mShortcutPreference.setOnClickCallback(this);
final CharSequence title = getString(R.string.accessibility_shortcut_title, mPackageName);
mShortcutPreference.setTitle(title);
final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
generalCategory.addPreference(mShortcutPreference);
}
protected void initSettingsPreference() {
if (mSettingsTitle == null || mSettingsIntent == null) {
return;
}
void setComponentName(String componentName) {
this.mComponentName = componentName;
// Show the "Settings" menu as if it were a preference screen.
mSettingsPreference = new Preference(getPrefContext());
mSettingsPreference.setTitle(mSettingsTitle);
mSettingsPreference.setIconSpaceReserved(false);
mSettingsPreference.setIntent(mSettingsIntent);
final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
generalCategory.addPreference(mSettingsPreference);
}
private void initHtmlTextPreference() {
if (TextUtils.isEmpty(mHtmlDescription)) {
return;
}
final PreferenceScreen screen = getPreferenceScreen();
final CharSequence htmlDescription = Html.fromHtml(mHtmlDescription.toString(),
Html.FROM_HTML_MODE_COMPACT, mImageGetter, /* tagHandler= */ null);
final String iconContentDescription =
getString(R.string.accessibility_introduction_title, mPackageName);
final AccessibilityFooterPreference htmlFooterPreference =
new AccessibilityFooterPreference(screen.getContext());
htmlFooterPreference.setKey(KEY_HTML_DESCRIPTION_PREFERENCE);
htmlFooterPreference.setSummary(htmlDescription);
htmlFooterPreference.setContentDescription(
generateFooterContentDescription(htmlDescription));
// Only framework tools support help link
if (getHelpResource() != 0) {
htmlFooterPreference.setLearnMoreAction(view -> {
final Intent helpIntent = HelpUtils.getHelpIntent(
getContext(), getContext().getString(getHelpResource()),
getContext().getClass().getName());
view.startActivityForResult(helpIntent, 0);
});
final String learnMoreContentDescription = getPrefContext().getString(
R.string.footer_learn_more_content_description, mPackageName);
htmlFooterPreference.setLearnMoreContentDescription(learnMoreContentDescription);
htmlFooterPreference.setLinkEnabled(true);
} else {
htmlFooterPreference.setLinkEnabled(false);
}
screen.addPreference(htmlFooterPreference);
}
private void initFooterPreference() {
if (!TextUtils.isEmpty(mDescription)) {
createFooterPreference(getPreferenceScreen(), mDescription,
getString(R.string.accessibility_introduction_title, mPackageName));
}
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();
if (TextUtils.isEmpty(mHtmlDescription) && TextUtils.isEmpty(mDescription)) {
final CharSequence defaultDescription =
getText(R.string.accessibility_service_default_description);
createFooterPreference(getPreferenceScreen(), defaultDescription,
getString(R.string.accessibility_introduction_title, mPackageName));
}
}
private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) {
final View dialogTextArea = dialogView.findViewById(R.id.container);
dialogTextArea.setOnClickListener(v -> {
checkBox.toggle();
updateUserShortcutType(/* saveChanges= */ false);
});
/**
* Creates {@link AccessibilityFooterPreference} and append into {@link PreferenceScreen}
*
* @param screen The preference screen to add the footer preference
* @param summary The summary of the preference summary.
* @param iconContentDescription The content description of icon in the footer.
*/
@VisibleForTesting
void createFooterPreference(PreferenceScreen screen, CharSequence summary,
String iconContentDescription) {
final AccessibilityFooterPreference footerPreference =
new AccessibilityFooterPreference(screen.getContext());
footerPreference.setSummary(summary);
footerPreference.setContentDescription(
generateFooterContentDescription(summary));
// Only framework tools support help link
if (getHelpResource() != 0) {
footerPreference.setLearnMoreAction(view -> {
final Intent helpIntent = HelpUtils.getHelpIntent(
getContext(), getContext().getString(getHelpResource()),
getContext().getClass().getName());
view.startActivityForResult(helpIntent, 0);
});
final String learnMoreContentDescription = getPrefContext().getString(
R.string.footer_learn_more_content_description, mPackageName);
footerPreference.setLearnMoreContentDescription(learnMoreContentDescription);
}
screen.addPreference(footerPreference);
}
private void initializeDialogCheckBox(Dialog dialog) {
private CharSequence generateFooterContentDescription(CharSequence footerContent) {
final StringBuffer sb = new StringBuffer();
sb.append(getPrefContext().getString(
R.string.accessibility_introduction_title, mPackageName))
.append("\n\n")
.append(footerContent);
return sb;
}
@VisibleForTesting
void setupEditShortcutDialog(Dialog dialog) {
final View dialogSoftwareView = dialog.findViewById(R.id.software_shortcut);
mSoftwareTypeCheckBox = dialogSoftwareView.findViewById(R.id.checkbox);
setDialogTextAreaClickListener(dialogSoftwareView, mSoftwareTypeCheckBox);
@@ -506,57 +557,58 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
mHardwareTypeCheckBox = dialogHardwareView.findViewById(R.id.checkbox);
setDialogTextAreaClickListener(dialogHardwareView, mHardwareTypeCheckBox);
updateAlertDialogCheckState();
updateEditShortcutDialogCheckBox();
}
private void updateAlertDialogCheckState() {
if (mUserShortcutTypesCache != UserShortcutType.EMPTY) {
updateCheckStatus(mSoftwareTypeCheckBox, UserShortcutType.SOFTWARE);
updateCheckStatus(mHardwareTypeCheckBox, UserShortcutType.HARDWARE);
private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) {
final View dialogTextArea = dialogView.findViewById(R.id.container);
dialogTextArea.setOnClickListener(v -> checkBox.toggle());
}
private void updateEditShortcutDialogCheckBox() {
// If it is during onConfigChanged process then restore the value, or get the saved value
// when shortcutPreference is checked.
int value = restoreOnConfigChangedValue();
if (value == NOT_SET) {
final int lastNonEmptyUserShortcutType = PreferredShortcuts.retrieveUserShortcutType(
getPrefContext(), mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
value = mShortcutPreference.isChecked() ? lastNonEmptyUserShortcutType
: UserShortcutType.EMPTY;
}
mSoftwareTypeCheckBox.setChecked(
hasShortcutType(value, UserShortcutType.SOFTWARE));
mHardwareTypeCheckBox.setChecked(
hasShortcutType(value, UserShortcutType.HARDWARE));
}
private void updateCheckStatus(CheckBox checkBox, @UserShortcutType int type) {
checkBox.setChecked((mUserShortcutTypesCache & type) == type);
private int restoreOnConfigChangedValue() {
final int savedValue = mSavedCheckBoxValue;
mSavedCheckBoxValue = NOT_SET;
return savedValue;
}
private void updateUserShortcutType(boolean saveChanges) {
mUserShortcutTypesCache = UserShortcutType.EMPTY;
private boolean hasShortcutType(int value, @UserShortcutType int type) {
return (value & type) == type;
}
/**
* Returns accumulated {@link UserShortcutType} checkbox value or {@code NOT_SET} if checkboxes
* did not exist.
*/
protected int getShortcutTypeCheckBoxValue() {
if (mSoftwareTypeCheckBox == null || mHardwareTypeCheckBox == null) {
return NOT_SET;
}
int value = UserShortcutType.EMPTY;
if (mSoftwareTypeCheckBox.isChecked()) {
mUserShortcutTypesCache |= UserShortcutType.SOFTWARE;
value |= UserShortcutType.SOFTWARE;
}
if (mHardwareTypeCheckBox.isChecked()) {
mUserShortcutTypesCache |= UserShortcutType.HARDWARE;
value |= 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);
return value;
}
protected CharSequence getShortcutTypeSummary(Context context) {
@@ -568,20 +620,17 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
return context.getText(R.string.switch_off_text);
}
final int shortcutTypes = getUserShortcutTypes(context, UserShortcutType.SOFTWARE);
int resId = R.string.accessibility_shortcut_edit_summary_software;
if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
resId = AccessibilityUtil.isTouchExploreEnabled(context)
? R.string.accessibility_shortcut_edit_dialog_title_software_gesture_talkback
: R.string.accessibility_shortcut_edit_dialog_title_software_gesture;
}
final CharSequence softwareTitle = context.getText(resId);
final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(context,
mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
List<CharSequence> list = new ArrayList<>();
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
final List<CharSequence> list = new ArrayList<>();
final CharSequence softwareTitle = context.getText(
R.string.accessibility_shortcut_edit_summary_software);
if (hasShortcutType(shortcutTypes, UserShortcutType.SOFTWARE)) {
list.add(softwareTitle);
}
if ((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE) {
if (hasShortcutType(shortcutTypes, UserShortcutType.HARDWARE)) {
final CharSequence hardwareTitle = context.getText(
R.string.accessibility_shortcut_hardware_keyword);
list.add(hardwareTitle);
@@ -591,50 +640,29 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
if (list.isEmpty()) {
list.add(softwareTitle);
}
final String joinStrings = TextUtils.join(/* delimiter= */", ", list);
return CaseMap.toTitle().wholeString().noLowercase().apply(Locale.getDefault(), /* iter= */
null, joinStrings);
}
protected int getUserShortcutTypes(Context context, @UserShortcutType int defaultValue) {
if (mComponentName == null) {
return defaultValue;
}
final Set<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();
null, LocaleUtils.getConcatenatedString(list));
}
/**
* This method will be invoked when a button in the edit shortcut dialog is clicked.
*
* @param dialog The dialog that received the click
* @param which The button that was clicked
* @param which The button that was clicked
*/
protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
if (mComponentName == null) {
return;
}
updateUserShortcutType(/* saveChanges= */ true);
AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), mUserShortcutTypes,
mComponentName);
AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), ~mUserShortcutTypes,
mComponentName);
mShortcutPreference.setChecked(mUserShortcutTypes != UserShortcutType.EMPTY);
mShortcutPreference.setSummary(
getShortcutTypeSummary(getPrefContext()));
final int value = getShortcutTypeCheckBoxValue();
saveNonEmptyUserShortcutType(value);
AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), value, mComponentName);
AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), ~value, mComponentName);
mShortcutPreference.setChecked(value != UserShortcutType.EMPTY);
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
protected void updateShortcutPreferenceData() {
@@ -642,47 +670,29 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
return;
}
// Get the user shortcut type from settings provider.
mUserShortcutTypes = AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(),
mComponentName);
if (mUserShortcutTypes != UserShortcutType.EMPTY) {
setUserShortcutType(getPrefContext(), mUserShortcutTypes);
} else {
// Get the user shortcut type from shared_prefs if cannot get from settings provider.
mUserShortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
final int shortcutTypes = AccessibilityUtil.getUserShortcutTypesFromSettings(
getPrefContext(), mComponentName);
if (shortcutTypes != UserShortcutType.EMPTY) {
final PreferredShortcut shortcut = new PreferredShortcut(
mComponentName.flattenToString(), shortcutTypes);
PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
}
}
private void initShortcutPreference(Bundle savedInstanceState) {
// Restore the user shortcut type.
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_SHORTCUT_TYPE)) {
mUserShortcutTypesCache = savedInstanceState.getInt(EXTRA_SHORTCUT_TYPE,
UserShortcutType.EMPTY);
}
// Initial the shortcut preference.
mShortcutPreference = new ShortcutPreference(getPrefContext(), null);
mShortcutPreference.setPersistent(false);
mShortcutPreference.setKey(getShortcutPreferenceKey());
mShortcutPreference.setOnClickCallback(this);
final CharSequence title = getString(R.string.accessibility_shortcut_title, mPackageName);
mShortcutPreference.setTitle(title);
}
protected void updateShortcutPreference() {
if (mComponentName == null) {
return;
}
final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(),
mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
mShortcutPreference.setChecked(
AccessibilityUtil.hasValuesInSettings(getPrefContext(), shortcutTypes,
mComponentName));
AccessibilityUtil.hasValuesInSettings(getPrefContext(), shortcutTypes,
mComponentName));
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
private String getShortcutPreferenceKey() {
protected String getShortcutPreferenceKey() {
return KEY_SHORTCUT_PREFERENCE;
}
@@ -692,7 +702,8 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
return;
}
final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(),
mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
if (preference.isChecked()) {
AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), shortcutTypes,
mComponentName);
@@ -706,20 +717,11 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
@Override
public void onSettingsClicked(ShortcutPreference preference) {
// Do not restore shortcut in shortcut chooser dialog when shortcutPreference is turned off.
mUserShortcutTypesCache = mShortcutPreference.isChecked()
? getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE)
: UserShortcutType.EMPTY;
}
private void createFooterPreference(CharSequence title) {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
preferenceScreen.addPreference(new FooterPreference.Builder(getActivity()).setTitle(
title).build());
showDialog(DialogEnums.EDIT_SHORTCUT);
}
/**
* Setups a configurable default if the setting has never been set.
* Setups a configurable default if the setting has never been set.
*/
private static void setupDefaultShortcutIfNecessary(Context context) {
final String targetKey = Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
@@ -745,4 +747,15 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference
shortcutName.flattenToString());
}
}
@VisibleForTesting
void saveNonEmptyUserShortcutType(int type) {
if (type == UserShortcutType.EMPTY) {
return;
}
final PreferredShortcut shortcut = new PreferredShortcut(
mComponentName.flattenToString(), type);
PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
}
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import android.content.ContentResolver;
import android.content.Context;
import android.hardware.display.ColorDisplayManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.preference.PreferenceCategory;
import androidx.preference.SwitchPreference;
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.settings.R;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.SeekBarPreference;
import com.android.settings.widget.SettingsMainSwitchPreference;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.List;
/** Settings for reducing brightness. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class ToggleReduceBrightColorsPreferenceFragment extends ToggleFeaturePreferenceFragment {
private static final String REDUCE_BRIGHT_COLORS_ACTIVATED_KEY =
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED;
private static final String KEY_INTENSITY = "rbc_intensity";
private static final String KEY_PERSIST = "rbc_persist";
private final Handler mHandler = new Handler();
private SettingsContentObserver mSettingsContentObserver;
private ReduceBrightColorsIntensityPreferenceController mRbcIntensityPreferenceController;
private ReduceBrightColorsPersistencePreferenceController mRbcPersistencePreferenceController;
private ColorDisplayManager mColorDisplayManager;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(getPrefContext().getPackageName())
.appendPath(String.valueOf(R.raw.extra_dim_banner))
.build();
mComponentName = AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME;
mPackageName = getText(R.string.reduce_bright_colors_preference_title);
mHtmlDescription = getText(R.string.reduce_bright_colors_preference_subtitle);
final List<String> enableServiceFeatureKeys = new ArrayList<>(/* initialCapacity= */ 1);
enableServiceFeatureKeys.add(REDUCE_BRIGHT_COLORS_ACTIVATED_KEY);
mRbcIntensityPreferenceController =
new ReduceBrightColorsIntensityPreferenceController(getContext(), KEY_INTENSITY);
mRbcPersistencePreferenceController =
new ReduceBrightColorsPersistencePreferenceController(getContext(), KEY_PERSIST);
mRbcIntensityPreferenceController.displayPreference(getPreferenceScreen());
mRbcPersistencePreferenceController.displayPreference(getPreferenceScreen());
mSettingsContentObserver = new SettingsContentObserver(mHandler, enableServiceFeatureKeys) {
@Override
public void onChange(boolean selfChange, Uri uri) {
updateSwitchBarToggleSwitch();
}
};
mColorDisplayManager = getContext().getSystemService(ColorDisplayManager.class);
final View view = super.onCreateView(inflater, container, savedInstanceState);
// Parent sets the title when creating the view, so set it after calling super
mToggleServiceSwitchPreference.setTitle(R.string.reduce_bright_colors_switch_title);
updateGeneralCategoryOrder();
return view;
}
private void updateGeneralCategoryOrder() {
final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
final SeekBarPreference intensity = findPreference(KEY_INTENSITY);
getPreferenceScreen().removePreference(intensity);
intensity.setOrder(mShortcutPreference.getOrder() - 2);
generalCategory.addPreference(intensity);
final SwitchPreference persist = findPreference(KEY_PERSIST);
getPreferenceScreen().removePreference(persist);
persist.setOrder(mShortcutPreference.getOrder() - 1);
generalCategory.addPreference(persist);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
@Override
public void onResume() {
super.onResume();
updateSwitchBarToggleSwitch();
mSettingsContentObserver.register(getContentResolver());
}
@Override
public void onPause() {
mSettingsContentObserver.unregister(getContentResolver());
super.onPause();
}
@Override
public int getMetricsCategory() {
return SettingsEnums.REDUCE_BRIGHT_COLORS_SETTINGS;
}
@Override
public int getHelpResource() {
// TODO(170973645): Link to help support page
return 0;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.reduce_bright_colors_settings;
}
@Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
AccessibilityStatsLogUtils.logAccessibilityServiceEnabled(mComponentName, enabled);
mColorDisplayManager.setReduceBrightColorsActivated(enabled);
}
@Override
protected void onRemoveSwitchPreferenceToggleSwitch() {
super.onRemoveSwitchPreferenceToggleSwitch();
mToggleServiceSwitchPreference.setOnPreferenceClickListener(
/* onPreferenceClickListener= */ null);
}
@Override
protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) {
switchPreference.setTitle(R.string.reduce_bright_colors_preference_title);
}
@Override
int getUserShortcutTypes() {
return AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(),
mComponentName);
}
@Override
protected void updateSwitchBarToggleSwitch() {
final boolean checked = mColorDisplayManager.isReduceBrightColorsActivated();
mRbcIntensityPreferenceController.updateState(getPreferenceScreen()
.findPreference(KEY_INTENSITY));
mRbcPersistencePreferenceController.updateState(getPreferenceScreen()
.findPreference(KEY_PERSIST));
if (mToggleServiceSwitchPreference.isChecked() != checked) {
mToggleServiceSwitchPreference.setChecked(checked);
}
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.reduce_bright_colors_settings) {
@Override
protected boolean isPageSearchEnabled(Context context) {
return ColorDisplayManager.isReduceBrightColorsAvailable(context);
}
};
}

View File

@@ -17,6 +17,7 @@
package com.android.settings.accessibility;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
@@ -38,41 +39,44 @@ import android.view.accessibility.AccessibilityManager.TouchExplorationStateChan
import android.widget.CheckBox;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.DialogCreatable;
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityDialogUtils.DialogType;
import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
import com.android.settings.utils.LocaleUtils;
import com.google.android.setupcompat.util.WizardManagerHelper;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Collectors;
/**
* Fragment that shows the actual UI for providing basic magnification accessibility service setup
* and does not have toggle bar to turn on service to use.
*/
public class ToggleScreenMagnificationPreferenceFragment extends
ToggleFeaturePreferenceFragment {
private static final String EXTRA_SHORTCUT_TYPE = "shortcut_type";
private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
ToggleFeaturePreferenceFragment implements
MagnificationModePreferenceController.DialogHelper {
// TODO(b/147021230): Move duplicated functions with android/internal/accessibility into util.
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
private int mUserShortcutType = UserShortcutType.EMPTY;
private CheckBox mSoftwareTypeCheckBox;
private CheckBox mHardwareTypeCheckBox;
private CheckBox mTripleTapTypeCheckBox;
// TODO(b/147021230): Will move common functions and variables to
// android/internal/accessibility folder. For now, magnification need to be treated
// individually.
private static final char COMPONENT_NAME_SEPARATOR = ':';
private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
private MagnificationModePreferenceController mModePreferenceController;
private DialogCreatable mDialogDelegate;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -85,7 +89,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends
mPackageName = getString(R.string.accessibility_screen_magnification_title);
mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(getPrefContext().getPackageName())
.appendPath(String.valueOf(R.drawable.accessibility_magnification_banner))
.appendPath(String.valueOf(R.raw.accessibility_magnification_banner))
.build();
mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
removeDialog(DialogEnums.EDIT_SHORTCUT);
@@ -94,19 +98,6 @@ public class ToggleScreenMagnificationPreferenceFragment extends
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
initShortcutPreference();
super.onViewCreated(view, savedInstanceState);
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(EXTRA_SHORTCUT_TYPE, mUserShortcutTypesCache);
super.onSaveInstanceState(outState);
}
@Override
public void onResume() {
super.onResume();
@@ -114,9 +105,6 @@ public class ToggleScreenMagnificationPreferenceFragment extends
final AccessibilityManager am = getPrefContext().getSystemService(
AccessibilityManager.class);
am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
updateShortcutPreferenceData();
updateShortcutPreference();
}
@Override
@@ -130,6 +118,12 @@ public class ToggleScreenMagnificationPreferenceFragment extends
@Override
public Dialog onCreateDialog(int dialogId) {
if (mDialogDelegate != null) {
final Dialog dialog = mDialogDelegate.onCreateDialog(dialogId);
if (dialog != null) {
return dialog;
}
}
final AlertDialog dialog;
switch (dialogId) {
case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
@@ -138,25 +132,71 @@ public class ToggleScreenMagnificationPreferenceFragment extends
case DialogEnums.MAGNIFICATION_EDIT_SHORTCUT:
final CharSequence dialogTitle = getPrefContext().getString(
R.string.accessibility_shortcut_title, mPackageName);
dialog = AccessibilityEditDialogUtils.showMagnificationEditShortcutDialog(
getPrefContext(), dialogTitle,
this::callOnAlertDialogCheckboxClicked);
initializeDialogCheckBox(dialog);
final int dialogType = WizardManagerHelper.isAnySetupWizard(getIntent())
? DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW
: DialogType.EDIT_SHORTCUT_MAGNIFICATION;
dialog = AccessibilityDialogUtils.showEditShortcutDialog(getPrefContext(),
dialogType, dialogTitle, this::callOnAlertDialogCheckboxClicked);
setupMagnificationEditShortcutDialog(dialog);
return dialog;
default:
return super.onCreateDialog(dialogId);
}
}
private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) {
final View dialogTextArea = dialogView.findViewById(R.id.container);
dialogTextArea.setOnClickListener(v -> {
checkBox.toggle();
updateUserShortcutType(/* saveChanges= */ false);
});
@Override
protected void initSettingsPreference() {
// If the device doesn't support magnification area, it should hide the settings preference.
if (!getContext().getResources().getBoolean(
com.android.internal.R.bool.config_magnification_area)) {
return;
}
mSettingsPreference = new Preference(getPrefContext());
mSettingsPreference.setTitle(R.string.accessibility_magnification_mode_title);
mSettingsPreference.setKey(MagnificationModePreferenceController.PREF_KEY);
mSettingsPreference.setPersistent(false);
final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
generalCategory.addPreference(mSettingsPreference);
mModePreferenceController = new MagnificationModePreferenceController(getContext(),
MagnificationModePreferenceController.PREF_KEY);
mModePreferenceController.setDialogHelper(this);
getSettingsLifecycle().addObserver(mModePreferenceController);
mModePreferenceController.displayPreference(getPreferenceScreen());
}
private void initializeDialogCheckBox(AlertDialog dialog) {
@Override
public void showDialog(int dialogId) {
super.showDialog(dialogId);
}
@Override
public void setDialogDelegate(DialogCreatable delegate) {
mDialogDelegate = delegate;
}
@Override
protected int getShortcutTypeCheckBoxValue() {
if (mSoftwareTypeCheckBox == null || mHardwareTypeCheckBox == null) {
return NOT_SET;
}
int value = UserShortcutType.EMPTY;
if (mSoftwareTypeCheckBox.isChecked()) {
value |= UserShortcutType.SOFTWARE;
}
if (mHardwareTypeCheckBox.isChecked()) {
value |= UserShortcutType.HARDWARE;
}
if (mTripleTapTypeCheckBox.isChecked()) {
value |= UserShortcutType.TRIPLETAP;
}
return value;
}
@VisibleForTesting
void setupMagnificationEditShortcutDialog(AlertDialog dialog) {
final View dialogSoftwareView = dialog.findViewById(R.id.software_shortcut);
mSoftwareTypeCheckBox = dialogSoftwareView.findViewById(R.id.checkbox);
setDialogTextAreaClickListener(dialogSoftwareView, mSoftwareTypeCheckBox);
@@ -170,67 +210,46 @@ public class ToggleScreenMagnificationPreferenceFragment extends
setDialogTextAreaClickListener(dialogTripleTapView, mTripleTapTypeCheckBox);
final View advancedView = dialog.findViewById(R.id.advanced_shortcut);
updateAlertDialogCheckState();
// Window magnification mode doesn't support advancedView.
if (isWindowMagnification(getPrefContext())) {
advancedView.setVisibility(View.GONE);
return;
}
// Shows the triple tap checkbox directly if clicked.
if (mTripleTapTypeCheckBox.isChecked()) {
advancedView.setVisibility(View.GONE);
dialogTripleTapView.setVisibility(View.VISIBLE);
}
updateMagnificationEditShortcutDialogCheckBox();
}
private void updateAlertDialogCheckState() {
if (mUserShortcutTypesCache != UserShortcutType.EMPTY) {
updateCheckStatus(mSoftwareTypeCheckBox, UserShortcutType.SOFTWARE);
updateCheckStatus(mHardwareTypeCheckBox, UserShortcutType.HARDWARE);
updateCheckStatus(mTripleTapTypeCheckBox, UserShortcutType.TRIPLETAP);
}
private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) {
final View dialogTextArea = dialogView.findViewById(R.id.container);
dialogTextArea.setOnClickListener(v -> checkBox.toggle());
}
private void updateCheckStatus(CheckBox checkBox, @UserShortcutType int type) {
checkBox.setChecked((mUserShortcutTypesCache & type) == type);
private void updateMagnificationEditShortcutDialogCheckBox() {
// If it is during onConfigChanged process then restore the value, or get the saved value
// when shortcutPreference is checked.
int value = restoreOnConfigChangedValue();
if (value == NOT_SET) {
final int lastNonEmptyUserShortcutType = PreferredShortcuts.retrieveUserShortcutType(
getPrefContext(), MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.SOFTWARE);
value = mShortcutPreference.isChecked() ? lastNonEmptyUserShortcutType
: UserShortcutType.EMPTY;
}
mSoftwareTypeCheckBox.setChecked(
hasShortcutType(value, UserShortcutType.SOFTWARE));
mHardwareTypeCheckBox.setChecked(
hasShortcutType(value, UserShortcutType.HARDWARE));
mTripleTapTypeCheckBox.setChecked(
hasShortcutType(value, UserShortcutType.TRIPLETAP));
}
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;
}
if (saveChanges) {
final boolean isChanged = (mUserShortcutTypesCache != UserShortcutType.EMPTY);
if (isChanged) {
setUserShortcutType(getPrefContext(), mUserShortcutTypesCache);
}
mUserShortcutType = mUserShortcutTypesCache;
}
private int restoreOnConfigChangedValue() {
final int savedValue = mSavedCheckBoxValue;
mSavedCheckBoxValue = NOT_SET;
return savedValue;
}
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);
private boolean hasShortcutType(int value, @UserShortcutType int type) {
return (value & type) == type;
}
@Override
@@ -239,26 +258,23 @@ public class ToggleScreenMagnificationPreferenceFragment extends
return context.getText(R.string.switch_off_text);
}
final int shortcutType = getUserShortcutTypes(context, UserShortcutType.EMPTY);
int resId = R.string.accessibility_shortcut_edit_summary_software;
if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
resId = AccessibilityUtil.isTouchExploreEnabled(context)
? R.string.accessibility_shortcut_edit_dialog_title_software_gesture_talkback
: R.string.accessibility_shortcut_edit_dialog_title_software_gesture;
}
final CharSequence softwareTitle = context.getText(resId);
final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(context,
MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.SOFTWARE);
List<CharSequence> list = new ArrayList<>();
if ((shortcutType & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
final List<CharSequence> list = new ArrayList<>();
final CharSequence softwareTitle = context.getText(
R.string.accessibility_shortcut_edit_summary_software);
if (hasShortcutType(shortcutTypes, UserShortcutType.SOFTWARE)) {
list.add(softwareTitle);
}
if ((shortcutType & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE) {
if (hasShortcutType(shortcutTypes, UserShortcutType.HARDWARE)) {
final CharSequence hardwareTitle = context.getText(
R.string.accessibility_shortcut_hardware_keyword);
list.add(hardwareTitle);
}
if ((shortcutType & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP) {
if (hasShortcutType(shortcutTypes, UserShortcutType.TRIPLETAP)) {
final CharSequence tripleTapTitle = context.getText(
R.string.accessibility_shortcut_triple_tap_keyword);
list.add(tripleTapTitle);
@@ -268,37 +284,28 @@ public class ToggleScreenMagnificationPreferenceFragment extends
if (list.isEmpty()) {
list.add(softwareTitle);
}
final String joinStrings = TextUtils.join(/* delimiter= */", ", list);
return CaseMap.toTitle().wholeString().noLowercase().apply(Locale.getDefault(), /* iter= */
null, joinStrings);
}
@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();
null, LocaleUtils.getConcatenatedString(list));
}
@Override
protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
updateUserShortcutType(/* saveChanges= */ true);
optInAllMagnificationValuesToSettings(getPrefContext(), mUserShortcutType);
optOutAllMagnificationValuesFromSettings(getPrefContext(), ~mUserShortcutType);
mShortcutPreference.setChecked(mUserShortcutType != UserShortcutType.EMPTY);
final int value = getShortcutTypeCheckBoxValue();
saveNonEmptyUserShortcutType(value);
optInAllMagnificationValuesToSettings(getPrefContext(), value);
optOutAllMagnificationValuesFromSettings(getPrefContext(), ~value);
mShortcutPreference.setChecked(value != UserShortcutType.EMPTY);
mShortcutPreference.setSummary(
getShortcutTypeSummary(getPrefContext()));
}
@Override
public int getHelpResource() {
return R.string.help_url_magnification;
}
@Override
public int getMetricsCategory() {
// TODO: Distinguish between magnification modes
@@ -307,6 +314,13 @@ public class ToggleScreenMagnificationPreferenceFragment extends
@Override
public int getDialogMetricsCategory(int dialogId) {
if (mDialogDelegate != null) {
final int category = mDialogDelegate.getDialogMetricsCategory(dialogId);
if (category != 0) {
return category;
}
}
switch (dialogId) {
case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION;
@@ -336,13 +350,13 @@ public class ToggleScreenMagnificationPreferenceFragment extends
@Override
protected void onInstallSwitchPreferenceToggleSwitch() {
super.onInstallSwitchPreferenceToggleSwitch();
mToggleServiceDividerSwitchPreference.setVisible(false);
mToggleServiceSwitchPreference.setVisible(false);
}
@Override
public void onToggleClicked(ShortcutPreference preference) {
final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(),
MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.SOFTWARE);
if (preference.isChecked()) {
optInAllMagnificationValuesToSettings(getPrefContext(), shortcutTypes);
showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
@@ -354,44 +368,54 @@ public class ToggleScreenMagnificationPreferenceFragment extends
@Override
public void onSettingsClicked(ShortcutPreference preference) {
// Do not restore shortcut in shortcut chooser dialog when shortcutPreference is turned off.
mUserShortcutTypesCache = mShortcutPreference.isChecked()
? getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE)
: UserShortcutType.EMPTY;
showDialog(DialogEnums.MAGNIFICATION_EDIT_SHORTCUT);
}
@Override
protected void updateShortcutPreferenceData() {
// Get the user shortcut type from settings provider.
mUserShortcutType = getUserShortcutTypeFromSettings(getPrefContext());
if (mUserShortcutType != UserShortcutType.EMPTY) {
setUserShortcutType(getPrefContext(), mUserShortcutType);
} else {
// Get the user shortcut type from shared_prefs if cannot get from settings provider.
mUserShortcutType = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
final int shortcutTypes = getUserShortcutTypeFromSettings(getPrefContext());
if (shortcutTypes != UserShortcutType.EMPTY) {
final PreferredShortcut shortcut = new PreferredShortcut(
MAGNIFICATION_CONTROLLER_NAME, shortcutTypes);
PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
}
}
private void initShortcutPreference() {
@Override
protected void initShortcutPreference() {
mShortcutPreference = new ShortcutPreference(getPrefContext(), null);
mShortcutPreference.setPersistent(false);
mShortcutPreference.setKey(KEY_SHORTCUT_PREFERENCE);
mShortcutPreference.setKey(getShortcutPreferenceKey());
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
mShortcutPreference.setOnClickCallback(this);
final CharSequence title = getString(R.string.accessibility_shortcut_title, mPackageName);
mShortcutPreference.setTitle(title);
final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
generalCategory.addPreference(mShortcutPreference);
}
@Override
protected void updateShortcutPreference() {
final int shortcutTypes = getUserShortcutTypes(getPrefContext(), UserShortcutType.SOFTWARE);
final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(),
MAGNIFICATION_CONTROLLER_NAME, UserShortcutType.SOFTWARE);
mShortcutPreference.setChecked(
hasMagnificationValuesInSettings(getPrefContext(), shortcutTypes));
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
@VisibleForTesting
void saveNonEmptyUserShortcutType(int type) {
if (type == UserShortcutType.EMPTY) {
return;
}
final PreferredShortcut shortcut = new PreferredShortcut(
MAGNIFICATION_CONTROLLER_NAME, type);
PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
}
@VisibleForTesting
static void optInAllMagnificationValuesToSettings(Context context, int shortcutTypes) {
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {

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 ToggleScreenMagnificationPreferenceFragmentForSetupWizard
extends ToggleScreenMagnificationPreferenceFragment {
@@ -32,14 +33,28 @@ public class ToggleScreenMagnificationPreferenceFragmentForSetupWizard
// Log the final choice in value if it's different from the previous value.
Bundle args = getArguments();
if ((args != null) && args.containsKey(AccessibilitySettings.EXTRA_CHECKED)) {
if (mToggleServiceDividerSwitchPreference.isChecked() != args.getBoolean(
if (mToggleServiceSwitchPreference.isChecked() != args.getBoolean(
AccessibilitySettings.EXTRA_CHECKED)) {
// TODO: Distinguish between magnification modes
mMetricsFeatureProvider.action(getContext(),
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION,
mToggleServiceDividerSwitchPreference.isChecked());
mToggleServiceSwitchPreference.isChecked());
}
}
super.onStop();
}
@Override
public int getHelpResource() {
// Hides help center in action bar and footer bar in SuW
return 0;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Hide the setting from the vision settings.
mSettingsPreference.setVisible(false);
}
}

View File

@@ -28,7 +28,7 @@ public class ToggleScreenReaderPreferenceFragmentForSetupWizard
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mToggleSwitchWasInitiallyChecked = mToggleServiceDividerSwitchPreference.isChecked();
mToggleSwitchWasInitiallyChecked = mToggleServiceSwitchPreference.isChecked();
}
@Override
@@ -39,10 +39,10 @@ public class ToggleScreenReaderPreferenceFragmentForSetupWizard
@Override
public void onStop() {
// Log the final choice in value if it's different from the previous value.
if (mToggleServiceDividerSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
if (mToggleServiceSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
mMetricsFeatureProvider.action(getContext(),
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SCREEN_READER,
mToggleServiceDividerSwitchPreference.isChecked());
mToggleServiceSwitchPreference.isChecked());
}
super.onStop();
}

View File

@@ -28,7 +28,7 @@ public class ToggleSelectToSpeakPreferenceFragmentForSetupWizard
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mToggleSwitchWasInitiallyChecked = mToggleServiceDividerSwitchPreference.isChecked();
mToggleSwitchWasInitiallyChecked = mToggleServiceSwitchPreference.isChecked();
}
@Override
@@ -39,10 +39,10 @@ public class ToggleSelectToSpeakPreferenceFragmentForSetupWizard
@Override
public void onStop() {
// Log the final choice in value if it's different from the previous value.
if (mToggleServiceDividerSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
if (mToggleServiceSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
mMetricsFeatureProvider.action(getContext(),
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SELECT_TO_SPEAK,
mToggleServiceDividerSwitchPreference.isChecked());
mToggleServiceSwitchPreference.isChecked());
}
super.onStop();

View File

@@ -25,6 +25,7 @@ import android.view.TextureView.SurfaceTextureListener;
import androidx.annotation.GuardedBy;
import androidx.annotation.RawRes;
import androidx.annotation.VisibleForTesting;
/**
* Plays the video by {@link MediaPlayer} on {@link TextureView}, calls {@link #create(Context, int,
@@ -32,19 +33,25 @@ import androidx.annotation.RawRes;
* is no longer used, call {@link #release()} so that MediaPlayer object can be released.
*/
public class VideoPlayer implements SurfaceTextureListener {
private final Context context;
private final Object mediaPlayerLock = new Object();
private final Context mContext;
private final Object mMediaPlayerLock = new Object();
// Media player object can't be used after it has been released, so it will be set to null. But
// VideoPlayer is asynchronized, media player object might be paused or resumed again before
// released media player is set to null. Therefore, lock mediaPlayer and mediaPlayerState by
// mediaPlayerLock keep their states consistent.
@VisibleForTesting
@GuardedBy("mediaPlayerLock")
private MediaPlayer mediaPlayer;
MediaPlayer mMediaPlayer;
@VisibleForTesting
@GuardedBy("mediaPlayerLock")
private State mediaPlayerState = State.NONE;
State mMediaPlayerState = State.NONE;
@RawRes
private final int videoRes;
private Surface animationSurface;
private final int mVideoRes;
@VisibleForTesting
Surface mAnimationSurface;
/**
@@ -58,54 +65,54 @@ public class VideoPlayer implements SurfaceTextureListener {
}
private VideoPlayer(Context context, @RawRes int videoRes, TextureView textureView) {
this.context = context;
this.videoRes = videoRes;
this.mContext = context;
this.mVideoRes = videoRes;
textureView.setSurfaceTextureListener(this);
}
public void pause() {
synchronized (mediaPlayerLock) {
if (mediaPlayerState == State.STARTED) {
mediaPlayerState = State.PAUSED;
mediaPlayer.pause();
synchronized (mMediaPlayerLock) {
if (mMediaPlayerState == State.STARTED) {
mMediaPlayerState = State.PAUSED;
mMediaPlayer.pause();
}
}
}
public void resume() {
synchronized (mediaPlayerLock) {
if (mediaPlayerState == State.PAUSED) {
mediaPlayer.start();
mediaPlayerState = State.STARTED;
synchronized (mMediaPlayerLock) {
if (mMediaPlayerState == State.PAUSED) {
mMediaPlayer.start();
mMediaPlayerState = State.STARTED;
}
}
}
/** Release media player when it's no longer needed. */
public void release() {
synchronized (mediaPlayerLock) {
if (mediaPlayerState != State.NONE && mediaPlayerState != State.END) {
mediaPlayerState = State.END;
mediaPlayer.release();
mediaPlayer = null;
synchronized (mMediaPlayerLock) {
if (mMediaPlayerState != State.NONE && mMediaPlayerState != State.END) {
mMediaPlayerState = State.END;
mMediaPlayer.release();
mMediaPlayer = null;
}
}
if (animationSurface != null) {
animationSurface.release();
animationSurface = null;
if (mAnimationSurface != null) {
mAnimationSurface.release();
mAnimationSurface = null;
}
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
animationSurface = new Surface(surface);
synchronized (mediaPlayerLock) {
mediaPlayer = MediaPlayer.create(context, videoRes);
mediaPlayerState = State.PREPARED;
mediaPlayer.setSurface(animationSurface);
mediaPlayer.setLooping(true);
mediaPlayer.start();
mediaPlayerState = State.STARTED;
mAnimationSurface = new Surface(surface);
synchronized (mMediaPlayerLock) {
mMediaPlayer = MediaPlayer.create(mContext, mVideoRes);
mMediaPlayerState = State.PREPARED;
mMediaPlayer.setSurface(mAnimationSurface);
mMediaPlayer.setLooping(true);
mMediaPlayer.start();
mMediaPlayerState = State.STARTED;
}
}

View File

@@ -24,8 +24,6 @@ import android.view.View;
import com.android.settings.R;
import com.google.common.collect.ImmutableSet;
/**
* Fragment that only allowed hardware {@link UserShortcutType} for shortcut to open.
*
@@ -67,10 +65,9 @@ public class VolumeShortcutToggleAccessibilityServicePreferenceFragment extends
}
private void setAllowedPreferredShortcutType(int type) {
final AccessibilityUserShortcutType shortcut = new AccessibilityUserShortcutType(
mComponentName.flattenToString(), type);
final String componentNameString = mComponentName.flattenToString();
final PreferredShortcut shortcut = new PreferredShortcut(componentNameString, type);
SharedPreferenceUtils.setUserShortcutType(getPrefContext(),
ImmutableSet.of(shortcut.flattenToString()));
PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
}
}

View File

@@ -29,7 +29,7 @@ public class VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mToggleSwitchWasInitiallyChecked = mToggleServiceDividerSwitchPreference.isChecked();
mToggleSwitchWasInitiallyChecked = mToggleServiceSwitchPreference.isChecked();
}
@Override
@@ -40,10 +40,10 @@ public class VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard
@Override
public void onStop() {
// Log the final choice in value if it's different from the previous value.
if (mToggleServiceDividerSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
if (mToggleServiceSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
mMetricsFeatureProvider.action(getContext(),
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SCREEN_READER,
mToggleServiceDividerSwitchPreference.isChecked());
mToggleServiceSwitchPreference.isChecked());
}
super.onStop();

View File

@@ -29,7 +29,7 @@ public class VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mToggleSwitchWasInitiallyChecked = mToggleServiceDividerSwitchPreference.isChecked();
mToggleSwitchWasInitiallyChecked = mToggleServiceSwitchPreference.isChecked();
}
@Override
@@ -40,10 +40,10 @@ public class VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard
@Override
public void onStop() {
// Log the final choice in value if it's different from the previous value.
if (mToggleServiceDividerSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
if (mToggleServiceSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
mMetricsFeatureProvider.action(getContext(),
SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SELECT_TO_SPEAK,
mToggleServiceDividerSwitchPreference.isChecked());
mToggleServiceSwitchPreference.isChecked());
}
super.onStop();

View File

@@ -0,0 +1,81 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility.rtt;
import android.content.Context;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* A util class checks some SIM card information and permissions.
*/
public abstract class TelecomUtil {
private static final String TAG = "TelecomUtil";
/** Get a list of phone accounts which are call capable. */
public static List<PhoneAccountHandle> getCallCapablePhoneAccounts(Context context) {
return Optional.ofNullable(getTelecomManager(context).getCallCapablePhoneAccounts())
.orElse(new ArrayList<>());
}
/** Returns a {@link TelecomManager} instance. */
public static TelecomManager getTelecomManager(Context context) {
return context.getApplicationContext().getSystemService(TelecomManager.class);
}
/** Returns a subscription id of the SIM. */
public static int getSubIdForPhoneAccountHandle(
Context context, PhoneAccountHandle phoneAccountHandle) {
Optional<SubscriptionInfo> info = getSubscriptionInfo(context, phoneAccountHandle);
return info.map(SubscriptionInfo::getSubscriptionId)
.orElse(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
}
/**
* @return the {@link SubscriptionInfo} of the SIM if {@code phoneAccountHandle} corresponds
* to a valid SIM. Absent otherwise.
*/
private static Optional<SubscriptionInfo> getSubscriptionInfo(
Context context, PhoneAccountHandle phoneAccountHandle) {
if (TextUtils.isEmpty(phoneAccountHandle.getId())) {
return Optional.empty();
}
SubscriptionManager subscriptionManager = context.getSystemService(
SubscriptionManager.class);
List<SubscriptionInfo> subscriptionInfos =
subscriptionManager.getActiveSubscriptionInfoList();
if (subscriptionInfos == null) {
return Optional.empty();
}
for (SubscriptionInfo info : subscriptionInfos) {
if (phoneAccountHandle.getId().startsWith(info.getIccId())) {
return Optional.of(info);
}
}
Log.d(TAG, "Failed to find SubscriptionInfo for phoneAccountHandle");
return Optional.empty();
}
}

View File

@@ -27,6 +27,9 @@ import android.os.UserManager;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.applications.autofill.PasswordsPreferenceController;
import com.android.settings.applications.defaultapps.DefaultAutofillPreferenceController;
import com.android.settings.applications.defaultapps.DefaultWorkAutofillPreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.search.BaseSearchIndexProvider;
@@ -67,15 +70,29 @@ public class AccountDashboardFragment extends DashboardFragment {
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
final String[] authorities = getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
return buildPreferenceControllers(context, this /* parent */, authorities);
public void onAttach(Context context) {
super.onAttach(context);
getSettingsLifecycle().addObserver(use(PasswordsPreferenceController.class));
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
SettingsPreferenceFragment parent, String[] authorities) {
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
buildAutofillPreferenceControllers(context, controllers);
final String[] authorities = getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
buildAccountPreferenceControllers(context, this /* parent */, authorities, controllers);
return controllers;
}
static void buildAutofillPreferenceControllers(
Context context, List<AbstractPreferenceController> controllers) {
controllers.add(new DefaultAutofillPreferenceController(context));
controllers.add(new DefaultWorkAutofillPreferenceController(context));
}
private static void buildAccountPreferenceControllers(
Context context, SettingsPreferenceFragment parent, String[] authorities,
List<AbstractPreferenceController> controllers) {
final AccountPreferenceController accountPrefController =
new AccountPreferenceController(context, parent, authorities,
ProfileSelectFragment.ProfileType.ALL);
@@ -86,7 +103,6 @@ public class AccountDashboardFragment extends DashboardFragment {
controllers.add(new AutoSyncDataPreferenceController(context, parent));
controllers.add(new AutoSyncPersonalDataPreferenceController(context, parent));
controllers.add(new AutoSyncWorkDataPreferenceController(context, parent));
return controllers;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
@@ -95,8 +111,11 @@ public class AccountDashboardFragment extends DashboardFragment {
@Override
public List<AbstractPreferenceController> createPreferenceControllers(
Context context) {
return buildPreferenceControllers(
context, null /* parent */, null /* authorities*/);
final List<AbstractPreferenceController> controllers = new ArrayList<>();
buildAccountPreferenceControllers(
context, null /* parent */, null /* authorities*/, controllers);
buildAutofillPreferenceControllers(context, controllers);
return controllers;
}
@Override

View File

@@ -18,11 +18,14 @@ package com.android.settings.accounts;
import static android.provider.Settings.EXTRA_AUTHORITIES;
import static com.android.settings.accounts.AccountDashboardFragment.buildAutofillPreferenceControllers;
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.applications.autofill.PasswordsPreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.users.AutoSyncDataPreferenceController;
@@ -60,15 +63,23 @@ public class AccountPersonalDashboardFragment extends DashboardFragment {
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
final String[] authorities = getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
return buildPreferenceControllers(context, this /* parent */, authorities);
public void onAttach(Context context) {
super.onAttach(context);
getSettingsLifecycle().addObserver(use(PasswordsPreferenceController.class));
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
SettingsPreferenceFragment parent, String[] authorities) {
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
buildAutofillPreferenceControllers(context, controllers);
final String[] authorities = getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
buildAccountPreferenceControllers(context, this /* parent */, authorities, controllers);
return controllers;
}
private static void buildAccountPreferenceControllers(
Context context, SettingsPreferenceFragment parent, String[] authorities,
List<AbstractPreferenceController> controllers) {
final AccountPreferenceController accountPrefController =
new AccountPreferenceController(context, parent, authorities,
ProfileSelectFragment.ProfileType.PERSONAL);
@@ -78,7 +89,6 @@ public class AccountPersonalDashboardFragment extends DashboardFragment {
controllers.add(accountPrefController);
controllers.add(new AutoSyncDataPreferenceController(context, parent));
controllers.add(new AutoSyncPersonalDataPreferenceController(context, parent));
return controllers;
}
// TODO: b/141601408. After featureFlag settings_work_profile is launched, unmark this
@@ -88,6 +98,7 @@ public class AccountPersonalDashboardFragment extends DashboardFragment {
// @Override
// public List<AbstractPreferenceController> createPreferenceControllers(
// Context context) {
// ..Add autofill here too..
// return buildPreferenceControllers(
// context, null /* parent */, null /* authorities*/);
// }

View File

@@ -75,7 +75,7 @@ public class AccountPreferenceController extends AbstractPreferenceController
private static final String TAG = "AccountPrefController";
private static final int ORDER_ACCOUNT_PROFILES = 1;
private static final int ORDER_ACCOUNT_PROFILES = 101;
private static final int ORDER_LAST = 1002;
private static final int ORDER_NEXT_TO_LAST = 1001;
private static final int ORDER_NEXT_TO_NEXT_TO_LAST = 1000;
@@ -323,11 +323,12 @@ public class AccountPreferenceController extends AbstractPreferenceController
mHelper.createAccessiblePreferenceCategory(
mFragment.getPreferenceManager().getContext());
preferenceGroup.setOrder(mAccountProfileOrder++);
preferenceGroup.setTitle(R.string.account_settings); // default title; may be modified below
if (isSingleProfile()) {
preferenceGroup.setTitle(context.getString(R.string.account_for_section_header,
BidiFormatter.getInstance().unicodeWrap(userInfo.name)));
preferenceGroup.setContentDescription(
mContext.getString(R.string.account_settings));
final String title = context.getString(R.string.account_for_section_header,
BidiFormatter.getInstance().unicodeWrap(userInfo.name));
preferenceGroup.setTitle(title);
preferenceGroup.setContentDescription(title);
} else if (userInfo.isManagedProfile()) {
if (mType == ProfileSelectFragment.ProfileType.ALL) {
preferenceGroup.setTitle(R.string.category_work);

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