Merge Android U (ab/10368041)

Bug: 291102124
Merged-In: I17a6c8a571b4a0b7d943dfd710cde0f18d03da39
Change-Id: I4ed5b2e4c6c59527bb544e8b6dff2b9d4cee9025
This commit is contained in:
Xin Li
2023-08-25 13:50:56 -07:00
2542 changed files with 197328 additions and 207148 deletions

View File

@@ -1,307 +0,0 @@
/*
* Copyright (C) 2014 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.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
import java.util.List;
public class EncryptionInterstitial extends SettingsActivity {
private static final String TAG = EncryptionInterstitial.class.getSimpleName();
protected static final String EXTRA_PASSWORD_QUALITY = "extra_password_quality";
protected static final String EXTRA_UNLOCK_METHOD_INTENT = "extra_unlock_method_intent";
public static final String EXTRA_REQUIRE_PASSWORD = "extra_require_password";
private static final int CHOOSE_LOCK_REQUEST = 100;
@Override
public Intent getIntent() {
Intent modIntent = new Intent(super.getIntent());
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, EncryptionInterstitialFragment.class.getName());
return modIntent;
}
@Override
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
final int new_resid = SetupWizardUtils.getTheme(this, getIntent());
super.onApplyThemeResource(theme, new_resid, first);
}
@Override
protected boolean isValidFragment(String fragmentName) {
return EncryptionInterstitialFragment.class.getName().equals(fragmentName);
}
public static Intent createStartIntent(Context ctx, int quality,
boolean requirePasswordDefault, Intent unlockMethodIntent) {
return new Intent(ctx, EncryptionInterstitial.class)
.putExtra(EXTRA_PASSWORD_QUALITY, quality)
.putExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, R.string.encryption_interstitial_header)
.putExtra(EXTRA_REQUIRE_PASSWORD, requirePasswordDefault)
.putExtra(EXTRA_UNLOCK_METHOD_INTENT, unlockMethodIntent);
}
@Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
findViewById(R.id.content_parent).setFitsSystemWindows(false);
}
public static class EncryptionInterstitialFragment extends InstrumentedFragment {
private boolean mPasswordRequired;
private Intent mUnlockMethodIntent;
private int mRequestedPasswordQuality;
@Override
public int getMetricsCategory() {
return SettingsEnums.ENCRYPTION;
}
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.encryption_interstitial, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final boolean forFingerprint = getActivity().getIntent().getBooleanExtra(
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);
final int msgId;
switch (mRequestedPasswordQuality) {
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
msgId = forFingerprint ?
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:
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
msgId = forFingerprint ?
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:
msgId = forFingerprint ?
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;
}
TextView message = (TextView) getActivity().findViewById(R.id.sud_layout_description);
message.setText(msgId);
setRequirePasswordState(getActivity().getIntent().getBooleanExtra(
EXTRA_REQUIRE_PASSWORD, true));
GlifLayout layout = (GlifLayout) view;
layout.setHeaderText(getActivity().getTitle());
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
mixin.setSecondaryButton(
new FooterButton.Builder(getContext())
.setText(R.string.encryption_interstitial_no)
.setListener(this::onNoButtonClicked)
.setButtonType(FooterButton.ButtonType.SKIP)
.setTheme(R.style.SudGlifButton_Secondary)
.build()
);
mixin.setPrimaryButton(
new FooterButton.Builder(getContext())
.setText(R.string.encryption_interstitial_yes)
.setListener(this::onYesButtonClicked)
.setButtonType(FooterButton.ButtonType.NEXT)
.setTheme(R.style.SudGlifButton_Primary)
.build()
);
}
protected void startLockIntent() {
if (mUnlockMethodIntent != null) {
mUnlockMethodIntent.putExtra(EXTRA_REQUIRE_PASSWORD, mPasswordRequired);
startActivityForResult(mUnlockMethodIntent, CHOOSE_LOCK_REQUEST);
} else {
Log.wtf(TAG, "no unlock intent to start");
finish();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CHOOSE_LOCK_REQUEST && resultCode != RESULT_CANCELED) {
getActivity().setResult(resultCode, data);
finish();
}
}
private void onYesButtonClicked(View view) {
final boolean accEn = AccessibilityManager.getInstance(getActivity()).isEnabled();
if (accEn && !mPasswordRequired) {
setRequirePasswordState(false); // clear the UI state
AccessibilityWarningDialogFragment.newInstance(mRequestedPasswordQuality)
.show(
getChildFragmentManager(),
AccessibilityWarningDialogFragment.TAG);
} else {
setRequirePasswordState(true);
startLockIntent();
}
}
private void onNoButtonClicked(View view) {
setRequirePasswordState(false);
startLockIntent();
}
private void setRequirePasswordState(boolean required) {
mPasswordRequired = required;
}
public void finish() {
Activity activity = getActivity();
if (activity == null) return;
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
} else {
activity.finish();
}
}
}
public static class AccessibilityWarningDialogFragment extends InstrumentedDialogFragment
implements DialogInterface.OnClickListener {
public static final String TAG = "AccessibilityWarningDialog";
public static AccessibilityWarningDialogFragment newInstance(int passwordQuality) {
AccessibilityWarningDialogFragment fragment = new AccessibilityWarningDialogFragment();
Bundle args = new Bundle(1);
args.putInt(EXTRA_PASSWORD_QUALITY, passwordQuality);
fragment.setArguments(args);
return fragment;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final int titleId;
final int messageId;
switch (getArguments().getInt(EXTRA_PASSWORD_QUALITY)) {
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
titleId = R.string.encrypt_talkback_dialog_require_pattern;
messageId = R.string.encrypt_talkback_dialog_message_pattern;
break;
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
titleId = R.string.encrypt_talkback_dialog_require_pin;
messageId = R.string.encrypt_talkback_dialog_message_pin;
break;
default:
titleId = R.string.encrypt_talkback_dialog_require_password;
messageId = R.string.encrypt_talkback_dialog_message_password;
break;
}
final Activity activity = getActivity();
List<AccessibilityServiceInfo> list =
AccessibilityManager.getInstance(activity)
.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
final CharSequence exampleAccessibility;
if (list.isEmpty()) {
// This should never happen. But we shouldn't crash
exampleAccessibility = "";
} else {
exampleAccessibility = list.get(0).getResolveInfo()
.loadLabel(activity.getPackageManager());
}
return new AlertDialog.Builder(activity)
.setTitle(titleId)
.setMessage(getString(messageId, exampleAccessibility))
.setCancelable(true)
.setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.cancel, this)
.create();
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_ENCRYPTION_INTERSTITIAL_ACCESSIBILITY;
}
@Override
public void onClick(DialogInterface dialog, int which) {
EncryptionInterstitialFragment fragment =
(EncryptionInterstitialFragment) getParentFragment();
if (fragment != null) {
if (which == DialogInterface.BUTTON_POSITIVE) {
fragment.setRequirePasswordState(true);
fragment.startLockIntent();
} else if (which == DialogInterface.BUTTON_NEGATIVE) {
fragment.setRequirePasswordState(false);
}
}
}
}
}

View File

@@ -57,6 +57,8 @@ import androidx.preference.SwitchPreference;
import com.android.settings.network.ProxySubscriptionManager;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.telephony.MobileNetworkUtils;
import com.android.settingslib.utils.StringUtil;
import java.util.ArrayList;
import java.util.List;
@@ -174,7 +176,9 @@ public class IccLockSettings extends SettingsPreferenceFragment
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Utils.isMonkeyRunning()) {
if (Utils.isMonkeyRunning() ||
!SubscriptionUtil.isSimHardwareVisible(getContext()) ||
MobileNetworkUtils.isMobileNetworkUserRestricted(getContext())) {
finish();
return;
}
@@ -476,7 +480,7 @@ public class IccLockSettings extends SettingsPreferenceFragment
mPin = preference.getText();
if (!reasonablePin(mPin)) {
// inject error message and display dialog again
mError = mRes.getString(R.string.sim_bad_pin);
mError = mRes.getString(R.string.sim_invalid_pin_hint);
showPinDialog();
return;
}
@@ -673,9 +677,8 @@ public class IccLockSettings extends SettingsPreferenceFragment
} else if (attemptsRemaining == 1) {
displayMessage = mRes.getString(R.string.wrong_pin_code_one, attemptsRemaining);
} else if (attemptsRemaining > 1) {
displayMessage = mRes
.getQuantityString(R.plurals.wrong_pin_code, attemptsRemaining,
attemptsRemaining);
displayMessage = StringUtil.getIcuPluralsString(getPrefContext(), attemptsRemaining,
R.string.wrong_pin_code);
} else {
displayMessage = mRes.getString(R.string.pin_failed);
}

View File

@@ -44,7 +44,6 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.sysprop.VoldProperties;
import android.telephony.euicc.EuiccManager;
import android.text.TextUtils;
import android.util.Log;
@@ -64,6 +63,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.settings.core.InstrumentedFragment;
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.RestrictedLockUtilsInternal;
@@ -309,12 +309,9 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis
* If the external storage is emulated, it will be erased with a factory
* reset at any rate. There is no need to have a separate option until
* we have a factory reset that only erases some directories and not
* others. Likewise, if it's non-removable storage, it could potentially have been
* encrypted, and will also need to be wiped.
* others.
*/
boolean isExtStorageEmulated = Environment.isExternalStorageEmulated();
if (isExtStorageEmulated
|| (!Environment.isExternalStorageRemovable() && isExtStorageEncrypted())) {
if (Environment.isExternalStorageEmulated()) {
mExternalStorageContainer.setVisibility(View.GONE);
final View externalOption = mContentView.findViewById(R.id.erase_external_option_text);
@@ -323,9 +320,7 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis
final View externalAlsoErased = mContentView.findViewById(R.id.also_erases_external);
externalAlsoErased.setVisibility(View.VISIBLE);
// If it's not emulated, it is on a separate partition but it means we're doing
// a force wipe due to encryption.
mExternalStorage.setChecked(!isExtStorageEmulated);
mExternalStorage.setChecked(false);
} else {
mExternalStorageContainer.setOnClickListener(new View.OnClickListener() {
@@ -381,6 +376,14 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis
mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
/**
* Whether to show any UI which is SIM related.
*/
@VisibleForTesting
boolean showAnySubscriptionInfo(Context context) {
return (context != null) && SubscriptionUtil.isSimHardwareVisible(context);
}
/**
* Whether to show strings indicating that the eUICC will be wiped.
*
@@ -390,7 +393,7 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis
@VisibleForTesting
boolean showWipeEuicc() {
Context context = getContext();
if (!isEuiccEnabled(context)) {
if (!showAnySubscriptionInfo(context) || !isEuiccEnabled(context)) {
return false;
}
ContentResolver cr = context.getContentResolver();
@@ -457,11 +460,6 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis
}
}
private boolean isExtStorageEncrypted() {
String state = VoldProperties.decrypt().orElse("");
return !"".equals(state);
}
private void loadAccountList(final UserManager um) {
View accountsLabel = mContentView.findViewById(R.id.accounts_label);
LinearLayout contents = (LinearLayout) mContentView.findViewById(R.id.accounts);

View File

@@ -268,8 +268,10 @@ public class MainClearConfirm extends InstrumentedFragment {
@VisibleForTesting
void setSubtitle() {
if (mEraseEsims) {
((TextView) mContentView.findViewById(R.id.sud_layout_description))
.setText(R.string.main_clear_final_desc_esim);
TextView confirmationMessage = mContentView.findViewById(R.id.sud_layout_description);
if (confirmationMessage != null) {
confirmationMessage.setText(R.string.main_clear_final_desc_esim);
}
}
}

View File

@@ -33,6 +33,7 @@ import android.provider.Settings;
import androidx.appcompat.app.AlertDialog;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.utils.StringUtil;
import java.util.HashMap;
import java.util.Locale;
@@ -65,16 +66,17 @@ public class MonitoringCertInfoActivity extends Activity implements OnClickListe
final int numberOfCertificates = getIntent().getIntExtra(
Settings.EXTRA_NUMBER_OF_CERTIFICATES, 1);
final int titleId = RestrictedLockUtils.getProfileOrDeviceOwner(this, user) != null
? R.plurals.ssl_ca_cert_settings_button // Check certificate
: R.plurals.ssl_ca_cert_dialog_title; // Trust or remove certificate
final CharSequence title = getResources().getQuantityText(titleId, numberOfCertificates);
? R.string.ssl_ca_cert_settings_button // Check certificate
: R.string.ssl_ca_cert_dialog_title; // Trust or remove certificate
final CharSequence title = StringUtil.getIcuPluralsString(this, numberOfCertificates,
titleId);
setTitle(title);
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(title);
builder.setCancelable(true);
builder.setPositiveButton(getResources().getQuantityText(
R.plurals.ssl_ca_cert_settings_button, numberOfCertificates) , this);
builder.setPositiveButton(StringUtil.getIcuPluralsString(this, numberOfCertificates,
R.string.ssl_ca_cert_settings_button) , this);
builder.setNeutralButton(R.string.cancel, null);
builder.setOnDismissListener(this);

View File

@@ -24,6 +24,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.hardware.input.InputManager;
import android.hardware.input.InputSettings;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
@@ -74,16 +75,16 @@ public class PointerSpeedPreference extends SeekBarDialogPreference implements
super.onBindDialogView(view);
mSeekBar = getSeekBar(view);
mSeekBar.setMax(InputManager.MAX_POINTER_SPEED - InputManager.MIN_POINTER_SPEED);
mOldSpeed = mIm.getPointerSpeed(getContext());
mSeekBar.setProgress(mOldSpeed - InputManager.MIN_POINTER_SPEED);
mSeekBar.setMax(InputSettings.MAX_POINTER_SPEED - InputSettings.MIN_POINTER_SPEED);
mOldSpeed = InputSettings.getPointerSpeed(getContext());
mSeekBar.setProgress(mOldSpeed - InputSettings.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);
mIm.tryPointerSpeed(progress + InputSettings.MIN_POINTER_SPEED);
}
if (progress != mLastProgress) {
seekBar.performHapticFeedback(CLOCK_TICK);
@@ -100,13 +101,13 @@ public class PointerSpeedPreference extends SeekBarDialogPreference implements
public void onStopTrackingTouch(SeekBar seekBar) {
mTouchInProgress = false;
mIm.tryPointerSpeed(seekBar.getProgress() + InputManager.MIN_POINTER_SPEED);
mIm.tryPointerSpeed(seekBar.getProgress() + InputSettings.MIN_POINTER_SPEED);
mJankMonitor.end(CUJ_SETTINGS_SLIDER);
}
private void onSpeedChanged() {
int speed = mIm.getPointerSpeed(getContext());
mSeekBar.setProgress(speed - InputManager.MIN_POINTER_SPEED);
int speed = InputSettings.getPointerSpeed(getContext());
mSeekBar.setProgress(speed - InputSettings.MIN_POINTER_SPEED);
}
@Override
@@ -116,8 +117,8 @@ public class PointerSpeedPreference extends SeekBarDialogPreference implements
final ContentResolver resolver = getContext().getContentResolver();
if (positiveResult) {
mIm.setPointerSpeed(getContext(),
mSeekBar.getProgress() + InputManager.MIN_POINTER_SPEED);
InputSettings.setPointerSpeed(getContext(),
mSeekBar.getProgress() + InputSettings.MIN_POINTER_SPEED);
} else {
restoreOldState();
}
@@ -158,7 +159,7 @@ public class PointerSpeedPreference extends SeekBarDialogPreference implements
SavedState myState = (SavedState) state;
super.onRestoreInstanceState(myState.getSuperState());
mOldSpeed = myState.oldSpeed;
mIm.tryPointerSpeed(myState.progress + InputManager.MIN_POINTER_SPEED);
mIm.tryPointerSpeed(myState.progress + InputSettings.MIN_POINTER_SPEED);
}
private static class SavedState extends BaseSavedState {

View File

@@ -85,7 +85,7 @@ public class ResetNetwork extends InstrumentedFragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.reset_network_title);
getActivity().setTitle(R.string.reset_mobile_network_settings_title);
mActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
@@ -103,7 +103,7 @@ public class ResetNetwork extends InstrumentedFragment {
final ChooseLockSettingsHelper.Builder builder =
new ChooseLockSettingsHelper.Builder(getActivity(), this);
return builder.setRequestCode(request)
.setTitle(res.getText(R.string.reset_network_title))
.setTitle(res.getText(R.string.reset_mobile_network_settings_title))
.setActivityResultLauncher(mActivityResultLauncher)
.show();
}
@@ -124,10 +124,7 @@ public class ResetNetwork extends InstrumentedFragment {
ResetNetworkRequest request = new ResetNetworkRequest(
ResetNetworkRequest.RESET_CONNECTIVITY_MANAGER |
ResetNetworkRequest.RESET_VPN_MANAGER |
ResetNetworkRequest.RESET_WIFI_MANAGER |
ResetNetworkRequest.RESET_WIFI_P2P_MANAGER |
ResetNetworkRequest.RESET_BLUETOOTH_MANAGER
ResetNetworkRequest.RESET_VPN_MANAGER
);
if (mSubscriptions != null && mSubscriptions.size() > 0) {
int selectedIndex = mSubscriptionSpinner.getSelectedItemPosition();
@@ -146,7 +143,7 @@ public class ResetNetwork extends InstrumentedFragment {
new SubSettingLauncher(getContext())
.setDestination(ResetNetworkConfirm.class.getName())
.setArguments(args)
.setTitleRes(R.string.reset_network_confirm_title)
.setTitleRes(R.string.reset_mobile_network_settings_confirm_title)
.setSourceMetricsCategory(getMetricsCategory())
.launch();
}
@@ -251,6 +248,9 @@ public class ResetNetwork extends InstrumentedFragment {
}
private List<SubscriptionInfo> getActiveSubscriptionInfoList() {
if (!SubscriptionUtil.isSimHardwareVisible(getActivity())) {
return Collections.emptyList();
}
SubscriptionManager mgr = getActivity().getSystemService(SubscriptionManager.class);
if (mgr == null) {
Log.w(TAG, "No SubscriptionManager");
@@ -280,6 +280,9 @@ public class ResetNetwork extends InstrumentedFragment {
}
private boolean showEuiccSettings(Context context) {
if (!SubscriptionUtil.isSimHardwareVisible(context)) {
return false;
}
EuiccManager euiccManager =
(EuiccManager) context.getSystemService(Context.EUICC_SERVICE);
if (!euiccManager.isEnabled()) {
@@ -299,7 +302,7 @@ public class ResetNetwork extends InstrumentedFragment {
return view;
}
mContentView = inflater.inflate(R.layout.reset_network, null);
mContentView = inflater.inflate(R.layout.reset_mobile_network_settings, null);
establishInitialState(getActiveSubscriptionInfoList());
return mContentView;

View File

@@ -61,6 +61,7 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
@VisibleForTesting ResetNetworkRequest mResetNetworkRequest;
private ProgressDialog mProgressDialog;
private AlertDialog mAlertDialog;
@VisibleForTesting ResetSubscriptionContract mResetSubscriptionContract;
private OnSubscriptionsChangedListener mSubscriptionsChangedListener;
/**
@@ -130,16 +131,11 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
}
// abandon execution if subscription no longer active
int subId = mResetNetworkRequest.getResetApnSubId();
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
SubscriptionManager mgr = getSubscriptionManager();
// always remove listener
stopMonitorSubscriptionChange(mgr);
if (!isSubscriptionRemainActive(mgr, subId)) {
Log.w(TAG, "subId " + subId + " disappear when confirm");
mActivity.finish();
return;
}
Integer subId = mResetSubscriptionContract.getAnyMissingSubscriptionId();
if (subId != null) {
Log.w(TAG, "subId " + subId + " no longer active");
getActivity().onBackPressed();
return;
}
// Should dismiss the progress dialog firstly if it is showing
@@ -186,7 +182,7 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
Bundle savedInstanceState) {
View view = (new ResetNetworkRestrictionViewBuilder(mActivity)).build();
if (view != null) {
stopMonitorSubscriptionChange(getSubscriptionManager());
mResetSubscriptionContract.close();
Log.w(TAG, "Access deny.");
return view;
}
@@ -208,13 +204,15 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
mActivity = getActivity();
if (mResetNetworkRequest.getResetApnSubId()
== ResetNetworkRequest.INVALID_SUBSCRIPTION_ID) {
return;
}
// close confirmation dialog when reset specific subscription
// but removed priori to the confirmation button been pressed
startMonitorSubscriptionChange(getSubscriptionManager());
mResetSubscriptionContract = new ResetSubscriptionContract(getContext(),
mResetNetworkRequest) {
@Override
public void onSubscriptionInactive(int subscriptionId) {
// close UI if subscription no longer active
Log.w(TAG, "subId " + subscriptionId + " no longer active.");
getActivity().onBackPressed();
}
};
}
@Override
@@ -223,63 +221,22 @@ public class ResetNetworkConfirm extends InstrumentedFragment {
mResetNetworkRequest.writeIntoBundle(outState);
}
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() {
int subId = mResetNetworkRequest.getResetApnSubId();
SubscriptionManager mgr = getSubscriptionManager();
if (isSubscriptionRemainActive(mgr, subId)) {
return;
}
// close UI if subscription no longer active
Log.w(TAG, "subId " + subId + " 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
public void onDestroy() {
if (mResetNetworkTask != null) {
mResetNetworkTask.cancel(true /* mayInterruptIfRunning */);
mResetNetworkTask = null;
}
if (mResetSubscriptionContract != null) {
mResetSubscriptionContract.close();
mResetSubscriptionContract = null;
}
if (mProgressDialog != null) {
mProgressDialog.dismiss();
}
if (mAlertDialog != null) {
mAlertDialog.dismiss();
}
stopMonitorSubscriptionChange(getSubscriptionManager());
super.onDestroy();
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright (C) 2022 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.Context;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.IntStream;
/**
* A Class monitoring the availability of subscription IDs provided within reset request.
*
* This is to detect the situation when user changing SIM card during the presenting of
* confirmation UI.
*/
public class ResetSubscriptionContract implements AutoCloseable {
private static final String TAG = "ResetSubscriptionContract";
private final Context mContext;
private ExecutorService mExecutorService;
private final int [] mResetSubscriptionIds;
@VisibleForTesting
protected OnSubscriptionsChangedListener mSubscriptionsChangedListener;
private AtomicBoolean mSubscriptionsUpdateNotify = new AtomicBoolean();
/**
* Constructor
* @param context Context
* @param resetRequest the request object for perform network reset operation.
*/
public ResetSubscriptionContract(Context context, ResetNetworkRequest resetRequest) {
mContext = context;
// Only keeps specific subscription ID required to perform reset operation
IntStream subIdStream = IntStream.of(
resetRequest.getResetTelephonyAndNetworkPolicyManager()
, resetRequest.getResetApnSubId());
mResetSubscriptionIds = subIdStream.sorted().distinct()
.filter(id -> SubscriptionManager.isUsableSubscriptionId(id))
.toArray();
if (mResetSubscriptionIds.length <= 0) {
return;
}
// Monitoring callback through background thread
mExecutorService = Executors.newSingleThreadExecutor();
startMonitorSubscriptionChange();
}
/**
* A method for detecting if there's any subscription under monitor no longer active.
* @return subscription ID which is no longer active.
*/
public Integer getAnyMissingSubscriptionId() {
if (mResetSubscriptionIds.length <= 0) {
return null;
}
SubscriptionManager mgr = getSubscriptionManager();
if (mgr == null) {
Log.w(TAG, "Fail to access subscription manager");
return mResetSubscriptionIds[0];
}
for (int idx = 0; idx < mResetSubscriptionIds.length; idx++) {
int subId = mResetSubscriptionIds[idx];
if (mgr.getActiveSubscriptionInfo(subId) == null) {
Log.w(TAG, "SubId " + subId + " no longer active.");
return subId;
}
}
return null;
}
/**
* Async callback when detecting if there's any subscription under monitor no longer active.
* @param subscriptionId subscription ID which is no longer active.
*/
public void onSubscriptionInactive(int subscriptionId) {}
@VisibleForTesting
protected SubscriptionManager getSubscriptionManager() {
return mContext.getSystemService(SubscriptionManager.class);
}
@VisibleForTesting
protected OnSubscriptionsChangedListener getChangeListener() {
return new OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
/**
* Reducing the processing time on main UI thread through a flag.
* Once flag get into false, which means latest callback has been
* processed.
*/
mSubscriptionsUpdateNotify.set(true);
// Back to main UI thread
mContext.getMainExecutor().execute(() -> {
// Remove notifications and perform checking.
if (mSubscriptionsUpdateNotify.getAndSet(false)) {
Integer subId = getAnyMissingSubscriptionId();
if (subId != null) {
onSubscriptionInactive(subId);
}
}
});
}
};
}
private void startMonitorSubscriptionChange() {
SubscriptionManager mgr = getSubscriptionManager();
if (mgr == null) {
return;
}
// update monitor listener
mSubscriptionsChangedListener = getChangeListener();
mgr.addOnSubscriptionsChangedListener(
mExecutorService, mSubscriptionsChangedListener);
}
// Implementation of AutoCloseable
public void close() {
if (mExecutorService == null) {
return;
}
// Stop monitoring subscription change
SubscriptionManager mgr = getSubscriptionManager();
if (mgr != null) {
mgr.removeOnSubscriptionsChangedListener(mSubscriptionsChangedListener);
}
// Release Executor
mExecutorService.shutdownNow();
mExecutorService = null;
}
}

View File

@@ -29,6 +29,7 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.biometrics.face.FaceSettings;
import com.android.settings.communal.CommunalPreferenceController;
import com.android.settings.core.FeatureFlags;
import com.android.settings.enterprise.EnterprisePrivacySettings;
import com.android.settings.network.MobileNetworkIntentConverter;
@@ -46,12 +47,31 @@ public class Settings extends SettingsActivity {
/*
* Settings subclasses for launching independently.
*/
public static class MemtagPageActivity extends SettingsActivity { /* empty */}
public static class AssistGestureSettingsActivity extends SettingsActivity { /* empty */}
public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }
public static class CreateShortcutActivity extends SettingsActivity { /* empty */ }
public static class FaceSettingsActivity extends SettingsActivity { /* empty */ }
public static class FaceSettingsActivity extends SettingsActivity {
@Override
protected void onCreate(Bundle savedState) {
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
setTheme(R.style.SettingsPreferenceTheme_SetupWizard);
ThemeHelper.trySetDynamicColor(this);
super.onCreate(savedState);
}
}
/** Container for {@link FaceSettings} to use with a pre-defined task affinity. */
public static class FaceSettingsInternalActivity extends SettingsActivity { /* empty */ }
public static class FaceSettingsInternalActivity extends SettingsActivity {
@Override
protected void onCreate(Bundle savedState) {
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
setTheme(R.style.SettingsPreferenceTheme_SetupWizard);
ThemeHelper.trySetDynamicColor(this);
super.onCreate(savedState);
}
}
public static class FingerprintSettingsActivity extends SettingsActivity { /* empty */ }
public static class CombinedBiometricSettingsActivity extends SettingsActivity { /* empty */ }
public static class CombinedBiometricProfileSettingsActivity extends SettingsActivity { /* empty */ }
@@ -111,6 +131,12 @@ public class Settings extends SettingsActivity {
public static class SpellCheckersSettingsActivity extends SettingsActivity { /* empty */ }
public static class LocalePickerActivity extends SettingsActivity { /* empty */ }
public static class LanguageAndInputSettingsActivity extends SettingsActivity { /* empty */ }
public static class LanguageSettingsActivity extends SettingsActivity { /* empty */ }
/** Activity for the regional preferences settings. */
public static class RegionalPreferencesActivity extends SettingsActivity { /* empty */ }
public static class KeyboardSettingsActivity extends SettingsActivity { /* empty */ }
/** Activity for the navigation mode settings. */
public static class NavigationModeSettingsActivity extends SettingsActivity { /* empty */ }
public static class UserDictionarySettingsActivity extends SettingsActivity { /* empty */ }
public static class DarkThemeSettingsActivity extends SettingsActivity { /* empty */ }
public static class DisplaySettingsActivity extends SettingsActivity { /* empty */ }
@@ -138,6 +164,10 @@ public class Settings extends SettingsActivity {
public static class BlueToothPairingActivity extends SettingsActivity { /* empty */ }
/** Activity for Reduce Bright Colors. */
public static class ReduceBrightColorsSettingsActivity extends SettingsActivity { /* empty */ }
/** Activity for text reading settings. */
public static class TextReadingSettingsActivity extends SettingsActivity { /* empty */ }
/** Activity for text color and motion settings. */
public static class ColorAndMotionActivity extends SettingsActivity { /* empty */ }
/** Activity for the security dashboard. */
public static class SecurityDashboardActivity extends SettingsActivity {
@@ -198,7 +228,35 @@ public class Settings extends SettingsActivity {
}
}
/** Activity for the Advanced security settings. */
public static class SecurityAdvancedSettings extends SettingsActivity { /* empty */ }
public static class SecurityAdvancedSettings extends SettingsActivity {
private static final String TAG = "SecurityAdvancedActivity";
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
handleMoreSettingsRedirection();
}
/** Redirects to More Settings if Safety center is enabled. */
@VisibleForTesting
public void handleMoreSettingsRedirection() {
if (isFinishing()) {
// Don't trampoline if already exiting this activity.
return;
}
if (SafetyCenterManagerWrapper.get().isEnabled(this)) {
try {
startActivity(
new Intent("com.android.settings.MORE_SECURITY_PRIVACY_SETTINGS"));
finish();
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Unable to open More Settings", e);
}
}
}
}
/** Activity for the More settings page. */
public static class MoreSecurityPrivacySettingsActivity extends SettingsActivity { /* empty */ }
public static class UsageAccessSettingsActivity extends SettingsActivity { /* empty */ }
public static class AppUsageAccessSettingsActivity extends SettingsActivity { /* empty */ }
public static class LocationSettingsActivity extends SettingsActivity { /* empty */ }
@@ -276,6 +334,16 @@ public class Settings extends SettingsActivity {
public static class AndroidBeamSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiDisplaySettingsActivity extends SettingsActivity { /* empty */ }
public static class DreamSettingsActivity extends SettingsActivity { /* empty */ }
/** Activity to manage communal settings */
public static class CommunalSettingsActivity extends SettingsActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!CommunalPreferenceController.isAvailable(this)) {
finish();
}
}
}
public static class NotificationStationActivity extends SettingsActivity { /* empty */ }
public static class UserSettingsActivity extends SettingsActivity { /* empty */ }
public static class NotificationAccessSettingsActivity extends SettingsActivity { /* empty */ }
@@ -284,6 +352,7 @@ public class Settings extends SettingsActivity {
public static class PremiumSmsAccessActivity extends SettingsActivity { /* empty */ }
public static class PictureInPictureSettingsActivity extends SettingsActivity { /* empty */ }
public static class TurnScreenOnSettingsActivity extends SettingsActivity { /* empty */ }
public static class AppTurnScreenOnSettingsActivity extends SettingsActivity { /* empty */ }
public static class AppPictureInPictureSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenAccessSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenAccessDetailSettingsActivity extends SettingsActivity {}
@@ -306,6 +375,8 @@ public class Settings extends SettingsActivity {
public static class AppBubbleNotificationSettingsActivity extends SettingsActivity { /* empty */ }
public static class NotificationAssistantSettingsActivity extends SettingsActivity{ /* empty */ }
public static class NotificationAppListActivity extends SettingsActivity { /* empty */ }
/** Activity to manage Cloned Apps page */
public static class ClonedAppsListActivity extends SettingsActivity { /* empty */ }
public static class NotificationReviewPermissionsActivity extends SettingsActivity { /* empty */ }
public static class AppNotificationSettingsActivity extends SettingsActivity { /* empty */ }
public static class ChannelNotificationSettingsActivity extends SettingsActivity { /* empty */ }
@@ -334,6 +405,8 @@ public class Settings extends SettingsActivity {
public static class AppMediaManagementAppsActivity extends SettingsActivity { /* empty */ }
public static class WriteSettingsActivity extends SettingsActivity { /* empty */ }
public static class ChangeWifiStateActivity extends SettingsActivity { /* empty */ }
/** Activity to manage NFC Tag applications. */
public static class ChangeNfcTagAppsActivity extends SettingsActivity { /* empty */ }
public static class AppDrawOverlaySettingsActivity extends SettingsActivity { /* empty */ }
public static class AppWriteSettingsActivity extends SettingsActivity { /* empty */ }
/** Activity to manage app battery usage details. */
@@ -371,7 +444,9 @@ public class Settings extends SettingsActivity {
}
public static class WebViewAppPickerActivity extends SettingsActivity { /* empty */ }
public static class AdvancedConnectedDeviceActivity extends SettingsActivity { /* empty */ }
public static class NfcSettingsActivity extends SettingsActivity { /* empty */ }
public static class BluetoothDeviceDetailActivity extends SettingsActivity { /* empty */ }
public static class StylusUsiDetailsActivity extends SettingsActivity { /* empty */ }
public static class BluetoothBroadcastActivity extends SettingsActivity { /* empty */ }
public static class BluetoothFindBroadcastsActivity extends SettingsActivity { /* empty */ }
public static class WifiCallingDisclaimerActivity extends SettingsActivity { /* empty */ }
@@ -419,6 +494,11 @@ public class Settings extends SettingsActivity {
}
}
/** Actviity to manage apps with {@link android.Manifest.permission#RUN_USER_INITIATED_JOBS} */
public static class LongBackgroundTasksActivity extends SettingsActivity { /* empty */ }
/** App specific version of {@link LongBackgroundTasksActivity} */
public static class LongBackgroundTasksAppActivity extends SettingsActivity { /* empty */ }
/**
* Activity for BugReportHandlerPicker.
*/

View File

@@ -24,6 +24,7 @@ import static com.android.settings.applications.appinfo.AppButtonsPreferenceCont
import android.app.ActionBar;
import android.app.ActivityManager;
import android.app.settings.SettingsEnums;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -230,11 +231,32 @@ public class SettingsActivity extends SettingsBaseActivity
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
if (name.equals(getPackageName() + "_preferences")) {
return new SharedPreferencesLogger(this, getMetricsTag(),
FeatureFactory.getFactory(this).getMetricsFeatureProvider());
if (!TextUtils.equals(name, getPackageName() + "_preferences")) {
return super.getSharedPreferences(name, mode);
}
return super.getSharedPreferences(name, mode);
String tag = getMetricsTag();
return new SharedPreferencesLogger(this, tag,
FeatureFactory.getFactory(this).getMetricsFeatureProvider(),
lookupMetricsCategory());
}
private int lookupMetricsCategory() {
int category = SettingsEnums.PAGE_UNKNOWN;
Bundle args = null;
if (getIntent() != null) {
args = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
}
Fragment fragment = Utils.getTargetFragment(this, getMetricsTag(), args);
if (fragment instanceof Instrumentable) {
category = ((Instrumentable) fragment).getMetricsCategory();
}
Log.d(LOG_TAG, "MetricsCategory is " + category);
return category;
}
private String getMetricsTag() {
@@ -242,13 +264,11 @@ public class SettingsActivity extends SettingsBaseActivity
if (getIntent() != null && getIntent().hasExtra(EXTRA_SHOW_FRAGMENT)) {
tag = getInitialFragmentName(getIntent());
}
if (TextUtils.isEmpty(tag)) {
Log.w(LOG_TAG, "MetricsTag is invalid " + tag);
tag = getClass().getName();
}
if (tag.startsWith("com.android.settings.")) {
tag = tag.replace("com.android.settings.", "");
}
return tag;
}
@@ -320,7 +340,7 @@ public class SettingsActivity extends SettingsBaseActivity
}
mMainSwitch = findViewById(R.id.switch_bar);
if (mMainSwitch != null) {
mMainSwitch.setMetricsTag(getMetricsTag());
mMainSwitch.setMetricsCategory(lookupMetricsCategory());
mMainSwitch.setTranslationZ(findViewById(R.id.main_content).getTranslationZ() + 1);
}
@@ -446,7 +466,8 @@ public class SettingsActivity extends SettingsBaseActivity
if (userInfo.isManagedProfile()) {
trampolineIntent.setClass(this, DeepLinkHomepageActivityInternal.class)
.putExtra(EXTRA_USER_HANDLE, getUser());
startActivityAsUser(trampolineIntent, um.getPrimaryUser().getUserHandle());
startActivityAsUser(trampolineIntent,
um.getProfileParent(userInfo.id).getUserHandle());
} else {
startActivity(trampolineIntent);
}
@@ -552,6 +573,11 @@ public class SettingsActivity extends SettingsBaseActivity
@VisibleForTesting
void launchSettingFragment(String initialFragmentName, Intent intent) {
if (initialFragmentName != null) {
if (SettingsActivityUtil.launchSpaActivity(this, initialFragmentName, intent)) {
finish();
return;
}
setTitleFromIntent(intent);
Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2022 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.Context
import android.content.Intent
import android.util.FeatureFlagUtils
import com.android.settings.applications.appinfo.AlarmsAndRemindersDetails
import com.android.settings.applications.appinfo.DrawOverlayDetails
import com.android.settings.applications.appinfo.ExternalSourcesDetails
import com.android.settings.applications.appinfo.ManageExternalStorageDetails
import com.android.settings.applications.appinfo.MediaManagementAppsDetails
import com.android.settings.applications.appinfo.WriteSettingsDetails
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureDetails
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureSettings
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
import com.android.settings.spa.SpaActivity.Companion.startSpaActivityForApp
import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider
import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider
import com.android.settings.spa.app.specialaccess.MediaManagementAppsAppListProvider
import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider
import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider
import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
import com.android.settings.wifi.ChangeWifiStateDetails
object SettingsActivityUtil {
private val FRAGMENT_TO_SPA_DESTINATION_MAP = mapOf(
PictureInPictureSettings::class.qualifiedName to
PictureInPictureListProvider.getAppListRoute(),
)
private val FRAGMENT_TO_SPA_APP_DESTINATION_PREFIX_MAP = mapOf(
PictureInPictureDetails::class.qualifiedName to
PictureInPictureListProvider.getAppInfoRoutePrefix(),
DrawOverlayDetails::class.qualifiedName to
DisplayOverOtherAppsAppListProvider.getAppInfoRoutePrefix(),
WriteSettingsDetails::class.qualifiedName to
ModifySystemSettingsAppListProvider.getAppInfoRoutePrefix(),
AlarmsAndRemindersDetails::class.qualifiedName to
AlarmsAndRemindersAppListProvider.getAppInfoRoutePrefix(),
ExternalSourcesDetails::class.qualifiedName to
InstallUnknownAppsListProvider.getAppInfoRoutePrefix(),
ManageExternalStorageDetails::class.qualifiedName to
AllFilesAccessAppListProvider.getAppInfoRoutePrefix(),
MediaManagementAppsDetails::class.qualifiedName to
MediaManagementAppsAppListProvider.getAppInfoRoutePrefix(),
ChangeWifiStateDetails::class.qualifiedName to
WifiControlAppListProvider.getAppInfoRoutePrefix(),
)
@JvmStatic
fun Context.launchSpaActivity(fragmentName: String, intent: Intent): Boolean {
if (!FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_ENABLE_SPA)) {
return false
}
FRAGMENT_TO_SPA_DESTINATION_MAP[fragmentName]?.let { destination ->
startSpaActivity(destination)
return true
}
FRAGMENT_TO_SPA_APP_DESTINATION_PREFIX_MAP[fragmentName]?.let { appDestinationPrefix ->
startSpaActivityForApp(appDestinationPrefix, intent)
return true
}
return false
}
}

View File

@@ -17,10 +17,20 @@
package com.android.settings;
import android.app.Application;
import android.database.ContentObserver;
import android.net.Uri;
import android.provider.Settings;
import android.util.FeatureFlagUtils;
import com.android.settings.activityembedding.ActivityEmbeddingRulesController;
import com.android.settings.activityembedding.ActivityEmbeddingUtils;
import com.android.settings.core.instrumentation.ElapsedTimeUtils;
import com.android.settings.homepage.SettingsHomepageActivity;
import com.android.settings.spa.SettingsSpaEnvironment;
import com.android.settingslib.applications.AppIconCacheManager;
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory;
import com.google.android.setupcompat.util.WizardManagerHelper;
import java.lang.ref.WeakReference;
@@ -33,9 +43,31 @@ public class SettingsApplication extends Application {
public void onCreate() {
super.onCreate();
final ActivityEmbeddingRulesController controller =
new ActivityEmbeddingRulesController(this);
controller.initRules();
// Add null checking to avoid test case failed.
if (getApplicationContext() != null) {
ElapsedTimeUtils.assignSuwFinishedTimeStamp(getApplicationContext());
}
// Set Spa environment.
setSpaEnvironment();
if (ActivityEmbeddingUtils.isSettingsSplitEnabled(this)
&& FeatureFlagUtils.isEnabled(this,
FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN)) {
if (WizardManagerHelper.isUserSetupComplete(this)) {
new ActivityEmbeddingRulesController(this).initRules();
} else {
new DeviceProvisionedObserver().registerContentObserver();
}
}
}
/**
* Set the spa environment instance.
* Override this function to set different spa environment for different Settings app.
*/
protected void setSpaEnvironment() {
SpaEnvironmentFactory.INSTANCE.reset(new SettingsSpaEnvironment(this));
}
public void setHomeActivity(SettingsHomepageActivity homeActivity) {
@@ -51,4 +83,30 @@ public class SettingsApplication extends Application {
super.onTrimMemory(level);
AppIconCacheManager.getInstance().trimMemory(level);
}
private class DeviceProvisionedObserver extends ContentObserver {
private final Uri mDeviceProvisionedUri = Settings.Secure.getUriFor(
Settings.Secure.USER_SETUP_COMPLETE);
DeviceProvisionedObserver() {
super(null /* handler */);
}
@Override
public void onChange(boolean selfChange, Uri uri, int flags) {
if (!mDeviceProvisionedUri.equals(uri)) {
return;
}
SettingsApplication.this.getContentResolver().unregisterContentObserver(this);
new ActivityEmbeddingRulesController(SettingsApplication.this).initRules();
}
public void registerContentObserver() {
SettingsApplication.this.getContentResolver().registerContentObserver(
mDeviceProvisionedUri,
false /* notifyForDescendants */,
this);
}
}
}

View File

@@ -31,12 +31,15 @@ import android.os.storage.VolumeInfo;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.IndentingPrintWriter;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.settings.applications.ProcStatsData;
import com.android.settings.datausage.lib.DataUsageLib;
import com.android.settings.fuelgauge.batterytip.AnomalyConfigJobService;
import com.android.settings.network.MobileNetworkRepository;
import com.android.settingslib.net.DataUsageController;
import org.json.JSONArray;
@@ -48,6 +51,10 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
public class SettingsDumpService extends Service {
public static final String EXTRA_KEY_SHOW_NETWORK_DUMP = "show_network_dump";
private static final String TAG = "SettingsDumpService";
@VisibleForTesting
static final String KEY_SERVICE = "service";
@VisibleForTesting
@@ -64,6 +71,16 @@ public class SettingsDumpService extends Service {
static final Intent BROWSER_INTENT =
new Intent("android.intent.action.VIEW", Uri.parse("http://"));
private boolean mShouldShowNetworkDump = false;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
mShouldShowNetworkDump = intent.getBooleanExtra(EXTRA_KEY_SHOW_NETWORK_DUMP, false);
}
return Service.START_REDELIVER_INTENT;
}
@Override
public IBinder onBind(Intent intent) {
return null;
@@ -71,20 +88,27 @@ public class SettingsDumpService extends Service {
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
JSONObject dump = new JSONObject();
try {
dump.put(KEY_SERVICE, "Settings State");
dump.put(KEY_STORAGE, dumpStorage());
dump.put(KEY_DATAUSAGE, dumpDataUsage());
dump.put(KEY_MEMORY, dumpMemory());
dump.put(KEY_DEFAULT_BROWSER_APP, dumpDefaultBrowser());
dump.put(KEY_ANOMALY_DETECTION, dumpAnomalyDetection());
} catch (Exception e) {
e.printStackTrace();
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
if (!mShouldShowNetworkDump) {
JSONObject dump = new JSONObject();
pw.println(TAG + ": ");
pw.increaseIndent();
try {
dump.put(KEY_SERVICE, "Settings State");
dump.put(KEY_STORAGE, dumpStorage());
dump.put(KEY_DATAUSAGE, dumpDataUsage());
dump.put(KEY_MEMORY, dumpMemory());
dump.put(KEY_DEFAULT_BROWSER_APP, dumpDefaultBrowser());
dump.put(KEY_ANOMALY_DETECTION, dumpAnomalyDetection());
} catch (Exception e) {
Log.w(TAG, "exception in dump: ", e);
}
pw.println(dump);
pw.flush();
pw.decreaseIndent();
} else {
dumpMobileNetworkSettings(pw);
}
writer.println(dump);
}
private JSONObject dumpMemory() throws JSONException {
@@ -186,4 +210,8 @@ public class SettingsDumpService extends Service {
return obj;
}
private void dumpMobileNetworkSettings(IndentingPrintWriter writer) {
MobileNetworkRepository.getInstance(this).dump(writer);
}
}

View File

@@ -37,9 +37,9 @@ import android.os.UserManager;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.window.embedding.SplitController;
import com.android.settings.Settings.CreateShortcutActivity;
import com.android.settings.activityembedding.ActivityEmbeddingUtils;
import com.android.settings.homepage.DeepLinkHomepageActivity;
import com.android.settings.search.SearchStateReceiver;
import com.android.settingslib.utils.ThreadUtils;
@@ -66,6 +66,7 @@ public class SettingsInitialize extends BroadcastReceiver {
UserInfo userInfo = um.getUserInfo(UserHandle.myUserId());
final PackageManager pm = context.getPackageManager();
managedProfileSetup(context, pm, broadcast, userInfo);
cloneProfileSetup(context, pm, userInfo);
webviewSettingSetup(context, pm, userInfo);
ThreadUtils.postOnBackgroundThread(() -> refreshExistingShortcuts(context));
enableTwoPaneDeepLinkActivityIfNecessary(pm, context);
@@ -104,13 +105,24 @@ public class SettingsInitialize extends BroadcastReceiver {
}
// Disable launcher icon
ComponentName settingsComponentName = new ComponentName(context, Settings.class);
pm.setComponentEnabledSetting(settingsComponentName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
disableComponent(pm, new ComponentName(context, Settings.class));
// Disable shortcut picker.
ComponentName shortcutComponentName = new ComponentName(
context, CreateShortcutActivity.class);
pm.setComponentEnabledSetting(shortcutComponentName,
disableComponent(pm, new ComponentName(context, CreateShortcutActivity.class));
}
private void cloneProfileSetup(Context context, PackageManager pm, UserInfo userInfo) {
if (userInfo == null || !userInfo.isCloneProfile()) {
return;
}
// Disable launcher icon
disableComponent(pm, new ComponentName(context, Settings.class));
//Disable Shortcut picker
disableComponent(pm, new ComponentName(context, CreateShortcutActivity.class));
}
private void disableComponent(PackageManager pm, ComponentName componentName) {
pm.setComponentEnabledSetting(componentName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
}
@@ -154,7 +166,7 @@ public class SettingsInitialize extends BroadcastReceiver {
DeepLinkHomepageActivity.class);
final ComponentName searchStateReceiver = new ComponentName(context,
SearchStateReceiver.class);
final int enableState = SplitController.getInstance(context).isSplitSupported()
final int enableState = ActivityEmbeddingUtils.isSettingsSplitEnabled(context)
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
pm.setComponentEnabledSetting(deepLinkHome, enableState, PackageManager.DONT_KILL_APP);

View File

@@ -1,56 +0,0 @@
/*
* Copyright (C) 2014 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.Context;
import android.content.Intent;
/**
* Setup Wizard's version of EncryptionInterstitial screen. It inherits the logic and basic
* structure from EncryptionInterstitial class, and should remain similar to that behaviorally. This
* class should only overload base methods for minor theme and behavior differences specific to
* Setup Wizard. Other changes should be done to EncryptionInterstitial class instead and let this
* class inherit those changes.
*/
public class SetupEncryptionInterstitial extends EncryptionInterstitial {
public static Intent createStartIntent(Context ctx, int quality,
boolean requirePasswordDefault, Intent unlockMethodIntent) {
Intent startIntent = EncryptionInterstitial.createStartIntent(ctx, quality,
requirePasswordDefault, unlockMethodIntent);
startIntent.setClass(ctx, SetupEncryptionInterstitial.class);
startIntent.putExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)
.putExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, -1);
return startIntent;
}
@Override
public Intent getIntent() {
Intent modIntent = new Intent(super.getIntent());
modIntent.putExtra(EXTRA_SHOW_FRAGMENT,
SetupEncryptionInterstitialFragment.class.getName());
return modIntent;
}
@Override
protected boolean isValidFragment(String fragmentName) {
return SetupEncryptionInterstitialFragment.class.getName().equals(fragmentName);
}
public static class SetupEncryptionInterstitialFragment extends EncryptionInterstitialFragment {
}
}

View File

@@ -17,27 +17,41 @@
package com.android.settings;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.os.UserManager;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
import com.android.settings.network.telephony.MobileNetworkUtils;
public class TestingSettings extends SettingsPreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.testing_settings);
final UserManager um = UserManager.get(getContext());
if (!um.isAdminUser()) {
if (!isRadioInfoVisible(getContext())) {
PreferenceScreen preferenceScreen = (PreferenceScreen)
findPreference("radio_info_settings");
getPreferenceScreen().removePreference(preferenceScreen);
}
}
@VisibleForTesting
protected boolean isRadioInfoVisible(Context context) {
UserManager um = context.getSystemService(UserManager.class);
if (um != null) {
if (!um.isAdminUser()) {
return false;
}
}
return !MobileNetworkUtils.isMobileNetworkUserRestricted(context);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.TESTING;

View File

@@ -309,6 +309,8 @@ class TrustedCredentialsDialogBuilder extends AlertDialog.Builder {
LinearLayout certLayout = new LinearLayout(mActivity);
certLayout.setOrientation(LinearLayout.VERTICAL);
// Prevent content overlapping with spinner
certLayout.setClipChildren(true);
certLayout.addView(spinner);
for (int i = 0; i < views.size(); ++i) {
View certificateView = views.get(i);

View File

@@ -25,7 +25,6 @@ import android.annotation.UiThread;
import android.app.Activity;
import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -68,7 +67,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.UnlaunchableAppActivity;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.TrustedCredentialsSettings.Tab;
import com.android.settings.core.InstrumentedFragment;
import com.android.settingslib.core.lifecycle.ObservableFragment;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
@@ -81,7 +80,7 @@ import java.util.function.IntConsumer;
/**
* Fragment to display trusted credentials settings for one tab.
*/
public class TrustedCredentialsFragment extends InstrumentedFragment
public class TrustedCredentialsFragment extends ObservableFragment
implements TrustedCredentialsDialogBuilder.DelegateInterface {
public static final String ARG_POSITION = "tab";
@@ -176,11 +175,6 @@ public class TrustedCredentialsFragment extends InstrumentedFragment
return mFragmentView;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.TRUSTED_CREDENTIALS;
}
private void createChildView(
LayoutInflater inflater, ViewGroup parent, Bundle childState, int i) {
boolean isWork = mGroupAdapter.getUserInfoByGroup(i).isManagedProfile();

View File

@@ -1,255 +0,0 @@
/**
* Copyright (C) 2007 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.Activity;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* Activity to display package usage statistics.
*/
public class UsageStatsActivity extends Activity implements OnItemSelectedListener {
private static final String TAG = "UsageStatsActivity";
private static final boolean localLOGV = false;
private UsageStatsManager mUsageStatsManager;
private LayoutInflater mInflater;
private UsageStatsAdapter mAdapter;
private PackageManager mPm;
public static class AppNameComparator implements Comparator<UsageStats> {
private Map<String, String> mAppLabelList;
AppNameComparator(Map<String, String> appList) {
mAppLabelList = appList;
}
@Override
public final int compare(UsageStats a, UsageStats b) {
String alabel = mAppLabelList.get(a.getPackageName());
String blabel = mAppLabelList.get(b.getPackageName());
return alabel.compareTo(blabel);
}
}
public static class LastTimeUsedComparator implements Comparator<UsageStats> {
@Override
public final int compare(UsageStats a, UsageStats b) {
// return by descending order
return Long.compare(b.getLastTimeUsed(), a.getLastTimeUsed());
}
}
public static class UsageTimeComparator implements Comparator<UsageStats> {
@Override
public final int compare(UsageStats a, UsageStats b) {
return Long.compare(b.getTotalTimeInForeground(), a.getTotalTimeInForeground());
}
}
// View Holder used when displaying views
static class AppViewHolder {
TextView pkgName;
TextView lastTimeUsed;
TextView usageTime;
}
class UsageStatsAdapter extends BaseAdapter {
// Constants defining order for display order
private static final int _DISPLAY_ORDER_USAGE_TIME = 0;
private static final int _DISPLAY_ORDER_LAST_TIME_USED = 1;
private static final int _DISPLAY_ORDER_APP_NAME = 2;
private int mDisplayOrder = _DISPLAY_ORDER_USAGE_TIME;
private LastTimeUsedComparator mLastTimeUsedComparator = new LastTimeUsedComparator();
private UsageTimeComparator mUsageTimeComparator = new UsageTimeComparator();
private AppNameComparator mAppLabelComparator;
private final ArrayMap<String, String> mAppLabelMap = new ArrayMap<>();
private final ArrayList<UsageStats> mPackageStats = new ArrayList<>();
UsageStatsAdapter() {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_YEAR, -5);
final List<UsageStats> stats =
mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST,
cal.getTimeInMillis(), System.currentTimeMillis());
if (stats == null) {
return;
}
ArrayMap<String, UsageStats> map = new ArrayMap<>();
final int statCount = stats.size();
for (int i = 0; i < statCount; i++) {
final android.app.usage.UsageStats pkgStats = stats.get(i);
// load application labels for each application
try {
ApplicationInfo appInfo = mPm.getApplicationInfo(pkgStats.getPackageName(), 0);
String label = appInfo.loadLabel(mPm).toString();
mAppLabelMap.put(pkgStats.getPackageName(), label);
UsageStats existingStats =
map.get(pkgStats.getPackageName());
if (existingStats == null) {
map.put(pkgStats.getPackageName(), pkgStats);
} else {
existingStats.add(pkgStats);
}
} catch (NameNotFoundException e) {
// This package may be gone.
}
}
mPackageStats.addAll(map.values());
// Sort list
mAppLabelComparator = new AppNameComparator(mAppLabelMap);
sortList();
}
@Override
public int getCount() {
return mPackageStats.size();
}
@Override
public Object getItem(int position) {
return mPackageStats.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unneccessary calls
// to findViewById() on each row.
AppViewHolder holder;
// When convertView is not null, we can reuse it directly, there is no need
// to reinflate it. We only inflate a new View when the convertView supplied
// by ListView is null.
if (convertView == null) {
convertView = mInflater.inflate(R.layout.usage_stats_item, null);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new AppViewHolder();
holder.pkgName = (TextView) convertView.findViewById(R.id.package_name);
holder.lastTimeUsed = (TextView) convertView.findViewById(R.id.last_time_used);
holder.usageTime = (TextView) convertView.findViewById(R.id.usage_time);
convertView.setTag(holder);
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder = (AppViewHolder) convertView.getTag();
}
// Bind the data efficiently with the holder
UsageStats pkgStats = mPackageStats.get(position);
if (pkgStats != null) {
String label = mAppLabelMap.get(pkgStats.getPackageName());
holder.pkgName.setText(label);
holder.lastTimeUsed.setText(DateUtils.formatSameDayTime(pkgStats.getLastTimeUsed(),
System.currentTimeMillis(), DateFormat.MEDIUM, DateFormat.MEDIUM));
holder.usageTime.setText(
DateUtils.formatElapsedTime(pkgStats.getTotalTimeInForeground() / 1000));
} else {
Log.w(TAG, "No usage stats info for package:" + position);
}
return convertView;
}
void sortList(int sortOrder) {
if (mDisplayOrder == sortOrder) {
// do nothing
return;
}
mDisplayOrder= sortOrder;
sortList();
}
private void sortList() {
if (mDisplayOrder == _DISPLAY_ORDER_USAGE_TIME) {
if (localLOGV) Log.i(TAG, "Sorting by usage time");
Collections.sort(mPackageStats, mUsageTimeComparator);
} else if (mDisplayOrder == _DISPLAY_ORDER_LAST_TIME_USED) {
if (localLOGV) Log.i(TAG, "Sorting by last time used");
Collections.sort(mPackageStats, mLastTimeUsedComparator);
} else if (mDisplayOrder == _DISPLAY_ORDER_APP_NAME) {
if (localLOGV) Log.i(TAG, "Sorting by application name");
Collections.sort(mPackageStats, mAppLabelComparator);
}
notifyDataSetChanged();
}
}
/** Called when the activity is first created. */
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.usage_stats);
mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mPm = getPackageManager();
Spinner typeSpinner = (Spinner) findViewById(R.id.typeSpinner);
typeSpinner.setOnItemSelectedListener(this);
ListView listView = (ListView) findViewById(R.id.pkg_list);
mAdapter = new UsageStatsAdapter();
listView.setAdapter(mAdapter);
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mAdapter.sortList(position);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// do nothing
}
}

View File

@@ -43,13 +43,14 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.wifi.helper.SavedWifiHelper;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedLockUtilsInternal;
@@ -75,6 +76,9 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment
private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
@VisibleForTesting
protected SavedWifiHelper mSavedWifiHelper;
@Override
public int getMetricsCategory() {
return SettingsEnums.USER_CREDENTIALS;
@@ -89,15 +93,23 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment
@Override
public void onClick(final View view) {
final Credential item = (Credential) view.getTag();
if (item != null) {
CredentialDialogFragment.show(this, item);
if (item == null) return;
if (item.isInUse()) {
item.setUsedByNames(mSavedWifiHelper.getCertificateNetworkNames(item.alias));
}
showCredentialDialogFragment(item);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.user_credentials);
mSavedWifiHelper = SavedWifiHelper.getInstance(getContext(), getSettingsLifecycle());
}
@VisibleForTesting
protected void showCredentialDialogFragment(Credential item) {
CredentialDialogFragment.show(this, item);
}
protected void announceRemoval(String alias) {
@@ -113,7 +125,9 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment
}
}
public static class CredentialDialogFragment extends InstrumentedDialogFragment {
/** The fragment to show the credential information. */
public static class CredentialDialogFragment extends InstrumentedDialogFragment
implements DialogInterface.OnShowListener {
private static final String TAG = "CredentialDialogFragment";
private static final String ARG_CREDENTIAL = "credential";
@@ -163,17 +177,23 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment
dialog.dismiss();
}
};
// TODO: b/127865361
// a safe means of clearing wifi certificates. Configs refer to aliases
// directly so deleting certs will break dependent access points.
// However, Wi-Fi used to remove this certificate from storage if the network
// was removed, regardless if it is used in more than one network.
// It has been decided to allow removing certificates from this menu, as we
// assume that the user who manually adds certificates must have a way to
// manually remove them.
builder.setNegativeButton(R.string.trusted_credentials_remove_label, listener);
}
return builder.create();
AlertDialog dialog = builder.create();
dialog.setOnShowListener(this);
return dialog;
}
/**
* Override for the negative button enablement on demand.
*/
@Override
public void onShow(DialogInterface dialogInterface) {
final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
if (item.isInUse()) {
((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEGATIVE)
.setEnabled(false);
}
}
@Override
@@ -297,11 +317,13 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment
private SortedMap<String, Credential> getCredentialsForUid(KeyStore keyStore, int uid) {
try {
final SortedMap<String, Credential> aliasMap = new TreeMap<>();
boolean isSystem = UserHandle.getAppId(uid) == Process.SYSTEM_UID;
Enumeration<String> aliases = keyStore.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
Credential c = new Credential(alias, uid);
if (!c.isSystem()) {
c.setInUse(mSavedWifiHelper.isCertificateInUse(alias));
}
Key key = null;
try {
key = keyStore.getKey(alias, null);
@@ -315,19 +337,6 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment
// We don't display any symmetric key entries.
continue;
}
if (isSystem) {
// Do not show work profile keys in user credentials
if (alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT) ||
alias.startsWith(LockPatternUtils.PROFILE_KEY_NAME_DECRYPT)) {
continue;
}
// Do not show synthetic password keys in user credential
// We should never reach this point because the synthetic password key
// is symmetric.
if (alias.startsWith(LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX)) {
continue;
}
}
// At this point we have determined that we have an asymmetric key.
// so we have at least a USER_KEY and USER_CERTIFICATE.
c.storedTypes.add(Credential.Type.USER_KEY);
@@ -438,12 +447,13 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment
}
((TextView) view.findViewById(R.id.alias)).setText(item.alias);
((TextView) view.findViewById(R.id.purpose)).setText(item.isSystem()
? R.string.credential_for_vpn_and_apps
: R.string.credential_for_wifi);
updatePurposeView(view.findViewById(R.id.purpose), item);
view.findViewById(R.id.contents).setVisibility(expanded ? View.VISIBLE : View.GONE);
if (expanded) {
updateUsedByViews(view.findViewById(R.id.credential_being_used_by_title),
view.findViewById(R.id.credential_being_used_by_content), item);
for (int i = 0; i < credentialViewTypes.size(); i++) {
final View detail = view.findViewById(credentialViewTypes.keyAt(i));
detail.setVisibility(item.storedTypes.contains(credentialViewTypes.valueAt(i))
@@ -453,6 +463,30 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment
return view;
}
@VisibleForTesting
protected static void updatePurposeView(TextView purpose, Credential item) {
int subTextResId = R.string.credential_for_vpn_and_apps;
if (!item.isSystem()) {
subTextResId = (item.isInUse())
? R.string.credential_for_wifi_in_use
: R.string.credential_for_wifi;
}
purpose.setText(subTextResId);
}
@VisibleForTesting
protected static void updateUsedByViews(TextView title, TextView content, Credential item) {
List<String> usedByNames = item.getUsedByNames();
if (usedByNames.size() > 0) {
title.setVisibility(View.VISIBLE);
content.setText(String.join("\n", usedByNames));
content.setVisibility(View.VISIBLE);
} else {
title.setVisibility(View.GONE);
content.setVisibility(View.GONE);
}
}
static class AliasEntry {
public String alias;
public int uid;
@@ -483,6 +517,16 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment
*/
final int uid;
/**
* Indicate whether or not this credential is in use.
*/
boolean mIsInUse;
/**
* The list of networks which use this credential.
*/
List<String> mUsedByNames = new ArrayList<>();
/**
* Should contain some non-empty subset of:
* <ul>
@@ -539,10 +583,28 @@ public class UserCredentialsSettings extends SettingsPreferenceFragment
return UserHandle.getAppId(uid) == Process.SYSTEM_UID;
}
public String getAlias() { return alias; }
public String getAlias() {
return alias;
}
public EnumSet<Type> getStoredTypes() {
return storedTypes;
}
public void setInUse(boolean inUse) {
mIsInUse = inUse;
}
public boolean isInUse() {
return mIsInUse;
}
public void setUsedByNames(List<String> names) {
mUsedByNames = new ArrayList<>(names);
}
public List<String> getUsedByNames() {
return new ArrayList<String>(mUsedByNames);
}
}
}

View File

@@ -21,7 +21,6 @@ import static android.content.Intent.EXTRA_USER_ID;
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
import android.annotation.Nullable;
import android.app.ActionBar;
import android.app.Activity;
import android.app.ActivityManager;
@@ -42,6 +41,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.pm.UserProperties;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -96,7 +96,9 @@ import android.widget.TabWidget;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
@@ -117,6 +119,7 @@ import com.android.settingslib.widget.AdaptiveIcon;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public final class Utils extends com.android.settingslib.Utils {
@@ -164,6 +167,18 @@ public final class Utils extends com.android.settingslib.Utils {
public static final String PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS =
"app_hibernation_targets_pre_s_apps";
/**
* Whether or not Cloned Apps menu is available in Apps page. Default is false.
*/
public static final String PROPERTY_CLONED_APPS_ENABLED = "cloned_apps_enabled";
/**
* Whether or not Delete All App Clones sub-menu is available in the Cloned Apps page.
* Default is false.
*/
public static final String PROPERTY_DELETE_ALL_APP_CLONES_ENABLED =
"delete_all_app_clones_enabled";
/**
* Finds a matching activity for a preference's intent. If a matching
* activity is not found, it will remove the preference.
@@ -436,18 +451,20 @@ public final class Utils extends com.android.settingslib.Utils {
* {@link #getManagedProfile} this method returns enabled and disabled managed profiles.
*/
public static UserHandle getManagedProfileWithDisabled(UserManager userManager) {
// TODO: Call getManagedProfileId from here once Robolectric supports
// API level 24 and UserManager.getProfileIdsWithDisabled can be Mocked (to avoid having
// yet another implementation that loops over user profiles in this method). In the meantime
// we need to use UserManager.getProfiles that is available on API 23 (the one currently
// used for Settings Robolectric tests).
final int myUserId = UserHandle.myUserId();
final List<UserInfo> profiles = userManager.getProfiles(myUserId);
return getManagedProfileWithDisabled(userManager, UserHandle.myUserId());
}
/**
* Returns the managed profile of the given user or {@code null} if none is found. Unlike
* {@link #getManagedProfile} this method returns enabled and disabled managed profiles.
*/
private static UserHandle getManagedProfileWithDisabled(UserManager um, int parentUserId) {
final List<UserInfo> profiles = um.getProfiles(parentUserId);
final int count = profiles.size();
for (int i = 0; i < count; i++) {
final UserInfo profile = profiles.get(i);
if (profile.isManagedProfile()
&& profile.getUserHandle().getIdentifier() != myUserId) {
&& profile.getUserHandle().getIdentifier() != parentUserId) {
return profile.getUserHandle();
}
}
@@ -456,15 +473,14 @@ public final class Utils extends com.android.settingslib.Utils {
/**
* Retrieves the id for the given user's managed profile.
* Unlike {@link #getManagedProfile} this method returns enabled and disabled managed profiles.
*
* @return the managed profile id or UserHandle.USER_NULL if there is none.
*/
public static int getManagedProfileId(UserManager um, int parentUserId) {
final int[] profileIds = um.getProfileIdsWithDisabled(parentUserId);
for (int profileId : profileIds) {
if (profileId != parentUserId) {
return profileId;
}
final UserHandle profile = getManagedProfileWithDisabled(um, parentUserId);
if (profile != null) {
return profile.getIdentifier();
}
return UserHandle.USER_NULL;
}
@@ -590,7 +606,9 @@ public final class Utils extends com.android.settingslib.Utils {
return inflater.inflate(resId, parent, false);
}
public static ArraySet<String> getHandledDomains(PackageManager pm, String packageName) {
/** Gets all the domains that the given package could handled. */
@NonNull
public static Set<String> getHandledDomains(PackageManager pm, String packageName) {
final List<IntentFilterVerificationInfo> iviList =
pm.getIntentFilterVerifications(packageName);
final List<IntentFilter> filters = pm.getAllIntentFilters(packageName);
@@ -598,9 +616,7 @@ public final class Utils extends com.android.settingslib.Utils {
final ArraySet<String> result = new ArraySet<>();
if (iviList != null && iviList.size() > 0) {
for (IntentFilterVerificationInfo ivi : iviList) {
for (String host : ivi.getDomains()) {
result.add(host);
}
result.addAll(ivi.getDomains());
}
}
if (filters != null && filters.size() > 0) {
@@ -691,23 +707,26 @@ public final class Utils extends com.android.settingslib.Utils {
&& 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);
return allowAnyUser ? userId : checkUserOwnsFrpCredential(context, userId);
} else {
return allowAnyUser ? userId : enforceSameOwner(context, userId);
}
}
/**
* Returns the given user id if the current user is the system user.
* Returns the given user id if the current user owns frp credential.
*
* @throws SecurityException if the current user is not the system user.
* @throws SecurityException if the current user do not own the frp credential.
*/
public static int enforceSystemUser(Context context, int userId) {
if (UserHandle.myUserId() == UserHandle.USER_SYSTEM) {
@VisibleForTesting
static int checkUserOwnsFrpCredential(Context context, int userId) {
final UserManager um = context.getSystemService(UserManager.class);
if (LockPatternUtils.userOwnsFrpCredential(context,
um.getUserInfo(UserHandle.myUserId()))) {
return userId;
}
throw new SecurityException("Given user id " + userId + " must only be used from "
+ "USER_SYSTEM, but current user is " + UserHandle.myUserId());
throw new SecurityException("Current user id " + UserHandle.myUserId()
+ " does not own frp credential.");
}
/**
@@ -800,7 +819,9 @@ public final class Utils extends com.android.settingslib.Utils {
}
}
public static CharSequence getApplicationLabel(Context context, String packageName) {
/** Gets the application label of the given package name. */
@Nullable
public static CharSequence getApplicationLabel(Context context, @NonNull String packageName) {
try {
final ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
packageName,
@@ -967,17 +988,6 @@ public final class Utils extends com.android.settingslib.Utils {
return false;
}
/**
* Return the resource id to represent the install status for an app
*/
@StringRes
public static int getInstallationStatus(ApplicationInfo info) {
if ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
return R.string.not_installed;
}
return info.enabled ? R.string.installed : R.string.disabled;
}
private static boolean isVolumeValid(VolumeInfo volume) {
return (volume != null) && (volume.getType() == VolumeInfo.TYPE_PRIVATE)
&& volume.isMountedReadable();
@@ -1161,7 +1171,7 @@ public final class Utils extends com.android.settingslib.Utils {
final boolean isWork = args != null ? args.getInt(ProfileSelectFragment.EXTRA_PROFILE)
== ProfileSelectFragment.ProfileType.WORK : false;
try {
if (activity.getSystemService(UserManager.class).getUserProfiles().size() > 1
if (isNewTabNeeded(activity)
&& ProfileFragmentBridge.FRAGMENT_MAP.get(fragmentName) != null
&& !isWork && !isPersonal) {
f = Fragment.instantiate(activity,
@@ -1175,6 +1185,24 @@ public final class Utils extends com.android.settingslib.Utils {
return f;
}
/**
* Checks if a new tab is needed or not for any user profile associated with the context user.
*
* <p> Checks if any user has the property {@link UserProperties#SHOW_IN_SETTINGS_SEPARATE} set.
*/
public static boolean isNewTabNeeded(Activity activity) {
UserManager userManager = activity.getSystemService(UserManager.class);
List<UserHandle> profiles = userManager.getUserProfiles();
for (UserHandle userHandle : profiles) {
UserProperties userProperties = userManager.getUserProperties(userHandle);
if (userProperties.getShowInSettings()
== UserProperties.SHOW_IN_SETTINGS_SEPARATE) {
return true;
}
}
return false;
}
/**
* Returns true if current binder uid is Settings Intelligence.
*/
@@ -1232,21 +1260,39 @@ public final class Utils extends com.android.settingslib.Utils {
return context.getColor(R.color.accent_select_primary_text);
}
/**
* Returns user id of clone profile if present, else returns -1.
*/
public static int getCloneUserId(Context context) {
UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
for (UserHandle userHandle : userManager.getUserProfiles()) {
if (userManager.getUserInfo(userHandle.getIdentifier()).isCloneProfile()) {
return userHandle.getIdentifier();
}
}
return -1;
}
/**
* Returns if the current user is able to use Dreams.
*/
public static boolean canCurrentUserDream(Context context) {
final UserHandle mainUser = context.getSystemService(UserManager.class).getMainUser();
if (mainUser == null) {
return false;
}
return context.createContextAsUser(mainUser, 0).getSystemService(UserManager.class)
.isUserForeground();
}
/**
* Returns if dreams are available to the current user.
*/
public static boolean areDreamsAvailableToCurrentUser(Context context) {
if (!context.getResources().getBoolean(
com.android.internal.R.bool.config_dreamsSupported)) {
return false;
}
if (!context.getResources().getBoolean(
com.android.internal.R.bool.config_dreamsOnlyEnabledForDockUser)) {
return true;
}
final UserManager userManager = context.getSystemService(UserManager.class);
return userManager != null && userManager.isSystemUser();
final boolean dreamsSupported = context.getResources().getBoolean(
com.android.internal.R.bool.config_dreamsSupported);
final boolean dreamsOnlyEnabledForDockUser = context.getResources().getBoolean(
com.android.internal.R.bool.config_dreamsOnlyEnabledForDockUser);
return dreamsSupported && (!dreamsOnlyEnabledForDockUser || canCurrentUserDream(context));
}
}

View File

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

View File

@@ -19,21 +19,30 @@ package com.android.settings.accessibility;
import android.content.Context;
import android.provider.Settings;
import androidx.annotation.IntDef;
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;
import java.util.Optional;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** Preference controller that controls the button or gesture in accessibility button page. */
public class AccessibilityButtonGesturePreferenceController extends BasePreferenceController
implements Preference.OnPreferenceChangeListener {
private Optional<Integer> mDefaultGesture = Optional.empty();
@Retention(RetentionPolicy.SOURCE)
@IntDef({
Mode.BUTTON,
Mode.GESTURE,
})
private @interface Mode {
int BUTTON = 1;
int GESTURE = 2;
}
public AccessibilityButtonGesturePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
@@ -47,12 +56,9 @@ public class AccessibilityButtonGesturePreferenceController extends BasePreferen
@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);
putCurrentAccessibilityButtonMode(value);
}
return true;
}
@@ -62,21 +68,17 @@ public class AccessibilityButtonGesturePreferenceController extends BasePreferen
super.updateState(preference);
final ListPreference listPreference = (ListPreference) preference;
listPreference.setValue(getCurrentAccessibilityButtonMode());
listPreference.setValue(String.valueOf(getCurrentAccessibilityButtonMode()));
}
private String getCurrentAccessibilityButtonMode() {
final int mode = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, getDefaultGestureValue());
return String.valueOf(mode);
@Mode
private int getCurrentAccessibilityButtonMode() {
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, Mode.BUTTON);
}
private int getDefaultGestureValue() {
if (!mDefaultGesture.isPresent()) {
final String[] valuesList = mContext.getResources().getStringArray(
R.array.accessibility_button_gesture_selector_values);
mDefaultGesture = Optional.of(Integer.parseInt(valuesList[0]));
}
return mDefaultGesture.get();
private void putCurrentAccessibilityButtonMode(@Mode int mode) {
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, mode);
}
}

View File

@@ -19,21 +19,30 @@ package com.android.settings.accessibility;
import android.content.Context;
import android.provider.Settings;
import androidx.annotation.IntDef;
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;
import java.util.Optional;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** Preference controller that controls the preferred location in accessibility button page. */
public class AccessibilityButtonLocationPreferenceController extends BasePreferenceController
implements Preference.OnPreferenceChangeListener {
private Optional<Integer> mDefaultLocation = Optional.empty();
@Retention(RetentionPolicy.SOURCE)
@IntDef({
Location.FLOATING_MENU,
Location.NAVIGATION_BAR,
})
private @interface Location {
int FLOATING_MENU = 1;
int NAVIGATION_BAR = 0;
}
public AccessibilityButtonLocationPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
@@ -47,12 +56,9 @@ public class AccessibilityButtonLocationPreferenceController extends BasePrefere
@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);
putCurrentAccessibilityButtonMode(value);
}
return true;
}
@@ -62,21 +68,17 @@ public class AccessibilityButtonLocationPreferenceController extends BasePrefere
super.updateState(preference);
final ListPreference listPreference = (ListPreference) preference;
listPreference.setValue(getCurrentAccessibilityButtonMode());
listPreference.setValue(String.valueOf(getCurrentAccessibilityButtonMode()));
}
private String getCurrentAccessibilityButtonMode() {
final int mode = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, getDefaultLocationValue());
return String.valueOf(mode);
@Location
private int getCurrentAccessibilityButtonMode() {
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, Location.FLOATING_MENU);
}
private int getDefaultLocationValue() {
if (!mDefaultLocation.isPresent()) {
final String[] valuesList = mContext.getResources().getStringArray(
R.array.accessibility_button_location_selector_values);
mDefaultLocation = Optional.of(Integer.parseInt(valuesList[0]));
}
return mDefaultLocation.get();
private void putCurrentAccessibilityButtonMode(@Location int location) {
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, location);
}
}

View File

@@ -17,55 +17,17 @@
package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.res.Resources;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.List;
/** Settings fragment containing accessibility control timeout preference. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public final class AccessibilityControlTimeoutPreferenceFragment extends DashboardFragment
implements AccessibilityTimeoutController.OnChangeListener {
public final class AccessibilityControlTimeoutPreferenceFragment extends DashboardFragment {
static final String TAG = "AccessibilityControlTimeoutPreferenceFragment";
private static final List<AbstractPreferenceController> sControllers = new ArrayList<>();
@Override
public void onCheckedChanged(Preference preference) {
for (AbstractPreferenceController controller : sControllers) {
controller.updateState(preference);
}
}
@Override
public void onResume() {
super.onResume();
for (AbstractPreferenceController controller :
buildPreferenceControllers(getPrefContext(), getSettingsLifecycle())) {
((AccessibilityTimeoutController)controller).setOnChangeListener(this);
}
}
@Override
public void onPause() {
super.onPause();
for (AbstractPreferenceController controller :
buildPreferenceControllers(getPrefContext(), getSettingsLifecycle())) {
((AccessibilityTimeoutController)controller).setOnChangeListener(null);
}
}
@Override
public int getMetricsCategory() {
@@ -82,39 +44,11 @@ public final class AccessibilityControlTimeoutPreferenceFragment extends Dashboa
return R.xml.accessibility_control_timeout_settings;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
return buildPreferenceControllers(context, getSettingsLifecycle());
}
@Override
public int getHelpResource() {
return R.string.help_url_timeout;
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
Lifecycle lifecycle) {
if (sControllers.size() == 0) {
Resources resources = context.getResources();
String[] timeoutKeys = resources.getStringArray(
R.array.accessibility_timeout_control_selector_keys);
for (int i=0; i < timeoutKeys.length; i++) {
sControllers.add(new AccessibilityTimeoutController(
context, lifecycle, timeoutKeys[i]));
}
}
return sControllers;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_control_timeout_settings) {
@Override
public List<AbstractPreferenceController> createPreferenceControllers(
Context context) {
return buildPreferenceControllers(context, null);
}
};
new BaseSearchIndexProvider(R.xml.accessibility_control_timeout_settings);
}

View File

@@ -17,6 +17,7 @@
package com.android.settings.accessibility;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_BUTTON_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -31,6 +32,7 @@ import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.FeatureFlagUtils;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
@@ -41,6 +43,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.accessibility.AccessibilityUtils;
import java.util.List;
@@ -112,6 +115,13 @@ public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment {
return new LaunchFragmentArguments(destination, /* arguments= */ null);
}
if (ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.equals(componentName)
&& FeatureFlagUtils.isEnabled(getContext(),
FeatureFlagUtils.SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE)) {
final String destination = AccessibilityHearingAidsFragment.class.getName();
return new LaunchFragmentArguments(destination, /* arguments= */ null);
}
return null;
}
@@ -230,6 +240,10 @@ public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment {
new ComponentName(packageName, tileServiceClassName).flattenToString());
}
final int metricsCategory = FeatureFactory.getFactory(getActivity().getApplicationContext())
.getAccessibilityMetricsFeatureProvider()
.getDownloadedFeatureMetricsCategory(componentName);
extras.putInt(AccessibilitySettings.EXTRA_METRICS_CATEGORY, metricsCategory);
extras.putParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME, componentName);
extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, info.getAnimatedImageRes());

View File

@@ -21,9 +21,9 @@ import static android.view.View.VISIBLE;
import static com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
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.text.Spannable;
import android.text.SpannableString;
@@ -42,7 +42,6 @@ import android.widget.TextSwitcher;
import android.widget.TextView;
import androidx.annotation.AnimRes;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -50,13 +49,13 @@ import androidx.annotation.Nullable;
import androidx.annotation.RawRes;
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.core.SubSettingLauncher;
import com.android.settingslib.widget.LottieColorUtils;
import com.airbnb.lottie.LottieAnimationView;
@@ -97,27 +96,18 @@ public final class AccessibilityGestureNavigationTutorial {
* Displays a dialog that guides users to use accessibility features with accessibility
* gestures under system gesture navigation mode.
*/
public static void showGestureNavigationTutorialDialog(Context context,
public static AlertDialog showGestureNavigationTutorialDialog(Context context,
DialogInterface.OnDismissListener onDismissListener) {
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setView(createTutorialDialogContentView(context,
DialogType.GESTURE_NAVIGATION_SETTINGS))
.setNegativeButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener)
.setPositiveButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener)
.setOnDismissListener(onDismissListener)
.create();
alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
alertDialog.setCanceledOnTouchOutside(false);
alertDialog.show();
}
static AlertDialog showAccessibilityButtonTutorialDialog(Context context) {
final AlertDialog alertDialog = createDialog(context,
DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON);
if (!AccessibilityUtil.isGestureNavigateEnabled(context)) {
updateMessageWithIcon(context, alertDialog);
}
return alertDialog;
}
@@ -131,12 +121,75 @@ public final class AccessibilityGestureNavigationTutorial {
}
static AlertDialog createAccessibilityTutorialDialog(Context context, int shortcutTypes,
@Nullable DialogInterface.OnClickListener negativeButtonListener) {
return new AlertDialog.Builder(context)
.setView(createShortcutNavigationContentView(context, shortcutTypes))
.setNegativeButton(R.string.accessibility_tutorial_dialog_button,
negativeButtonListener)
@Nullable DialogInterface.OnClickListener actionButtonListener) {
final int category = SettingsEnums.SWITCH_SHORTCUT_DIALOG_ACCESSIBILITY_BUTTON_SETTINGS;
final DialogInterface.OnClickListener linkButtonListener =
(dialog, which) -> new SubSettingLauncher(context)
.setDestination(AccessibilityButtonFragment.class.getName())
.setSourceMetricsCategory(category)
.launch();
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setPositiveButton(R.string.accessibility_tutorial_dialog_button,
actionButtonListener)
.setNegativeButton(R.string.accessibility_tutorial_dialog_link_button,
linkButtonListener)
.create();
final List<TutorialPage> tutorialPages =
createShortcutTutorialPages(context, shortcutTypes);
Preconditions.checkArgument(!tutorialPages.isEmpty(),
/* errorMessage= */ "Unexpected tutorial pages size");
final TutorialPageChangeListener.OnPageSelectedCallback callback = index -> {
final int pageType = tutorialPages.get(index).getType();
alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(
pageType == UserShortcutType.SOFTWARE ? VISIBLE : GONE);
};
alertDialog.setView(createShortcutNavigationContentView(context, tutorialPages, callback));
// Showing first page won't invoke onPageSelectedCallback. Need to check the first tutorial
// page type manually to set correct visibility of the link button.
alertDialog.setOnShowListener(dialog -> {
final int firstPageType = tutorialPages.get(0).getType();
alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(
firstPageType == UserShortcutType.SOFTWARE ? VISIBLE : GONE);
});
return alertDialog;
}
static AlertDialog createAccessibilityTutorialDialogForSetupWizard(Context context,
int shortcutTypes) {
return createAccessibilityTutorialDialogForSetupWizard(context, shortcutTypes,
mOnClickListener);
}
static AlertDialog createAccessibilityTutorialDialogForSetupWizard(Context context,
int shortcutTypes, @Nullable DialogInterface.OnClickListener actionButtonListener) {
final int category = SettingsEnums.SWITCH_SHORTCUT_DIALOG_ACCESSIBILITY_BUTTON_SETTINGS;
final DialogInterface.OnClickListener linkButtonListener =
(dialog, which) -> new SubSettingLauncher(context)
.setDestination(AccessibilityButtonFragment.class.getName())
.setSourceMetricsCategory(category)
.launch();
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setPositiveButton(R.string.accessibility_tutorial_dialog_button,
actionButtonListener)
.create();
final List<TutorialPage> tutorialPages =
createShortcutTutorialPages(context, shortcutTypes);
Preconditions.checkArgument(!tutorialPages.isEmpty(),
/* errorMessage= */ "Unexpected tutorial pages size");
alertDialog.setView(createShortcutNavigationContentView(context, tutorialPages, null));
return alertDialog;
}
/**
@@ -190,7 +243,7 @@ public final class AccessibilityGestureNavigationTutorial {
private static AlertDialog createDialog(Context context, int dialogType) {
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setView(createTutorialDialogContentView(context, dialogType))
.setNegativeButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener)
.setPositiveButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener)
.create();
alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
@@ -200,50 +253,6 @@ public final class AccessibilityGestureNavigationTutorial {
return alertDialog;
}
private static void updateMessageWithIcon(Context context, AlertDialog alertDialog) {
final TextView gestureTutorialMessage = alertDialog.findViewById(
R.id.button_tutorial_message);
// Get the textView line height to update [icon] size. Must be called after show()
final int lineHeight = gestureTutorialMessage.getLineHeight();
gestureTutorialMessage.setText(getMessageStringWithIcon(context, lineHeight));
}
private static SpannableString getMessageStringWithIcon(Context context, int lineHeight) {
final String messageString = context
.getString(R.string.accessibility_tutorial_dialog_message_button);
final SpannableString spannableMessage = SpannableString.valueOf(messageString);
// Icon
final int indexIconStart = messageString.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;
}
private static class TutorialPagerAdapter extends PagerAdapter {
private final List<TutorialPage> mTutorialPages;
private TutorialPagerAdapter(List<TutorialPage> tutorialPages) {
@@ -313,14 +322,13 @@ public final class AccessibilityGestureNavigationTutorial {
return inflater.inflate(R.layout.accessibility_lottie_animation_view, /* root= */ null);
}
private static View createShortcutNavigationContentView(Context context, int shortcutTypes) {
private static View createShortcutNavigationContentView(Context context,
List<TutorialPage> tutorialPages,
TutorialPageChangeListener.OnPageSelectedCallback onPageSelectedCallback) {
final LayoutInflater inflater = context.getSystemService(LayoutInflater.class);
final View contentView = inflater.inflate(
R.layout.accessibility_shortcut_tutorial_dialog, /* root= */ null);
final List<TutorialPage> tutorialPages =
createShortcutTutorialPages(context, shortcutTypes);
Preconditions.checkArgument(!tutorialPages.isEmpty(),
/* errorMessage= */ "Unexpected tutorial pages size");
final LinearLayout indicatorContainer = contentView.findViewById(R.id.indicator_container);
indicatorContainer.setVisibility(tutorialPages.size() > 1 ? VISIBLE : GONE);
@@ -344,9 +352,10 @@ public final class AccessibilityGestureNavigationTutorial {
viewPager.setImportantForAccessibility(tutorialPages.size() > 1
? View.IMPORTANT_FOR_ACCESSIBILITY_YES
: View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
viewPager.addOnPageChangeListener(
new TutorialPageChangeListener(context, viewPager, title, instruction,
tutorialPages));
TutorialPageChangeListener listener = new TutorialPageChangeListener(context, viewPager,
title, instruction, tutorialPages);
listener.setOnPageSelectedCallback(onPageSelectedCallback);
return contentView;
}
@@ -367,6 +376,7 @@ public final class AccessibilityGestureNavigationTutorial {
}
private static TutorialPage createSoftwareTutorialPage(@NonNull Context context) {
final int type = UserShortcutType.SOFTWARE;
final CharSequence title = getSoftwareTitle(context);
final View image = createSoftwareImage(context);
final CharSequence instruction = getSoftwareInstruction(context);
@@ -374,10 +384,11 @@ public final class AccessibilityGestureNavigationTutorial {
createImageView(context, R.drawable.ic_accessibility_page_indicator);
indicatorIcon.setEnabled(false);
return new TutorialPage(title, image, indicatorIcon, instruction);
return new TutorialPage(type, title, image, indicatorIcon, instruction);
}
private static TutorialPage createHardwareTutorialPage(@NonNull Context context) {
final int type = UserShortcutType.HARDWARE;
final CharSequence title =
context.getText(R.string.accessibility_tutorial_dialog_title_volume);
final View image =
@@ -388,10 +399,11 @@ public final class AccessibilityGestureNavigationTutorial {
context.getText(R.string.accessibility_tutorial_dialog_message_volume);
indicatorIcon.setEnabled(false);
return new TutorialPage(title, image, indicatorIcon, instruction);
return new TutorialPage(type, title, image, indicatorIcon, instruction);
}
private static TutorialPage createTripleTapTutorialPage(@NonNull Context context) {
final int type = UserShortcutType.TRIPLETAP;
final CharSequence title =
context.getText(R.string.accessibility_tutorial_dialog_title_triple);
final View image =
@@ -403,7 +415,7 @@ public final class AccessibilityGestureNavigationTutorial {
createImageView(context, R.drawable.ic_accessibility_page_indicator);
indicatorIcon.setEnabled(false);
return new TutorialPage(title, image, indicatorIcon, instruction);
return new TutorialPage(type, title, image, indicatorIcon, instruction);
}
@VisibleForTesting
@@ -487,13 +499,15 @@ public final class AccessibilityGestureNavigationTutorial {
}
private static class TutorialPage {
private final int mType;
private final CharSequence mTitle;
private final View mIllustrationView;
private final ImageView mIndicatorIcon;
private final CharSequence mInstruction;
TutorialPage(CharSequence title, View illustrationView, ImageView indicatorIcon,
TutorialPage(int type, CharSequence title, View illustrationView, ImageView indicatorIcon,
CharSequence instruction) {
this.mType = type;
this.mTitle = title;
this.mIllustrationView = illustrationView;
this.mIndicatorIcon = indicatorIcon;
@@ -502,6 +516,10 @@ public final class AccessibilityGestureNavigationTutorial {
setupIllustrationChildViewsGravity();
}
public int getType() {
return mType;
}
public CharSequence getTitle() {
return mTitle;
}
@@ -543,6 +561,7 @@ public final class AccessibilityGestureNavigationTutorial {
private final TextSwitcher mInstruction;
private final List<TutorialPage> mTutorialPages;
private final ViewPager mViewPager;
private OnPageSelectedCallback mOnPageSelectedCallback;
TutorialPageChangeListener(Context context, ViewPager viewPager, ViewGroup title,
ViewGroup instruction, List<TutorialPage> tutorialPages) {
@@ -551,6 +570,14 @@ public final class AccessibilityGestureNavigationTutorial {
this.mTitle = (TextSwitcher) title;
this.mInstruction = (TextSwitcher) instruction;
this.mTutorialPages = tutorialPages;
this.mOnPageSelectedCallback = null;
this.mViewPager.addOnPageChangeListener(this);
}
public void setOnPageSelectedCallback(
OnPageSelectedCallback callback) {
this.mOnPageSelectedCallback = callback;
}
@Override
@@ -591,11 +618,22 @@ public final class AccessibilityGestureNavigationTutorial {
mViewPager.setContentDescription(
mContext.getString(R.string.accessibility_tutorial_pager,
currentPageNumber, mTutorialPages.size()));
if (mOnPageSelectedCallback != null) {
mOnPageSelectedCallback.onPageSelected(position);
}
}
@Override
public void onPageScrollStateChanged(int state) {
// Do nothing.
}
/** The interface that provides a callback method after tutorial page is selected. */
private interface OnPageSelectedCallback {
/** The callback method after tutorial page is selected. */
void onPageSelected(int index);
}
}
}

View File

@@ -16,10 +16,10 @@
package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapClient;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -27,7 +27,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.util.FeatureFlagUtils;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentManager;
@@ -40,57 +40,42 @@ import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.HearingAidInfo;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.Set;
/**
* Controller that shows and updates the bluetooth device name
*/
public class AccessibilityHearingAidPreferenceController extends BasePreferenceController
implements LifecycleObserver, OnStart, OnStop, BluetoothCallback {
implements LifecycleObserver, OnStart, OnStop, BluetoothCallback,
LocalBluetoothProfileManager.ServiceListener {
private static final String TAG = "AccessibilityHearingAidPreferenceController";
private Preference mHearingAidPreference;
private final BroadcastReceiver mHearingAidChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
final int state = intent.getIntExtra(BluetoothHearingAid.EXTRA_STATE,
BluetoothHearingAid.STATE_DISCONNECTED);
if (state == BluetoothHearingAid.STATE_CONNECTED) {
updateState(mHearingAidPreference);
} else {
mHearingAidPreference
.setSummary(R.string.accessibility_hearingaid_not_connected_summary);
}
} else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR);
if (state != BluetoothAdapter.STATE_ON) {
mHearingAidPreference
.setSummary(R.string.accessibility_hearingaid_not_connected_summary);
}
}
updateState(mHearingAidPreference);
}
};
private final LocalBluetoothManager mLocalBluetoothManager;
private final BluetoothAdapter mBluetoothAdapter;
private final LocalBluetoothProfileManager mProfileManager;
private final HearingAidHelper mHelper;
private FragmentManager mFragmentManager;
public AccessibilityHearingAidPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mLocalBluetoothManager = getLocalBluetoothManager();
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mLocalBluetoothManager = com.android.settings.bluetooth.Utils.getLocalBluetoothManager(
context);
mProfileManager = mLocalBluetoothManager.getProfileManager();
mHelper = new HearingAidHelper(context);
}
@Override
@@ -101,28 +86,42 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
@Override
public int getAvailabilityStatus() {
return isHearingAidProfileSupported() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
return mHelper.isHearingAidSupported() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
public void onStart() {
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(mHearingAidChangedReceiver, filter);
mLocalBluetoothManager.getEventManager().registerCallback(this);
// Can't get connected hearing aids when hearing aids related profiles are not ready. The
// profiles will be ready after the services are connected. Needs to add listener and
// updates the information when all hearing aids related services are connected.
if (!mHelper.isAllHearingAidRelatedProfilesReady()) {
mProfileManager.addServiceListener(this);
}
}
@Override
public void onStop() {
mContext.unregisterReceiver(mHearingAidChangedReceiver);
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
mProfileManager.removeServiceListener(this);
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
final CachedBluetoothDevice device = getConnectedHearingAidDevice();
final CachedBluetoothDevice device = mHelper.getConnectedHearingAidDevice();
if (FeatureFlagUtils.isEnabled(mContext,
FeatureFlagUtils.SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE)) {
launchHearingAidPage();
return true;
}
if (device == null) {
launchHearingAidInstructionDialog();
} else {
@@ -135,31 +134,47 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
@Override
public CharSequence getSummary() {
final CachedBluetoothDevice device = getConnectedHearingAidDevice();
final CachedBluetoothDevice device = mHelper.getConnectedHearingAidDevice();
if (device == null) {
return mContext.getText(R.string.accessibility_hearingaid_not_connected_summary);
}
final int connectedNum = getConnectedHearingAidDeviceNum();
final CharSequence name = device.getName();
final int side = device.getDeviceSide();
final CachedBluetoothDevice subDevice = device.getSubDevice();
if (connectedNum > 1) {
return mContext.getString(R.string.accessibility_hearingaid_more_device_summary, name);
}
// Check if another side of LE audio hearing aid is connected as a pair
final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
if (memberDevices.stream().anyMatch(m -> m.isConnected())) {
return mContext.getString(
R.string.accessibility_hearingaid_left_and_right_side_device_summary,
name);
}
// Check if another side of ASHA hearing aid is connected as a pair
final CachedBluetoothDevice subDevice = device.getSubDevice();
if (subDevice != null && subDevice.isConnected()) {
return mContext.getString(
R.string.accessibility_hearingaid_left_and_right_side_device_summary, name);
}
if (side == HearingAidProfile.DeviceSide.SIDE_INVALID) {
final int side = device.getDeviceSide();
if (side == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) {
return mContext.getString(
R.string.accessibility_hearingaid_active_device_summary, name);
R.string.accessibility_hearingaid_left_and_right_side_device_summary, name);
} else if (side == HearingAidInfo.DeviceSide.SIDE_LEFT) {
return mContext.getString(
R.string.accessibility_hearingaid_left_side_device_summary, name);
} else if (side == HearingAidInfo.DeviceSide.SIDE_RIGHT) {
return mContext.getString(
R.string.accessibility_hearingaid_right_side_device_summary, name);
}
return (side == HearingAidProfile.DeviceSide.SIDE_LEFT)
? mContext.getString(
R.string.accessibility_hearingaid_left_side_device_summary, name)
: mContext.getString(
R.string.accessibility_hearingaid_right_side_device_summary, name);
// Invalid side
return mContext.getString(
R.string.accessibility_hearingaid_active_device_summary, name);
}
@Override
@@ -173,63 +188,25 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
}
}
@Override
public void onServiceConnected() {
if (mHelper.isAllHearingAidRelatedProfilesReady()) {
updateState(mHearingAidPreference);
mProfileManager.removeServiceListener(this);
}
}
@Override
public void onServiceDisconnected() {
// Do nothing
}
public void setFragmentManager(FragmentManager fragmentManager) {
mFragmentManager = fragmentManager;
}
@VisibleForTesting
CachedBluetoothDevice getConnectedHearingAidDevice() {
if (!isHearingAidProfileSupported()) {
return null;
}
final CachedBluetoothDeviceManager deviceManager =
mLocalBluetoothManager.getCachedDeviceManager();
final HearingAidProfile hearingAidProfile =
mLocalBluetoothManager.getProfileManager().getHearingAidProfile();
final List<BluetoothDevice> deviceList = hearingAidProfile.getConnectedDevices();
for (BluetoothDevice obj : deviceList) {
if (!deviceManager.isSubDevice(obj)) {
return deviceManager.findDevice(obj);
}
}
return null;
}
private int getConnectedHearingAidDeviceNum() {
if (!isHearingAidProfileSupported()) {
return 0;
}
final CachedBluetoothDeviceManager deviceManager =
mLocalBluetoothManager.getCachedDeviceManager();
final HearingAidProfile hearingAidProfile =
mLocalBluetoothManager.getProfileManager().getHearingAidProfile();
final List<BluetoothDevice> deviceList = hearingAidProfile.getConnectedDevices();
return (int) deviceList.stream()
.filter(device -> !deviceManager.isSubDevice(device))
.count();
}
private boolean isHearingAidProfileSupported() {
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
return false;
}
final List<Integer> supportedList = mBluetoothAdapter.getSupportedProfiles();
return supportedList.contains(BluetoothProfile.HEARING_AID);
}
private LocalBluetoothManager getLocalBluetoothManager() {
final FutureTask<LocalBluetoothManager> localBtManagerFutureTask = new FutureTask<>(
// Avoid StrictMode ThreadPolicy violation
() -> com.android.settings.bluetooth.Utils.getLocalBtManager(mContext));
try {
localBtManagerFutureTask.run();
return localBtManagerFutureTask.get();
} catch (InterruptedException | ExecutionException e) {
Log.w(TAG, "Error getting LocalBluetoothManager.", e);
return null;
}
return mHelper.getConnectedHearingAidDeviceList().size();
}
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
@@ -250,7 +227,7 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
.setDestination(BluetoothDeviceDetailsFragment.class.getName())
.setArguments(args)
.setTitleRes(R.string.device_details_title)
.setSourceMetricsCategory(SettingsEnums.ACCESSIBILITY)
.setSourceMetricsCategory(getMetricsCategory())
.launch();
}
@@ -259,4 +236,11 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
HearingAidDialogFragment fragment = HearingAidDialogFragment.newInstance();
fragment.show(mFragmentManager, HearingAidDialogFragment.class.toString());
}
private void launchHearingAidPage() {
new SubSettingLauncher(mContext)
.setDestination(AccessibilityHearingAidsFragment.class.getName())
.setSourceMetricsCategory(getMetricsCategory())
.launch();
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.preference.PreferenceCategory;
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.settings.R;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
/** Accessibility settings for hearing aids. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class AccessibilityHearingAidsFragment extends AccessibilityShortcutPreferenceFragment {
private static final String TAG = "AccessibilityHearingAidsFragment";
private static final String KEY_HEARING_OPTIONS_CATEGORY = "hearing_options_category";
private static final int SHORTCUT_PREFERENCE_IN_CATEGORY_INDEX = 20;
private String mFeatureName;
public AccessibilityHearingAidsFragment() {
super(DISALLOW_CONFIG_BLUETOOTH);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
use(AvailableHearingDevicePreferenceController.class).init(this);
use(SavedHearingDevicePreferenceController.class).init(this);
}
@Override
public void onCreate(Bundle savedInstanceState) {
mFeatureName = getContext().getString(R.string.accessibility_hearingaid_title);
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = super.onCreateView(inflater, container, savedInstanceState);
final PreferenceCategory controlCategory = findPreference(KEY_HEARING_OPTIONS_CATEGORY);
// To move the shortcut preference under controlCategory need to remove the original added.
mShortcutPreference.setOrder(SHORTCUT_PREFERENCE_IN_CATEGORY_INDEX);
getPreferenceScreen().removePreference(mShortcutPreference);
controlCategory.addPreference(mShortcutPreference);
return view;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_HEARING_AID_SETTINGS;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_hearing_aids;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected ComponentName getComponentName() {
return AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
}
@Override
protected CharSequence getLabelName() {
return mFeatureName;
}
@Override
protected ComponentName getTileComponentName() {
// Don't have quick settings tile for now.
return null;
}
@Override
protected CharSequence getTileTooltipContent(int type) {
// Don't have quick settings tile for now.
return null;
}
@Override
protected boolean showGeneralCategory() {
// Have static preference under dynamically created PreferenceCategory KEY_GENERAL_CATEGORY.
// In order to modify that, we need to use our own PreferenceCategory for this page.
return false;
}
@Override
protected CharSequence getShortcutTitle() {
return getText(R.string.accessibility_hearing_device_shortcut_title);
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_hearing_aids);
}

View File

@@ -28,12 +28,13 @@ import com.android.settings.core.TogglePreferenceController;
import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnCreate;
import com.android.settingslib.core.lifecycle.events.OnDestroy;
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
/** PrimarySwitchPreferenceController that shows quick settings tooltip on first use. */
public abstract class AccessibilityQuickSettingsPrimarySwitchPreferenceController
extends TogglePreferenceController
implements LifecycleObserver, OnCreate, OnSaveInstanceState {
implements LifecycleObserver, OnCreate, OnDestroy, OnSaveInstanceState {
private static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow";
private final Handler mHandler;
private PrimarySwitchPreference mPreference;
@@ -62,10 +63,16 @@ public abstract class AccessibilityQuickSettingsPrimarySwitchPreferenceControlle
}
}
@Override
public void onDestroy() {
mHandler.removeCallbacksAndMessages(null);
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (mTooltipWindow != null) {
outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, mTooltipWindow.isShowing());
final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
if (mNeedsQSTooltipReshow || isTooltipWindowShowing) {
outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true);
}
}

View File

@@ -31,11 +31,13 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.accessibility.util.AccessibilityUtils;
import com.android.internal.content.PackageMonitor;
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType;
@@ -43,6 +45,7 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.search.SearchIndexableRaw;
@@ -61,13 +64,15 @@ public class AccessibilitySettings extends DashboardFragment {
private static final String CATEGORY_SCREEN_READER = "screen_reader_category";
private static final String CATEGORY_CAPTIONS = "captions_category";
private static final String CATEGORY_AUDIO = "audio_category";
private static final String CATEGORY_SPEECH = "speech_category";
private static final String CATEGORY_DISPLAY = "display_category";
private static final String CATEGORY_INTERACTION_CONTROL = "interaction_control_category";
private static final String CATEGORY_DOWNLOADED_SERVICES = "user_installed_services_category";
@VisibleForTesting
static final String CATEGORY_INTERACTION_CONTROL = "interaction_control_category";
private static final String[] CATEGORIES = new String[]{
CATEGORY_SCREEN_READER, CATEGORY_CAPTIONS, CATEGORY_AUDIO, CATEGORY_DISPLAY,
CATEGORY_INTERACTION_CONTROL, CATEGORY_DOWNLOADED_SERVICES
CATEGORY_SPEECH, CATEGORY_INTERACTION_CONTROL, CATEGORY_DOWNLOADED_SERVICES
};
// Extras passed to sub-fragments.
@@ -87,6 +92,7 @@ public class AccessibilitySettings extends DashboardFragment {
static final String EXTRA_ANIMATED_IMAGE_RES = "animated_image_res";
static final String EXTRA_HTML_DESCRIPTION = "html_description";
static final String EXTRA_TIME_FOR_LOGGING = "start_time_to_log_a11y_tool";
static final String EXTRA_METRICS_CATEGORY = "metrics_category";
// Timeout before we update the services if packages are added/removed
// since the AccessibilityManagerService has to do that processing first
@@ -111,6 +117,11 @@ public class AccessibilitySettings extends DashboardFragment {
sendUpdate();
}
@Override
public void onPackageModified(@NonNull String packageName) {
sendUpdate();
}
@Override
public void onPackageAppeared(String packageName, int reason) {
sendUpdate();
@@ -136,7 +147,8 @@ public class AccessibilitySettings extends DashboardFragment {
private final Map<String, PreferenceCategory> mCategoryToPrefCategoryMap =
new ArrayMap<>();
private final Map<Preference, PreferenceCategory> mServicePreferenceToPreferenceCategoryMap =
@VisibleForTesting
final Map<Preference, PreferenceCategory> mServicePreferenceToPreferenceCategoryMap =
new ArrayMap<>();
private final Map<ComponentName, PreferenceCategory> mPreBundledServiceComponentToCategoryMap =
new ArrayMap<>();
@@ -146,11 +158,14 @@ public class AccessibilitySettings extends DashboardFragment {
public AccessibilitySettings() {
// Observe changes to anything that the shortcut can toggle, so we can reflect updates
final Collection<AccessibilityShortcutController.ToggleableFrameworkFeatureInfo> features =
final Collection<AccessibilityShortcutController.FrameworkFeatureInfo> features =
AccessibilityShortcutController.getFrameworkShortcutFeaturesMap().values();
final List<String> shortcutFeatureKeys = new ArrayList<>(features.size());
for (AccessibilityShortcutController.ToggleableFrameworkFeatureInfo feature : features) {
shortcutFeatureKeys.add(feature.getSettingKey());
for (AccessibilityShortcutController.FrameworkFeatureInfo feature : features) {
final String key = feature.getSettingKey();
if (key != null) {
shortcutFeatureKeys.add(key);
}
}
// Observe changes from accessibility selection menu
@@ -302,6 +317,7 @@ public class AccessibilitySettings extends DashboardFragment {
void updateAllPreferences() {
updateSystemPreferences();
updateServicePreferences();
updatePreferencesState();
}
private void registerContentMonitors() {
@@ -337,9 +353,17 @@ public class AccessibilitySettings extends DashboardFragment {
R.array.config_preinstalled_audio_services);
initializePreBundledServicesMapFromArray(CATEGORY_DISPLAY,
R.array.config_preinstalled_display_services);
initializePreBundledServicesMapFromArray(CATEGORY_SPEECH,
R.array.config_preinstalled_speech_services);
initializePreBundledServicesMapFromArray(CATEGORY_INTERACTION_CONTROL,
R.array.config_preinstalled_interaction_control_services);
// ACCESSIBILITY_MENU_IN_SYSTEM is a default pre-bundled interaction control service.
// If the device opts out of including this service then this is a no-op.
mPreBundledServiceComponentToCategoryMap.put(
AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM,
mCategoryToPrefCategoryMap.get(CATEGORY_INTERACTION_CONTROL));
final List<RestrictedPreference> preferenceList = getInstalledAccessibilityList(
getPrefContext());
@@ -370,6 +394,8 @@ public class AccessibilitySettings extends DashboardFragment {
R.array.config_order_interaction_control_services);
updateCategoryOrderFromArray(CATEGORY_DISPLAY,
R.array.config_order_display_services);
updateCategoryOrderFromArray(CATEGORY_SPEECH,
R.array.config_order_speech_services);
// Need to check each time when updateServicePreferences() called.
if (downloadedServicesCategory.getPreferenceCount() == 0) {
@@ -378,8 +404,9 @@ public class AccessibilitySettings extends DashboardFragment {
getPreferenceScreen().addPreference(downloadedServicesCategory);
}
// Hide screen reader category if it is empty.
// Hide category if it is empty.
updatePreferenceCategoryVisibility(CATEGORY_SCREEN_READER);
updatePreferenceCategoryVisibility(CATEGORY_SPEECH);
}
private List<RestrictedPreference> getInstalledAccessibilityList(Context context) {
@@ -477,6 +504,13 @@ public class AccessibilitySettings extends DashboardFragment {
// Do nothing.
}
private void updatePreferencesState() {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
getPreferenceControllers().forEach(controllers::addAll);
controllers.forEach(controller -> controller.updateState(
findPreference(controller.getPreferenceKey())));
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_settings) {
@Override

View File

@@ -19,7 +19,6 @@ package com.android.settings.accessibility;
import static android.app.Activity.RESULT_CANCELED;
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;
@@ -88,25 +87,31 @@ public class AccessibilitySettingsForSetupWizard extends DashboardFragment
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final GlifPreferenceLayout layout = (GlifPreferenceLayout) view;
final String title = getContext().getString(R.string.vision_settings_title);
final String description = getContext().getString(R.string.vision_settings_description);
final Drawable icon = getContext().getDrawable(R.drawable.ic_accessibility_visibility);
AccessibilitySetupWizardUtils.updateGlifPreferenceLayout(getContext(), layout, title,
description, icon);
if (view instanceof GlifPreferenceLayout) {
final GlifPreferenceLayout layout = (GlifPreferenceLayout) view;
final String title = getContext().getString(R.string.vision_settings_title);
final String description = getContext().getString(R.string.vision_settings_description);
final Drawable icon = getContext().getDrawable(R.drawable.ic_accessibility_visibility);
AccessibilitySetupWizardUtils.updateGlifPreferenceLayout(getContext(), layout, title,
description, icon);
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
AccessibilitySetupWizardUtils.setPrimaryButton(getContext(), mixin, R.string.done, () -> {
setResult(RESULT_CANCELED);
finish();
});
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
AccessibilitySetupWizardUtils.setPrimaryButton(getContext(), mixin, R.string.done,
() -> {
setResult(RESULT_CANCELED);
finish();
});
}
}
@Override
public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
final GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
return layout.onCreateRecyclerView(inflater, parent, savedInstanceState);
Bundle savedInstanceState) {
if (parent instanceof GlifPreferenceLayout) {
final GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
return layout.onCreateRecyclerView(inflater, parent, savedInstanceState);
}
return super.onCreateRecyclerView(inflater, parent, savedInstanceState);
}
@Override
@@ -121,11 +126,9 @@ public class AccessibilitySettingsForSetupWizard extends DashboardFragment
public void onResume() {
super.onResume();
updateAccessibilityServicePreference(mScreenReaderPreference,
SCREEN_READER_PACKAGE_NAME, SCREEN_READER_SERVICE_NAME,
VolumeShortcutToggleScreenReaderPreferenceFragmentForSetupWizard.class.getName());
SCREEN_READER_PACKAGE_NAME, SCREEN_READER_SERVICE_NAME);
updateAccessibilityServicePreference(mSelectToSpeakPreference,
SELECT_TO_SPEAK_PACKAGE_NAME, SELECT_TO_SPEAK_SERVICE_NAME,
VolumeShortcutToggleSelectToSpeakPreferenceFragmentForSetupWizard.class.getName());
SELECT_TO_SPEAK_PACKAGE_NAME, SELECT_TO_SPEAK_SERVICE_NAME);
configureMagnificationPreferenceIfNeeded(mDisplayMagnificationPreference);
}
@@ -184,7 +187,7 @@ public class AccessibilitySettingsForSetupWizard extends DashboardFragment
}
private void updateAccessibilityServicePreference(RestrictedPreference preference,
String packageName, String serviceName, String targetFragment) {
String packageName, String serviceName) {
final AccessibilityServiceInfo info = findService(packageName, serviceName);
if (info == null) {
getPreferenceScreen().removePreference(preference);
@@ -200,9 +203,6 @@ public class AccessibilitySettingsForSetupWizard extends DashboardFragment
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.
final Bundle extras = preference.getExtras();

View File

@@ -20,7 +20,6 @@ import android.os.Bundle;
import android.view.Menu;
import android.view.accessibility.AccessibilityEvent;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
@@ -31,18 +30,14 @@ import com.android.settings.core.SubSettingLauncher;
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 {
private static final String SAVE_KEY_TITLE = "activity_title";
@VisibleForTesting
static final String CLASS_NAME_FONT_SIZE_SETTINGS_FOR_SUW =
"com.android.settings.FontSizeSettingsForSetupWizardActivity";
@Override
protected void onSaveInstanceState(Bundle savedState) {
savedState.putCharSequence(SAVE_KEY_TITLE, getTitle());
@@ -88,7 +83,6 @@ public class AccessibilitySettingsForSetupWizardActivity extends SettingsActivit
: Instrumentable.METRICS_CATEGORY_UNKNOWN)
.setExtras(SetupWizardUtils.copyLifecycleExtra(getIntent().getExtras(),
new Bundle()))
.setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_FADE)
.launch();
return true;
}
@@ -101,8 +95,11 @@ public class AccessibilitySettingsForSetupWizardActivity extends SettingsActivit
}
private void applyTheme() {
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
setTheme(R.style.SettingsPreferenceTheme_SetupWizard);
ThemeHelper.trySetDynamicColor(this);
final boolean isAnySetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
if (isAnySetupWizard) {
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
setTheme(R.style.SettingsPreferenceTheme_SetupWizard);
ThemeHelper.trySetDynamicColor(this);
}
}
}

View File

@@ -20,6 +20,7 @@ import static com.android.settings.accessibility.AccessibilityDialogUtils.Dialog
import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_GENERAL_CATEGORY;
import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_SAVED_QS_TOOLTIP_TYPE;
import android.app.Activity;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
@@ -43,7 +44,7 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.RestrictedDashboardFragment;
import com.android.settings.utils.LocaleUtils;
import com.google.android.setupcompat.util.WizardManagerHelper;
@@ -55,7 +56,7 @@ import java.util.Locale;
/**
* Base class for accessibility fragments shortcut functions and dialog management.
*/
public abstract class AccessibilityShortcutPreferenceFragment extends DashboardFragment
public abstract class AccessibilityShortcutPreferenceFragment extends RestrictedDashboardFragment
implements ShortcutPreference.OnClickCallback {
private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
protected static final String KEY_SAVED_USER_SHORTCUT_TYPE = "shortcut_type";
@@ -65,6 +66,7 @@ public abstract class AccessibilityShortcutPreferenceFragment extends DashboardF
protected int mSavedCheckBoxValue = NOT_SET;
protected ShortcutPreference mShortcutPreference;
protected Dialog mDialog;
private AccessibilityManager.TouchExplorationStateChangeListener
mTouchExplorationStateChangeListener;
private AccessibilitySettingsContentObserver mSettingsContentObserver;
@@ -74,6 +76,10 @@ public abstract class AccessibilityShortcutPreferenceFragment extends DashboardF
private boolean mNeedsQSTooltipReshow = false;
private int mNeedsQSTooltipType = QuickSettingsTooltipType.GUIDE_TO_EDIT;
public AccessibilityShortcutPreferenceFragment(String restrictionKey) {
super(restrictionKey);
}
/** Returns the accessibility component name. */
protected abstract ComponentName getComponentName();
@@ -150,19 +156,27 @@ public abstract class AccessibilityShortcutPreferenceFragment extends DashboardF
// Reshow tooltip when activity recreate, such as rotate device.
if (mNeedsQSTooltipReshow) {
getView().post(this::showQuickSettingsTooltipIfNeeded);
view.post(() -> {
final Activity activity = getActivity();
if (activity != null && !activity.isFinishing()) {
showQuickSettingsTooltipIfNeeded();
}
});
}
}
@Override
public void onResume() {
super.onResume();
final AccessibilityManager am = getPrefContext().getSystemService(
AccessibilityManager.class);
am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
mSettingsContentObserver.register(getContentResolver());
updateShortcutPreferenceData();
updateShortcutPreference();
updateEditShortcutDialogIfNeeded();
}
@Override
@@ -180,8 +194,9 @@ public abstract class AccessibilityShortcutPreferenceFragment extends DashboardF
if (value != NOT_SET) {
outState.putInt(KEY_SAVED_USER_SHORTCUT_TYPE, value);
}
if (mTooltipWindow != null) {
outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, mTooltipWindow.isShowing());
final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
if (mNeedsQSTooltipReshow || isTooltipWindowShowing) {
outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true);
outState.putInt(KEY_SAVED_QS_TOOLTIP_TYPE, mNeedsQSTooltipType);
}
super.onSaveInstanceState(outState);
@@ -189,23 +204,30 @@ public abstract class AccessibilityShortcutPreferenceFragment extends DashboardF
@Override
public Dialog onCreateDialog(int dialogId) {
final Dialog dialog;
switch (dialogId) {
case DialogEnums.EDIT_SHORTCUT:
final int dialogType = WizardManagerHelper.isAnySetupWizard(getIntent())
? AccessibilityDialogUtils.DialogType.EDIT_SHORTCUT_GENERIC_SUW :
AccessibilityDialogUtils.DialogType.EDIT_SHORTCUT_GENERIC;
dialog = AccessibilityDialogUtils.showEditShortcutDialog(
mDialog = AccessibilityDialogUtils.showEditShortcutDialog(
getPrefContext(), dialogType, getShortcutTitle(),
this::callOnAlertDialogCheckboxClicked);
setupEditShortcutDialog(dialog);
return dialog;
setupEditShortcutDialog(mDialog);
return mDialog;
case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
dialog = AccessibilityGestureNavigationTutorial
.createAccessibilityTutorialDialog(getPrefContext(),
getUserShortcutTypes(), this::callOnTutorialDialogButtonClicked);
dialog.setCanceledOnTouchOutside(false);
return dialog;
if (WizardManagerHelper.isAnySetupWizard(getIntent())) {
mDialog = AccessibilityGestureNavigationTutorial
.createAccessibilityTutorialDialogForSetupWizard(
getPrefContext(), getUserShortcutTypes(),
this::callOnTutorialDialogButtonClicked);
} else {
mDialog = AccessibilityGestureNavigationTutorial
.createAccessibilityTutorialDialog(
getPrefContext(), getUserShortcutTypes(),
this::callOnTutorialDialogButtonClicked);
}
mDialog.setCanceledOnTouchOutside(false);
return mDialog;
default:
throw new IllegalArgumentException("Unsupported dialogId " + dialogId);
}
@@ -300,6 +322,18 @@ public abstract class AccessibilityShortcutPreferenceFragment extends DashboardF
getComponentName());
};
private static CharSequence getSoftwareShortcutTypeSummary(Context context) {
int resId;
if (AccessibilityUtil.isFloatingMenuEnabled(context)) {
resId = R.string.accessibility_shortcut_edit_summary_software;
} else if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
resId = R.string.accessibility_shortcut_edit_summary_software_gesture;
} else {
resId = R.string.accessibility_shortcut_edit_summary_software;
}
return context.getText(resId);
}
/**
* This method will be invoked when a button in the tutorial dialog is clicked.
*
@@ -349,6 +383,13 @@ public abstract class AccessibilityShortcutPreferenceFragment extends DashboardF
getPreferenceScreen().addPreference(generalCategory);
}
private void updateEditShortcutDialogIfNeeded() {
if (mDialog == null || !mDialog.isShowing()) {
return;
}
AccessibilityDialogUtils.updateShortcutInDialog(getContext(), mDialog);
}
@VisibleForTesting
void saveNonEmptyUserShortcutType(int type) {
if (type == AccessibilityUtil.UserShortcutType.EMPTY) {
@@ -400,11 +441,9 @@ public abstract class AccessibilityShortcutPreferenceFragment extends DashboardF
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);
list.add(getSoftwareShortcutTypeSummary(context));
}
if (hasShortcutType(shortcutTypes, AccessibilityUtil.UserShortcutType.HARDWARE)) {
final CharSequence hardwareTitle = context.getText(
@@ -414,7 +453,7 @@ public abstract class AccessibilityShortcutPreferenceFragment extends DashboardF
// Show software shortcut if first time to use.
if (list.isEmpty()) {
list.add(softwareTitle);
list.add(getSoftwareShortcutTypeSummary(context));
}
return CaseMap.toTitle().wholeString().noLowercase().apply(Locale.getDefault(), /* iter= */

View File

@@ -131,6 +131,8 @@ public final class AccessibilityStatsLogUtils {
return FrameworkStatsLog.HEARING_AID_INFO_REPORTED__BOND_ENTRY__ACCESSIBILITY_HEARING_AID_PAIR_ANOTHER;
case SettingsStatsLog.SETTINGS_UICHANGED__PAGE_ID__BLUETOOTH_FRAGMENT:
return FrameworkStatsLog.HEARING_AID_INFO_REPORTED__BOND_ENTRY__BLUETOOTH;
case SettingsStatsLog.SETTINGS_UICHANGED__PAGE_ID__ACCESSIBILITY_HEARING_AID_SETTINGS:
return FrameworkStatsLog.HEARING_AID_INFO_REPORTED__BOND_ENTRY__ACCESSIBILITY_HEARING_AID_SETTINGS;
default:
return FrameworkStatsLog.HEARING_AID_INFO_REPORTED__BOND_ENTRY__PAGE_UNKNOWN;
}

View File

@@ -19,79 +19,114 @@ package com.android.settings.accessibility;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import androidx.lifecycle.LifecycleObserver;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import com.google.common.primitives.Ints;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Controller class that control accessibility time out settings.
*/
public class AccessibilityTimeoutController extends AbstractPreferenceController implements
LifecycleObserver, SelectorWithWidgetPreference.OnClickListener, PreferenceControllerMixin {
static final String CONTENT_TIMEOUT_SETTINGS_SECURE =
Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS;
static final String CONTROL_TIMEOUT_SETTINGS_SECURE =
Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS;
/** Controller class that control accessibility timeout settings. */
public class AccessibilityTimeoutController extends BasePreferenceController implements
LifecycleObserver, OnStart, OnStop , SelectorWithWidgetPreference.OnClickListener {
private static final List<String> ACCESSIBILITY_TIMEOUT_FEATURE_KEYS = Arrays.asList(
Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS,
Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS
);
// pair the preference key and timeout value.
private final Map<String, Integer> mAccessibilityTimeoutKeyToValueMap = new HashMap<>();
// RadioButtonPreference key, each preference represent a timeout value.
private final String mPreferenceKey;
private final ContentResolver mContentResolver;
private final Resources mResources;
private OnChangeListener mOnChangeListener;
private SelectorWithWidgetPreference mPreference;
private int mAccessibilityUiTimeoutValue;
private AccessibilitySettingsContentObserver mSettingsContentObserver;
public AccessibilityTimeoutController(Context context, Lifecycle lifecycle,
String preferenceKey) {
super(context);
public AccessibilityTimeoutController(Context context, String preferenceKey) {
super(context, preferenceKey);
mContentResolver = context.getContentResolver();
mResources = context.getResources();
if (lifecycle != null) {
lifecycle.addObserver(this);
}
mPreferenceKey = preferenceKey;
mSettingsContentObserver = new AccessibilitySettingsContentObserver(
new Handler(Looper.getMainLooper()));
mSettingsContentObserver.registerKeysToObserverCallback(ACCESSIBILITY_TIMEOUT_FEATURE_KEYS,
key -> updateState(mPreference));
}
protected static int getSecureAccessibilityTimeoutValue(ContentResolver resolver, String name) {
String timeOutSec = Settings.Secure.getString(resolver, name);
if (timeOutSec == null) {
return 0;
}
Integer timeOutValue = Ints.tryParse(timeOutSec);
return timeOutValue == null ? 0 : timeOutValue;
@VisibleForTesting
AccessibilityTimeoutController(Context context, String preferenceKey,
AccessibilitySettingsContentObserver contentObserver) {
this(context, preferenceKey);
mSettingsContentObserver = contentObserver;
}
public void setOnChangeListener(OnChangeListener listener) {
mOnChangeListener = listener;
@Override
public void onStart() {
mSettingsContentObserver.register(mContentResolver);
}
@Override
public void onStop() {
mSettingsContentObserver.unregister(mContentResolver);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
mPreference.setOnClickListener(this);
}
@Override
public void onRadioButtonClicked(SelectorWithWidgetPreference preference) {
final String value = String.valueOf(getTimeoutValueToKeyMap().get(mPreferenceKey));
// save value to both content and control timeout setting.
Settings.Secure.putString(mContentResolver,
Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS, value);
Settings.Secure.putString(mContentResolver,
Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, value);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
mAccessibilityUiTimeoutValue = AccessibilityTimeoutUtils.getSecureAccessibilityTimeoutValue(
mContentResolver);
// reset RadioButton
mPreference.setChecked(false);
final int preferenceValue = getTimeoutValueToKeyMap().get(mPreference.getKey());
if (mAccessibilityUiTimeoutValue == preferenceValue) {
mPreference.setChecked(true);
}
}
private Map<String, Integer> getTimeoutValueToKeyMap() {
if (mAccessibilityTimeoutKeyToValueMap.size() == 0) {
String[] timeoutKeys = mResources.getStringArray(
R.array.accessibility_timeout_control_selector_keys);
int[] timeoutValues = mResources.getIntArray(
final int[] timeoutValues = mResources.getIntArray(
R.array.accessibility_timeout_selector_values);
final int timeoutValueCount = timeoutValues.length;
for (int i = 0; i < timeoutValueCount; i++) {
mAccessibilityTimeoutKeyToValueMap.put(timeoutKeys[i], timeoutValues[i]);
@@ -99,77 +134,4 @@ public class AccessibilityTimeoutController extends AbstractPreferenceController
}
return mAccessibilityTimeoutKeyToValueMap;
}
private void putSecureString(String name, String value) {
Settings.Secure.putString(mContentResolver, name, value);
}
private void handlePreferenceChange(String value) {
// save value to both content and control timeout setting.
putSecureString(Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS, value);
putSecureString(Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, value);
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public String getPreferenceKey() {
return mPreferenceKey;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = (SelectorWithWidgetPreference)
screen.findPreference(getPreferenceKey());
mPreference.setOnClickListener(this);
}
@Override
public void onRadioButtonClicked(SelectorWithWidgetPreference preference) {
int value = getTimeoutValueToKeyMap().get(mPreferenceKey);
handlePreferenceChange(String.valueOf(value));
if (mOnChangeListener != null) {
mOnChangeListener.onCheckedChanged(mPreference);
}
}
private int getAccessibilityTimeoutValue() {
// get accessibility control timeout value
int timeoutValue = getSecureAccessibilityTimeoutValue(mContentResolver,
CONTROL_TIMEOUT_SETTINGS_SECURE);
return timeoutValue;
}
protected void updatePreferenceCheckedState(int value) {
if (mAccessibilityUiTimeoutValue == value) {
mPreference.setChecked(true);
}
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
mAccessibilityUiTimeoutValue = getAccessibilityTimeoutValue();
// reset RadioButton
mPreference.setChecked(false);
int preferenceValue = getTimeoutValueToKeyMap().get(mPreference.getKey());
updatePreferenceCheckedState(preferenceValue);
}
/**
* Listener interface handles checked event.
*/
public interface OnChangeListener {
/**
* A hook that is called when preference checked.
*/
void onCheckedChanged(Preference preference);
}
}

View File

@@ -16,17 +16,25 @@
package com.android.settings.accessibility;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.google.common.primitives.Ints;
/** Preference controller for accessibility timeout. */
public class AccessibilityTimeoutPreferenceController extends BasePreferenceController {
private final ContentResolver mContentResolver;
private final Resources mResources;
public AccessibilityTimeoutPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mContentResolver = mContext.getContentResolver();
mResources = mContext.getResources();
}
@Override
@@ -36,14 +44,13 @@ public class AccessibilityTimeoutPreferenceController extends BasePreferenceCont
@Override
public CharSequence getSummary() {
final String[] timeoutSummarys = mContext.getResources().getStringArray(
final String[] timeoutSummaries = mResources.getStringArray(
R.array.accessibility_timeout_summaries);
final int[] timeoutValues = mContext.getResources().getIntArray(
final int[] timeoutValues = mResources.getIntArray(
R.array.accessibility_timeout_selector_values);
final int timeoutValue = AccessibilityTimeoutController.getSecureAccessibilityTimeoutValue(
mContext.getContentResolver(),
AccessibilityTimeoutController.CONTROL_TIMEOUT_SETTINGS_SECURE);
final int timeoutValue = AccessibilityTimeoutUtils.getSecureAccessibilityTimeoutValue(
mContentResolver);
final int idx = Ints.indexOf(timeoutValues, timeoutValue);
return timeoutSummarys[idx == -1 ? 0 : idx];
return timeoutSummaries[idx == -1 ? 0 : idx];
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2022 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.provider.Settings;
import com.google.common.primitives.Ints;
/** Provides utility methods to accessibility timeout. */
public final class AccessibilityTimeoutUtils {
/**
* Gets accessibility control timeout value.
*
* @param resolver the resolver to use
* @return accessibility control timeout value
*/
public static int getSecureAccessibilityTimeoutValue(ContentResolver resolver) {
final String timeOutSec = Settings.Secure.getString(resolver,
Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS);
if (timeOutSec == null) {
return 0;
}
Integer timeOutValue = Ints.tryParse(timeOutSec);
return timeOutValue == null ? 0 : timeOutValue;
}
private AccessibilityTimeoutUtils(){}
}

View File

@@ -16,6 +16,9 @@
package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import android.content.Context;
import android.provider.Settings;
import android.view.accessibility.AccessibilityManager;
@@ -23,8 +26,19 @@ import android.view.accessibility.AccessibilityManager;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
/** Preference controller for autoclick (dwell timing). */
public class AutoclickPreferenceController extends BasePreferenceController {
/**
* Resource ids from which autoclick preference summaries should be derived. The strings have
* placeholder for integer delay value.
*/
private static final int[] AUTOCLICK_PREFERENCE_SUMMARIES = {
R.string.accessibilty_autoclick_preference_subtitle_short_delay,
R.string.accessibilty_autoclick_preference_subtitle_medium_delay,
R.string.accessibilty_autoclick_preference_subtitle_long_delay
};
public AutoclickPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@@ -37,14 +51,29 @@ public class AutoclickPreferenceController extends BasePreferenceController {
@Override
public CharSequence getSummary() {
final boolean enabled = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, 0) == 1;
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, OFF) == ON;
if (!enabled) {
return mContext.getResources().getText(R.string.accessibility_feature_state_off);
}
final int delay = Settings.Secure.getInt(mContext.getContentResolver(),
final int delayMillis = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
AccessibilityManager.AUTOCLICK_DELAY_DEFAULT);
return ToggleAutoclickPreferenceFragment.getAutoclickPreferenceSummary(
mContext.getResources(), delay);
final int summaryIndex = getAutoclickPreferenceSummaryIndex(delayMillis);
return AutoclickUtils.getAutoclickDelaySummary(mContext,
AUTOCLICK_PREFERENCE_SUMMARIES[summaryIndex], delayMillis);
}
}
/** Finds index of the summary that should be used for the provided autoclick delay. */
private int getAutoclickPreferenceSummaryIndex(int delay) {
if (delay <= AutoclickUtils.MIN_AUTOCLICK_DELAY_MS) {
return 0;
}
if (delay >= AutoclickUtils.MAX_AUTOCLICK_DELAY_MS) {
return AUTOCLICK_PREFERENCE_SUMMARIES.length - 1;
}
int delayRange =
AutoclickUtils.MAX_AUTOCLICK_DELAY_MS - AutoclickUtils.MIN_AUTOCLICK_DELAY_MS;
int rangeSize = (delayRange) / (AUTOCLICK_PREFERENCE_SUMMARIES.length - 1);
return (delay - AutoclickUtils.MIN_AUTOCLICK_DELAY_MS) / rangeSize;
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2022 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 java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
import android.annotation.PluralsRes;
import android.annotation.StringRes;
import android.content.Context;
import android.content.res.Resources;
import com.android.settings.R;
import com.android.settingslib.utils.StringUtil;
import java.lang.annotation.Retention;
import java.util.HashMap;
import java.util.Map;
/** Provides utility methods related auto click. */
public final class AutoclickUtils {
/** Used for autoclick mode in the preferences editor. */
static final String KEY_DELAY_MODE = "delay_mode";
/** Used for autoclick custom delay in the preferences editor. */
static final String KEY_CUSTOM_DELAY_VALUE = "custom_delay_value";
/** Min allowed autoclick delay value. */
static final int MIN_AUTOCLICK_DELAY_MS = 200;
/** Max allowed autoclick delay value. */
static final int MAX_AUTOCLICK_DELAY_MS = 1000;
/**
* Allowed autoclick delay values are discrete. This is the difference between two allowed
* values.
*/
static final int AUTOCLICK_DELAY_STEP = 100;
@Retention(SOURCE)
@IntDef({
Quantity.ONE,
Quantity.FEW
})
private @interface Quantity {
int ONE = 1;
int FEW = 3;
}
/**
* Gets string that should be used for provided autoclick delay.
*
* @param context context from which string should be retrieved.
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
* entry. The value 0 is an invalid identifier.
* @param delayMillis Delay for whose value summary should be retrieved.
*/
public static CharSequence getAutoclickDelaySummary(Context context,
@StringRes int id, int delayMillis) {
final int quantity = (delayMillis == 1000) ? Quantity.ONE : Quantity.FEW;
final float delaySecond = (float) delayMillis / 1000;
// Only show integer when delay time is 1.
final String decimalFormat = (delaySecond == 1) ? "%.0f" : "%.1f";
Map<String, Object> arguments = new HashMap<>();
arguments.put("count", quantity);
arguments.put("time", String.format(decimalFormat, delaySecond));
return StringUtil.getIcuPluralsString(context, arguments, id);
}
private AutoclickUtils(){}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2023 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.bluetooth.BluetoothProfile;
import android.content.Context;
import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
/**
* Controller to update the {@link androidx.preference.PreferenceCategory} for all
* connected hearing devices, including ASHA and HAP profile.
* Parent class {@link BaseBluetoothDevicePreferenceController} will use
* {@link DevicePreferenceCallback} to add/remove {@link Preference}.
*/
public class AvailableHearingDevicePreferenceController extends
BaseBluetoothDevicePreferenceController implements LifecycleObserver, OnStart, OnStop,
BluetoothCallback {
private static final String TAG = "AvailableHearingDevicePreferenceController";
private BluetoothDeviceUpdater mAvailableHearingDeviceUpdater;
private final LocalBluetoothManager mLocalBluetoothManager;
private FragmentManager mFragmentManager;
public AvailableHearingDevicePreferenceController(Context context,
String preferenceKey) {
super(context, preferenceKey);
mLocalBluetoothManager = com.android.settings.bluetooth.Utils.getLocalBluetoothManager(
context);
}
/**
* Initializes objects in this controller. Need to call this before onStart().
*
* <p>Should not call this more than 1 time.
*
* @param fragment The {@link DashboardFragment} uses the controller.
*/
public void init(DashboardFragment fragment) {
if (mAvailableHearingDeviceUpdater != null) {
throw new IllegalStateException("Should not call init() more than 1 time.");
}
mAvailableHearingDeviceUpdater = new AvailableHearingDeviceUpdater(fragment.getContext(),
this, fragment.getMetricsCategory());
mFragmentManager = fragment.getParentFragmentManager();
}
@Override
public void onStart() {
mAvailableHearingDeviceUpdater.registerCallback();
mAvailableHearingDeviceUpdater.refreshPreference();
mLocalBluetoothManager.getEventManager().registerCallback(this);
}
@Override
public void onStop() {
mAvailableHearingDeviceUpdater.unregisterCallback();
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
if (isAvailable()) {
final Context context = screen.getContext();
mAvailableHearingDeviceUpdater.setPrefContext(context);
mAvailableHearingDeviceUpdater.forceUpdate();
}
}
@Override
public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
if (activeDevice == null) {
return;
}
if (bluetoothProfile == BluetoothProfile.HEARING_AID) {
HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, activeDevice);
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2023 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.bluetooth.BluetoothDevice;
import android.content.Context;
import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
/**
* Maintains and updates connected hearing devices, including ASHA and HAP profile.
*/
public class AvailableHearingDeviceUpdater extends AvailableMediaBluetoothDeviceUpdater {
private static final String PREF_KEY = "connected_hearing_device";
public AvailableHearingDeviceUpdater(Context context,
DevicePreferenceCallback devicePreferenceCallback, int metricsCategory) {
super(context, devicePreferenceCallback, metricsCategory);
}
@Override
public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) {
final BluetoothDevice device = cachedDevice.getDevice();
final boolean isConnectedHearingAidDevice = (cachedDevice.isConnectedHearingAidDevice()
&& (device.getBondState() == BluetoothDevice.BOND_BONDED));
return isConnectedHearingAidDevice && isDeviceInCachedDevicesList(cachedDevice);
}
@Override
protected String getPreferenceKey() {
return PREF_KEY;
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2023 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.pm.PackageManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.core.BasePreferenceController;
/**
* Abstract base class for bluetooth preference controller to handle UI logic, e.g. availability
* status, preference added, and preference removed.
*/
public abstract class BaseBluetoothDevicePreferenceController extends BasePreferenceController
implements DevicePreferenceCallback {
private PreferenceCategory mPreferenceCategory;
public BaseBluetoothDevicePreferenceController(Context context,
String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH))
? AVAILABLE
: CONDITIONALLY_UNAVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreferenceCategory = screen.findPreference(getPreferenceKey());
mPreferenceCategory.setVisible(false);
}
@Override
public void onDeviceAdded(Preference preference) {
if (mPreferenceCategory.getPreferenceCount() == 0) {
mPreferenceCategory.setVisible(true);
}
mPreferenceCategory.addPreference(preference);
}
@Override
public void onDeviceRemoved(Preference preference) {
mPreferenceCategory.removePreference(preference);
if (mPreferenceCategory.getPreferenceCount() == 0) {
mPreferenceCategory.setVisible(false);
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import android.content.Context;
import android.provider.Settings;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.overlay.FeatureFactory;
/**
* Controller for Camera flash notification.
*/
public class CameraFlashNotificationPreferenceController extends TogglePreferenceController {
public CameraFlashNotificationPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return FlashNotificationsUtil.isTorchAvailable(mContext)
? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
public boolean isChecked() {
return Settings.System.getInt(mContext.getContentResolver(),
Settings.System.CAMERA_FLASH_NOTIFICATION, OFF) != OFF;
}
@Override
public boolean setChecked(boolean isChecked) {
FeatureFactory.getFactory(mContext).getMetricsFeatureProvider().changed(
getMetricsCategory(), getPreferenceKey(), isChecked ? 1 : 0);
return Settings.System.putInt(mContext.getContentResolver(),
Settings.System.CAMERA_FLASH_NOTIFICATION, (isChecked ? ON : OFF));
}
@Override
public int getSliceHighlightMenuRes() {
return R.string.menu_key_accessibility;
}
}

View File

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

View File

@@ -0,0 +1,214 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import android.content.ContentResolver;
import android.content.Context;
import android.provider.Settings;
import android.view.View;
import android.view.accessibility.CaptioningManager;
import android.view.accessibility.CaptioningManager.CaptionStyle;
import com.android.internal.widget.SubtitleView;
import com.android.settings.R;
import com.android.settingslib.accessibility.AccessibilityUtils;
import com.google.common.annotations.VisibleForTesting;
import java.util.Locale;
/** Helper class for caption. */
public class CaptionHelper {
/* WebVtt specifies line height as 5.3% of the viewport height. */
@VisibleForTesting
static final float LINE_HEIGHT_RATIO = 0.0533f;
private final Context mContext;
private final ContentResolver mContentResolver;
private final CaptioningManager mCaptioningManager;
public CaptionHelper(Context context) {
mContext = context;
mContentResolver = mContext.getContentResolver();
mCaptioningManager = context.getSystemService(CaptioningManager.class);
}
/**
* Sets the user's preferred captioning enabled state.
*
* @param enabled Whether to enable or disable captioning manager.
*/
public void setEnabled(boolean enabled) {
if (isEnabled() == enabled) {
return;
}
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, enabled ? ON : OFF);
}
/**
* Gets if the captioning manager is enabled.
*
* @return True if the captioning manager is enabled, false otherwise.
*/
public boolean isEnabled() {
return mCaptioningManager.isEnabled();
}
/**
* Updates font style of captioning properties for preview screen.
*
* @param previewText preview text
* @param previewWindow preview window
* @param styleId font style id
*/
public void applyCaptionProperties(SubtitleView previewText, View previewWindow,
int styleId) {
previewText.setStyle(styleId);
final float fontScale = mCaptioningManager.getFontScale();
if (previewWindow != null) {
// Assume the viewport is clipped with a 16:9 aspect ratio.
final float virtualHeight = Math.max(9 * previewWindow.getWidth(),
16 * previewWindow.getHeight()) / 16.0f;
previewText.setTextSize(virtualHeight * LINE_HEIGHT_RATIO * fontScale);
} else {
final float textSize = mContext.getResources().getDimension(
R.dimen.captioning_preview_text_size);
previewText.setTextSize(textSize * fontScale);
}
final Locale locale = mCaptioningManager.getLocale();
if (locale != null) {
final CharSequence localizedText = AccessibilityUtils.getTextForLocale(
mContext, locale, R.string.captioning_preview_characters);
previewText.setText(localizedText);
} else {
previewText.setText(R.string.captioning_preview_characters);
}
}
/**
* Sets the user's preferred captioning background color.
*
* @param color The captioning background color
*/
public void setBackgroundColor(int color) {
Settings.Secure.putInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, color);
}
/** Returns the captioning background color.*/
public int getBackgroundColor() {
final CaptionStyle attrs = CaptionStyle.getCustomStyle(mContentResolver);
return attrs.hasBackgroundColor() ? attrs.backgroundColor : CaptionStyle.COLOR_UNSPECIFIED;
}
/**
* Sets the user's preferred captioning foreground color.
*
* @param color The captioning foreground color
*/
public void setForegroundColor(int color) {
Settings.Secure.putInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, color);
}
/** Returns the captioning foreground color.*/
public int getForegroundColor() {
final CaptionStyle attrs = CaptionStyle.getCustomStyle(mContentResolver);
return attrs.hasForegroundColor() ? attrs.foregroundColor : CaptionStyle.COLOR_UNSPECIFIED;
}
/**
* Sets the user's preferred captioning window color.
*
* @param color The captioning window color
*/
public void setWindowColor(int color) {
Settings.Secure.putInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, color);
}
/** Returns the captioning window color.*/
public int getWindowColor() {
final CaptionStyle attrs = CaptionStyle.getCustomStyle(mContentResolver);
return attrs.hasWindowColor() ? attrs.windowColor : CaptionStyle.COLOR_UNSPECIFIED;
}
/**
* Sets the user's preferred captioning edge color.
*
* @param color The captioning edge color
*/
public void setEdgeColor(int color) {
Settings.Secure.putInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, color);
}
/** Returns the captioning edge color.*/
public int getEdgeColor() {
final CaptionStyle attrs = CaptionStyle.getCustomStyle(mContentResolver);
return attrs.edgeColor;
}
/**
* Sets the user's preferred captioning edge type.
*
* @param type The captioning edge type
*/
public void setEdgeType(int type) {
Settings.Secure.putInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, type);
}
/** Returns the captioning edge type.*/
public int getEdgeType() {
final CaptionStyle attrs = CaptionStyle.getCustomStyle(mContentResolver);
return attrs.edgeType;
}
/**
* Sets the captioning raw user style.
*
* @param type The captioning raw user style
*/
public void setRawUserStyle(int type) {
Settings.Secure.putInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET, type);
}
/** Returns the captioning raw preset number.*/
public int getRawUserStyle() {
return mCaptioningManager.getRawUserStyle();
}
/** Returns the captioning visual properties.*/
public CaptionStyle getUserStyle() {
return mCaptioningManager.getUserStyle();
}
/** Returns the captioning locale language.*/
public Locale getLocale() {
return mCaptioningManager.getLocale();
}
}

View File

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

View File

@@ -1,129 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Bundle;
import android.provider.Settings;
import android.view.accessibility.CaptioningManager;
import android.widget.Switch;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
import com.android.settings.R;
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 java.util.ArrayList;
import java.util.List;
/** Settings fragment containing captioning properties. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
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 SettingsMainSwitchPreference mSwitch;
private Preference mTextAppearance;
private Preference mMoreOptions;
private final List<Preference> mPreferenceList = new ArrayList<>();
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_CAPTION_PROPERTIES;
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
super.onCreatePreferences(savedInstanceState, rootKey);
mCaptioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
initializeAllPreferences();
installUpdateListeners();
}
@Override
public void onResume() {
super.onResume();
mSwitch.setChecked(mCaptioningManager.isEnabled());
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.captioning_settings;
}
@Override
protected String getLogTag() {
return TAG;
}
private void initializeAllPreferences() {
mSwitch = (SettingsMainSwitchPreference) findPreference(PREF_SWITCH);
mTextAppearance = (Preference) findPreference(PREF_TEXT);
mMoreOptions = (Preference) findPreference(PREF_MORE);
mPreferenceList.add(mTextAppearance);
mPreferenceList.add(mMoreOptions);
}
private void installUpdateListeners() {
mSwitch.setOnPreferenceChangeListener(this);
mSwitch.addOnSwitchChangeListener(this);
}
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
final ContentResolver cr = getActivity().getContentResolver();
if (mSwitch == preference) {
Settings.Secure.putInt(
cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, (boolean) value ? 1 : 0);
}
return true;
}
@Override
public int getHelpResource() {
return R.string.help_url_caption;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.captioning_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,88 @@
/*
* Copyright (C) 2022 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.graphics.Color;
import android.view.accessibility.CaptioningManager.CaptionStyle;
/** Provides utility methods related caption. */
public final class CaptionUtils {
/**
* Unpacks the specified color value to get the color value.
*
* @param value the specified color value.
*/
public static int parseColor(int value) {
final int colorValue;
if (!CaptionStyle.hasColor(value)) {
// "Default" color with variable alpha.
colorValue = CaptionStyle.COLOR_UNSPECIFIED;
} else if ((value >>> 24) == 0) {
// "None" color with variable alpha.
colorValue = Color.TRANSPARENT;
} else {
// Normal color.
colorValue = value | 0xFF000000;
}
return colorValue;
}
/**
* Unpacks the specified color value to get the opacity value.
*
* @param value the specified color value.
*/
public static int parseOpacity(int value) {
final int opacityValue;
if (!CaptionStyle.hasColor(value)) {
// "Default" color with variable alpha.
opacityValue = (value & 0xFF) << 24;
} else if ((value >>> 24) == 0) {
// "None" color with variable alpha.
opacityValue = (value & 0xFF) << 24;
} else {
// Normal color.
opacityValue = value & 0xFF000000;
}
// Opacity value is always white.
return opacityValue | 0xFFFFFF;
}
/**
* Packs the specified color value and specified opacity value into merged color value.
*
* @param colorValue the color value.
* @param opacityValue the opacity value.
*/
public static int mergeColorOpacity(int colorValue, int opacityValue) {
final int value;
// "Default" is 0x00FFFFFF or, for legacy support, 0x00000100.
if (!CaptionStyle.hasColor(colorValue)) {
// Encode "default" as 0x00FFFFaa.
value = 0x00FFFF00 | Color.alpha(opacityValue);
} else if (colorValue == Color.TRANSPARENT) {
// Encode "none" as 0x000000aa.
value = Color.alpha(opacityValue);
} else {
// Encode custom color normally.
value = (colorValue & 0x00FFFFFF) | (opacityValue & 0xFF000000);
}
return value;
}
}

View File

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

View File

@@ -25,12 +25,12 @@ import com.android.settings.core.BasePreferenceController;
import com.google.common.primitives.Floats;
import com.google.common.primitives.Ints;
/** Controller that shows the caption scale and style summary. */
public class CaptionAppearancePreferenceController extends BasePreferenceController {
/** Controller that shows the captioning scale and style summary. */
public class CaptioningAppearancePreferenceController extends BasePreferenceController {
private final CaptioningManager mCaptioningManager;
public CaptionAppearancePreferenceController(Context context, String preferenceKey) {
public CaptioningAppearancePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptioningManager = context.getSystemService(CaptioningManager.class);
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2022 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.res.Resources;
import android.graphics.Color;
import android.view.accessibility.CaptioningManager.CaptionStyle;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.accessibility.ListDialogPreference.OnValueChangedListener;
import com.android.settings.core.BasePreferenceController;
/** Preference controller for captioning background color. */
public class CaptioningBackgroundColorController extends BasePreferenceController
implements OnValueChangedListener {
private final CaptionHelper mCaptionHelper;
private int mCachedNonDefaultOpacity = CaptionStyle.COLOR_UNSPECIFIED;
public CaptioningBackgroundColorController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptionHelper = new CaptionHelper(context);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final ColorPreference preference = screen.findPreference(getPreferenceKey());
final Resources res = mContext.getResources();
final int[] colorValues = res.getIntArray(R.array.captioning_color_selector_values);
final String[] colorTitles = res.getStringArray(
R.array.captioning_color_selector_titles);
// Add "none" as an additional option for backgrounds.
final int[] backgroundColorValues = new int[colorValues.length + 1];
final String[] backgroundColorTitles = new String[colorTitles.length + 1];
System.arraycopy(colorValues, 0, backgroundColorValues, 1, colorValues.length);
System.arraycopy(colorTitles, 0, backgroundColorTitles, 1, colorTitles.length);
backgroundColorValues[0] = Color.TRANSPARENT;
backgroundColorTitles[0] = mContext.getString(R.string.color_none);
preference.setTitles(backgroundColorTitles);
preference.setValues(backgroundColorValues);
final int backBackgroundColor = mCaptionHelper.getBackgroundColor();
final int color = CaptionUtils.parseColor(backBackgroundColor);
preference.setValue(color);
preference.setOnValueChangedListener(this);
}
@Override
public void onValueChanged(ListDialogPreference preference, int value) {
final boolean isNonDefaultColor = CaptionStyle.hasColor(value);
final int opacity = getNonDefaultOpacity(isNonDefaultColor);
final int merged = CaptionUtils.mergeColorOpacity(value, opacity);
mCaptionHelper.setBackgroundColor(merged);
mCaptionHelper.setEnabled(true);
}
private int getNonDefaultOpacity(boolean isNonDefaultColor) {
final int backBackgroundColor = mCaptionHelper.getBackgroundColor();
final int opacity = CaptionUtils.parseOpacity(backBackgroundColor);
if (isNonDefaultColor) {
final int lastOpacity = mCachedNonDefaultOpacity != CaptionStyle.COLOR_UNSPECIFIED
? mCachedNonDefaultOpacity : opacity;
// Reset cached opacity to use current color opacity to merge color.
mCachedNonDefaultOpacity = CaptionStyle.COLOR_UNSPECIFIED;
return lastOpacity;
}
// When default captioning color was selected, the opacity become 100% and make opacity
// preference disable. Cache the latest opacity to show the correct opacity later.
mCachedNonDefaultOpacity = opacity;
return opacity;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2022 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.res.Resources;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.accessibility.ListDialogPreference.OnValueChangedListener;
import com.android.settings.core.BasePreferenceController;
/** Preference controller for captioning background opacity. */
public class CaptioningBackgroundOpacityController extends BasePreferenceController
implements OnValueChangedListener {
private final CaptionHelper mCaptionHelper;
public CaptioningBackgroundOpacityController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptionHelper = new CaptionHelper(context);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final ColorPreference preference = screen.findPreference(getPreferenceKey());
final Resources res = mContext.getResources();
final int[] opacityValues = res.getIntArray(R.array.captioning_opacity_selector_values);
final String[] opacityTitles = res.getStringArray(
R.array.captioning_opacity_selector_titles);
preference.setTitles(opacityTitles);
preference.setValues(opacityValues);
final int backBackgroundColor = mCaptionHelper.getBackgroundColor();
final int opacity = CaptionUtils.parseOpacity(backBackgroundColor);
preference.setValue(opacity);
preference.setOnValueChangedListener(this);
}
@Override
public void onValueChanged(ListDialogPreference preference, int value) {
final int backBackgroundColor = mCaptionHelper.getBackgroundColor();
final int color = CaptionUtils.parseColor(backBackgroundColor);
final int merged = CaptionUtils.mergeColorOpacity(color, value);
mCaptionHelper.setBackgroundColor(merged);
mCaptionHelper.setEnabled(true);
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C) 2022 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.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.view.accessibility.CaptioningManager;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import java.util.Arrays;
import java.util.List;
/** Preference controller for captioning custom visibility. */
public class CaptioningCustomController extends BasePreferenceController
implements LifecycleObserver, OnStart, OnStop {
private Preference mCustom;
private final CaptionHelper mCaptionHelper;
private final ContentResolver mContentResolver;
@VisibleForTesting
AccessibilitySettingsContentObserver mSettingsContentObserver;
@VisibleForTesting
static final List<String> CAPTIONING_FEATURE_KEYS = Arrays.asList(
Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET
);
public CaptioningCustomController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptionHelper = new CaptionHelper(context);
mContentResolver = context.getContentResolver();
mSettingsContentObserver = new AccessibilitySettingsContentObserver(
new Handler(Looper.getMainLooper()));
mSettingsContentObserver.registerKeysToObserverCallback(CAPTIONING_FEATURE_KEYS,
key -> refreshShowingCustom());
}
@VisibleForTesting
CaptioningCustomController(Context context, String preferenceKey,
AccessibilitySettingsContentObserver contentObserver) {
this(context, preferenceKey);
mSettingsContentObserver = contentObserver;
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mCustom = screen.findPreference(getPreferenceKey());
refreshShowingCustom();
}
@Override
public void onStart() {
mSettingsContentObserver.register(mContentResolver);
}
@Override
public void onStop() {
mSettingsContentObserver.unregister(mContentResolver);
}
private void refreshShowingCustom() {
final boolean isCustomPreset =
mCaptionHelper.getRawUserStyle() == CaptioningManager.CaptionStyle.PRESET_CUSTOM;
mCustom.setVisible(isCustomPreset);
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2022 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.res.Resources;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.accessibility.ListDialogPreference.OnValueChangedListener;
import com.android.settings.core.BasePreferenceController;
/** Preference controller for captioning edge color. */
public class CaptioningEdgeColorController extends BasePreferenceController
implements OnValueChangedListener {
private final CaptionHelper mCaptionHelper;
public CaptioningEdgeColorController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptionHelper = new CaptionHelper(context);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final ColorPreference preference = screen.findPreference(getPreferenceKey());
final Resources res = mContext.getResources();
final int[] colorValues = res.getIntArray(R.array.captioning_color_selector_values);
final String[] colorTitles = res.getStringArray(
R.array.captioning_color_selector_titles);
preference.setTitles(colorTitles);
preference.setValues(colorValues);
preference.setValue(mCaptionHelper.getEdgeColor());
preference.setOnValueChangedListener(this);
}
@Override
public void onValueChanged(ListDialogPreference preference, int value) {
mCaptionHelper.setEdgeColor(value);
mCaptionHelper.setEnabled(true);
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2022 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.accessibility.ListDialogPreference.OnValueChangedListener;
import com.android.settings.core.BasePreferenceController;
/** Preference controller for captioning edge type. */
public class CaptioningEdgeTypeController extends BasePreferenceController
implements OnValueChangedListener {
private final CaptionHelper mCaptionHelper;
public CaptioningEdgeTypeController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptionHelper = new CaptionHelper(context);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final EdgeTypePreference preference = screen.findPreference(getPreferenceKey());
preference.setValue(mCaptionHelper.getEdgeType());
preference.setOnValueChangedListener(this);
}
@Override
public void onValueChanged(ListDialogPreference preference, int value) {
mCaptionHelper.setEdgeType(value);
mCaptionHelper.setEnabled(true);
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.provider.Settings;
import android.view.accessibility.CaptioningManager;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import com.android.settings.core.BasePreferenceController;
/** Preference controller for captioning font size. */
public class CaptioningFontSizeController extends BasePreferenceController
implements Preference.OnPreferenceChangeListener {
private final CaptioningManager mCaptioningManager;
private final CaptionHelper mCaptionHelper;
public CaptioningFontSizeController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptioningManager = context.getSystemService(CaptioningManager.class);
mCaptionHelper = new CaptionHelper(context);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
final ListPreference listPreference = (ListPreference) preference;
final float fontSize = mCaptioningManager.getFontScale();
listPreference.setValue(Float.toString(fontSize));
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
Settings.Secure.putFloat(
mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE,
Float.parseFloat((String) newValue));
mCaptionHelper.setEnabled(true);
return true;
}
}

View File

@@ -20,12 +20,10 @@ import android.content.Context;
import com.android.settings.R;
/**
* Preference controller for caption footer.
*/
public class CaptionFooterPreferenceController extends AccessibilityFooterPreferenceController {
/** Preference controller for captioning footer. */
public class CaptioningFooterPreferenceController extends AccessibilityFooterPreferenceController {
public CaptionFooterPreferenceController(Context context, String key) {
public CaptioningFooterPreferenceController(Context context, String key) {
super(context, key);
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2022 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.res.Resources;
import android.view.accessibility.CaptioningManager.CaptionStyle;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.accessibility.ListDialogPreference.OnValueChangedListener;
import com.android.settings.core.BasePreferenceController;
/** Preference controller for captioning foreground color. */
public class CaptioningForegroundColorController extends BasePreferenceController
implements OnValueChangedListener {
private final CaptionHelper mCaptionHelper;
private int mCachedNonDefaultOpacity = CaptionStyle.COLOR_UNSPECIFIED;
public CaptioningForegroundColorController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptionHelper = new CaptionHelper(context);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final ColorPreference preference = screen.findPreference(getPreferenceKey());
final Resources res = mContext.getResources();
final int[] colorValues = res.getIntArray(R.array.captioning_color_selector_values);
final String[] colorTitles = res.getStringArray(
R.array.captioning_color_selector_titles);
preference.setTitles(colorTitles);
preference.setValues(colorValues);
final int foregroundColor = mCaptionHelper.getForegroundColor();
final int color = CaptionUtils.parseColor(foregroundColor);
preference.setValue(color);
preference.setOnValueChangedListener(this);
}
@Override
public void onValueChanged(ListDialogPreference preference, int value) {
final boolean isNonDefaultColor = CaptionStyle.hasColor(value);
final int opacity = getNonDefaultOpacity(isNonDefaultColor);
final int merged = CaptionUtils.mergeColorOpacity(value, opacity);
mCaptionHelper.setForegroundColor(merged);
mCaptionHelper.setEnabled(true);
}
private int getNonDefaultOpacity(boolean isNonDefaultColor) {
final int foregroundColor = mCaptionHelper.getForegroundColor();
final int opacity = CaptionUtils.parseOpacity(foregroundColor);
if (isNonDefaultColor) {
final int lastOpacity = mCachedNonDefaultOpacity != CaptionStyle.COLOR_UNSPECIFIED
? mCachedNonDefaultOpacity : opacity;
// Reset cached opacity to use current color opacity to merge color.
mCachedNonDefaultOpacity = CaptionStyle.COLOR_UNSPECIFIED;
return lastOpacity;
}
// When default captioning color was selected, the opacity become 100% and make opacity
// preference disable. Cache the latest opacity to show the correct opacity later.
mCachedNonDefaultOpacity = opacity;
return opacity;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2022 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.res.Resources;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.accessibility.ListDialogPreference.OnValueChangedListener;
import com.android.settings.core.BasePreferenceController;
/** Preference controller for captioning foreground opacity. */
public class CaptioningForegroundOpacityController extends BasePreferenceController
implements OnValueChangedListener {
private final CaptionHelper mCaptionHelper;
public CaptioningForegroundOpacityController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptionHelper = new CaptionHelper(context);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final ColorPreference preference = screen.findPreference(getPreferenceKey());
final Resources res = mContext.getResources();
final int[] opacityValues = res.getIntArray(R.array.captioning_opacity_selector_values);
final String[] opacityTitles = res.getStringArray(
R.array.captioning_opacity_selector_titles);
preference.setTitles(opacityTitles);
preference.setValues(opacityValues);
final int foregroundColor = mCaptionHelper.getForegroundColor();
final int opacity = CaptionUtils.parseOpacity(foregroundColor);
preference.setValue(opacity);
preference.setOnValueChangedListener(this);
}
@Override
public void onValueChanged(ListDialogPreference preference, int value) {
final int foregroundColor = mCaptionHelper.getForegroundColor();
final int color = CaptionUtils.parseColor(foregroundColor);
final int merged = CaptionUtils.mergeColorOpacity(color, value);
mCaptionHelper.setForegroundColor(merged);
mCaptionHelper.setEnabled(true);
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.provider.Settings;
import android.view.accessibility.CaptioningManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.core.BasePreferenceController;
/** Controller that shows the captioning locale summary. */
public class CaptioningLocalePreferenceController extends BasePreferenceController
implements Preference.OnPreferenceChangeListener {
private final CaptioningManager mCaptioningManager;
public CaptioningLocalePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptioningManager = context.getSystemService(CaptioningManager.class);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final LocalePreference localePreference = screen.findPreference(getPreferenceKey());
final String rawLocale = mCaptioningManager.getRawLocale();
localePreference.setValue(rawLocale == null ? "" : rawLocale);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final LocalePreference localePreference = (LocalePreference) preference;
Settings.Secure.putString(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_CAPTIONING_LOCALE, (String) newValue);
localePreference.setValue((String) newValue);
return true;
}
}

View File

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

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2022 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.res.Resources;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
/** Preference controller for captioning preset. */
public class CaptioningPresetController extends BasePreferenceController
implements ListDialogPreference.OnValueChangedListener {
private final CaptionHelper mCaptionHelper;
public CaptioningPresetController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptionHelper = new CaptionHelper(context);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final PresetPreference preference = screen.findPreference(getPreferenceKey());
final Resources res = mContext.getResources();
final int[] presetValues = res.getIntArray(R.array.captioning_preset_selector_values);
final String[] presetTitles = res.getStringArray(R.array.captioning_preset_selector_titles);
preference.setTitles(presetTitles);
preference.setValues(presetValues);
final int preset = mCaptionHelper.getRawUserStyle();
preference.setValue(preset);
preference.setOnValueChangedListener(this);
}
@Override
public void onValueChanged(ListDialogPreference preference, int value) {
mCaptionHelper.setRawUserStyle(value);
mCaptionHelper.setEnabled(true);
}
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright (C) 2022 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.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.view.View;
import android.view.accessibility.CaptioningManager.CaptionStyle;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
import com.android.internal.widget.SubtitleView;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.accessibility.AccessibilityUtils;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settingslib.widget.LayoutPreference;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
/** Controller that shows the captioning locale summary. */
public class CaptioningPreviewPreferenceController extends BasePreferenceController
implements LifecycleObserver, OnStart, OnStop {
@VisibleForTesting
static final List<String> CAPTIONING_FEATURE_KEYS = Arrays.asList(
Settings.Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR,
Settings.Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR,
Settings.Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR,
Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR,
Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET,
Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE,
Settings.Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE,
Settings.Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE
);
private final Handler mHandler = new Handler(Looper.getMainLooper());
@VisibleForTesting
AccessibilitySettingsContentObserver mSettingsContentObserver;
private CaptionHelper mCaptionHelper;
private LayoutPreference mPreference;
public CaptioningPreviewPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptionHelper = new CaptionHelper(context);
mSettingsContentObserver = new AccessibilitySettingsContentObserver(mHandler);
mSettingsContentObserver.registerKeysToObserverCallback(CAPTIONING_FEATURE_KEYS,
key -> refreshPreviewText());
}
@Override
public void onStart() {
mSettingsContentObserver.register(mContext.getContentResolver());
}
@Override
public void onStop() {
mContext.getContentResolver().unregisterContentObserver(mSettingsContentObserver);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
final View previewViewport = mPreference.findViewById(R.id.preview_viewport);
previewViewport.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if ((oldRight - oldLeft) != (right - left)) {
// Remove the listener once the callback is triggered.
previewViewport.removeOnLayoutChangeListener(this);
mHandler.post(() -> refreshPreviewText());
}
}
});
}
private void refreshPreviewText() {
final SubtitleView previewText = mPreference.findViewById(R.id.preview_text);
if (previewText != null) {
final View previewViewport = mPreference.findViewById(R.id.preview_viewport);
final int styleId = mCaptionHelper.getRawUserStyle();
mCaptionHelper.applyCaptionProperties(previewText, previewViewport, styleId);
final Locale locale = mCaptionHelper.getLocale();
if (locale != null) {
final CharSequence localizedText = AccessibilityUtils.getTextForLocale(
mContext, locale, R.string.captioning_preview_text);
previewText.setText(localizedText);
} else {
previewText.setText(R.string.captioning_preview_text);
}
final View previewWindow = mPreference.findViewById(R.id.preview_window);
final CaptionStyle style = mCaptionHelper.getUserStyle();
if (style.hasWindowColor()) {
previewWindow.setBackgroundColor(style.windowColor);
} else {
final CaptionStyle defStyle = CaptionStyle.DEFAULT;
previewWindow.setBackgroundColor(defStyle.windowColor);
}
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2013 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 captioning properties. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class CaptioningPropertiesFragment extends DashboardFragment {
private static final String TAG = "CaptioningPropertiesFragment";
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_CAPTION_PROPERTIES;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.captioning_settings;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
public int getHelpResource() {
return R.string.help_url_caption;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.captioning_settings);
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2022 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.widget.Switch;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.widget.SettingsMainSwitchPreference;
import com.android.settingslib.widget.OnMainSwitchChangeListener;
/** Preference controller for captioning more options. */
public class CaptioningTogglePreferenceController extends TogglePreferenceController
implements OnMainSwitchChangeListener {
private final CaptionHelper mCaptionHelper;
public CaptioningTogglePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptionHelper = new CaptionHelper(context);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public boolean isChecked() {
return mCaptionHelper.isEnabled();
}
@Override
public boolean setChecked(boolean isChecked) {
mCaptionHelper.setEnabled(isChecked);
return true;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
SettingsMainSwitchPreference preference = screen.findPreference(getPreferenceKey());
preference.addOnSwitchChangeListener(this);
preference.setChecked(isChecked());
}
@Override
public void onSwitchChanged(Switch switchView, boolean isChecked) {
if (isChecked != isChecked()) {
setChecked(isChecked);
}
}
@Override
public int getSliceHighlightMenuRes() {
return R.string.menu_key_accessibility;
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.provider.Settings;
import android.view.accessibility.CaptioningManager.CaptionStyle;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import com.android.settings.core.BasePreferenceController;
/** Preference controller for captioning type face. */
public class CaptioningTypefaceController extends BasePreferenceController
implements Preference.OnPreferenceChangeListener {
private final CaptionHelper mCaptionHelper;
public CaptioningTypefaceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptionHelper = new CaptionHelper(context);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
final ListPreference listPreference = (ListPreference) preference;
final CaptionStyle attrs = CaptionStyle.getCustomStyle(mContext.getContentResolver());
final String rawTypeface = attrs.mRawTypeface;
listPreference.setValue(rawTypeface == null ? "" : rawTypeface);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
Settings.Secure.putString(
mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE,
(String) newValue);
mCaptionHelper.setEnabled(true);
return true;
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2022 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.res.Resources;
import android.graphics.Color;
import android.view.accessibility.CaptioningManager.CaptionStyle;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.accessibility.ListDialogPreference.OnValueChangedListener;
import com.android.settings.core.BasePreferenceController;
/** Preference controller for captioning window color. */
public class CaptioningWindowColorController extends BasePreferenceController
implements OnValueChangedListener {
private final CaptionHelper mCaptionHelper;
private int mCachedNonDefaultOpacity = CaptionStyle.COLOR_UNSPECIFIED;
public CaptioningWindowColorController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptionHelper = new CaptionHelper(context);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final ColorPreference preference = screen.findPreference(getPreferenceKey());
final Resources res = mContext.getResources();
final int[] colorValues = res.getIntArray(R.array.captioning_color_selector_values);
final String[] colorTitles = res.getStringArray(
R.array.captioning_color_selector_titles);
// Add "none" as an additional option for window backgrounds.
final int[] backgroundColorValues = new int[colorValues.length + 1];
final String[] backgroundColorTitles = new String[colorTitles.length + 1];
System.arraycopy(colorValues, 0, backgroundColorValues, 1, colorValues.length);
System.arraycopy(colorTitles, 0, backgroundColorTitles, 1, colorTitles.length);
backgroundColorValues[0] = Color.TRANSPARENT;
backgroundColorTitles[0] = mContext.getString(R.string.color_none);
preference.setTitles(backgroundColorTitles);
preference.setValues(backgroundColorValues);
final int windowColor = mCaptionHelper.getWindowColor();
final int color = CaptionUtils.parseColor(windowColor);
preference.setValue(color);
preference.setOnValueChangedListener(this);
}
@Override
public void onValueChanged(ListDialogPreference preference, int value) {
final boolean isNonDefaultColor = CaptionStyle.hasColor(value);
final int opacity = getNonDefaultOpacity(isNonDefaultColor);
final int merged = CaptionUtils.mergeColorOpacity(value, opacity);
mCaptionHelper.setWindowColor(merged);
mCaptionHelper.setEnabled(true);
}
private int getNonDefaultOpacity(boolean isNonDefaultColor) {
final int windowColor = mCaptionHelper.getWindowColor();
final int opacity = CaptionUtils.parseOpacity(windowColor);
if (isNonDefaultColor) {
final int lastOpacity = mCachedNonDefaultOpacity != CaptionStyle.COLOR_UNSPECIFIED
? mCachedNonDefaultOpacity : opacity;
// Reset cached opacity to use current color opacity to merge color.
mCachedNonDefaultOpacity = CaptionStyle.COLOR_UNSPECIFIED;
return lastOpacity;
}
// When default captioning color was selected, the opacity become 100% and make opacity
// preference disable. Cache the latest opacity to show the correct opacity later.
mCachedNonDefaultOpacity = opacity;
return opacity;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2022 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.res.Resources;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.accessibility.ListDialogPreference.OnValueChangedListener;
import com.android.settings.core.BasePreferenceController;
/** Preference controller for captioning window opacity. */
public class CaptioningWindowOpacityController extends BasePreferenceController
implements OnValueChangedListener {
private final CaptionHelper mCaptionHelper;
public CaptioningWindowOpacityController(Context context, String preferenceKey) {
super(context, preferenceKey);
mCaptionHelper = new CaptionHelper(context);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final ColorPreference preference = screen.findPreference(getPreferenceKey());
final Resources res = mContext.getResources();
final int[] opacityValues = res.getIntArray(R.array.captioning_opacity_selector_values);
final String[] opacityTitles = res.getStringArray(
R.array.captioning_opacity_selector_titles);
preference.setTitles(opacityTitles);
preference.setValues(opacityValues);
final int windowColor = mCaptionHelper.getWindowColor();
final int opacity = CaptionUtils.parseOpacity(windowColor);
preference.setValue(opacity);
preference.setOnValueChangedListener(this);
}
@Override
public void onValueChanged(ListDialogPreference preference, int value) {
final int windowColor = mCaptionHelper.getWindowColor();
final int color = CaptionUtils.parseColor(windowColor);
final int merged = CaptionUtils.mergeColorOpacity(color, value);
mCaptionHelper.setWindowColor(merged);
mCaptionHelper.setEnabled(true);
}
}

View File

@@ -19,6 +19,7 @@ package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import android.hardware.display.ColorDisplayManager;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
import androidx.preference.Preference;
@@ -28,8 +29,12 @@ 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.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.List;
/** Accessibility settings for color and motion. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class ColorAndMotionFragment extends DashboardFragment {
@@ -46,18 +51,48 @@ public class ColorAndMotionFragment extends DashboardFragment {
private Preference mDisplayDaltonizerPreferenceScreen;
private SwitchPreference mToggleDisableAnimationsPreference;
private SwitchPreference mToggleLargePointerIconPreference;
private AccessibilitySettingsContentObserver mSettingsContentObserver;
private final List<String> mShortcutFeatureKeys = new ArrayList<>();
@Override
public int getMetricsCategory() {
return SettingsEnums.ACCESSIBILITY_COLOR_AND_MOTION;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
initializeAllPreferences();
updateSystemPreferences();
mShortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
mShortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED);
mSettingsContentObserver = new AccessibilitySettingsContentObserver(new Handler());
mSettingsContentObserver.registerKeysToObserverCallback(mShortcutFeatureKeys,
key -> updatePreferencesState());
}
private void updatePreferencesState() {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
getPreferenceControllers().forEach(controllers::addAll);
controllers.forEach(controller -> controller.updateState(
findPreference(controller.getPreferenceKey())));
}
@Override
public void onStart() {
super.onStart();
mSettingsContentObserver.register(getContentResolver());
}
@Override
public void onStop() {
super.onStop();
mSettingsContentObserver.unregister(getContentResolver());
}
@Override

View File

@@ -0,0 +1,163 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import androidx.annotation.ColorInt;
import androidx.annotation.Nullable;
import com.android.settings.R;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* Layout for Screen flash notification color picker.
*/
public class ColorSelectorLayout extends LinearLayout {
// Holds the checked id. The selection is empty by default
private int mCheckedId = -1;
// Tracks children radio buttons checked state
private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;
private ColorSelectorLayout.OnCheckedChangeListener mOnCheckedChangeListener;
private final List<Integer> mRadioButtonResourceIds = Arrays.asList(
R.id.color_radio_button_00,
R.id.color_radio_button_01,
R.id.color_radio_button_02,
R.id.color_radio_button_03,
R.id.color_radio_button_04,
R.id.color_radio_button_05,
R.id.color_radio_button_06,
R.id.color_radio_button_07,
R.id.color_radio_button_08,
R.id.color_radio_button_09,
R.id.color_radio_button_10,
R.id.color_radio_button_11
);
private List<Integer> mColorList;
public ColorSelectorLayout(Context context) {
super(context);
mChildOnCheckedChangeListener = new CheckedStateTracker();
inflate(mContext, R.layout.layout_color_selector, this);
init();
mColorList = Arrays.stream(mContext.getResources()
.getIntArray(R.array.screen_flash_notification_preset_opacity_colors))
.boxed()
.collect(Collectors.toList());
}
public ColorSelectorLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mChildOnCheckedChangeListener = new CheckedStateTracker();
inflate(mContext, R.layout.layout_color_selector, this);
init();
mColorList = Arrays.stream(mContext.getResources()
.getIntArray(R.array.screen_flash_notification_preset_opacity_colors))
.boxed()
.collect(Collectors.toList());
}
private void init() {
for (int resId : mRadioButtonResourceIds) {
RadioButton radioButton = findViewById(resId);
if (radioButton != null) {
radioButton.setOnCheckedChangeListener(mChildOnCheckedChangeListener);
}
}
}
void setOnCheckedChangeListener(ColorSelectorLayout.OnCheckedChangeListener listener) {
mOnCheckedChangeListener = listener;
}
void setCheckedColor(@ColorInt int color) {
int resId = getResId(mColorList.indexOf(color));
if (resId != NO_ID && resId == mCheckedId) return;
if (mCheckedId != NO_ID) {
setCheckedStateForView(mCheckedId, false);
}
if (resId != NO_ID) {
setCheckedStateForView(resId, true);
}
setCheckedId(resId);
}
int getCheckedColor(int defaultColor) {
int checkedItemIndex = mRadioButtonResourceIds.indexOf(mCheckedId);
if (checkedItemIndex < 0 || checkedItemIndex >= mColorList.size()) {
return defaultColor;
} else {
return mColorList.get(checkedItemIndex);
}
}
private int getResId(int index) {
if (index < 0 || index >= mRadioButtonResourceIds.size()) {
return NO_ID;
} else {
return mRadioButtonResourceIds.get(index);
}
}
private void setCheckedId(int resId) {
mCheckedId = resId;
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(this);
}
}
private void setCheckedStateForView(int viewId, boolean checked) {
final View checkedView = findViewById(viewId);
if (checkedView instanceof RadioButton) {
((RadioButton) checkedView).setChecked(checked);
}
}
interface OnCheckedChangeListener {
void onCheckedChanged(ColorSelectorLayout group);
}
private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (!isChecked) {
return;
}
if (mCheckedId != NO_ID) {
setCheckedStateForView(mCheckedId, false);
}
int id = buttonView.getId();
setCheckedId(id);
}
}
}

View File

@@ -16,40 +16,66 @@
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.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
public class DisableAnimationsPreferenceController extends TogglePreferenceController {
import java.util.Arrays;
import java.util.List;
/** A toggle preference controller for disable animations. */
public class DisableAnimationsPreferenceController extends TogglePreferenceController implements
LifecycleObserver, OnStart, OnStop {
@VisibleForTesting
static final String ANIMATION_ON_VALUE = "1";
static final float ANIMATION_ON_VALUE = 1.0f;
@VisibleForTesting
static final String ANIMATION_OFF_VALUE = "0";
static final float ANIMATION_OFF_VALUE = 0.0f;
// Settings that should be changed when toggling animations
@VisibleForTesting
static final String[] TOGGLE_ANIMATION_TARGETS = {
static final List<String> TOGGLE_ANIMATION_TARGETS = Arrays.asList(
Settings.Global.WINDOW_ANIMATION_SCALE, Settings.Global.TRANSITION_ANIMATION_SCALE,
Settings.Global.ANIMATOR_DURATION_SCALE
);
private final ContentObserver mSettingsContentObserver = new ContentObserver(
new Handler(Looper.getMainLooper())){
@Override
public void onChange(boolean selfChange) {
updateState(mPreference);
}
};
private final ContentResolver mContentResolver;
private SwitchPreference mPreference;
public DisableAnimationsPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mContentResolver = context.getContentResolver();
}
@Override
public boolean isChecked() {
boolean allAnimationsDisabled = true;
for (String animationSetting : TOGGLE_ANIMATION_TARGETS) {
if (!TextUtils.equals(
Settings.Global.getString(mContext.getContentResolver(), animationSetting),
ANIMATION_OFF_VALUE)) {
final float value = Settings.Global.getFloat(mContentResolver, animationSetting,
ANIMATION_ON_VALUE);
if (value > ANIMATION_OFF_VALUE) {
allAnimationsDisabled = false;
break;
}
@@ -59,11 +85,11 @@ public class DisableAnimationsPreferenceController extends TogglePreferenceContr
@Override
public boolean setChecked(boolean isChecked) {
final String newAnimationValue = isChecked ? ANIMATION_OFF_VALUE : ANIMATION_ON_VALUE;
final float newAnimationValue = isChecked ? ANIMATION_OFF_VALUE : ANIMATION_ON_VALUE;
boolean allAnimationSet = true;
for (String animationPreference : TOGGLE_ANIMATION_TARGETS) {
allAnimationSet &= Settings.Global.putString(mContext.getContentResolver(),
animationPreference, newAnimationValue);
allAnimationSet &= Settings.Global.putFloat(mContentResolver, animationPreference,
newAnimationValue);
}
return allAnimationSet;
}
@@ -77,4 +103,23 @@ public class DisableAnimationsPreferenceController extends TogglePreferenceContr
public int getSliceHighlightMenuRes() {
return R.string.menu_key_accessibility;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
}
@Override
public void onStart() {
for (String key : TOGGLE_ANIMATION_TARGETS) {
mContentResolver.registerContentObserver(Settings.Global.getUriFor(key),
false, mSettingsContentObserver, UserHandle.USER_ALL);
}
}
@Override
public void onStop() {
mContentResolver.unregisterContentObserver(mSettingsContentObserver);
}
}

View File

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

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2023 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.SystemProperties;
import android.util.ArraySet;
import com.android.settings.core.BasePreferenceController;
import java.util.Collections;
import java.util.Set;
/** Preference controller for illustration in flash notifications page. */
public class FlashNotificationIllustrationPreferenceController extends BasePreferenceController {
public FlashNotificationIllustrationPreferenceController(Context context,
String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
// TODO(b/280748155): Update tablet illustration when it's available. Hide it for now.
String characteristics = SystemProperties.get("ro.build.characteristics");
String[] characteristicsSplit = characteristics.split(",");
Set<String> productCharacteristics = new ArraySet<>(characteristicsSplit.length);
Collections.addAll(productCharacteristics, characteristicsSplit);
final boolean isTablet = productCharacteristics.contains("tablet");
return isTablet ? UNSUPPORTED_ON_DEVICE : AVAILABLE;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2023 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 footer in flash notifications page. */
public class FlashNotificationsFooterPreferenceController extends
AccessibilityFooterPreferenceController {
public FlashNotificationsFooterPreferenceController(Context context,
String key) {
super(context, key);
}
@Override
protected String getIntroductionTitle() {
return mContext.getString(R.string.flash_notifications_about_title);
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
/** Preference controller that controls the text content of Flash Notifications intro. */
public class FlashNotificationsIntroPreferenceController extends BasePreferenceController {
public FlashNotificationsIntroPreferenceController(Context context,
String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final int titleResource = FlashNotificationsUtil.isTorchAvailable(mContext)
? R.string.flash_notifications_intro
: R.string.flash_notifications_intro_without_camera_flash;
final Preference preference = screen.findPreference(getPreferenceKey());
if (preference != null) {
preference.setTitle(titleResource);
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.util.FeatureFlagUtils;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
/**
* Controller for flash notifications.
*/
public class FlashNotificationsPreferenceController extends BasePreferenceController {
public FlashNotificationsPreferenceController(Context context, String key) {
super(context, key);
}
@Override
public int getAvailabilityStatus() {
boolean isFeatureOn = FeatureFlagUtils.isEnabled(mContext,
FeatureFlagUtils.SETTINGS_FLASH_NOTIFICATIONS);
return isFeatureOn ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
}
@Override
public CharSequence getSummary() {
final int res;
switch (FlashNotificationsUtil.getFlashNotificationsState(mContext)) {
case FlashNotificationsUtil.State.CAMERA:
res = R.string.flash_notifications_summary_on_camera;
break;
case FlashNotificationsUtil.State.SCREEN:
res = R.string.flash_notifications_summary_on_screen;
break;
case FlashNotificationsUtil.State.CAMERA_SCREEN:
res = R.string.flash_notifications_summary_on_camera_and_screen;
break;
case FlashNotificationsUtil.State.OFF:
default:
res = R.string.flash_notifications_summary_off;
break;
}
return mContext.getString(res);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2023 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.Context;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
/**
* Fragment for flash notifications.
*/
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class FlashNotificationsPreferenceFragment extends DashboardFragment {
@Override
protected int getPreferenceScreenResId() {
return R.xml.flash_notifications_settings;
}
@Override
protected String getLogTag() {
return "FlashNotificationsPreferenceFragment";
}
@Override
public int getMetricsCategory() {
return SettingsEnums.FLASH_NOTIFICATION_SETTINGS;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
use(ScreenFlashNotificationPreferenceController.class).setParentFragment(this);
}
@Override
public int getHelpResource() {
return R.string.help_url_flash_notifications;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.flash_notifications_settings);
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2023 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.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settingslib.Utils;
/**
* Preference for Flash notifications preview.
*/
public class FlashNotificationsPreviewPreference extends Preference {
private Drawable mBackgroundEnabled;
private Drawable mBackgroundDisabled;
@ColorInt
private int mTextColorDisabled;
public FlashNotificationsPreviewPreference(Context context) {
super(context);
init();
}
public FlashNotificationsPreviewPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public FlashNotificationsPreviewPreference(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public FlashNotificationsPreviewPreference(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
setLayoutResource(R.layout.flash_notification_preview_preference);
mBackgroundEnabled = getContext().getDrawable(R.drawable.settingslib_switch_bar_bg_on);
mBackgroundDisabled = getContext().getDrawable(R.drawable.switch_bar_bg_disabled);
mTextColorDisabled = Utils.getColorAttrDefaultColor(getContext(),
android.R.attr.textColorPrimary);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final boolean enabled = isEnabled();
final View frame = holder.findViewById(R.id.frame);
if (frame != null) {
frame.setBackground(enabled ? mBackgroundEnabled : mBackgroundDisabled);
}
final TextView title = (TextView) holder.findViewById(android.R.id.title);
if (title != null) {
@ColorInt final int textColorEnabled = title.getCurrentTextColor();
title.setAlpha(enabled ? 1f : 0.38f);
title.setTextColor(enabled ? textColorEnabled : mTextColorDisabled);
}
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
notifyChanged();
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2023 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.FlashNotificationsUtil.ACTION_FLASH_NOTIFICATION_START_PREVIEW;
import static com.android.settings.accessibility.FlashNotificationsUtil.EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE;
import static com.android.settings.accessibility.FlashNotificationsUtil.TYPE_SHORT_PREVIEW;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.core.BasePreferenceController;
/**
* Controller for flash notifications preview.
*/
public class FlashNotificationsPreviewPreferenceController extends
BasePreferenceController implements LifecycleEventObserver {
private Preference mPreference;
private final ContentResolver mContentResolver;
@VisibleForTesting
final ContentObserver mContentObserver = new ContentObserver(
new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange, @Nullable Uri uri) {
updateState(mPreference);
}
};
public FlashNotificationsPreviewPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mContentResolver = context.getContentResolver();
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
updateState(mPreference);
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (getPreferenceKey().equals(preference.getKey())) {
Intent intent = new Intent(ACTION_FLASH_NOTIFICATION_START_PREVIEW);
intent.putExtra(EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE, TYPE_SHORT_PREVIEW);
mContext.sendBroadcast(intent);
return true;
}
return super.handlePreferenceTreeClick(preference);
}
@Override
public void onStateChanged(@NonNull LifecycleOwner lifecycleOwner,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_RESUME) {
mContentResolver.registerContentObserver(
Settings.System.getUriFor(Settings.System.CAMERA_FLASH_NOTIFICATION),
/* notifyForDescendants= */ false, mContentObserver);
mContentResolver.registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_FLASH_NOTIFICATION),
/* notifyForDescendants= */ false, mContentObserver);
} else if (event == Lifecycle.Event.ON_PAUSE) {
mContentResolver.unregisterContentObserver(mContentObserver);
}
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
if (preference == null) {
return;
}
preference.setEnabled(FlashNotificationsUtil.getFlashNotificationsState(mContext)
!= FlashNotificationsUtil.State.OFF);
}
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright (C) 2023 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.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.provider.Settings;
import android.util.Log;
import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
class FlashNotificationsUtil {
static final String LOG_TAG = "FlashNotificationsUtil";
static final String ACTION_FLASH_NOTIFICATION_START_PREVIEW =
"com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW";
static final String ACTION_FLASH_NOTIFICATION_STOP_PREVIEW =
"com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW";
static final String EXTRA_FLASH_NOTIFICATION_PREVIEW_COLOR =
"com.android.internal.intent.extra.FLASH_NOTIFICATION_PREVIEW_COLOR";
static final String EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE =
"com.android.internal.intent.extra.FLASH_NOTIFICATION_PREVIEW_TYPE";
static final int TYPE_SHORT_PREVIEW = 0;
static final int TYPE_LONG_PREVIEW = 1;
static final int DEFAULT_SCREEN_FLASH_COLOR = ScreenFlashNotificationColor.YELLOW.mColorInt;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
FlashNotificationsUtil.State.OFF,
FlashNotificationsUtil.State.CAMERA,
FlashNotificationsUtil.State.SCREEN,
FlashNotificationsUtil.State.CAMERA_SCREEN,
})
@interface State {
int OFF = 0;
int CAMERA = 1;
int SCREEN = 2;
int CAMERA_SCREEN = 3;
}
static boolean isTorchAvailable(@NonNull Context context) {
// TODO This is duplicated logic of FlashNotificationsController.getCameraId.
final CameraManager cameraManager = context.getSystemService(CameraManager.class);
if (cameraManager == null) return false;
try {
final String[] ids = cameraManager.getCameraIdList();
for (String id : ids) {
final CameraCharacteristics c = cameraManager.getCameraCharacteristics(id);
final Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
if (flashAvailable == null) continue;
final Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
if (lensFacing == null) continue;
if (flashAvailable && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
return true;
}
}
} catch (CameraAccessException ignored) {
Log.w(LOG_TAG, "Failed to get valid camera for camera flash notification.");
}
return false;
}
static ScreenFlashNotificationColor getScreenColor(@ColorInt int colorInt)
throws ScreenColorNotFoundException {
colorInt |= ScreenFlashNotificationColor.ALPHA_MASK;
for (ScreenFlashNotificationColor color : ScreenFlashNotificationColor.values()) {
if (colorInt == color.mOpaqueColorInt) {
return color;
}
}
throw new ScreenColorNotFoundException();
}
@NonNull
static String getColorDescriptionText(@NonNull Context context, @ColorInt int color) {
try {
return context.getString(getScreenColor(color).mStringRes);
} catch (ScreenColorNotFoundException e) {
return "";
}
}
@State
static int getFlashNotificationsState(Context context) {
if (context == null) {
return State.OFF;
}
final boolean isTorchAvailable = FlashNotificationsUtil.isTorchAvailable(context);
final boolean isCameraFlashEnabled = Settings.System.getInt(context.getContentResolver(),
Settings.System.CAMERA_FLASH_NOTIFICATION, State.OFF) != State.OFF;
final boolean isScreenFlashEnabled = Settings.System.getInt(context.getContentResolver(),
Settings.System.SCREEN_FLASH_NOTIFICATION, State.OFF) != State.OFF;
return ((isTorchAvailable && isCameraFlashEnabled) ? State.CAMERA : State.OFF)
| (isScreenFlashEnabled ? State.SCREEN : State.OFF);
}
static class ScreenColorNotFoundException extends Exception {
}
}

View File

@@ -22,7 +22,6 @@ 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;
@@ -30,7 +29,6 @@ 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;
@@ -52,9 +50,6 @@ public class FloatingMenuSizePreferenceController extends BasePreferenceControll
@VisibleForTesting
ListPreference mPreference;
private final ArrayMap<String, String> mValueTitleMap = new ArrayMap<>();
private int mDefaultSize;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
Size.SMALL,
@@ -75,8 +70,6 @@ public class FloatingMenuSizePreferenceController extends BasePreferenceControll
updateAvailabilityStatus();
}
};
initValueTitleMap();
}
@Override
@@ -94,11 +87,9 @@ public class FloatingMenuSizePreferenceController extends BasePreferenceControll
@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;
}
@@ -108,7 +99,7 @@ public class FloatingMenuSizePreferenceController extends BasePreferenceControll
super.updateState(preference);
final ListPreference listPreference = (ListPreference) preference;
listPreference.setValue(String.valueOf(getAccessibilityFloatingMenuSize(mDefaultSize)));
listPreference.setValue(String.valueOf(getAccessibilityFloatingMenuSize()));
}
@Override
@@ -129,25 +120,10 @@ public class FloatingMenuSizePreferenceController extends BasePreferenceControll
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) {
private int getAccessibilityFloatingMenuSize() {
return Settings.Secure.getInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, defaultValue);
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, Size.SMALL);
}
private void putAccessibilityFloatingMenuSize(@Size int value) {

View File

@@ -18,7 +18,6 @@ package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import static com.android.settings.display.ToggleFontSizePreferenceFragment.fontSizeValueToIndex;
import android.content.ContentResolver;
import android.content.Context;
@@ -63,4 +62,20 @@ final class FontSizeData extends PreviewSizeData<Float> {
Settings.System.putFloat(resolver, Settings.System.FONT_SCALE,
getValues().get(currentProgress));
}
/**
* Utility function that returns the index in a string array with which the represented value is
* the closest to a given float value.
*/
private static int fontSizeValueToIndex(float val, String[] indices) {
float lastVal = Float.parseFloat(indices[0]);
for (int i = 1; i < indices.length; i++) {
float thisVal = Float.parseFloat(indices[i]);
if (val < (lastVal + (thisVal - lastVal) * .5f)) {
return i - 1;
}
lastVal = thisVal;
}
return indices.length - 1;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.util.FeatureFlagUtils;
import com.android.settings.core.BasePreferenceController;
/**
* The controller of the audio routing.
*/
public class HearingAidAudioRoutingPreferenceController extends BasePreferenceController {
public HearingAidAudioRoutingPreferenceController(Context context,
String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_AUDIO_ROUTING)
? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2022 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.media.AudioManager;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
/** Preference controller for Hearing Aid Compatibility (HAC) settings */
public class HearingAidCompatibilityPreferenceController extends TogglePreferenceController {
// Hearing Aid Compatibility settings values
static final String HAC_KEY = "HACSetting";
static final String HAC_VAL_ON = "ON";
static final String HAC_VAL_OFF = "OFF";
@VisibleForTesting
static final int HAC_DISABLED = 0;
@VisibleForTesting
static final int HAC_ENABLED = 1;
private final TelephonyManager mTelephonyManager;
private final AudioManager mAudioManager;
public HearingAidCompatibilityPreferenceController(Context context,
String preferenceKey) {
super(context, preferenceKey);
mTelephonyManager = context.getSystemService(TelephonyManager.class);
mAudioManager = context.getSystemService(AudioManager.class);
}
@Override
public int getAvailabilityStatus() {
return mTelephonyManager.isHearingAidCompatibilitySupported() ? AVAILABLE
: UNSUPPORTED_ON_DEVICE;
}
@Override
public boolean isChecked() {
final int hac = Settings.System.getInt(mContext.getContentResolver(),
Settings.System.HEARING_AID, HAC_DISABLED);
return hac == HAC_ENABLED;
}
@Override
public boolean setChecked(boolean isChecked) {
setAudioParameterHacEnabled(isChecked);
return Settings.System.putInt(mContext.getContentResolver(), Settings.System.HEARING_AID,
(isChecked ? HAC_ENABLED : HAC_DISABLED));
}
@Override
public int getSliceHighlightMenuRes() {
return R.string.menu_key_accessibility;
}
private void setAudioParameterHacEnabled(boolean enabled) {
mAudioManager.setParameters(HAC_KEY + "=" + (enabled ? HAC_VAL_ON : HAC_VAL_OFF));
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright (C) 2023 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.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* A helper class to get and check hearing aids and its status.
*/
public class HearingAidHelper {
private final BluetoothAdapter mBluetoothAdapter;
private final LocalBluetoothProfileManager mProfileManager;
private final CachedBluetoothDeviceManager mCachedDeviceManager;
public HearingAidHelper(Context context) {
final LocalBluetoothManager localBluetoothManager =
com.android.settings.bluetooth.Utils.getLocalBluetoothManager(context);
mProfileManager = localBluetoothManager.getProfileManager();
mCachedDeviceManager = localBluetoothManager.getCachedDeviceManager();
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
/**
* Gets the connected hearing aids device whose profiles are
* {@link BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#HAP_CLIENT}.
*
* @return a list of hearing aids {@link BluetoothDevice} objects
*/
public List<BluetoothDevice> getConnectedHearingAidDeviceList() {
if (!isHearingAidSupported()) {
return new ArrayList<>();
}
final List<BluetoothDevice> deviceList = new ArrayList<>();
final HapClientProfile hapClientProfile = mProfileManager.getHapClientProfile();
if (hapClientProfile != null) {
deviceList.addAll(hapClientProfile.getConnectedDevices());
}
final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
if (hearingAidProfile != null) {
deviceList.addAll(hearingAidProfile.getConnectedDevices());
}
return deviceList.stream()
.distinct()
.filter(d -> !mCachedDeviceManager.isSubDevice(d)).collect(Collectors.toList());
}
/**
* Gets the first connected hearing aids device.
*
* @return a {@link CachedBluetoothDevice} that is hearing aids device
*/
public CachedBluetoothDevice getConnectedHearingAidDevice() {
final List<BluetoothDevice> deviceList = getConnectedHearingAidDeviceList();
return deviceList.isEmpty() ? null : mCachedDeviceManager.findDevice(deviceList.get(0));
}
/**
* Checks if {@link BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#HAP_CLIENT}
* supported.
*/
public boolean isHearingAidSupported() {
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
return false;
}
final List<Integer> supportedList = mBluetoothAdapter.getSupportedProfiles();
return supportedList.contains(BluetoothProfile.HEARING_AID)
|| supportedList.contains(BluetoothProfile.HAP_CLIENT);
}
/**
* Checks if {@link BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#HAP_CLIENT}
* profiles all ready.
*/
public boolean isAllHearingAidRelatedProfilesReady() {
HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
if (hearingAidProfile != null && !hearingAidProfile.isProfileReady()) {
return false;
}
HapClientProfile hapClientProfile = mProfileManager.getHapClientProfile();
if (hapClientProfile != null && !hapClientProfile.isProfileReady()) {
return false;
}
return true;
}
}

View File

@@ -23,7 +23,7 @@ import androidx.fragment.app.FragmentManager;
import com.android.settings.bluetooth.HearingAidPairingDialogFragment;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.HearingAidInfo;
/** Provides utility methods related hearing aids. */
public final class HearingAidUtils {
@@ -40,8 +40,8 @@ public final class HearingAidUtils {
*/
public static void launchHearingAidPairingDialog(FragmentManager fragmentManager,
@NonNull CachedBluetoothDevice device) {
if (device.isConnectedHearingAidDevice()
&& device.getDeviceMode() == HearingAidProfile.DeviceMode.MODE_BINAURAL
if (device.isConnectedAshaHearingAidDevice()
&& device.getDeviceMode() == HearingAidInfo.DeviceMode.MODE_BINAURAL
&& device.getSubDevice() == null) {
launchHearingAidPairingDialogInternal(fragmentManager, device);
}
@@ -49,11 +49,11 @@ public final class HearingAidUtils {
private static void launchHearingAidPairingDialogInternal(FragmentManager fragmentManager,
@NonNull CachedBluetoothDevice device) {
if (device.getDeviceSide() == HearingAidProfile.DeviceSide.SIDE_INVALID) {
if (device.getDeviceSide() == HearingAidInfo.DeviceSide.SIDE_INVALID) {
Log.w(TAG, "Can not launch hearing aid pairing dialog for invalid side");
return;
}
HearingAidPairingDialogFragment.newInstance(device).show(fragmentManager,
HearingAidPairingDialogFragment.newInstance(device.getAddress()).show(fragmentManager,
HearingAidPairingDialogFragment.TAG);
}
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright (C) 2023 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.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.audiopolicy.AudioProductStrategy;
import android.util.Log;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants;
import com.android.settingslib.bluetooth.HearingAidAudioRoutingHelper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.primitives.Ints;
import java.util.List;
import java.util.stream.Collectors;
/**
* Abstract class for providing audio routing {@link ListPreference} common control for hearing
* device specifically.
*/
public abstract class HearingDeviceAudioRoutingBasePreferenceController extends
BasePreferenceController implements Preference.OnPreferenceChangeListener {
private static final String TAG = "HARoutingBasePreferenceController";
private static final boolean DEBUG = false;
private final HearingAidAudioRoutingHelper mAudioRoutingHelper;
private final HearingAidHelper mHearingAidHelper;
public HearingDeviceAudioRoutingBasePreferenceController(Context context,
String preferenceKey) {
this(context, preferenceKey,
new HearingAidAudioRoutingHelper(context),
new HearingAidHelper(context));
}
@VisibleForTesting
public HearingDeviceAudioRoutingBasePreferenceController(Context context,
String preferenceKey, HearingAidAudioRoutingHelper audioRoutingHelper,
HearingAidHelper hearingAidHelper) {
super(context, preferenceKey);
mAudioRoutingHelper = audioRoutingHelper;
mHearingAidHelper = hearingAidHelper;
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
final ListPreference listPreference = (ListPreference) preference;
final int routingValue = restoreRoutingValue(mContext);
listPreference.setValue(String.valueOf(routingValue));
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final Integer routingValue = Ints.tryParse((String) newValue);
saveRoutingValue(mContext, routingValue);
final CachedBluetoothDevice device = mHearingAidHelper.getConnectedHearingAidDevice();
if (device != null) {
trySetAudioRoutingConfig(getSupportedAttributeList(),
mHearingAidHelper.getConnectedHearingAidDevice(), routingValue);
}
return true;
}
private void trySetAudioRoutingConfig(int[] audioAttributes,
CachedBluetoothDevice hearingDevice,
@HearingAidAudioRoutingConstants.RoutingValue int routingValue) {
final List<AudioProductStrategy> supportedStrategies =
mAudioRoutingHelper.getSupportedStrategies(audioAttributes);
final AudioDeviceAttributes hearingDeviceAttributes =
mAudioRoutingHelper.getMatchedHearingDeviceAttributes(hearingDevice);
if (hearingDeviceAttributes == null) {
if (DEBUG) {
Log.d(TAG,
"Can not find expected AudioDeviceAttributes to config audio routing "
+ "maybe device is offline: "
+ hearingDevice.getDevice().getAnonymizedAddress());
}
return;
}
final boolean status = mAudioRoutingHelper.setPreferredDeviceRoutingStrategies(
supportedStrategies, hearingDeviceAttributes, routingValue);
if (!status) {
final List<String> strategiesName = supportedStrategies.stream()
.map(AudioProductStrategy::getName)
.collect(Collectors.toList());
Log.w(TAG, "routingMode: " + strategiesName + " routingValue: " + routingValue
+ " fail to configure AudioProductStrategy");
}
}
/**
* Gets a list of usage values defined in {@link AudioAttributes} that are used to identify
* {@link AudioProductStrategy} to configure audio routing.
*/
protected abstract int[] getSupportedAttributeList();
/**
* Saves the routing value.
*
* @param context the valid context used to get the {@link ContentResolver}
* @param routingValue one of the value defined in
* {@link HearingAidAudioRoutingConstants.RoutingValue}
*/
protected abstract void saveRoutingValue(Context context, int routingValue);
/**
* Restores the routing value and used to reflect status on ListPreference.
*
* @param context the valid context used to get the {@link ContentResolver}
* @return one of the value defined in {@link HearingAidAudioRoutingConstants.RoutingValue}
*/
protected abstract int restoreRoutingValue(Context context);
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.provider.Settings;
import com.android.settings.Utils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants;
/**
* The controller of the hearing device call routing list preference.
*/
public class HearingDeviceCallRoutingPreferenceController extends
HearingDeviceAudioRoutingBasePreferenceController {
private CachedBluetoothDevice mHearingDevice;
public HearingDeviceCallRoutingPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return Utils.isVoiceCapable(mContext) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
protected int[] getSupportedAttributeList() {
return HearingAidAudioRoutingConstants.CALL_ROUTING_ATTRIBUTES;
}
@Override
protected void saveRoutingValue(Context context, int routingValue) {
Settings.Secure.putInt(context.getContentResolver(),
Settings.Secure.HEARING_AID_CALL_ROUTING, routingValue);
}
@Override
protected int restoreRoutingValue(Context context) {
return Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.HEARING_AID_CALL_ROUTING,
HearingAidAudioRoutingConstants.RoutingValue.AUTO);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2023 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 footer in hearing device page. */
public class HearingDeviceFooterPreferenceController extends
AccessibilityFooterPreferenceController {
public HearingDeviceFooterPreferenceController(Context context,
String key) {
super(context, key);
}
@Override
protected String getIntroductionTitle() {
return mContext.getString(R.string.accessibility_hearing_device_about_title);
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.provider.Settings;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants;
/**
* The controller of the hearing device media routing list preference.
*/
public class HearingDeviceMediaRoutingPreferenceController extends
HearingDeviceAudioRoutingBasePreferenceController {
private CachedBluetoothDevice mHearingDevice;
public HearingDeviceMediaRoutingPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
protected int[] getSupportedAttributeList() {
return HearingAidAudioRoutingConstants.MEDIA_ROUTING_ATTRIBUTES;
}
@Override
protected void saveRoutingValue(Context context, int routingValue) {
Settings.Secure.putInt(context.getContentResolver(),
Settings.Secure.HEARING_AID_MEDIA_ROUTING, routingValue);
}
@Override
protected int restoreRoutingValue(Context context) {
return Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.HEARING_AID_MEDIA_ROUTING,
HearingAidAudioRoutingConstants.RoutingValue.AUTO);
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2023 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.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.le.ScanFilter;
import android.content.Context;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.bluetooth.BluetoothDevicePairingDetailBase;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import java.util.Collections;
/**
* HearingDevicePairingDetail is a page to scan hearing devices. This page shows scanning icons and
* pairing them.
*/
public class HearingDevicePairingDetail extends BluetoothDevicePairingDetailBase {
private static final String TAG = "HearingDevicePairingDetail";
@VisibleForTesting
static final String KEY_AVAILABLE_HEARING_DEVICES = "available_hearing_devices";
public HearingDevicePairingDetail() {
super();
final ScanFilter filter = new ScanFilter.Builder()
.setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}, new byte[]{0})
.build();
setFilter(Collections.singletonList(filter));
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
use(ViewAllBluetoothDevicesPreferenceController.class).init(this);
}
@Override
public void onStart() {
super.onStart();
mAvailableDevicesCategory.setProgress(mBluetoothAdapter.isEnabled());
}
@Override
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
super.onDeviceBondStateChanged(cachedDevice, bondState);
mAvailableDevicesCategory.setProgress(bondState == BluetoothDevice.BOND_NONE);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.HEARING_AID_PAIRING;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.hearing_device_pairing_detail;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
public String getDeviceListKey() {
return KEY_AVAILABLE_HEARING_DEVICES;
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.provider.Settings;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants;
/**
* The controller of the hearing device ringtone routing list preference.
*/
public class HearingDeviceRingtoneRoutingPreferenceController extends
HearingDeviceAudioRoutingBasePreferenceController {
private CachedBluetoothDevice mHearingDevice;
public HearingDeviceRingtoneRoutingPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
protected int[] getSupportedAttributeList() {
return HearingAidAudioRoutingConstants.RINGTONE_ROUTING_ATTRIBUTE;
}
@Override
protected void saveRoutingValue(Context context, int routingValue) {
Settings.Secure.putInt(context.getContentResolver(),
Settings.Secure.HEARING_AID_RINGTONE_ROUTING, routingValue);
}
@Override
protected int restoreRoutingValue(Context context) {
return Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.HEARING_AID_RINGTONE_ROUTING,
HearingAidAudioRoutingConstants.RoutingValue.AUTO);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import android.provider.Settings;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants;
/**
* The controller of the hearing device system sounds routing list preference.
*/
public class HearingDeviceSystemSoundsRoutingPreferenceController extends
HearingDeviceAudioRoutingBasePreferenceController {
private CachedBluetoothDevice mHearingDevice;
public HearingDeviceSystemSoundsRoutingPreferenceController(Context context,
String preferenceKey) {
super(context, preferenceKey);
}
@Override
protected int[] getSupportedAttributeList() {
return HearingAidAudioRoutingConstants.SYSTEM_SOUNDS_ROUTING_ATTRIBUTES;
}
@Override
protected void saveRoutingValue(Context context, int routingValue) {
Settings.Secure.putInt(context.getContentResolver(),
Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING, routingValue);
}
@Override
protected int restoreRoutingValue(Context context) {
return Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING,
HearingAidAudioRoutingConstants.RoutingValue.AUTO);
}
}

View File

@@ -42,38 +42,31 @@ import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
import com.android.settings.overlay.FeatureFactory;
import java.util.ArrayList;
import java.util.List;
/** Fragment for providing open activity button. */
public class LaunchAccessibilityActivityPreferenceFragment extends ToggleFeaturePreferenceFragment {
private static final String TAG = "LaunchA11yActivity";
private static final String TAG = "LaunchAccessibilityActivityPreferenceFragment";
private static final String EMPTY_STRING = "";
protected static final String KEY_LAUNCH_PREFERENCE = "launch_preference";
private ComponentName mTileComponentName;
@Override
public int getMetricsCategory() {
// Retrieve from getArguments() directly because this function will be executed from
// onAttach(), but variable mComponentName only available after onProcessArguments()
// which comes from onCreateView().
final ComponentName componentName = getArguments().getParcelable(
AccessibilitySettings.EXTRA_COMPONENT_NAME);
return FeatureFactory.getFactory(getActivity().getApplicationContext())
.getAccessibilityMetricsFeatureProvider()
.getDownloadedFeatureMetricsCategory(componentName);
return getArguments().getInt(AccessibilitySettings.EXTRA_METRICS_CATEGORY);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = super.onCreateView(inflater, container, savedInstanceState);
// Init new preference to replace the switch preference instead.
initLaunchPreference();
final View view = super.onCreateView(inflater, container, savedInstanceState);
removePreference(KEY_USE_SERVICE_PREFERENCE);
return view;
}
@@ -173,6 +166,7 @@ public class LaunchAccessibilityActivityPreferenceFragment extends ToggleFeature
/** Customizes the order by preference key. */
protected List<String> getPreferenceOrderList() {
final List<String> lists = new ArrayList<>();
lists.add(KEY_TOP_INTRO_PREFERENCE);
lists.add(KEY_ANIMATED_IMAGE);
lists.add(KEY_LAUNCH_PREFERENCE);
lists.add(KEY_GENERAL_CATEGORY);
@@ -230,4 +224,15 @@ public class LaunchAccessibilityActivityPreferenceFragment extends ToggleFeature
return settingsIntent;
}
@Override
protected int getPreferenceScreenResId() {
// TODO(b/171272809): Add back when controllers move to static type
return 0;
}
@Override
protected String getLogTag() {
return TAG;
}
}

View File

@@ -54,7 +54,7 @@ public class LockScreenRotationPreferenceController extends TogglePreferenceCont
*/
@Override
public boolean setChecked(boolean isChecked) {
RotationPolicy.setRotationLockForAccessibility(mContext, !isChecked);
RotationPolicy.setRotationLock(mContext, !isChecked);
return true;
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import android.content.Context;
import android.provider.Settings;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
/**
* Controller that accesses and switches the preference status of the magnification always on
* feature, where the magnifier will not deactivate on Activity transitions; it will only zoom out
* to 100%.
*/
public class MagnificationAlwaysOnPreferenceController extends TogglePreferenceController
implements LifecycleObserver {
private static final String TAG =
MagnificationAlwaysOnPreferenceController.class.getSimpleName();
static final String PREF_KEY = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED;
private SwitchPreference mSwitchPreference;
public MagnificationAlwaysOnPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public boolean isChecked() {
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED, ON) == ON;
}
@Override
public boolean setChecked(boolean isChecked) {
return Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED,
(isChecked ? ON : OFF));
}
@Override
public int getSliceHighlightMenuRes() {
return R.string.menu_key_accessibility;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mSwitchPreference = screen.findPreference(getPreferenceKey());
}
// TODO(b/186731461): Remove it when this controller is used in DashBoardFragment only.
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
void onResume() {
updateState();
}
/**
* Updates the state of preference components which has been displayed by
* {@link MagnificationAlwaysOnPreferenceController#displayPreference}.
*/
void updateState() {
updateState(mSwitchPreference);
}
}

View File

@@ -14,7 +14,6 @@
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;
@@ -95,19 +94,11 @@ public class MagnificationGesturesPreferenceController extends TogglePreferenceC
}
static void populateMagnificationGesturesPreferenceExtras(Bundle extras, Context context) {
extras.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
// TODO(b/270481978): It seems not necessary to put EXTRA_TITLE_RES.
extras.putInt(AccessibilitySettings.EXTRA_TITLE_RES,
R.string.accessibility_screen_magnification_gestures_title);
String intro = context.getString(R.string.accessibility_screen_magnification_intro_text);
extras.putCharSequence(AccessibilitySettings.EXTRA_INTRO, intro);
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);
// TODO(b/270481978): It seems not necessary to put EXTRA_VIDEO_RAW_RESOURCE_ID.
extras.putInt(AccessibilitySettings.EXTRA_VIDEO_RAW_RESOURCE_ID,
R.raw.accessibility_screen_magnification);
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import android.content.Context;
import android.provider.Settings;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
/**
* Controller that accesses and switches the preference status of the magnification joystick feature
*/
public class MagnificationJoystickPreferenceController extends TogglePreferenceController
implements LifecycleObserver {
private static final String TAG =
MagnificationJoystickPreferenceController.class.getSimpleName();
static final String PREF_KEY = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED;
private SwitchPreference mSwitchPreference;
public MagnificationJoystickPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public boolean isChecked() {
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED, OFF) == ON;
}
@Override
public boolean setChecked(boolean isChecked) {
return Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED,
(isChecked ? ON : OFF));
}
@Override
public int getSliceHighlightMenuRes() {
return R.string.menu_key_accessibility;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mSwitchPreference = screen.findPreference(getPreferenceKey());
}
// TODO(b/186731461): Remove it when this controller is used in DashBoardFragment only.
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
void onResume() {
updateState();
}
/**
* Updates the state of preference components which has been displayed by
* {@link MagnificationJoystickPreferenceController#displayPreference}.
*/
void updateState() {
updateState(mSwitchPreference);
}
}

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