Merge Android U (ab/10368041)
Bug: 291102124 Merged-In: I17a6c8a571b4a0b7d943dfd710cde0f18d03da39 Change-Id: I4ed5b2e4c6c59527bb544e8b6dff2b9d4cee9025
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
157
src/com/android/settings/ResetSubscriptionContract.java
Normal file
157
src/com/android/settings/ResetSubscriptionContract.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
82
src/com/android/settings/SettingsActivityUtil.kt
Normal file
82
src/com/android/settings/SettingsActivityUtil.kt
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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= */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(){}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
88
src/com/android/settings/accessibility/AutoclickUtils.java
Normal file
88
src/com/android/settings/accessibility/AutoclickUtils.java
Normal 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(){}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
214
src/com/android/settings/accessibility/CaptionHelper.java
Normal file
214
src/com/android/settings/accessibility/CaptionHelper.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
88
src/com/android/settings/accessibility/CaptionUtils.java
Normal file
88
src/com/android/settings/accessibility/CaptionUtils.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
163
src/com/android/settings/accessibility/ColorSelectorLayout.java
Normal file
163
src/com/android/settings/accessibility/ColorSelectorLayout.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
114
src/com/android/settings/accessibility/HearingAidHelper.java
Normal file
114
src/com/android/settings/accessibility/HearingAidHelper.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user