Merge 24Q4 (ab/12406339) into aosp-main-future

Bug: 370570306
Merged-In: Ie90e7495dd4a134538bae6e3e08eea0d02134b14
Change-Id: I20517e9ee410e95f2cbeb1247c0c0288ed9f006f
This commit is contained in:
Xin Li
2024-11-11 21:38:40 -08:00
1597 changed files with 77072 additions and 46403 deletions

View File

@@ -179,6 +179,8 @@ public class FallbackHome extends Activity {
SystemClock.uptimeMillis(), false);
finish();
}
} else {
Log.d(TAG, "User not yet unlocked");
}
}

View File

@@ -65,11 +65,13 @@ import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
import com.android.settings.biometrics.IdentityCheckBiometricErrorDialog;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper;
import com.android.settings.flags.Flags;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settings.password.ConfirmLockPattern;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
@@ -99,6 +101,7 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis
static final int KEYGUARD_REQUEST = 55;
@VisibleForTesting
static final int CREDENTIAL_CONFIRM_REQUEST = 56;
static final int BIOMETRICS_REQUEST = 57;
private static final String KEY_SHOW_ESIM_RESET_CHECKBOX =
"masterclear.allow_retain_esim_profiles_after_fdr";
@@ -156,7 +159,8 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis
@VisibleForTesting
boolean isValidRequestCode(int requestCode) {
return !((requestCode != KEYGUARD_REQUEST) && (requestCode != CREDENTIAL_CONFIRM_REQUEST));
return !((requestCode != KEYGUARD_REQUEST) && (requestCode != CREDENTIAL_CONFIRM_REQUEST)
&& (requestCode != BIOMETRICS_REQUEST));
}
@Override
@@ -176,12 +180,35 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis
if (resultCode != Activity.RESULT_OK) {
establishInitialState();
if (requestCode == BIOMETRICS_REQUEST) {
if (resultCode == ConfirmDeviceCredentialActivity.BIOMETRIC_LOCKOUT_ERROR_RESULT) {
IdentityCheckBiometricErrorDialog.showBiometricErrorDialog(getActivity(),
Utils.BiometricStatus.LOCKOUT, true /* twoFactorAuthentication */);
}
}
return;
}
if (requestCode == KEYGUARD_REQUEST) {
final int userId = getActivity().getUserId();
final Utils.BiometricStatus biometricAuthStatus =
Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
false /* biometricsAuthenticationRequested */,
userId);
if (biometricAuthStatus == Utils.BiometricStatus.OK) {
Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRICS_REQUEST,
userId, false /* hideBackground */);
return;
} else if (biometricAuthStatus != Utils.BiometricStatus.NOT_ACTIVE) {
IdentityCheckBiometricErrorDialog.showBiometricErrorDialog(getActivity(),
biometricAuthStatus, true /* twoFactorAuthentication */);
return;
}
}
Intent intent = null;
// If returning from a Keyguard request, try to show an account confirmation request if
// applciable.
// applicable.
if (CREDENTIAL_CONFIRM_REQUEST != requestCode
&& (intent = getAccountConfirmationIntent()) != null) {
showAccountCredentialConfirmation(intent);

View File

@@ -19,8 +19,6 @@ package com.android.settings;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.app.ActionBar;
import android.app.Activity;
import android.app.ProgressDialog;
import android.app.admin.DevicePolicyManager;
import android.app.admin.FactoryResetProtectionPolicy;
@@ -28,7 +26,6 @@ import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemProperties;
@@ -36,12 +33,10 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.service.oemlock.OemLockManager;
import android.service.persistentdata.PersistentDataBlockManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.VisibleForTesting;
@@ -62,7 +57,7 @@ import com.google.android.setupdesign.GlifLayout;
* has defined one, followed by a final strongly-worded "THIS WILL ERASE EVERYTHING
* ON THE PHONE" prompt. If at any time the phone is allowed to go to sleep, is
* locked, et cetera, then the confirmation sequence is abandoned.
*
* <p>
* This is the confirmation screen.
*/
public class MainClearConfirm extends InstrumentedFragment {
@@ -70,9 +65,11 @@ public class MainClearConfirm extends InstrumentedFragment {
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
@VisibleForTesting View mContentView;
@VisibleForTesting
GlifLayout mContentView;
private boolean mEraseSdCard;
@VisibleForTesting boolean mEraseEsims;
@VisibleForTesting
boolean mEraseEsims;
/**
* The user has gone through the multiple confirmation, so now we go ahead
@@ -89,8 +86,7 @@ public class MainClearConfirm extends InstrumentedFragment {
final PersistentDataBlockManager pdbManager;
// pre-flight check hardware support PersistentDataBlockManager
if (!SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("")) {
pdbManager = (PersistentDataBlockManager)
getActivity().getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
pdbManager = getActivity().getSystemService(PersistentDataBlockManager.class);
} else {
pdbManager = null;
}
@@ -152,6 +148,11 @@ public class MainClearConfirm extends InstrumentedFragment {
return false;
}
// Do not try to erase factory reset protection data if the protection is alive.
if (pdbManager.isFactoryResetProtectionActive()) {
return false;
}
// The persistent data block will persist if the device is still being provisioned.
if (isDeviceStillBeingProvisioned()) {
return false;
@@ -211,9 +212,7 @@ public class MainClearConfirm extends InstrumentedFragment {
* Configure the UI for the final confirmation interaction
*/
private void establishFinalConfirmationState() {
final GlifLayout layout = mContentView.findViewById(R.id.setup_wizard_layout);
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
final FooterBarMixin mixin = mContentView.getMixin(FooterBarMixin.class);
mixin.setPrimaryButton(
new FooterButton.Builder(getActivity())
.setText(R.string.main_clear_button_text)
@@ -224,21 +223,6 @@ public class MainClearConfirm extends InstrumentedFragment {
);
}
private void setUpActionBarAndTitle() {
final Activity activity = getActivity();
if (activity == null) {
Log.e(TAG, "No activity attached, skipping setUpActionBarAndTitle");
return;
}
final ActionBar actionBar = activity.getActionBar();
if (actionBar == null) {
Log.e(TAG, "No actionbar, skipping setUpActionBarAndTitle");
return;
}
actionBar.hide();
activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -254,32 +238,26 @@ public class MainClearConfirm extends InstrumentedFragment {
.show();
return new View(getActivity());
}
mContentView = inflater.inflate(R.layout.main_clear_confirm, null);
setUpActionBarAndTitle();
mContentView = (GlifLayout) inflater.inflate(R.layout.main_clear_confirm, null);
establishFinalConfirmationState();
setAccessibilityTitle();
setSubtitle();
setAccessibilityTitle();
return mContentView;
}
private void setAccessibilityTitle() {
CharSequence currentTitle = getActivity().getTitle();
TextView confirmationMessage = mContentView.findViewById(R.id.sud_layout_description);
CharSequence confirmationMessage = mContentView.getDescriptionText();
if (confirmationMessage != null) {
String accessibleText = new StringBuilder(currentTitle).append(",").append(
confirmationMessage.getText()).toString();
String accessibleText = currentTitle + "," + confirmationMessage;
getActivity().setTitle(Utils.createAccessibleSequence(currentTitle, accessibleText));
}
}
@VisibleForTesting
void setSubtitle() {
if (mEraseEsims) {
TextView confirmationMessage = mContentView.findViewById(R.id.sud_layout_description);
if (confirmationMessage != null) {
confirmationMessage.setText(R.string.main_clear_final_desc_esim);
}
}
mContentView.setDescriptionText(
mEraseEsims ? R.string.main_clear_final_desc_esim : R.string.main_clear_final_desc);
}
@Override

View File

@@ -18,8 +18,11 @@ package com.android.settings;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.Bundle;
import android.provider.Settings;
@@ -52,6 +55,7 @@ import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.ConfirmLockPattern;
import com.android.settings.system.reset.ResetNetworkConfirm;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
import java.util.ArrayList;
@@ -65,7 +69,7 @@ import java.util.Optional;
* prompt, followed by a keyguard pattern trace if the user has defined one, followed by a final
* strongly-worded "THIS WILL RESET EVERYTHING" prompt. If at any time the phone is allowed to go
* to sleep, is locked, et cetera, then the confirmation sequence is abandoned.
*
* <p>
* This is the initial screen.
*/
public class ResetNetwork extends InstrumentedFragment {
@@ -80,8 +84,20 @@ public class ResetNetwork extends InstrumentedFragment {
private View mContentView;
private Spinner mSubscriptionSpinner;
private Button mInitiateButton;
@VisibleForTesting View mEsimContainer;
@VisibleForTesting CheckBox mEsimCheckbox;
@VisibleForTesting
View mEsimContainer;
@VisibleForTesting
CheckBox mEsimCheckbox;
private BroadcastReceiver mDefaultSubChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() != SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED) {
return;
}
establishInitialState(getActiveSubscriptionInfoList());
}
};
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -96,6 +112,7 @@ public class ResetNetwork extends InstrumentedFragment {
/**
* Keyguard validation is run using the standard {@link ConfirmLockPattern}
* component as a subactivity
*
* @param request the request code to be returned once confirmation finishes
* @return true if confirmation launched
*/
@@ -131,7 +148,6 @@ public class ResetNetwork extends InstrumentedFragment {
if (Flags.resetMobileNetworkSettings()) {
resetOptions |= ResetNetworkRequest.RESET_IMS_STACK;
resetOptions |= ResetNetworkRequest.RESET_PHONE_PROCESS;
resetOptions |= ResetNetworkRequest.RESET_RILD;
}
ResetNetworkRequest request = new ResetNetworkRequest(resetOptions);
if (mSubscriptions != null && mSubscriptions.size() > 0) {
@@ -139,7 +155,7 @@ public class ResetNetwork extends InstrumentedFragment {
SubscriptionInfo subscription = mSubscriptions.get(selectedIndex);
int subId = subscription.getSubscriptionId();
request.setResetTelephonyAndNetworkPolicyManager(subId)
.setResetApn(subId);
.setResetApn(subId);
if (Flags.resetMobileNetworkSettings()) {
request.setResetImsSubId(subId);
}
@@ -215,7 +231,6 @@ public class ResetNetwork extends InstrumentedFragment {
}
int selectedIndex = 0;
int size = mSubscriptions.size();
List<String> subscriptionNames = new ArrayList<>();
for (SubscriptionInfo record : mSubscriptions) {
if (record.getSubscriptionId() == defaultSubscription) {
@@ -281,6 +296,8 @@ public class ResetNetwork extends InstrumentedFragment {
@Override
public void onResume() {
super.onResume();
getContext().registerReceiver(mDefaultSubChangeReceiver,
new IntentFilter(SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED));
if (mContentView == null) {
return;
@@ -297,6 +314,12 @@ public class ResetNetwork extends InstrumentedFragment {
establishInitialState(updatedSubscriptions);
}
@Override
public void onPause() {
super.onPause();
getContext().unregisterReceiver(mDefaultSubChangeReceiver);
}
private boolean showEuiccSettings(Context context) {
if (!SubscriptionUtil.isSimHardwareVisible(context)) {
return false;

View File

@@ -1,247 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings;
import android.app.Activity;
import android.app.ProgressDialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Looper;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.network.ResetNetworkOperationBuilder;
import com.android.settings.network.ResetNetworkRestrictionViewBuilder;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Confirm and execute a reset of the network settings to a clean "just out of the box"
* state. Multiple confirmations are required: first, a general "are you sure
* you want to do this?" prompt, followed by a keyguard pattern trace if the user
* has defined one, followed by a final strongly-worded "THIS WILL RESET EVERYTHING"
* prompt. If at any time the phone is allowed to go to sleep, is
* locked, et cetera, then the confirmation sequence is abandoned.
*
* This is the confirmation screen.
*/
public class ResetNetworkConfirm extends InstrumentedFragment {
private static final String TAG = "ResetNetworkConfirm";
@VisibleForTesting View mContentView;
@VisibleForTesting ResetNetworkTask mResetNetworkTask;
@VisibleForTesting Activity mActivity;
@VisibleForTesting ResetNetworkRequest mResetNetworkRequest;
private ProgressDialog mProgressDialog;
private AlertDialog mAlertDialog;
@VisibleForTesting ResetSubscriptionContract mResetSubscriptionContract;
private OnSubscriptionsChangedListener mSubscriptionsChangedListener;
/**
* Async task used to do all reset task. If error happens during
* erasing eSIM profiles or timeout, an error msg is shown.
*/
private class ResetNetworkTask extends AsyncTask<Void, Void, Boolean> {
private static final String TAG = "ResetNetworkTask";
private final Context mContext;
ResetNetworkTask(Context context) {
mContext = context;
}
@Override
protected Boolean doInBackground(Void... params) {
final AtomicBoolean resetEsimSuccess = new AtomicBoolean(true);
String resetEsimPackageName = mResetNetworkRequest.getResetEsimPackageName();
ResetNetworkOperationBuilder builder = mResetNetworkRequest
.toResetNetworkOperationBuilder(mContext, Looper.getMainLooper());
if (resetEsimPackageName != null) {
// Override reset eSIM option for the result of reset operation
builder = builder.resetEsim(resetEsimPackageName,
success -> { resetEsimSuccess.set(success); }
);
}
builder.build().run();
boolean isResetSucceed = resetEsimSuccess.get();
Log.d(TAG, "network factoryReset complete. succeeded: "
+ String.valueOf(isResetSucceed));
return isResetSucceed;
}
@Override
protected void onPostExecute(Boolean succeeded) {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
if (succeeded) {
Toast.makeText(mContext, R.string.reset_network_complete_toast, Toast.LENGTH_SHORT)
.show();
} else {
mAlertDialog = new AlertDialog.Builder(mContext)
.setTitle(R.string.reset_esim_error_title)
.setMessage(R.string.reset_esim_error_msg)
.setPositiveButton(android.R.string.ok, null /* listener */)
.show();
}
}
}
/**
* The user has gone through the multiple confirmation, so now we go ahead
* and reset the network settings to its factory-default state.
*/
@VisibleForTesting
Button.OnClickListener mFinalClickListener = new Button.OnClickListener() {
@Override
public void onClick(View v) {
if (Utils.isMonkeyRunning()) {
return;
}
// abandon execution if subscription no longer active
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
// Or not the progress dialog maybe not dismissed in fast clicking.
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
mProgressDialog = getProgressDialog(mActivity);
mProgressDialog.show();
mResetNetworkTask = new ResetNetworkTask(mActivity);
mResetNetworkTask.execute();
}
};
private ProgressDialog getProgressDialog(Context context) {
final ProgressDialog progressDialog = new ProgressDialog(context);
progressDialog.setIndeterminate(true);
progressDialog.setCancelable(false);
progressDialog.setMessage(
context.getString(R.string.main_clear_progress_text));
return progressDialog;
}
/**
* Configure the UI for the final confirmation interaction
*/
private void establishFinalConfirmationState() {
mContentView.findViewById(R.id.execute_reset_network)
.setOnClickListener(mFinalClickListener);
}
@VisibleForTesting
void setSubtitle() {
if (mResetNetworkRequest.getResetEsimPackageName() != null) {
((TextView) mContentView.findViewById(R.id.reset_network_confirm))
.setText(R.string.reset_network_final_desc_esim);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = (new ResetNetworkRestrictionViewBuilder(mActivity)).build();
if (view != null) {
mResetSubscriptionContract.close();
Log.w(TAG, "Access deny.");
return view;
}
mContentView = inflater.inflate(R.layout.reset_network_confirm, null);
establishFinalConfirmationState();
setSubtitle();
return mContentView;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args == null) {
args = savedInstanceState;
}
mResetNetworkRequest = new ResetNetworkRequest(args);
mActivity = getActivity();
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
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mResetNetworkRequest.writeIntoBundle(outState);
}
@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();
}
super.onDestroy();
}
@Override
public int getMetricsCategory() {
return SettingsEnums.RESET_NETWORK_CONFIRM;
}
}

View File

@@ -1,157 +0,0 @@
/*
* 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.ExecutorService;
import java.util.concurrent.Executors;
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(), resetRequest.getResetImsSubId());
mResetSubscriptionIds = subIdStream.sorted().distinct()
.filter(id -> SubscriptionManager.isUsableSubscriptionId(id))
.toArray();
if (mResetSubscriptionIds.length <= 0) {
return;
}
// Monitoring callback through background thread
mExecutorService = Executors.newSingleThreadExecutor();
startMonitorSubscriptionChange();
}
/**
* A method for detecting if there's any subscription under monitor no longer active.
* @return subscription ID which is no longer active.
*/
public Integer getAnyMissingSubscriptionId() {
if (mResetSubscriptionIds.length <= 0) {
return null;
}
SubscriptionManager mgr = getSubscriptionManager();
if (mgr == null) {
Log.w(TAG, "Fail to access subscription manager");
return mResetSubscriptionIds[0];
}
for (int idx = 0; idx < mResetSubscriptionIds.length; idx++) {
int subId = mResetSubscriptionIds[idx];
if (mgr.getActiveSubscriptionInfo(subId) == null) {
Log.w(TAG, "SubId " + subId + " no longer active.");
return subId;
}
}
return null;
}
/**
* Async callback when detecting if there's any subscription under monitor no longer active.
* @param subscriptionId subscription ID which is no longer active.
*/
public void onSubscriptionInactive(int subscriptionId) {}
@VisibleForTesting
protected SubscriptionManager getSubscriptionManager() {
return mContext.getSystemService(SubscriptionManager.class);
}
@VisibleForTesting
protected OnSubscriptionsChangedListener getChangeListener() {
return new OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
/**
* Reducing the processing time on main UI thread through a flag.
* Once flag get into false, which means latest callback has been
* processed.
*/
mSubscriptionsUpdateNotify.set(true);
// Back to main UI thread
mContext.getMainExecutor().execute(() -> {
// Remove notifications and perform checking.
if (mSubscriptionsUpdateNotify.getAndSet(false)) {
Integer subId = getAnyMissingSubscriptionId();
if (subId != null) {
onSubscriptionInactive(subId);
}
}
});
}
};
}
private void startMonitorSubscriptionChange() {
SubscriptionManager mgr = getSubscriptionManager();
if (mgr == null) {
return;
}
// update monitor listener
mSubscriptionsChangedListener = getChangeListener();
mgr.addOnSubscriptionsChangedListener(
mExecutorService, mSubscriptionsChangedListener);
}
// Implementation of AutoCloseable
public void close() {
if (mExecutorService == null) {
return;
}
// Stop monitoring subscription change
SubscriptionManager mgr = getSubscriptionManager();
if (mgr != null) {
mgr.removeOnSubscriptionsChangedListener(mSubscriptionsChangedListener);
}
// Release Executor
mExecutorService.shutdownNow();
mExecutorService = null;
}
}

View File

@@ -18,6 +18,8 @@ package com.android.settings;
import static android.provider.Settings.ACTION_PRIVACY_SETTINGS;
import android.annotation.FlaggedApi;
import android.app.Flags;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Bundle;
@@ -25,6 +27,8 @@ import android.telephony.ims.ImsRcsManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.biometrics.face.FaceSettings;
import com.android.settings.communal.CommunalPreferenceController;
@@ -33,6 +37,7 @@ import com.android.settings.network.MobileNetworkIntentConverter;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
import com.android.settings.security.SecuritySettingsFeatureProvider;
import com.android.settings.wifi.WifiUtils;
import com.google.android.setupdesign.util.ThemeHelper;
@@ -71,7 +76,18 @@ public class Settings extends SettingsActivity {
public static class NetworkProviderSettingsActivity extends SettingsActivity { /* empty */ }
public static class NetworkSelectActivity extends SettingsActivity { /* empty */ }
/** Activity for the Wi-Fi network details settings. */
public static class WifiDetailsSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiDetailsSettingsActivity extends SettingsActivity {
@Override
protected void createUiFromIntent(@Nullable Bundle savedState, Intent intent) {
Bundle bundle = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
if (TextUtils.isEmpty(bundle.getString(WifiUtils.KEY_CHOSEN_WIFIENTRY_KEY))) {
Log.e(getLocalClassName(), "The key of WifiEntry is empty!");
finishAndRemoveTask();
return;
}
super.createUiFromIntent(savedState, intent);
}
}
public static class WifiP2pSettingsActivity extends SettingsActivity { /* empty */ }
public static class AvailableVirtualKeyboardActivity extends SettingsActivity { /* empty */ }
public static class KeyboardLayoutPickerActivity extends SettingsActivity { /* empty */ }
@@ -301,6 +317,7 @@ public class Settings extends SettingsActivity {
public static class UserSettingsActivity extends SettingsActivity { /* empty */ }
public static class NotificationAccessSettingsActivity extends SettingsActivity { /* empty */ }
public static class NotificationAccessDetailsActivity extends SettingsActivity { /* empty */ }
public static class ManageAdaptiveNotificationsActivity extends SettingsActivity { /* empty */ }
public static class VrListenersSettingsActivity extends SettingsActivity { /* empty */ }
public static class PremiumSmsAccessActivity extends SettingsActivity { /* empty */ }
public static class PictureInPictureSettingsActivity extends SettingsActivity { /* empty */ }
@@ -317,11 +334,13 @@ public class Settings extends SettingsActivity {
public static class PrintSettingsActivity extends SettingsActivity { /* empty */ }
public static class PrintJobSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeBehaviorSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeBlockedEffectsSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeAutomationSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeScheduleRuleSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeEventRuleSettingsActivity extends SettingsActivity { /* empty */ }
@FlaggedApi(Flags.FLAG_MODES_UI)
public static class ModeSettingsActivity extends SettingsActivity { /* empty */ }
@FlaggedApi(Flags.FLAG_MODES_UI)
public static class ModesSettingsActivity extends SettingsActivity { /* empty */ }
public static class SoundSettingsActivity extends SettingsActivity { /* empty */ }
public static class ConfigureNotificationSettingsActivity extends SettingsActivity { /* empty */ }
public static class ConversationListSettingsActivity extends SettingsActivity { /* empty */ }
@@ -428,7 +447,7 @@ public class Settings extends SettingsActivity {
super.onNewIntent(intent);
Log.d(TAG, "Starting onNewIntent");
setIntent(intent);
createUiFromIntent(null /* savedState */, convertIntent(intent));
}

View File

@@ -18,7 +18,9 @@ package com.android.settings;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.hardware.fingerprint.FingerprintManager;
import android.net.Uri;
import android.provider.Settings;
import android.util.FeatureFlagUtils;
@@ -31,6 +33,7 @@ import com.android.settings.activityembedding.ActivityEmbeddingUtils;
import com.android.settings.biometrics.fingerprint2.BiometricsEnvironment;
import com.android.settings.core.instrumentation.ElapsedTimeUtils;
import com.android.settings.development.DeveloperOptionsActivityLifecycle;
import com.android.settings.flags.Flags;
import com.android.settings.fuelgauge.BatterySettingsStorage;
import com.android.settings.homepage.SettingsHomepageActivity;
import com.android.settings.localepicker.LocaleNotificationDataManager;
@@ -39,17 +42,24 @@ import com.android.settings.overlay.FeatureFactoryImpl;
import com.android.settings.spa.SettingsSpaEnvironment;
import com.android.settingslib.applications.AppIconCacheManager;
import com.android.settingslib.datastore.BackupRestoreStorageManager;
import com.android.settingslib.metadata.PreferenceScreenMetadata;
import com.android.settingslib.metadata.PreferenceScreenRegistry;
import com.android.settingslib.metadata.ProvidePreferenceScreenOptions;
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory;
import com.google.android.setupcompat.util.WizardManagerHelper;
import java.lang.ref.WeakReference;
import java.util.List;
/** Settings application which sets up activity embedding rules for the large screen device. */
@ProvidePreferenceScreenOptions(
codegenCollector = "com.android.settings/PreferenceScreenCollector/get"
)
public class SettingsApplication extends Application {
private WeakReference<SettingsHomepageActivity> mHomeActivity = new WeakReference<>(null);
private BiometricsEnvironment mBiometricsEnvironment;
@Nullable private BiometricsEnvironment mBiometricsEnvironment;
@Override
protected void attachBaseContext(Context base) {
@@ -61,6 +71,11 @@ public class SettingsApplication extends Application {
public void onCreate() {
super.onCreate();
if (Flags.catalyst()) {
PreferenceScreenRegistry.INSTANCE.setPreferenceScreensSupplier(
this::getPreferenceScreens);
}
BackupRestoreStorageManager.getInstance(this)
.add(
new BatterySettingsStorage(this),
@@ -73,7 +88,6 @@ public class SettingsApplication extends Application {
// Set Spa environment.
setSpaEnvironment();
mBiometricsEnvironment = new BiometricsEnvironment(this);
if (ActivityEmbeddingUtils.isSettingsSplitEnabled(this)
&& FeatureFlagUtils.isEnabled(this,
@@ -88,6 +102,13 @@ public class SettingsApplication extends Application {
registerActivityLifecycleCallbacks(new DeveloperOptionsActivityLifecycle());
}
/** Returns the screens using metadata. */
protected List<PreferenceScreenMetadata> getPreferenceScreens() {
// PreferenceScreenCollector is generated by annotation processor from classes annotated
// with @ProvidePreferenceScreen
return PreferenceScreenCollector.get(this);
}
@Override
public void onTerminate() {
BackupRestoreStorageManager.getInstance(this).removeAll();
@@ -117,7 +138,20 @@ public class SettingsApplication extends Application {
@Nullable
public BiometricsEnvironment getBiometricEnvironment() {
return mBiometricsEnvironment;
if (Flags.fingerprintV2Enrollment()) {
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
final FingerprintManager fpm = getSystemService(FingerprintManager.class);
if (mBiometricsEnvironment == null) {
mBiometricsEnvironment = new BiometricsEnvironment(this, fpm);
}
return mBiometricsEnvironment;
} else {
return null;
}
}
return null;
}
@Override

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2024 The Android Open 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.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.search.Indexable;
/**
* Base class for fragment suitable for unit testing.
*/
public abstract class SettingsPreferenceFragmentBase extends SettingsPreferenceFragment
implements Indexable {
@Override
@SuppressWarnings({"RequiresNullabilityAnnotation"})
public void onCreate(final Bundle icicle) {
super.onCreate(icicle);
onCreateCallback(icicle);
}
@Override
@SuppressWarnings({"RequiresNullabilityAnnotation"})
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
onActivityCreatedCallback(savedInstanceState);
}
@Override
public void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
onSaveInstanceStateCallback(outState);
}
@Override
public void onStart() {
super.onStart();
onStartCallback();
}
@Override
public void onStop() {
super.onStop();
onStopCallback();
}
protected Activity getCurrentActivity() {
return getActivity();
}
/**
* Callback called from {@link #onCreate}
*/
public abstract void onCreateCallback(@Nullable Bundle icicle);
/**
* Callback called from {@link #onActivityCreated}
*/
public abstract void onActivityCreatedCallback(@Nullable Bundle savedInstanceState);
/**
* Callback called from {@link #onStart}
*/
public abstract void onStartCallback();
/**
* Callback called from {@link #onStop}
*/
public abstract void onStopCallback();
/**
* Callback called from {@link #onSaveInstanceState}
*/
public void onSaveInstanceStateCallback(@NonNull final Bundle outState) {
// Do nothing.
}
}

View File

@@ -24,6 +24,10 @@ import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
import static com.android.settings.password.ConfirmDeviceCredentialActivity.BIOMETRIC_PROMPT_AUTHENTICATORS;
import static com.android.settings.password.ConfirmDeviceCredentialActivity.BIOMETRIC_PROMPT_HIDE_BACKGROUND;
import static com.android.settings.password.ConfirmDeviceCredentialActivity.BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT;
import android.app.ActionBar;
import android.app.Activity;
import android.app.ActivityManager;
@@ -54,6 +58,7 @@ import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.SensorProperties;
import android.hardware.face.Face;
import android.hardware.face.FaceManager;
@@ -122,6 +127,7 @@ import com.android.settings.dashboard.profileselector.ProfileFragmentBridge;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment.ProfileType;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settingslib.widget.ActionBarShadowController;
import com.android.settingslib.widget.AdaptiveIcon;
@@ -193,6 +199,15 @@ public final class Utils extends com.android.settingslib.Utils {
return ActivityManager.isUserAMonkey();
}
/**
* Enum for returning biometric status.
* {@link OK} no error detected when requesting mandatory biometrics authentication
* {@link NOT_ACTIVE} mandatory biometrics is not active
* {@link LOCKOUT} biometric sensors are in lockout mode
* {@link ERROR} corresponds to other errors
*/
public enum BiometricStatus {OK, NOT_ACTIVE, LOCKOUT, ERROR}
/**
* Returns whether the device is voice-capable (meaning, it is also a phone).
*/
@@ -1418,13 +1433,15 @@ public final class Utils extends com.android.settingslib.Utils {
public static void setupEdgeToEdge(@NonNull FragmentActivity activity) {
ViewCompat.setOnApplyWindowInsetsListener(activity.findViewById(android.R.id.content),
(v, windowInsets) -> {
Insets insets = windowInsets.getInsets(
final Insets insets = windowInsets.getInsets(
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.ime()
| WindowInsetsCompat.Type.displayCutout());
int statusBarHeight = activity.getWindow().getDecorView().getRootWindowInsets()
.getInsets(WindowInsetsCompat.Type.statusBars()).top;
int newInsetsTop = activity.getWindow().getDecorView().getRootWindowInsets()
.getInsets(WindowInsetsCompat.Type.statusBars()
| WindowInsetsCompat.Type.captionBar()).top;
// Apply the insets paddings to the view.
v.setPadding(insets.left, statusBarHeight, insets.right, insets.bottom);
v.setPadding(insets.left, newInsetsTop, insets.right, insets.bottom);
// Return CONSUMED if you don't want the window insets to keep being
// passed down to descendant views.
@@ -1478,6 +1495,93 @@ public final class Utils extends com.android.settingslib.Utils {
disableComponent(pm, new ComponentName(context, Settings.CreateShortcutActivity.class));
}
/**
* Request biometric authentication if all requirements for mandatory biometrics is satisfied.
*
* @param context of the corresponding activity/fragment
* @param biometricsAuthenticationRequested if the activity/fragment has already requested for
* biometric prompt
* @param userId user id for the authentication request
* @return biometric status when mandatory biometrics authentication is requested
*/
public static BiometricStatus requestBiometricAuthenticationForMandatoryBiometrics(
@NonNull Context context,
boolean biometricsAuthenticationRequested, int userId) {
final BiometricManager biometricManager = context.getSystemService(BiometricManager.class);
if (biometricManager == null) {
Log.e(TAG, "Biometric Manager is null.");
return BiometricStatus.NOT_ACTIVE;
}
final int status = biometricManager.canAuthenticate(userId,
BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
if (android.hardware.biometrics.Flags.mandatoryBiometrics()
&& !biometricsAuthenticationRequested) {
switch(status) {
case BiometricManager.BIOMETRIC_SUCCESS:
return BiometricStatus.OK;
case BiometricManager.BIOMETRIC_ERROR_LOCKOUT:
return BiometricStatus.LOCKOUT;
case BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE:
case BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS:
return BiometricStatus.NOT_ACTIVE;
default:
return BiometricStatus.ERROR;
}
}
return BiometricStatus.NOT_ACTIVE;
}
/**
* Launch biometric prompt for mandatory biometrics. Call
* {@link #requestBiometricAuthenticationForMandatoryBiometrics(Context, boolean, int)}
* to check if all requirements for mandatory biometrics is satisfied
* before launching biometric prompt.
*
* @param fragment corresponding fragment of the surface
* @param requestCode for starting the new activity
* @param userId user id for the authentication request
* @param hideBackground if the background activity screen needs to be hidden
*/
public static void launchBiometricPromptForMandatoryBiometrics(@NonNull Fragment fragment,
int requestCode, int userId, boolean hideBackground) {
fragment.startActivityForResult(getIntentForBiometricAuthentication(fragment.getResources(),
userId, hideBackground), requestCode);
}
/**
* Launch biometric prompt for mandatory biometrics. Call
* {@link #requestBiometricAuthenticationForMandatoryBiometrics(Context, boolean, int)}
* to check if all requirements for mandatory biometrics is satisfied
* before launching biometric prompt.
*
* @param activity corresponding activity of the surface
* @param requestCode for starting the new activity
* @param userId user id for the authentication request
* @param hideBackground if the background activity screen needs to be hidden
*/
public static void launchBiometricPromptForMandatoryBiometrics(@NonNull Activity activity,
int requestCode, int userId, boolean hideBackground) {
activity.startActivityForResult(getIntentForBiometricAuthentication(
activity.getResources(), userId, hideBackground), requestCode);
}
private static Intent getIntentForBiometricAuthentication(Resources resources, int userId,
boolean hideBackground) {
final Intent intent = new Intent();
intent.putExtra(BIOMETRIC_PROMPT_AUTHENTICATORS,
BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
intent.putExtra(BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT,
resources.getString(R.string.cancel));
intent.putExtra(KeyguardManager.EXTRA_DESCRIPTION,
resources.getString(R.string.mandatory_biometrics_prompt_description));
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_ALLOW_ANY_USER, true);
intent.putExtra(EXTRA_USER_ID, userId);
intent.putExtra(BIOMETRIC_PROMPT_HIDE_BACKGROUND, hideBackground);
intent.setClassName(SETTINGS_PACKAGE_NAME,
ConfirmDeviceCredentialActivity.InternalActivity.class.getName());
return intent;
}
private static void disableComponent(PackageManager pm, ComponentName componentName) {
pm.setComponentEnabledSetting(componentName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);

View File

@@ -26,6 +26,7 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.android.settings.R;
@@ -101,6 +102,11 @@ public class AccessibilityActivityPreference extends RestrictedPreference {
return mLabel;
}
@NonNull
public ComponentName getComponentName() {
return mComponentName;
}
private Drawable getA11yActivityIcon() {
ActivityInfo activityInfo = mA11yShortcutInfo.getActivityInfo();
Drawable serviceIcon;

View File

@@ -33,8 +33,15 @@ public class AccessibilityButtonFragment extends DashboardFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final int titleResource = AccessibilityUtil.isGestureNavigateEnabled(getPrefContext())
? R.string.accessibility_button_gesture_title : R.string.accessibility_button_title;
final int titleResource;
if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
titleResource = R.string.accessibility_button_title;
} else {
titleResource = AccessibilityUtil.isGestureNavigateEnabled(getPrefContext())
? R.string.accessibility_button_gesture_title
: R.string.accessibility_button_title;
}
getActivity().setTitle(titleResource);
}

View File

@@ -50,8 +50,12 @@ public class AccessibilityButtonGesturePreferenceController extends BasePreferen
@Override
public int getAvailabilityStatus() {
return AccessibilityUtil.isGestureNavigateEnabled(mContext)
? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
return CONDITIONALLY_UNAVAILABLE;
} else {
return AccessibilityUtil.isGestureNavigateEnabled(mContext)
? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
}
}
@Override

View File

@@ -19,53 +19,22 @@ package com.android.settings.accessibility;
import static com.android.settings.accessibility.ItemInfoArrayAdapter.ItemInfo;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.icu.text.MessageFormat;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.ImageSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RawRes;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import com.android.server.accessibility.Flags;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.utils.AnnotationSpan;
import com.android.settingslib.widget.LottieColorUtils;
import com.airbnb.lottie.LottieAnimationView;
import com.airbnb.lottie.LottieDrawable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* Utility class for creating the edit dialog.
*/
@@ -75,25 +44,20 @@ public class AccessibilityDialogUtils {
/** Denotes the dialog emuns for show dialog. */
@Retention(RetentionPolicy.SOURCE)
public @interface DialogEnums {
/** OPEN: Settings > Accessibility > Any toggle service > Shortcut > Settings. */
int EDIT_SHORTCUT = 1;
/** OPEN: Settings > Accessibility > Magnification > Shortcut > Settings. */
int MAGNIFICATION_EDIT_SHORTCUT = 1001;
/**
* OPEN: Settings > Accessibility > Downloaded toggle service > Toggle use service to
* enable service.
*/
int ENABLE_WARNING_FROM_TOGGLE = 1002;
/** OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox. */
/**
* OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut options
* settings.
*/
int ENABLE_WARNING_FROM_SHORTCUT = 1003;
/**
* OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox
* toggle.
* OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut toggle
*/
int ENABLE_WARNING_FROM_SHORTCUT_TOGGLE = 1004;
@@ -127,83 +91,6 @@ public class AccessibilityDialogUtils {
int DIALOG_RESET_SETTINGS = 1009;
}
/**
* IntDef enum for dialog type that indicates different dialog for user to choose the shortcut
* type.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
DialogType.EDIT_SHORTCUT_GENERIC,
DialogType.EDIT_SHORTCUT_GENERIC_SUW,
DialogType.EDIT_SHORTCUT_MAGNIFICATION,
DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW,
})
public @interface DialogType {
int EDIT_SHORTCUT_GENERIC = 0;
int EDIT_SHORTCUT_GENERIC_SUW = 1;
int EDIT_SHORTCUT_MAGNIFICATION = 2;
int EDIT_SHORTCUT_MAGNIFICATION_SUW = 3;
}
/**
* Method to show the edit shortcut dialog.
*
* @param context A valid context
* @param dialogType The type of edit shortcut dialog
* @param dialogTitle The title of edit shortcut dialog
* @param listener The listener to determine the action of edit shortcut dialog
* @return A edit shortcut dialog for showing
*/
public static AlertDialog showEditShortcutDialog(Context context, int dialogType,
CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
final AlertDialog alertDialog = createDialog(context, dialogType, dialogTitle, listener);
alertDialog.show();
setScrollIndicators(alertDialog);
return alertDialog;
}
/**
* Updates the shortcut content in edit shortcut dialog.
*
* @param context A valid context
* @param editShortcutDialog Need to be a type of edit shortcut dialog
* @return True if the update is successful
*/
public static boolean updateShortcutInDialog(Context context,
Dialog editShortcutDialog) {
final View container = editShortcutDialog.findViewById(R.id.container_layout);
if (container != null) {
initSoftwareShortcut(context, container);
initHardwareShortcut(context, container);
return true;
}
return false;
}
private static AlertDialog createDialog(Context context, int dialogType,
CharSequence dialogTitle, DialogInterface.OnClickListener listener) {
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setView(createEditDialogContentView(context, dialogType))
.setTitle(dialogTitle)
.setPositiveButton(R.string.save, listener)
.setNegativeButton(R.string.cancel,
(DialogInterface dialog, int which) -> dialog.dismiss())
.create();
return alertDialog;
}
/**
* Sets the scroll indicators for dialog view. The indicators appears while content view is
* out of vision for vertical scrolling.
*/
private static void setScrollIndicators(AlertDialog dialog) {
final ScrollView scrollView = dialog.findViewById(R.id.container_layout);
setScrollIndicators(scrollView);
}
/**
* Sets the scroll indicators for dialog view. The indicators appear while content view is
* out of vision for vertical scrolling.
@@ -217,284 +104,6 @@ public class AccessibilityDialogUtils {
View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
}
/**
* Get a content View for the edit shortcut dialog.
*
* @param context A valid context
* @param dialogType The type of edit shortcut dialog
* @return A content view suitable for viewing
*/
private static View createEditDialogContentView(Context context, int dialogType) {
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View contentView = null;
switch (dialogType) {
case DialogType.EDIT_SHORTCUT_GENERIC:
contentView = inflater.inflate(
R.layout.accessibility_edit_shortcut, null);
initSoftwareShortcut(context, contentView);
initHardwareShortcut(context, contentView);
break;
case DialogType.EDIT_SHORTCUT_GENERIC_SUW:
contentView = inflater.inflate(
R.layout.accessibility_edit_shortcut, null);
initSoftwareShortcutForSUW(context, contentView);
initHardwareShortcut(context, contentView);
break;
case DialogType.EDIT_SHORTCUT_MAGNIFICATION:
contentView = inflater.inflate(
R.layout.accessibility_edit_shortcut_magnification, null);
initSoftwareShortcut(context, contentView);
initHardwareShortcut(context, contentView);
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
initTwoFingerDoubleTapMagnificationShortcut(context, contentView);
}
initMagnifyShortcut(context, contentView);
initAdvancedWidget(contentView);
break;
case DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW:
contentView = inflater.inflate(
R.layout.accessibility_edit_shortcut_magnification, null);
initSoftwareShortcutForSUW(context, contentView);
initHardwareShortcut(context, contentView);
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
initTwoFingerDoubleTapMagnificationShortcut(context, contentView);
}
initMagnifyShortcut(context, contentView);
initAdvancedWidget(contentView);
break;
default:
throw new IllegalArgumentException();
}
return contentView;
}
private static void setupShortcutWidget(View view, CharSequence titleText,
CharSequence summaryText, @DrawableRes int imageResId) {
setupShortcutWidgetWithTitleAndSummary(view, titleText, summaryText);
setupShortcutWidgetWithImageResource(view, imageResId);
}
private static void setupShortcutWidgetWithImageRawResource(Context context,
View view, CharSequence titleText,
CharSequence summaryText, @RawRes int imageRawResId) {
setupShortcutWidgetWithTitleAndSummary(view, titleText, summaryText);
setupShortcutWidgetWithImageRawResource(context, view, imageRawResId);
}
private static void setupShortcutWidgetWithTitleAndSummary(View view, CharSequence titleText,
CharSequence summaryText) {
final CheckBox checkBox = view.findViewById(R.id.checkbox);
checkBox.setText(titleText);
final TextView summary = view.findViewById(R.id.summary);
if (TextUtils.isEmpty(summaryText)) {
summary.setVisibility(View.GONE);
} else {
summary.setText(summaryText);
summary.setMovementMethod(LinkMovementMethod.getInstance());
summary.setFocusable(false);
}
}
private static void setupShortcutWidgetWithImageResource(View view,
@DrawableRes int imageResId) {
final ImageView imageView = view.findViewById(R.id.image);
imageView.setImageResource(imageResId);
}
private static void setupShortcutWidgetWithImageRawResource(Context context, View view,
@RawRes int imageRawResId) {
final LottieAnimationView lottieView = view.findViewById(R.id.image);
lottieView.setFailureListener(
result -> Log.w(TAG, "Invalid image raw resource id: " + imageRawResId,
result));
lottieView.setAnimation(imageRawResId);
lottieView.setRepeatCount(LottieDrawable.INFINITE);
LottieColorUtils.applyDynamicColors(context, lottieView);
lottieView.playAnimation();
}
private static void initSoftwareShortcutForSUW(Context context, View view) {
final View dialogView = view.findViewById(R.id.software_shortcut);
final CharSequence title = context.getText(
R.string.accessibility_shortcut_edit_dialog_title_software);
final TextView summary = dialogView.findViewById(R.id.summary);
final int lineHeight = summary.getLineHeight();
setupShortcutWidget(dialogView, title,
retrieveSoftwareShortcutSummaryForSUW(context, lineHeight),
retrieveSoftwareShortcutImageResId(context));
}
private static void initSoftwareShortcut(Context context, View view) {
final View dialogView = view.findViewById(R.id.software_shortcut);
final TextView summary = dialogView.findViewById(R.id.summary);
final int lineHeight = summary.getLineHeight();
setupShortcutWidget(dialogView,
retrieveTitle(context),
retrieveSoftwareShortcutSummary(context, lineHeight),
retrieveSoftwareShortcutImageResId(context));
}
private static void initHardwareShortcut(Context context, View view) {
final View dialogView = view.findViewById(R.id.hardware_shortcut);
final CharSequence title = context.getText(
R.string.accessibility_shortcut_edit_dialog_title_hardware);
final CharSequence summary = context.getText(
R.string.accessibility_shortcut_edit_dialog_summary_hardware);
setupShortcutWidget(dialogView, title, summary,
R.drawable.a11y_shortcut_type_hardware);
}
private static void initMagnifyShortcut(Context context, View view) {
final View dialogView = view.findViewById(R.id.triple_tap_shortcut);
final CharSequence title = context.getText(
R.string.accessibility_shortcut_edit_dialog_title_triple_tap);
String summary = context.getString(
R.string.accessibility_shortcut_edit_dialog_summary_triple_tap);
// Format the number '3' in the summary.
final Object[] arguments = {3};
summary = MessageFormat.format(summary, arguments);
setupShortcutWidgetWithImageRawResource(context, dialogView, title, summary,
R.raw.a11y_shortcut_type_triple_tap);
}
private static void initTwoFingerDoubleTapMagnificationShortcut(Context context, View view) {
// TODO(b/306153204): Update shortcut string and image when UX provides them
final View dialogView = view.findViewById(R.id.two_finger_triple_tap_shortcut);
final CharSequence title = context.getText(
R.string.accessibility_shortcut_edit_dialog_title_two_finger_double_tap);
String summary = context.getString(
R.string.accessibility_shortcut_edit_dialog_summary_two_finger_double_tap);
// Format the number '2' in the summary.
final Object[] arguments = {2};
summary = MessageFormat.format(summary, arguments);
setupShortcutWidgetWithImageRawResource(context, dialogView, title, summary,
R.raw.a11y_shortcut_type_triple_tap);
dialogView.setVisibility(View.VISIBLE);
}
private static void initAdvancedWidget(View view) {
final LinearLayout advanced = view.findViewById(R.id.advanced_shortcut);
final View tripleTap = view.findViewById(R.id.triple_tap_shortcut);
advanced.setOnClickListener((View v) -> {
advanced.setVisibility(View.GONE);
tripleTap.setVisibility(View.VISIBLE);
});
}
private static CharSequence retrieveSoftwareShortcutSummaryForSUW(Context context,
int lineHeight) {
final SpannableStringBuilder sb = new SpannableStringBuilder();
if (!AccessibilityUtil.isFloatingMenuEnabled(context)) {
sb.append(getSummaryStringWithIcon(context, lineHeight));
}
return sb;
}
private static CharSequence retrieveTitle(Context context) {
int resId;
if (AccessibilityUtil.isFloatingMenuEnabled(context)) {
resId = R.string.accessibility_shortcut_edit_dialog_title_software;
} else if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
resId = R.string.accessibility_shortcut_edit_dialog_title_software_by_gesture;
} else {
resId = R.string.accessibility_shortcut_edit_dialog_title_software;
}
return context.getText(resId);
}
private static CharSequence retrieveSoftwareShortcutSummary(Context context, int lineHeight) {
final SpannableStringBuilder sb = new SpannableStringBuilder();
if (AccessibilityUtil.isFloatingMenuEnabled(context)) {
sb.append(getCustomizeAccessibilityButtonLink(context));
} else if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
final int resId = AccessibilityUtil.isTouchExploreEnabled(context)
? R.string.accessibility_shortcut_edit_dialog_summary_software_gesture_talkback
: R.string.accessibility_shortcut_edit_dialog_summary_software_gesture;
sb.append(context.getText(resId));
sb.append("\n\n");
sb.append(getCustomizeAccessibilityButtonLink(context));
} else {
sb.append(getSummaryStringWithIcon(context, lineHeight));
sb.append("\n\n");
sb.append(getCustomizeAccessibilityButtonLink(context));
}
return sb;
}
private static int retrieveSoftwareShortcutImageResId(Context context) {
int resId;
if (AccessibilityUtil.isFloatingMenuEnabled(context)) {
resId = R.drawable.a11y_shortcut_type_software_floating;
} else if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
resId = AccessibilityUtil.isTouchExploreEnabled(context)
? R.drawable.a11y_shortcut_type_software_gesture_talkback
: R.drawable.a11y_shortcut_type_software_gesture;
} else {
resId = R.drawable.a11y_shortcut_type_software;
}
return resId;
}
private static CharSequence getCustomizeAccessibilityButtonLink(Context context) {
final View.OnClickListener linkListener = v -> new SubSettingLauncher(context)
.setDestination(AccessibilityButtonFragment.class.getName())
.setSourceMetricsCategory(
SettingsEnums.SWITCH_SHORTCUT_DIALOG_ACCESSIBILITY_BUTTON_SETTINGS)
.launch();
final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo(
AnnotationSpan.LinkInfo.DEFAULT_ANNOTATION, linkListener);
return AnnotationSpan.linkify(context.getText(
R.string.accessibility_shortcut_edit_dialog_summary_software_floating), linkInfo);
}
private static SpannableString getSummaryStringWithIcon(Context context, int lineHeight) {
final String summary = context
.getString(R.string.accessibility_shortcut_edit_dialog_summary_software);
final SpannableString spannableMessage = SpannableString.valueOf(summary);
// Icon
final int indexIconStart = summary.indexOf("%s");
final int indexIconEnd = indexIconStart + 2;
final Drawable icon = context.getDrawable(R.drawable.ic_accessibility_new);
final ImageSpan imageSpan = new ImageSpan(icon);
imageSpan.setContentDescription("");
icon.setBounds(0, 0, lineHeight, lineHeight);
spannableMessage.setSpan(
imageSpan, indexIconStart, indexIconEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannableMessage;
}
/**
* Returns the color associated with the specified attribute in the context's theme.
*/
@ColorInt
private static int getThemeAttrColor(final Context context, final int attributeColor) {
final int colorResId = getAttrResourceId(context, attributeColor);
return ContextCompat.getColor(context, colorResId);
}
/**
* Returns the identifier of the resolved resource assigned to the given attribute.
*/
private static int getAttrResourceId(final Context context, final int attributeColor) {
final int[] attrs = {attributeColor};
final TypedArray typedArray = context.obtainStyledAttributes(attrs);
final int colorResId = typedArray.getResourceId(0, 0);
typedArray.recycle();
return colorResId;
}
/**
* Creates a dialog with the given view.
*

View File

@@ -0,0 +1,152 @@
/*
* Copyright (C) 2024 The Android Open 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.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroupAdapter;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
import com.android.settingslib.widget.IllustrationPreference;
/** Utilities for {@code Settings > Accessibility} fragments. */
public class AccessibilityFragmentUtils {
// TODO: b/350782252 - Replace with an official library-provided solution when available.
/**
* Modifies the existing {@link RecyclerViewAccessibilityDelegate} of the provided
* {@link RecyclerView} for this fragment to report the number of visible and important
* items on this page via the RecyclerView's {@link AccessibilityNodeInfo}.
*
* <p><strong>Note:</strong> This is special-cased to the structure of these fragments:
* one column, N rows (one per preference, including category titles and header+footer
* preferences), <=N 'important' rows (image prefs without content descriptions). This
* is not intended for use with generic {@link RecyclerView}s.
*/
public static RecyclerView addCollectionInfoToAccessibilityDelegate(RecyclerView recyclerView) {
if (!Flags.toggleFeatureFragmentCollectionInfo()) {
return recyclerView;
}
final RecyclerViewAccessibilityDelegate delegate =
recyclerView.getCompatAccessibilityDelegate();
if (delegate == null) {
// No delegate, so do nothing. This should not occur for real RecyclerViews.
return recyclerView;
}
recyclerView.setAccessibilityDelegateCompat(
new RvAccessibilityDelegateWrapper(recyclerView, delegate) {
@Override
public void onInitializeAccessibilityNodeInfo(@NonNull View host,
@NonNull AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
if (!(recyclerView.getAdapter()
instanceof final PreferenceGroupAdapter preferenceGroupAdapter)) {
return;
}
final int visibleCount = preferenceGroupAdapter.getItemCount();
int importantCount = 0;
for (int i = 0; i < visibleCount; i++) {
if (isPreferenceImportantToA11y(preferenceGroupAdapter.getItem(i))) {
importantCount++;
}
}
info.unwrap().setCollectionInfo(
new AccessibilityNodeInfo.CollectionInfo(
/*rowCount=*/visibleCount,
/*columnCount=*/1,
/*hierarchical=*/false,
AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE,
/*itemCount=*/visibleCount,
/*importantForAccessibilityItemCount=*/importantCount));
}
});
return recyclerView;
}
/**
* Returns whether the preference will be marked as important to accessibility for the sake
* of calculating {@link AccessibilityNodeInfo.CollectionInfo} counts.
*
* <p>The accessibility service itself knows this information for an individual preference
* on the screen, but it expects the preference's {@link RecyclerView} to also provide the
* same information for its entire set of adapter items.
*/
@VisibleForTesting
static boolean isPreferenceImportantToA11y(Preference pref) {
if ((pref instanceof IllustrationPreference illustrationPref
&& TextUtils.isEmpty(illustrationPref.getContentDescription()))
|| pref instanceof PaletteListPreference) {
// Illustration preference that is visible but unannounced by accessibility services.
return false;
}
// All other preferences from the PreferenceGroupAdapter are important.
return true;
}
/**
* Wrapper around a {@link RecyclerViewAccessibilityDelegate} that allows customizing
* a subset of methods and while also deferring to the original. All overridden methods
* in instantiations of this class should call {@code super}.
*/
private static class RvAccessibilityDelegateWrapper extends RecyclerViewAccessibilityDelegate {
private final RecyclerViewAccessibilityDelegate mOriginal;
RvAccessibilityDelegateWrapper(RecyclerView recyclerView,
RecyclerViewAccessibilityDelegate original) {
super(recyclerView);
mOriginal = original;
}
@Override
public boolean performAccessibilityAction(@NonNull View host, int action, Bundle args) {
return mOriginal.performAccessibilityAction(host, action, args);
}
@Override
public void onInitializeAccessibilityNodeInfo(@NonNull View host,
@NonNull AccessibilityNodeInfoCompat info) {
mOriginal.onInitializeAccessibilityNodeInfo(host, info);
}
@Override
public void onInitializeAccessibilityEvent(@NonNull View host,
@NonNull AccessibilityEvent event) {
mOriginal.onInitializeAccessibilityEvent(host, event);
}
@Override
@NonNull
public AccessibilityDelegateCompat getItemDelegate() {
if (mOriginal == null) {
// Needed for super constructor which calls getItemDelegate before mOriginal is
// defined, but unused by actual clients of this RecyclerViewAccessibilityDelegate
// which invoke getItemDelegate() after the constructor finishes.
return new ItemDelegate(this);
}
return mOriginal.getItemDelegate();
}
}
}

View File

@@ -21,6 +21,7 @@ import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
@@ -42,6 +43,7 @@ public abstract class AccessibilityQuickSettingsPrimarySwitchPreferenceControlle
private boolean mNeedsQSTooltipReshow = false;
/** Returns the accessibility tile component name. */
@Nullable
abstract ComponentName getTileComponentName();
/** Returns the accessibility tile tooltip content. */

View File

@@ -16,8 +16,12 @@
package com.android.settings.accessibility;
import android.content.ComponentName;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.search.SearchIndexableRaw;
import java.util.List;
@@ -28,10 +32,22 @@ import java.util.List;
public interface AccessibilitySearchFeatureProvider {
/**
* Returns a list of raw data for indexing. See {@link SearchIndexableRaw}
* Returns accessibility features to be searched where the accessibility features are always on
* the device and their feature names won't change.
*
* @param context a valid context {@link Context} instance
* @return a list of {@link SearchIndexableRaw} references. Can be null.
* @return a list of {@link SearchIndexableRaw} references
*/
@Nullable
List<SearchIndexableRaw> getSearchIndexableRawData(Context context);
/**
* Returns synonyms of the Accessibility component that is used for search.
*
* @param context the context that is used for grabbing resources
* @param componentName the ComponentName of the accessibility feature
* @return a comma separated synonyms e.g. "wifi, wi-fi, network connection"
*/
@NonNull
String getSynonymsForComponent(@NonNull Context context, @NonNull ComponentName componentName);
}

View File

@@ -16,8 +16,12 @@
package com.android.settings.accessibility;
import android.content.ComponentName;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.search.SearchIndexableRaw;
import java.util.List;
@@ -27,8 +31,16 @@ import java.util.List;
*/
public class AccessibilitySearchFeatureProviderImpl implements AccessibilitySearchFeatureProvider {
@Nullable
@Override
public List<SearchIndexableRaw> getSearchIndexableRawData(Context context) {
return null;
}
@NonNull
@Override
public String getSynonymsForComponent(@NonNull Context context,
@NonNull ComponentName componentName) {
return "";
}
}

View File

@@ -26,6 +26,7 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.android.settings.R;
@@ -95,6 +96,11 @@ public class AccessibilityServicePreference extends RestrictedPreference {
super.performClick();
}
@NonNull
public ComponentName getComponentName() {
return mComponentName;
}
private Drawable getA11yServiceIcon() {
ResolveInfo resolveInfo = mA11yServiceInfo.getResolveInfo();
Drawable serviceIcon;

View File

@@ -16,12 +16,13 @@
package com.android.settings.accessibility;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.DEFAULT;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityShortcutInfo;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ServiceInfo;
import android.hardware.input.InputManager;
import android.os.Bundle;
import android.os.Handler;
@@ -29,7 +30,6 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Pair;
import android.view.InputDevice;
import android.view.accessibility.AccessibilityManager;
@@ -57,8 +57,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/** Activity with the accessibility settings. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
@@ -73,7 +71,8 @@ public class AccessibilitySettings extends DashboardFragment implements
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_DOWNLOADED_SERVICES = "user_installed_services_category";
@VisibleForTesting
static final String CATEGORY_DOWNLOADED_SERVICES = "user_installed_services_category";
private static final String CATEGORY_KEYBOARD_OPTIONS = "physical_keyboard_options_category";
@VisibleForTesting
static final String CATEGORY_INTERACTION_CONTROL = "interaction_control_category";
@@ -152,7 +151,7 @@ public class AccessibilitySettings extends DashboardFragment implements
};
@VisibleForTesting
final AccessibilitySettingsContentObserver mSettingsContentObserver;
AccessibilitySettingsContentObserver mSettingsContentObserver;
private final Map<String, PreferenceCategory> mCategoryToPrefCategoryMap =
new ArrayMap<>();
@@ -166,9 +165,14 @@ public class AccessibilitySettings extends DashboardFragment implements
private boolean mIsForeground = true;
public AccessibilitySettings() {
mSettingsContentObserver = new AccessibilitySettingsContentObserver(mHandler);
}
private void initializeSettingsContentObserver() {
// Observe changes to anything that the shortcut can toggle, so we can reflect updates
final Collection<AccessibilityShortcutController.FrameworkFeatureInfo> features =
AccessibilityShortcutController.getFrameworkShortcutFeaturesMap().values();
AccessibilityShortcutController
.getFrameworkShortcutFeaturesMap().values();
final List<String> shortcutFeatureKeys = new ArrayList<>(features.size());
for (AccessibilityShortcutController.FrameworkFeatureInfo feature : features) {
final String key = feature.getSettingKey();
@@ -186,7 +190,6 @@ public class AccessibilitySettings extends DashboardFragment implements
shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_STICKY_KEYS);
shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SLOW_KEYS);
shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS);
mSettingsContentObserver = new AccessibilitySettingsContentObserver(mHandler);
mSettingsContentObserver.registerKeysToObserverCallback(shortcutFeatureKeys,
key -> onContentChanged());
}
@@ -211,6 +214,7 @@ public class AccessibilitySettings extends DashboardFragment implements
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
initializeSettingsContentObserver();
initializeAllPreferences();
updateAllPreferences();
mNeedPreferencesUpdate = false;
@@ -283,7 +287,7 @@ public class AccessibilitySettings extends DashboardFragment implements
info.getResolveInfo().serviceInfo.packageName,
info.getResolveInfo().serviceInfo.name);
final boolean shortcutEnabled = AccessibilityUtil.getUserShortcutTypesFromSettings(
context, componentName) != AccessibilityUtil.UserShortcutType.EMPTY;
context, componentName) != DEFAULT;
serviceState = shortcutEnabled
? context.getText(R.string.accessibility_summary_shortcut_enabled)
: context.getText(R.string.generic_accessibility_feature_shortcut_off);
@@ -295,7 +299,7 @@ public class AccessibilitySettings extends DashboardFragment implements
final CharSequence serviceSummary = info.loadSummary(context.getPackageManager());
final String stateSummaryCombo = context.getString(
R.string.preference_summary_default_combination,
com.android.settingslib.R.string.preference_summary_default_combination,
serviceState, serviceSummary);
return TextUtils.isEmpty(serviceSummary) ? serviceState : stateSummaryCombo;
@@ -373,6 +377,7 @@ public class AccessibilitySettings extends DashboardFragment implements
}
protected void updateServicePreferences() {
final AccessibilityManager a11yManager = AccessibilityManager.getInstance(getPrefContext());
// Since services category is auto generated we have to do a pass
// to generate it since services can come and go and then based on
// the global accessibility state to decided whether it is enabled.
@@ -403,8 +408,18 @@ public class AccessibilitySettings extends DashboardFragment implements
AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM,
mCategoryToPrefCategoryMap.get(CATEGORY_INTERACTION_CONTROL));
final List<RestrictedPreference> preferenceList = getInstalledAccessibilityList(
getPrefContext());
final List<AccessibilityShortcutInfo> installedShortcutList =
a11yManager.getInstalledAccessibilityShortcutListAsUser(getPrefContext(),
UserHandle.myUserId());
final List<AccessibilityServiceInfo> installedServiceList =
a11yManager.getInstalledAccessibilityServiceList();
final List<RestrictedPreference> preferenceList = getInstalledAccessibilityPreferences(
getPrefContext(), installedShortcutList, installedServiceList);
if (Flags.checkPrebundledIsPreinstalled()) {
removeNonPreinstalledComponents(mPreBundledServiceComponentToCategoryMap,
installedShortcutList, installedServiceList);
}
final PreferenceCategory downloadedServicesCategory =
mCategoryToPrefCategoryMap.get(CATEGORY_DOWNLOADED_SERVICES);
@@ -449,29 +464,22 @@ public class AccessibilitySettings extends DashboardFragment implements
updatePreferenceCategoryVisibility(CATEGORY_KEYBOARD_OPTIONS);
}
private List<RestrictedPreference> getInstalledAccessibilityList(Context context) {
final AccessibilityManager a11yManager = AccessibilityManager.getInstance(context);
/**
* Gets a list of {@link RestrictedPreference}s for the provided a11y shortcuts and services.
*
* <p>{@code modifiableInstalledServiceList} may be modified to remove any entries with
* matching package name and label as an entry in {@code installedShortcutList}.
*
* @param installedShortcutList A list of installed {@link AccessibilityShortcutInfo}s.
* @param installedServiceList A list of installed {@link AccessibilityServiceInfo}s.
*/
private static List<RestrictedPreference> getInstalledAccessibilityPreferences(Context context,
List<AccessibilityShortcutInfo> installedShortcutList,
List<AccessibilityServiceInfo> installedServiceList) {
final RestrictedPreferenceHelper preferenceHelper = new RestrictedPreferenceHelper(context);
final List<AccessibilityShortcutInfo> installedShortcutList =
a11yManager.getInstalledAccessibilityShortcutListAsUser(context,
UserHandle.myUserId());
final List<AccessibilityActivityPreference> activityList =
preferenceHelper.createAccessibilityActivityPreferenceList(installedShortcutList);
final Set<Pair<String, CharSequence>> packageLabelPairs =
activityList.stream()
.map(a11yActivityPref -> new Pair<>(
a11yActivityPref.getPackageName(), a11yActivityPref.getLabel())
).collect(Collectors.toSet());
// Remove duplicate item here, new a ArrayList to copy unmodifiable list result
// (getInstalledAccessibilityServiceList).
final List<AccessibilityServiceInfo> installedServiceList = new ArrayList<>(
a11yManager.getInstalledAccessibilityServiceList());
if (!packageLabelPairs.isEmpty()) {
installedServiceList.removeIf(
target -> containsPackageAndLabelInList(packageLabelPairs, target));
}
final List<RestrictedPreference> serviceList =
preferenceHelper.createAccessibilityServicePreferenceList(installedServiceList);
@@ -482,14 +490,20 @@ public class AccessibilitySettings extends DashboardFragment implements
return preferenceList;
}
private boolean containsPackageAndLabelInList(
Set<Pair<String, CharSequence>> packageLabelPairs,
AccessibilityServiceInfo targetServiceInfo) {
final ServiceInfo serviceInfo = targetServiceInfo.getResolveInfo().serviceInfo;
final String servicePackageName = serviceInfo.packageName;
final CharSequence serviceLabel = serviceInfo.loadLabel(getPackageManager());
return packageLabelPairs.contains(new Pair<>(servicePackageName, serviceLabel));
private static void removeNonPreinstalledComponents(
Map<ComponentName, PreferenceCategory> componentToCategory,
List<AccessibilityShortcutInfo> shortcutInfos,
List<AccessibilityServiceInfo> serviceInfos) {
for (AccessibilityShortcutInfo info : shortcutInfos) {
if (!info.getActivityInfo().applicationInfo.isSystemApp()) {
componentToCategory.remove(info.getComponentName());
}
}
for (AccessibilityServiceInfo info : serviceInfos) {
if (!info.getResolveInfo().serviceInfo.applicationInfo.isSystemApp()) {
componentToCategory.remove(info.getComponentName());
}
}
}
private void initializePreBundledServicesMapFromArray(String categoryKey, int key) {
@@ -572,7 +586,7 @@ public class AccessibilitySettings extends DashboardFragment implements
}
}
private boolean isAnyHardKeyboardsExist() {
static boolean isAnyHardKeyboardsExist() {
for (int deviceId : InputDevice.getDeviceIds()) {
final InputDevice device = InputDevice.getDevice(deviceId);
if (device != null && !device.isVirtual() && device.isFullKeyboard()) {
@@ -609,6 +623,51 @@ public class AccessibilitySettings extends DashboardFragment implements
.getAccessibilitySearchFeatureProvider().getSearchIndexableRawData(
context);
}
@Override
public List<SearchIndexableRaw> getDynamicRawDataToIndex(Context context,
boolean enabled) {
List<SearchIndexableRaw> dynamicRawData = super.getDynamicRawDataToIndex(
context, enabled);
if (dynamicRawData == null) {
dynamicRawData = new ArrayList<>();
}
if (!Flags.fixA11ySettingsSearch()) {
return dynamicRawData;
}
AccessibilityManager a11yManager = context.getSystemService(
AccessibilityManager.class);
AccessibilitySearchFeatureProvider a11ySearchFeatureProvider =
FeatureFactory.getFeatureFactory()
.getAccessibilitySearchFeatureProvider();
List<RestrictedPreference> installedA11yFeaturesPref =
AccessibilitySettings.getInstalledAccessibilityPreferences(
context,
a11yManager.getInstalledAccessibilityShortcutListAsUser(
context, UserHandle.myUserId()),
a11yManager.getInstalledAccessibilityServiceList()
);
for (RestrictedPreference pref : installedA11yFeaturesPref) {
SearchIndexableRaw indexableRaw = new SearchIndexableRaw(context);
indexableRaw.key = pref.getKey();
indexableRaw.title = pref.getTitle().toString();
@NonNull String synonyms = "";
if (pref instanceof AccessibilityServicePreference) {
synonyms = a11ySearchFeatureProvider.getSynonymsForComponent(
context,
((AccessibilityServicePreference) pref).getComponentName());
} else if (pref instanceof AccessibilityActivityPreference) {
synonyms = a11ySearchFeatureProvider.getSynonymsForComponent(
context,
((AccessibilityActivityPreference) pref).getComponentName());
}
indexableRaw.keywords = synonyms;
dynamicRawData.add(indexableRaw);
}
return dynamicRawData;
}
};
@Override

View File

@@ -16,6 +16,10 @@
package com.android.settings.accessibility;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.DEFAULT;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_GENERAL_CATEGORY;
import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_SAVED_QS_TOOLTIP_TYPE;
@@ -35,7 +39,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.widget.CheckBox;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -43,6 +46,7 @@ import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment;
@@ -61,19 +65,13 @@ import java.util.Locale;
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";
protected static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow";
protected static final int NOT_SET = -1;
// Save user's shortcutType value when savedInstance has value (e.g. device rotated).
protected int mSavedCheckBoxValue = NOT_SET;
protected ShortcutPreference mShortcutPreference;
protected Dialog mDialog;
private AccessibilityManager.TouchExplorationStateChangeListener
mTouchExplorationStateChangeListener;
private AccessibilitySettingsContentObserver mSettingsContentObserver;
private CheckBox mSoftwareTypeCheckBox;
private CheckBox mHardwareTypeCheckBox;
private AccessibilityQuickSettingsTooltipWindow mTooltipWindow;
private boolean mNeedsQSTooltipReshow = false;
private int mNeedsQSTooltipType = QuickSettingsTooltipType.GUIDE_TO_EDIT;
@@ -100,10 +98,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
// Restore the user shortcut type and tooltip.
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(KEY_SAVED_USER_SHORTCUT_TYPE)) {
mSavedCheckBoxValue = savedInstanceState.getInt(KEY_SAVED_USER_SHORTCUT_TYPE,
NOT_SET);
}
if (savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_RESHOW)) {
mNeedsQSTooltipReshow = savedInstanceState.getBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW);
}
@@ -148,7 +142,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
getPreferenceScreen().addPreference(mShortcutPreference);
mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
removeDialog(DialogEnums.EDIT_SHORTCUT);
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
};
@@ -180,8 +173,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
mSettingsContentObserver.register(getContentResolver());
updateShortcutPreferenceData();
updateShortcutPreference();
updateEditShortcutDialogIfNeeded();
}
@Override
@@ -195,10 +186,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
@Override
public void onSaveInstanceState(Bundle outState) {
final int value = getShortcutTypeCheckBoxValue();
if (value != NOT_SET) {
outState.putInt(KEY_SAVED_USER_SHORTCUT_TYPE, value);
}
final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
if (mNeedsQSTooltipReshow || isTooltipWindowShowing) {
outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true);
@@ -210,15 +197,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
@Override
public Dialog onCreateDialog(int dialogId) {
switch (dialogId) {
case DialogEnums.EDIT_SHORTCUT:
final int dialogType = WizardManagerHelper.isAnySetupWizard(getIntent())
? AccessibilityDialogUtils.DialogType.EDIT_SHORTCUT_GENERIC_SUW :
AccessibilityDialogUtils.DialogType.EDIT_SHORTCUT_GENERIC;
mDialog = AccessibilityDialogUtils.showEditShortcutDialog(
getPrefContext(), dialogType, getShortcutTitle(),
this::callOnAlertDialogCheckboxClicked);
setupEditShortcutDialog(mDialog);
return mDialog;
case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
if (WizardManagerHelper.isAnySetupWizard(getIntent())) {
mDialog = AccessibilityShortcutsTutorial
@@ -245,8 +223,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
@Override
public int getDialogMetricsCategory(int dialogId) {
switch (dialogId) {
case DialogEnums.EDIT_SHORTCUT:
return SettingsEnums.DIALOG_ACCESSIBILITY_SERVICE_EDIT_SHORTCUT;
case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
return SettingsEnums.DIALOG_ACCESSIBILITY_TUTORIAL;
default:
@@ -256,17 +232,13 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
@Override
public void onSettingsClicked(ShortcutPreference preference) {
if (Flags.editShortcutsInFullScreen()) {
EditShortcutsPreferenceFragment.showEditShortcutScreen(
getContext(),
getMetricsCategory(),
getShortcutTitle(),
getComponentName(),
getIntent()
);
} else {
showDialog(DialogEnums.EDIT_SHORTCUT);
}
EditShortcutsPreferenceFragment.showEditShortcutScreen(
getContext(),
getMetricsCategory(),
getShortcutTitle(),
getComponentName(),
getIntent()
);
}
@Override
@@ -296,38 +268,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
return KEY_SHORTCUT_PREFERENCE;
}
@VisibleForTesting
void setupEditShortcutDialog(Dialog dialog) {
final View dialogSoftwareView = dialog.findViewById(R.id.software_shortcut);
mSoftwareTypeCheckBox = dialogSoftwareView.findViewById(R.id.checkbox);
setDialogTextAreaClickListener(dialogSoftwareView, mSoftwareTypeCheckBox);
final View dialogHardwareView = dialog.findViewById(R.id.hardware_shortcut);
mHardwareTypeCheckBox = dialogHardwareView.findViewById(R.id.checkbox);
setDialogTextAreaClickListener(dialogHardwareView, mHardwareTypeCheckBox);
updateEditShortcutDialogCheckBox();
}
/**
* Returns accumulated {@link AccessibilityUtil.UserShortcutType} checkbox value or
* {@code NOT_SET} if checkboxes did not exist.
*/
protected int getShortcutTypeCheckBoxValue() {
if (mSoftwareTypeCheckBox == null || mHardwareTypeCheckBox == null) {
return NOT_SET;
}
int value = AccessibilityUtil.UserShortcutType.EMPTY;
if (mSoftwareTypeCheckBox.isChecked()) {
value |= AccessibilityUtil.UserShortcutType.SOFTWARE;
}
if (mHardwareTypeCheckBox.isChecked()) {
value |= AccessibilityUtil.UserShortcutType.HARDWARE;
}
return value;
}
/**
* Returns the shortcut type list which has been checked by user.
*/
@@ -359,35 +299,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
showQuickSettingsTooltipIfNeeded();
}
/**
* This method will be invoked when a button in the edit shortcut dialog is clicked.
*
* @param dialog The dialog that received the click
* @param which The button that was clicked
*/
protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
if (getComponentName() == null) {
return;
}
final int value = getShortcutTypeCheckBoxValue();
saveNonEmptyUserShortcutType(value);
AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), value, getComponentName());
AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), ~value, getComponentName());
final boolean shortcutAssigned = value != AccessibilityUtil.UserShortcutType.EMPTY;
mShortcutPreference.setChecked(shortcutAssigned);
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
if (mHardwareTypeCheckBox.isChecked()) {
AccessibilityUtil.skipVolumeShortcutDialogTimeoutRestriction(getPrefContext());
}
// Show the quick setting tooltip if the shortcut assigned in the first time
if (shortcutAssigned) {
showQuickSettingsTooltipIfNeeded();
}
}
@VisibleForTesting
void initGeneralCategory() {
final PreferenceCategory generalCategory = new PreferenceCategory(getPrefContext());
@@ -397,24 +308,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
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) {
return;
}
final PreferredShortcut shortcut = new PreferredShortcut(
getComponentName().flattenToString(), type);
PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
}
/**
* Overrides to return customized description for general category above shortcut
*
@@ -437,11 +330,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
return false;
}
private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) {
final View dialogTextArea = dialogView.findViewById(R.id.container);
dialogTextArea.setOnClickListener(v -> checkBox.toggle());
}
protected CharSequence getShortcutTypeSummary(Context context) {
if (!mShortcutPreference.isSettingsEditable()) {
return context.getText(R.string.accessibility_shortcut_edit_dialog_title_hardware);
@@ -456,16 +344,16 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
// LINT.IfChange(shortcut_type_ui_order)
final List<CharSequence> list = new ArrayList<>();
if (android.view.accessibility.Flags.a11yQsShortcut()) {
if (hasShortcutType(shortcutTypes, AccessibilityUtil.UserShortcutType.QUICK_SETTINGS)) {
if (hasShortcutType(shortcutTypes, QUICK_SETTINGS)) {
final CharSequence qsTitle = context.getText(
R.string.accessibility_feature_shortcut_setting_summary_quick_settings);
list.add(qsTitle);
}
}
if (hasShortcutType(shortcutTypes, AccessibilityUtil.UserShortcutType.SOFTWARE)) {
if (hasShortcutType(shortcutTypes, SOFTWARE)) {
list.add(getSoftwareShortcutTypeSummary(context));
}
if (hasShortcutType(shortcutTypes, AccessibilityUtil.UserShortcutType.HARDWARE)) {
if (hasShortcutType(shortcutTypes, HARDWARE)) {
final CharSequence hardwareTitle = context.getText(
R.string.accessibility_shortcut_hardware_keyword);
list.add(hardwareTitle);
@@ -481,29 +369,7 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
null, LocaleUtils.getConcatenatedString(list));
}
private void updateEditShortcutDialogCheckBox() {
// If it is during onConfigChanged process then restore the value, or get the saved value
// when shortcutPreference is checked.
int value = restoreOnConfigChangedValue();
if (value == NOT_SET) {
final int lastNonEmptyUserShortcutType = getUserPreferredShortcutTypes();
value = mShortcutPreference.isChecked() ? lastNonEmptyUserShortcutType
: AccessibilityUtil.UserShortcutType.EMPTY;
}
mSoftwareTypeCheckBox.setChecked(
hasShortcutType(value, AccessibilityUtil.UserShortcutType.SOFTWARE));
mHardwareTypeCheckBox.setChecked(
hasShortcutType(value, AccessibilityUtil.UserShortcutType.HARDWARE));
}
private int restoreOnConfigChangedValue() {
final int savedValue = mSavedCheckBoxValue;
mSavedCheckBoxValue = NOT_SET;
return savedValue;
}
private boolean hasShortcutType(int value, @AccessibilityUtil.UserShortcutType int type) {
private boolean hasShortcutType(int value, @UserShortcutType int type) {
return (value & type) == type;
}
@@ -514,7 +380,7 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
final int shortcutTypes = AccessibilityUtil.getUserShortcutTypesFromSettings(
getPrefContext(), getComponentName());
if (shortcutTypes != AccessibilityUtil.UserShortcutType.EMPTY) {
if (shortcutTypes != DEFAULT) {
final PreferredShortcut shortcut = new PreferredShortcut(
getComponentName().flattenToString(), shortcutTypes);
PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);

View File

@@ -19,7 +19,11 @@ package com.android.settings.accessibility;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TWOFINGER_DOUBLETAP;
import android.app.settings.SettingsEnums;
import android.content.Context;
@@ -171,7 +175,7 @@ public final class AccessibilityShortcutsTutorial {
AlertDialog dialog, List<TutorialPage> pages, int selectedPageIndex) {
final Button button = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
final int pageType = pages.get(selectedPageIndex).getType();
final int buttonVisibility = pageType == UserShortcutType.SOFTWARE ? VISIBLE : GONE;
final int buttonVisibility = (pageType == SOFTWARE) ? VISIBLE : GONE;
button.setVisibility(buttonVisibility);
if (buttonVisibility == VISIBLE) {
final int textResId = AccessibilityUtil.isFloatingMenuEnabled(dialog.getContext())
@@ -393,7 +397,7 @@ public final class AccessibilityShortcutsTutorial {
}
private static TutorialPage createSoftwareTutorialPage(@NonNull Context context) {
final int type = UserShortcutType.SOFTWARE;
final int type = SOFTWARE;
final CharSequence title = getSoftwareTitle(context);
final View image = createSoftwareImage(context);
final CharSequence instruction = getSoftwareInstruction(context);
@@ -405,7 +409,7 @@ public final class AccessibilityShortcutsTutorial {
}
private static TutorialPage createHardwareTutorialPage(@NonNull Context context) {
final int type = UserShortcutType.HARDWARE;
final int type = HARDWARE;
final CharSequence title =
context.getText(R.string.accessibility_tutorial_dialog_title_volume);
final View image =
@@ -420,7 +424,7 @@ public final class AccessibilityShortcutsTutorial {
}
private static TutorialPage createTripleTapTutorialPage(@NonNull Context context) {
final int type = UserShortcutType.TRIPLETAP;
final int type = TRIPLETAP;
final CharSequence title =
context.getText(R.string.accessibility_tutorial_dialog_title_triple);
final View image =
@@ -436,7 +440,7 @@ public final class AccessibilityShortcutsTutorial {
}
private static TutorialPage createTwoFingerTripleTapTutorialPage(@NonNull Context context) {
final int type = UserShortcutType.TWOFINGER_DOUBLETAP;
final int type = TWOFINGER_DOUBLETAP;
final int numFingers = 2;
final CharSequence title = context.getString(
R.string.accessibility_tutorial_dialog_title_two_finger_double, numFingers);
@@ -454,7 +458,7 @@ public final class AccessibilityShortcutsTutorial {
private static TutorialPage createQuickSettingsTutorialPage(
@NonNull Context context, @NonNull CharSequence featureName, boolean inSetupWizard) {
final int type = UserShortcutType.QUICK_SETTINGS;
final int type = QUICK_SETTINGS;
final CharSequence title =
context.getText(R.string.accessibility_tutorial_dialog_title_quick_setting);
final View image =
@@ -494,28 +498,28 @@ public final class AccessibilityShortcutsTutorial {
// LINT.IfChange(shortcut_type_ui_order)
final List<TutorialPage> tutorialPages = new ArrayList<>();
if (android.view.accessibility.Flags.a11yQsShortcut()) {
if ((shortcutTypes & UserShortcutType.QUICK_SETTINGS)
== UserShortcutType.QUICK_SETTINGS) {
if ((shortcutTypes & QUICK_SETTINGS)
== QUICK_SETTINGS) {
tutorialPages.add(
createQuickSettingsTutorialPage(context, featureName, inSetupWizard));
}
}
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
if ((shortcutTypes & SOFTWARE) == SOFTWARE) {
tutorialPages.add(createSoftwareTutorialPage(context));
}
if ((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE) {
if ((shortcutTypes & HARDWARE) == HARDWARE) {
tutorialPages.add(createHardwareTutorialPage(context));
}
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
if ((shortcutTypes & UserShortcutType.TWOFINGER_DOUBLETAP)
== UserShortcutType.TWOFINGER_DOUBLETAP) {
if ((shortcutTypes & TWOFINGER_DOUBLETAP)
== TWOFINGER_DOUBLETAP) {
tutorialPages.add(createTwoFingerTripleTapTutorialPage(context));
}
}
if ((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP) {
if ((shortcutTypes & TRIPLETAP) == TRIPLETAP) {
tutorialPages.add(createTripleTapTutorialPage(context));
}
// LINT.ThenChange(/res/xml/accessibility_edit_shortcuts.xml:shortcut_type_ui_order)

View File

@@ -21,6 +21,12 @@ import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.DEFAULT;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -41,6 +47,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import com.android.internal.accessibility.util.ShortcutUtils;
import java.lang.annotation.Retention;
@@ -81,41 +88,6 @@ public final class AccessibilityUtil {
private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
/**
* Annotation for different user shortcut type UI type.
*
* {@code EMPTY} for displaying default value.
* {@code SOFTWARE} for displaying specifying the accessibility services or features which
* choose accessibility button in the navigation bar as preferred shortcut.
* {@code HARDWARE} for displaying specifying the accessibility services or features which
* choose accessibility shortcut as preferred shortcut.
* {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly
* tapping screen 3 times as preferred shortcut.
* {@code TWOFINGER_DOUBLETAP} for displaying specifying magnification to be toggled via
* quickly tapping screen 2 times with two fingers as preferred shortcut.
* {@code QUICK_SETTINGS} for displaying specifying the accessibility services or features which
* choose Quick Settings as preferred shortcut.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
UserShortcutType.EMPTY,
UserShortcutType.SOFTWARE,
UserShortcutType.HARDWARE,
UserShortcutType.TRIPLETAP,
UserShortcutType.TWOFINGER_DOUBLETAP,
UserShortcutType.QUICK_SETTINGS,
})
/** Denotes the user shortcut type. */
public @interface UserShortcutType {
int EMPTY = 0;
int SOFTWARE = 1;
int HARDWARE = 1 << 1;
int TRIPLETAP = 1 << 2;
int TWOFINGER_DOUBLETAP = 1 << 3;
int QUICK_SETTINGS = 1 << 4;
}
/**
* Denotes the quick setting tooltip type.
*
@@ -230,11 +202,11 @@ public final class AccessibilityUtil {
return;
}
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
optInValueToSettings(context, UserShortcutType.SOFTWARE, componentName);
if ((shortcutTypes & SOFTWARE) == SOFTWARE) {
optInValueToSettings(context, SOFTWARE, componentName);
}
if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
optInValueToSettings(context, UserShortcutType.HARDWARE, componentName);
if (((shortcutTypes & HARDWARE) == HARDWARE)) {
optInValueToSettings(context, HARDWARE, componentName);
}
}
@@ -301,11 +273,11 @@ public final class AccessibilityUtil {
return;
}
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
optOutValueFromSettings(context, UserShortcutType.SOFTWARE, componentName);
if ((shortcutTypes & SOFTWARE) == SOFTWARE) {
optOutValueFromSettings(context, SOFTWARE, componentName);
}
if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
optOutValueFromSettings(context, UserShortcutType.HARDWARE, componentName);
if (((shortcutTypes & HARDWARE) == HARDWARE)) {
optOutValueFromSettings(context, HARDWARE, componentName);
}
}
@@ -364,16 +336,16 @@ public final class AccessibilityUtil {
static boolean hasValuesInSettings(Context context, int shortcutTypes,
@NonNull ComponentName componentName) {
boolean exist = false;
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
exist = hasValueInSettings(context, UserShortcutType.SOFTWARE, componentName);
if ((shortcutTypes & SOFTWARE) == SOFTWARE) {
exist = hasValueInSettings(context, SOFTWARE, componentName);
}
if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
exist |= hasValueInSettings(context, UserShortcutType.HARDWARE, componentName);
if (((shortcutTypes & HARDWARE) == HARDWARE)) {
exist |= hasValueInSettings(context, HARDWARE, componentName);
}
if (android.view.accessibility.Flags.a11yQsShortcut()) {
if ((shortcutTypes & UserShortcutType.QUICK_SETTINGS)
== UserShortcutType.QUICK_SETTINGS) {
exist |= hasValueInSettings(context, UserShortcutType.QUICK_SETTINGS,
if ((shortcutTypes & QUICK_SETTINGS)
== QUICK_SETTINGS) {
exist |= hasValueInSettings(context, QUICK_SETTINGS,
componentName);
}
}
@@ -427,16 +399,16 @@ public final class AccessibilityUtil {
*/
static int getUserShortcutTypesFromSettings(Context context,
@NonNull ComponentName componentName) {
int shortcutTypes = UserShortcutType.EMPTY;
if (hasValuesInSettings(context, UserShortcutType.SOFTWARE, componentName)) {
shortcutTypes |= UserShortcutType.SOFTWARE;
int shortcutTypes = DEFAULT;
if (hasValuesInSettings(context, SOFTWARE, componentName)) {
shortcutTypes |= SOFTWARE;
}
if (hasValuesInSettings(context, UserShortcutType.HARDWARE, componentName)) {
shortcutTypes |= UserShortcutType.HARDWARE;
if (hasValuesInSettings(context, HARDWARE, componentName)) {
shortcutTypes |= HARDWARE;
}
if (android.view.accessibility.Flags.a11yQsShortcut()) {
if (hasValuesInSettings(context, UserShortcutType.QUICK_SETTINGS, componentName)) {
shortcutTypes |= UserShortcutType.QUICK_SETTINGS;
if (hasValuesInSettings(context, QUICK_SETTINGS, componentName)) {
shortcutTypes |= QUICK_SETTINGS;
}
}
@@ -455,11 +427,11 @@ public final class AccessibilityUtil {
}
switch (shortcutType) {
case UserShortcutType.SOFTWARE:
case SOFTWARE:
return Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
case UserShortcutType.HARDWARE:
case HARDWARE:
return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
case UserShortcutType.TRIPLETAP:
case TRIPLETAP:
return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
default:
throw new IllegalArgumentException(

View File

@@ -27,11 +27,13 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.display.AutoBrightnessSettings;
import com.android.settingslib.Utils;
import com.android.settingslib.widget.FooterPreference;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupdesign.GlifPreferenceLayout;
@@ -41,10 +43,14 @@ import com.google.android.setupdesign.GlifPreferenceLayout;
*/
public class AutoBrightnessPreferenceFragmentForSetupWizard extends AutoBrightnessSettings {
private static final String FOOTER_PREFERENCE_KEY = "auto_brightness_footer";
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
updateFooterContentDescription();
if (view instanceof GlifPreferenceLayout) {
final GlifPreferenceLayout layout = (GlifPreferenceLayout) view;
final String title = getContext().getString(
@@ -78,4 +84,15 @@ public class AutoBrightnessPreferenceFragmentForSetupWizard extends AutoBrightne
public int getMetricsCategory() {
return SettingsEnums.SUW_ACCESSIBILITY_AUTO_BRIGHTNESS;
}
private void updateFooterContentDescription() {
final PreferenceScreen screen = getPreferenceScreen();
final FooterPreference footerPreference = screen.findPreference(FOOTER_PREFERENCE_KEY);
if (footerPreference != null) {
String title = getString(R.string.auto_brightness_content_description_title);
final StringBuilder sb = new StringBuilder();
sb.append(title).append("\n\n").append(footerPreference.getTitle());
footerPreference.setContentDescription(sb);
}
}
}

View File

@@ -27,7 +27,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice;
*/
public class AvailableHearingDeviceUpdater extends AvailableMediaBluetoothDeviceUpdater {
private static final String PREF_KEY = "connected_hearing_device";
private static final String PREF_KEY_PREFIX = "connected_hearing_device_";
public AvailableHearingDeviceUpdater(Context context,
DevicePreferenceCallback devicePreferenceCallback, int metricsCategory) {
@@ -42,7 +42,7 @@ public class AvailableHearingDeviceUpdater extends AvailableMediaBluetoothDevice
}
@Override
protected String getPreferenceKey() {
return PREF_KEY;
protected String getPreferenceKeyPrefix() {
return PREF_KEY_PREFIX;
}
}

View File

@@ -20,6 +20,7 @@ import static android.view.HapticFeedbackConstants.CLOCK_TICK;
import static com.android.settings.Utils.isNightMode;
import android.annotation.StringRes;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -35,6 +36,7 @@ import android.widget.SeekBar;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.Utils;
/**
* A custom seekbar for the balance setting.
@@ -86,6 +88,14 @@ public class BalanceSeekBar extends SeekBar {
Settings.System.putFloatForUser(mContext.getContentResolver(),
Settings.System.MASTER_BALANCE, balance, UserHandle.USER_CURRENT);
}
final int max = getMax();
if (Flags.audioBalanceStateDescription() && max > 0) {
seekBar.setStateDescription(createStateDescription(mContext,
R.string.audio_seek_bar_state_left_first,
R.string.audio_seek_bar_state_right_first,
progress,
max));
}
// If fromUser is false, the call is a set from the framework on creation or on
// internal update. The progress may be zero, ignore (don't change system settings).
@@ -161,5 +171,21 @@ public class BalanceSeekBar extends SeekBar {
canvas.restore();
super.onDraw(canvas);
}
private static CharSequence createStateDescription(Context context,
@StringRes int resIdLeftFirst, @StringRes int resIdRightFirst,
int progress, float max) {
final boolean isLayoutRtl = context.getResources().getConfiguration().getLayoutDirection()
== LAYOUT_DIRECTION_RTL;
final int rightPercent = (int) (100 * (progress / max));
final int leftPercent = 100 - rightPercent;
final String rightPercentString = Utils.formatPercentage(rightPercent);
final String leftPercentString = Utils.formatPercentage(leftPercent);
if (rightPercent > leftPercent || (rightPercent == leftPercent && isLayoutRtl)) {
return context.getString(resIdRightFirst, rightPercentString, leftPercentString);
} else {
return context.getString(resIdLeftFirst, leftPercentString, rightPercentString);
}
}
}

View File

@@ -42,7 +42,8 @@ public class CaptioningAppearancePreferenceController extends BasePreferenceCont
@Override
public CharSequence getSummary() {
return mContext.getString(R.string.preference_summary_default_combination,
return mContext.getString(
com.android.settingslib.R.string.preference_summary_default_combination,
geFontScaleSummary(), getPresetSummary());
}

View File

@@ -17,6 +17,8 @@
package com.android.settings.accessibility;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.provider.Settings;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
@@ -50,5 +52,16 @@ public class CaptioningMoreOptionsFragment extends DashboardFragment {
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.captioning_more_options);
new BaseSearchIndexProvider(R.xml.captioning_more_options) {
@Override
protected boolean isPageSearchEnabled(Context context) {
if (!Flags.fixA11ySettingsSearch()) {
return super.isPageSearchEnabled(context);
}
// CaptioningMoreOptions is only searchable if captions are enabled, so that we
// don't show search results for settings that will cause no change to the user.
return Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, 0) == 1;
}
};
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2024 The Android Open 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.provider.Settings;
import android.view.accessibility.AccessibilityManager;
import com.google.common.primitives.Ints;
/**
* Utility class for retrieving accessibility daltonizer related values in secure settings.
*/
public class DaltonizerPreferenceUtil {
/**
* Return the daltonizer display mode stored in
* {@link Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER}.
* By default it returns {@link DALTONIZER_CORRECT_DEUTERANOMALY}.
*/
public static int getSecureAccessibilityDaltonizerValue(ContentResolver resolver) {
final String daltonizerStringValue = Settings.Secure.getString(
resolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER);
if (daltonizerStringValue == null) {
return AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY;
}
final Integer daltonizerIntValue = Ints.tryParse(daltonizerStringValue);
return daltonizerIntValue == null ? AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY
: daltonizerIntValue;
}
/**
* Returns the daltonizer enabled value in
* {@link Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED}.
* By default it returns false.
*/
public static boolean isSecureAccessibilityDaltonizerEnabled(ContentResolver resolver) {
return Settings.Secure.getInt(
resolver,
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
OFF) == ON;
}
}

View File

@@ -19,76 +19,56 @@ package com.android.settings.accessibility;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import androidx.lifecycle.LifecycleObserver;
import androidx.annotation.NonNull;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import com.google.common.primitives.Ints;
import java.util.HashMap;
import java.util.Map;
/** Controller class that control radio button of accessibility daltonizer settings. */
public class DaltonizerRadioButtonPreferenceController extends BasePreferenceController implements
LifecycleObserver, SelectorWithWidgetPreference.OnClickListener {
private static final String TYPE = Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER;
DefaultLifecycleObserver, SelectorWithWidgetPreference.OnClickListener {
private static final String DALTONIZER_TYPE_SETTINGS_KEY =
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER;
// pair the preference key and daltonizer value.
private final Map<String, Integer> mAccessibilityDaltonizerKeyToValueMap = new HashMap<>();
// RadioButtonPreference key, each preference represent a daltonizer value.
private final ContentResolver mContentResolver;
private final ContentObserver mSettingsContentObserver;
private final Resources mResources;
private DaltonizerRadioButtonPreferenceController.OnChangeListener mOnChangeListener;
private SelectorWithWidgetPreference mPreference;
private int mAccessibilityDaltonizerValue;
public DaltonizerRadioButtonPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mContentResolver = context.getContentResolver();
mResources = context.getResources();
}
public DaltonizerRadioButtonPreferenceController(Context context, Lifecycle lifecycle,
String preferenceKey) {
super(context, preferenceKey);
mContentResolver = context.getContentResolver();
mResources = context.getResources();
if (lifecycle != null) {
lifecycle.addObserver(this);
}
}
protected static int getSecureAccessibilityDaltonizerValue(ContentResolver resolver,
String name) {
final String daltonizerStringValue = Settings.Secure.getString(resolver, name);
if (daltonizerStringValue == null) {
return AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY;
}
final Integer daltonizerIntValue = Ints.tryParse(daltonizerStringValue);
return daltonizerIntValue == null ? AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY
: daltonizerIntValue;
}
public void setOnChangeListener(
DaltonizerRadioButtonPreferenceController.OnChangeListener listener) {
mOnChangeListener = listener;
mSettingsContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
if (mPreference != null) {
updateState(mPreference);
}
}
};
}
private Map<String, Integer> getDaltonizerValueToKeyMap() {
if (mAccessibilityDaltonizerKeyToValueMap.size() == 0) {
if (mAccessibilityDaltonizerKeyToValueMap.isEmpty()) {
final String[] daltonizerKeys = mResources.getStringArray(
R.array.daltonizer_mode_keys);
@@ -104,12 +84,8 @@ public class DaltonizerRadioButtonPreferenceController extends BasePreferenceCon
return mAccessibilityDaltonizerKeyToValueMap;
}
private void putSecureString(String name, String value) {
Settings.Secure.putString(mContentResolver, name, value);
}
private void handlePreferenceChange(String value) {
putSecureString(TYPE, value);
Settings.Secure.putString(mContentResolver, DALTONIZER_TYPE_SETTINGS_KEY, value);
}
@Override
@@ -124,44 +100,39 @@ public class DaltonizerRadioButtonPreferenceController extends BasePreferenceCon
screen.findPreference(getPreferenceKey());
mPreference.setOnClickListener(this);
mPreference.setAppendixVisibility(View.GONE);
updateState(mPreference);
}
@Override
public void onRadioButtonClicked(SelectorWithWidgetPreference preference) {
final int value = getDaltonizerValueToKeyMap().get(mPreferenceKey);
handlePreferenceChange(String.valueOf(value));
if (mOnChangeListener != null) {
mOnChangeListener.onCheckedChanged(mPreference);
}
}
private int getAccessibilityDaltonizerValue() {
final int daltonizerValue = getSecureAccessibilityDaltonizerValue(mContentResolver,
TYPE);
final int daltonizerValue =
DaltonizerPreferenceUtil.getSecureAccessibilityDaltonizerValue(mContentResolver);
return daltonizerValue;
}
protected void updatePreferenceCheckedState(int value) {
if (mAccessibilityDaltonizerValue == value) {
mPreference.setChecked(true);
}
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
mAccessibilityDaltonizerValue = getAccessibilityDaltonizerValue();
// reset RadioButton
mPreference.setChecked(false);
final int daltonizerValueInSetting = getAccessibilityDaltonizerValue();
final int preferenceValue = getDaltonizerValueToKeyMap().get(mPreference.getKey());
updatePreferenceCheckedState(preferenceValue);
mPreference.setChecked(preferenceValue == daltonizerValueInSetting);
}
/** Listener interface handles checked event. */
public interface OnChangeListener {
/** A hook that is called when preference checked. */
void onCheckedChanged(Preference preference);
@Override
public void onResume(@NonNull LifecycleOwner owner) {
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(DALTONIZER_TYPE_SETTINGS_KEY),
/* notifyForDescendants= */ false,
mSettingsContentObserver
);
}
@Override
public void onPause(@NonNull LifecycleOwner owner) {
mContentResolver.unregisterContentObserver(mSettingsContentObserver);
}
}

View File

@@ -15,28 +15,55 @@
*/
package com.android.settings.accessibility;
import static com.android.settings.accessibility.DaltonizerPreferenceUtil.isSecureAccessibilityDaltonizerEnabled;
import static com.android.settings.accessibility.DaltonizerPreferenceUtil.getSecureAccessibilityDaltonizerValue;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.server.accessibility.Flags;
import com.android.settings.R;
import com.android.settings.core.SliderPreferenceController;
import com.android.settings.widget.SeekBarPreference;
/**
* The controller of the seekbar preference for the saturation level of color correction.
*/
public class DaltonizerSaturationSeekbarPreferenceController extends SliderPreferenceController {
public class DaltonizerSaturationSeekbarPreferenceController
extends SliderPreferenceController
implements DefaultLifecycleObserver {
private static final int DEFAULT_SATURATION_LEVEL = 7;
private static final int SATURATION_MAX = 10;
private static final int SATURATION_MIN = 0;
private static final int SATURATION_MIN = 1;
private int mSliderPosition;
private final ContentResolver mContentResolver;
@Nullable
private SeekBarPreference mPreference;
public final ContentObserver mContentObserver = new ContentObserver(
new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
if (mPreference != null) {
updateState(mPreference);
}
}
};
public DaltonizerSaturationSeekbarPreferenceController(Context context,
String preferenceKey) {
super(context, preferenceKey);
@@ -49,10 +76,33 @@ public class DaltonizerSaturationSeekbarPreferenceController extends SliderPrefe
// TODO: Observer color correction on/off and enable/disable based on secure settings.
}
@Override
public void onStart(@NonNull LifecycleOwner owner) {
if (!isAvailable()) return;
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER),
true,
mContentObserver
);
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED),
true,
mContentObserver
);
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
if (!isAvailable()) return;
mContentResolver.unregisterContentObserver(mContentObserver);
mPreference = null;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
SeekBarPreference preference = screen.findPreference(getPreferenceKey());
mPreference = preference;
preference.setMax(getMax());
preference.setMin(getMin());
preference.setProgress(mSliderPosition);
@@ -62,7 +112,7 @@ public class DaltonizerSaturationSeekbarPreferenceController extends SliderPrefe
@Override
public int getAvailabilityStatus() {
if (Flags.enableColorCorrectionSaturation()) {
return AVAILABLE;
return shouldSeekBarEnabled() ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
}
return CONDITIONALLY_UNAVAILABLE;
}
@@ -85,6 +135,21 @@ public class DaltonizerSaturationSeekbarPreferenceController extends SliderPrefe
return true;
}
@Override
public void updateState(Preference preference) {
if (preference == null) {
return;
}
var shouldSeekbarEnabled = shouldSeekBarEnabled();
// setSummary not working yet on SeekBarPreference.
String summary = shouldSeekbarEnabled
? ""
: mContext.getString(R.string.daltonizer_saturation_unavailable_summary);
preference.setSummary(summary);
preference.setEnabled(shouldSeekbarEnabled);
}
@Override
public int getMax() {
return SATURATION_MAX;
@@ -94,4 +159,13 @@ public class DaltonizerSaturationSeekbarPreferenceController extends SliderPrefe
public int getMin() {
return SATURATION_MIN;
}
private boolean shouldSeekBarEnabled() {
boolean enabled = isSecureAccessibilityDaltonizerEnabled(mContentResolver);
int mode = getSecureAccessibilityDaltonizerValue(mContentResolver);
// mode == 0 is gray scale where saturation level isn't applicable.
// mode == -1 is disabled and also default.
return enabled && mode != -1 && mode != 0;
}
}

View File

@@ -110,7 +110,7 @@ public class FlashNotificationsPreviewPreferenceController extends
if (preference == null) {
return;
}
preference.setEnabled(FlashNotificationsUtil.getFlashNotificationsState(mContext)
preference.setVisible(FlashNotificationsUtil.getFlashNotificationsState(mContext)
!= FlashNotificationsUtil.State.OFF);
}
}

View File

@@ -28,6 +28,7 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
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;
@@ -64,6 +65,15 @@ public class FloatingMenuFadePreferenceController extends BasePreferenceControll
? AVAILABLE : DISABLED_DEPENDENT_SETTING;
}
@Override
public CharSequence getSummary() {
int rId = R.string.accessibility_button_fade_summary;
if (mPreference != null && !mPreference.isEnabled()) {
rId = R.string.accessibility_button_disabled_button_mode_summary;
}
return mContext.getString(rId);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
@@ -101,6 +111,7 @@ public class FloatingMenuFadePreferenceController extends BasePreferenceControll
private void updateAvailabilityStatus() {
mPreference.setEnabled(AccessibilityUtil.isFloatingMenuEnabled(mContext));
refreshSummary(mPreference);
}
private int getFloatingMenuFadeValue() {

View File

@@ -29,6 +29,7 @@ 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;
@@ -79,6 +80,18 @@ public class FloatingMenuSizePreferenceController extends BasePreferenceControll
? AVAILABLE : DISABLED_DEPENDENT_SETTING;
}
@Override
public CharSequence getSummary() {
if (mPreference != null) {
return mPreference.isEnabled()
? "%s"
: mContext.getString(
R.string.accessibility_button_disabled_button_mode_summary);
} else {
return "%s";
}
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
@@ -119,6 +132,7 @@ public class FloatingMenuSizePreferenceController extends BasePreferenceControll
private void updateAvailabilityStatus() {
mPreference.setEnabled(AccessibilityUtil.isFloatingMenuEnabled(mContext));
refreshSummary(mPreference);
}
@Size

View File

@@ -25,6 +25,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.flags.Flags;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.overlay.FeatureFactory;
/** Preference controller for Hearing Aid Compatibility (HAC) settings */
public class HearingAidCompatibilityPreferenceController extends TogglePreferenceController {
@@ -73,6 +74,8 @@ public class HearingAidCompatibilityPreferenceController extends TogglePreferenc
@Override
public boolean setChecked(boolean isChecked) {
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().changed(
getMetricsCategory(), getPreferenceKey(), isChecked ? 1 : 0);
setAudioParameterHacEnabled(isChecked);
return Settings.System.putInt(mContext.getContentResolver(), Settings.System.HEARING_AID,
(isChecked ? HAC_ENABLED : HAC_DISABLED));

View File

@@ -17,7 +17,6 @@
package com.android.settings.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.DialogInterface;
import com.android.settingslib.accessibility.AccessibilityUtils;
@@ -63,17 +62,4 @@ public class InvisibleToggleAccessibilityServicePreferenceFragment extends
super.onAllowButtonFromShortcutToggleClicked();
AccessibilityUtils.setAccessibilityServiceState(getContext(), mComponentName, true);
}
/**
* {@inheritDoc}
*
* Enables accessibility service when shortcutPreference is checked.
*/
@Override
protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
super.callOnAlertDialogCheckboxClicked(dialog, which);
final boolean enabled = mShortcutPreference.isChecked();
AccessibilityUtils.setAccessibilityServiceState(getContext(), mComponentName, enabled);
}
}

View File

@@ -19,16 +19,21 @@ package com.android.settings.accessibility;
import android.content.Context;
import android.hardware.input.InputSettings;
import android.util.Log;
import androidx.annotation.NonNull;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.inputmethod.PhysicalKeyboardFragment;
import java.util.List;
/**
* A toggle preference controller for keyboard bounce key.
*/
public class KeyboardBounceKeyPreferenceController extends TogglePreferenceController {
private static final String TAG = "BounceKeyPrefController";
static final String PREF_KEY = "toggle_keyboard_bounce_keys";
public KeyboardBounceKeyPreferenceController(Context context, String preferenceKey) {
@@ -58,4 +63,17 @@ public class KeyboardBounceKeyPreferenceController extends TogglePreferenceContr
public int getSliceHighlightMenuRes() {
return R.string.menu_key_accessibility;
}
@Override
public void updateNonIndexableKeys(@NonNull List<String> keys) {
super.updateNonIndexableKeys(keys);
if (Flags.fixA11ySettingsSearch() && !AccessibilitySettings.isAnyHardKeyboardsExist()) {
if (keys.contains(getPreferenceKey())) {
Log.w(TAG, "Skipping updateNonIndexableKeys, key already in list.");
return;
}
keys.add(getPreferenceKey());
}
}
}

View File

@@ -19,15 +19,21 @@ package com.android.settings.accessibility;
import android.content.Context;
import android.hardware.input.InputSettings;
import android.util.Log;
import androidx.annotation.NonNull;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.inputmethod.PhysicalKeyboardFragment;
import java.util.List;
/**
* A toggle preference controller for keyboard slow key.
*/
public class KeyboardSlowKeyPreferenceController extends TogglePreferenceController {
private static final String TAG = "SlowKeyPrefController";
static final String PREF_KEY = "toggle_keyboard_slow_keys";
@@ -58,4 +64,17 @@ public class KeyboardSlowKeyPreferenceController extends TogglePreferenceControl
public int getSliceHighlightMenuRes() {
return R.string.menu_key_accessibility;
}
@Override
public void updateNonIndexableKeys(@NonNull List<String> keys) {
super.updateNonIndexableKeys(keys);
if (Flags.fixA11ySettingsSearch() && !AccessibilitySettings.isAnyHardKeyboardsExist()) {
if (keys.contains(getPreferenceKey())) {
Log.w(TAG, "Skipping updateNonIndexableKeys, key already in list.");
return;
}
keys.add(getPreferenceKey());
}
}
}

View File

@@ -19,15 +19,20 @@ package com.android.settings.accessibility;
import android.content.Context;
import android.hardware.input.InputSettings;
import android.util.Log;
import androidx.annotation.NonNull;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import java.util.List;
/**
* A toggle preference controller for keyboard sticky key.
*/
public class KeyboardStickyKeyPreferenceController extends TogglePreferenceController {
private static final String TAG = "StickyKeyPrefController";
static final String PREF_KEY = "toggle_keyboard_sticky_keys";
public KeyboardStickyKeyPreferenceController(Context context, String preferenceKey) {
@@ -55,4 +60,17 @@ public class KeyboardStickyKeyPreferenceController extends TogglePreferenceContr
public int getSliceHighlightMenuRes() {
return R.string.menu_key_accessibility;
}
@Override
public void updateNonIndexableKeys(@NonNull List<String> keys) {
super.updateNonIndexableKeys(keys);
if (Flags.fixA11ySettingsSearch() && !AccessibilitySettings.isAnyHardKeyboardsExist()) {
if (keys.contains(getPreferenceKey())) {
Log.w(TAG, "Skipping updateNonIndexableKeys, key already in list.");
return;
}
keys.add(getPreferenceKey());
}
}
}

View File

@@ -27,9 +27,9 @@ import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.VibrationAttributes;
import android.os.Vibrator;
import android.os.vibrator.Flags;
import android.provider.Settings;
import android.util.Log;
@@ -70,7 +70,8 @@ public class KeyboardVibrationTogglePreferenceController extends TogglePreferenc
public KeyboardVibrationTogglePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mVibrator = context.getSystemService(Vibrator.class);
mContentObserver = new ContentObserver(new Handler(/* async= */ true)) {
Handler handler = Looper.myLooper() != null ? new Handler(/* async= */ true) : null;
mContentObserver = new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (uri.equals(MAIN_VIBRATION_SWITCH_URI)) {
@@ -110,10 +111,8 @@ public class KeyboardVibrationTogglePreferenceController extends TogglePreferenc
@Override
public int getAvailabilityStatus() {
if (Flags.keyboardCategoryEnabled()
&& mContext.getResources().getBoolean(R.bool.config_keyboard_vibration_supported)
&& mContext.getResources().getFloat(
com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude) > 0) {
if (mContext.getResources().getBoolean(
com.android.internal.R.bool.config_keyboardVibrationSettingsSupported)) {
return AVAILABLE;
}
return UNSUPPORTED_ON_DEVICE;
@@ -131,15 +130,9 @@ public class KeyboardVibrationTogglePreferenceController extends TogglePreferenc
mMetricsFeatureProvider.action(mContext,
SettingsEnums.ACTION_KEYBOARD_VIBRATION_CHANGED, isChecked);
if (success && isChecked) {
// Play the preview vibration effect when the toggle is on.
final VibrationAttributes touchAttrs =
VibrationPreferenceConfig.createPreviewVibrationAttributes(
VibrationAttributes.USAGE_TOUCH);
final VibrationAttributes keyboardAttrs =
new VibrationAttributes.Builder(touchAttrs)
.setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
.build();
VibrationPreferenceConfig.playVibrationPreview(mVibrator, keyboardAttrs);
// Play the preview vibration effect for the IME feedback when the toggle is on.
VibrationPreferenceConfig.playVibrationPreview(
mVibrator, VibrationAttributes.USAGE_IME_FEEDBACK);
}
return true;
}
@@ -155,8 +148,8 @@ public class KeyboardVibrationTogglePreferenceController extends TogglePreferenc
}
private boolean isKeyboardVibrationSwitchEnabled() {
return Settings.System.getInt(mContext.getContentResolver(), KEYBOARD_VIBRATION_ENABLED,
mVibrator.isDefaultKeyboardVibrationEnabled() ? ON : OFF) == ON;
return Settings.System.getInt(
mContext.getContentResolver(), KEYBOARD_VIBRATION_ENABLED, ON) == ON;
}
private boolean updateKeyboardVibrationSetting(boolean enable) {

View File

@@ -49,7 +49,8 @@ public class LargePointerIconPreferenceController extends TogglePreferenceContro
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
return android.view.flags.Flags.enableVectorCursorA11ySettings() ? CONDITIONALLY_UNAVAILABLE
: AVAILABLE;
}
@Override

View File

@@ -28,6 +28,7 @@ import android.os.Looper;
import android.provider.Settings;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -103,16 +104,10 @@ public class MagnificationOneFingerPanningPreferenceController extends
@Override
public CharSequence getSummary() {
if (!mSwitchPreference.isEnabled()) {
return mContext.getString(
R.string.accessibility_magnification_one_finger_panning_summary_unavailable);
}
return (isChecked())
? mContext.getString(
R.string.accessibility_magnification_one_finger_panning_summary_on)
: mContext.getString(
R.string.accessibility_magnification_one_finger_panning_summary_off);
@StringRes int resId = mSwitchPreference.isEnabled()
? R.string.accessibility_magnification_one_finger_panning_summary
: R.string.accessibility_magnification_one_finger_panning_summary_unavailable;
return mContext.getString(resId);
}
@Override

View File

@@ -19,7 +19,7 @@ package com.android.settings.accessibility;
import android.content.ComponentName;
import android.text.TextUtils;
import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import com.google.common.base.Objects;

View File

@@ -16,6 +16,10 @@
package com.android.settings.accessibility;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.DEFAULT;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
@@ -27,8 +31,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import com.android.internal.accessibility.util.ShortcutUtils;
import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
import java.util.HashSet;
import java.util.Map;
@@ -43,18 +47,18 @@ public final class PreferredShortcuts {
/**
* Retrieves the user preferred shortcut types for the given {@code componentName} from
* SharedPreferences. If the user doesn't have a preferred shortcut,
* {@link ShortcutConstants.UserShortcutType.SOFTWARE} is returned.
* {@link SOFTWARE} is returned.
*
* @param context {@link Context} to access the {@link SharedPreferences}
* @param componentName Name of the service or activity, should be the format of {@link
* ComponentName#flattenToString()}.
* @return {@link ShortcutConstants.UserShortcutType}
* @return {@link UserShortcutType}
*/
@ShortcutConstants.UserShortcutType
@UserShortcutType
public static int retrieveUserShortcutType(
@NonNull Context context, @NonNull String componentName) {
return retrieveUserShortcutType(
context, componentName, ShortcutConstants.UserShortcutType.SOFTWARE);
context, componentName, SOFTWARE);
}
/**
@@ -66,13 +70,13 @@ public final class PreferredShortcuts {
* ComponentName#flattenToString()}.
* @param defaultTypes The default shortcut types to use if the user doesn't have a
* preferred shortcut.
* @return {@link ShortcutConstants.UserShortcutType}
* @return {@link UserShortcutType}
*/
@ShortcutConstants.UserShortcutType
@UserShortcutType
public static int retrieveUserShortcutType(
@NonNull Context context,
@NonNull String componentName,
@ShortcutConstants.UserShortcutType int defaultTypes) {
@UserShortcutType int defaultTypes) {
// Create a mutable set to modify
final Set<String> info = new HashSet<>(getFromSharedPreferences(context));
@@ -121,7 +125,7 @@ public final class PreferredShortcuts {
final Map<Integer, Set<String>> shortcutTypeToTargets = new ArrayMap<>();
for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
if (!Flags.a11yQsShortcut()
&& shortcutType == ShortcutConstants.UserShortcutType.QUICK_SETTINGS) {
&& shortcutType == QUICK_SETTINGS) {
// Skip saving quick setting as preferred shortcut option when flag is not enabled
continue;
}
@@ -132,14 +136,14 @@ public final class PreferredShortcuts {
}
for (String target : components) {
int shortcutTypes = ShortcutConstants.UserShortcutType.DEFAULT;
int shortcutTypes = DEFAULT;
for (Map.Entry<Integer, Set<String>> entry : shortcutTypeToTargets.entrySet()) {
if (entry.getValue().contains(target)) {
shortcutTypes |= entry.getKey();
}
}
if (shortcutTypes != ShortcutConstants.UserShortcutType.DEFAULT) {
if (shortcutTypes != DEFAULT) {
final PreferredShortcut shortcut = new PreferredShortcut(
target, shortcutTypes);
PreferredShortcuts.saveUserShortcutType(context, shortcut);

View File

@@ -29,6 +29,7 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -125,9 +126,14 @@ public class ReduceBrightColorsPreferenceController
mContext.getContentResolver().unregisterContentObserver(mSettingsContentObserver);
}
@Nullable
@Override
protected ComponentName getTileComponentName() {
return REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME;
// TODO: When clean up the feature flag, change the parent class from
// AccessibilityQuickSettingsPrimarySwitchPreferenceController to
// TogglePreferenceController
return android.view.accessibility.Flags.a11yQsShortcut()
? null : REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME;
}
@Override

View File

@@ -128,7 +128,13 @@ public class RestrictedPreferenceHelper {
AccessibilityActivityPreference preference = new AccessibilityActivityPreference(
mContext, componentName.getPackageName(), activityInfo.applicationInfo.uid,
info);
setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled);
if (Flags.neverRestrictAccessibilityActivity()) {
// Accessibility Activities do not have elevated privileges so restricting
// them based on ECM or device admin does not give any value.
preference.setEnabled(true);
} else {
setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled);
}
preferenceList.add(preference);
}
return preferenceList;

View File

@@ -29,7 +29,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice;
*/
public class SavedHearingDeviceUpdater extends SavedBluetoothDeviceUpdater {
private static final String PREF_KEY = "saved_hearing_device";
private static final String PREF_KEY_PREFIX = "saved_hearing_device_";
public SavedHearingDeviceUpdater(Context context,
DevicePreferenceCallback devicePreferenceCallback, int metricsCategory) {
@@ -47,7 +47,7 @@ public class SavedHearingDeviceUpdater extends SavedBluetoothDeviceUpdater {
}
@Override
protected String getPreferenceKey() {
return PREF_KEY;
protected String getPreferenceKeyPrefix() {
return PREF_KEY_PREFIX;
}
}

View File

@@ -28,11 +28,13 @@ import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
import android.view.View;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
@@ -40,8 +42,6 @@ import com.android.settings.R;
import java.util.Timer;
import java.util.TimerTask;
import java.util.function.Consumer;
/**
* DialogFragment for Screen flash notification color picker.
@@ -49,30 +49,33 @@ import java.util.function.Consumer;
public class ScreenFlashNotificationColorDialogFragment extends DialogFragment implements
ColorSelectorLayout.OnCheckedChangeListener {
private static final int DEFAULT_COLOR = Color.TRANSPARENT;
private static final int PREVIEW_LONG_TIME_MS = 5000;
private static final int BETWEEN_STOP_AND_START_DELAY_MS = 250;
private static final int MARGIN_FOR_STOP_DELAY_MS = 100;
@VisibleForTesting
static final String EXTRA_COLOR = "extra_color";
@ColorInt
private int mCurrentColor = Color.TRANSPARENT;
private Consumer<Integer> mConsumer;
private int mCurrentColor = DEFAULT_COLOR;
private Timer mTimer = null;
private Boolean mIsPreview = false;
static ScreenFlashNotificationColorDialogFragment getInstance(int initialColor,
Consumer<Integer> colorConsumer) {
static ScreenFlashNotificationColorDialogFragment getInstance(int initialColor) {
final ScreenFlashNotificationColorDialogFragment result =
new ScreenFlashNotificationColorDialogFragment();
result.mCurrentColor = initialColor;
result.mConsumer = colorConsumer != null ? colorConsumer : i -> {
};
Bundle bundle = new Bundle();
bundle.putInt(EXTRA_COLOR, initialColor);
result.setArguments(bundle);
return result;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
mCurrentColor = getArguments().getInt(EXTRA_COLOR, DEFAULT_COLOR);
final View dialogView = getLayoutInflater().inflate(R.layout.layout_color_selector_dialog,
null);
@@ -89,9 +92,10 @@ public class ScreenFlashNotificationColorDialogFragment extends DialogFragment i
.setNeutralButton(R.string.flash_notifications_preview, null)
.setNegativeButton(R.string.color_selector_dialog_cancel, (dialog, which) -> {
})
.setPositiveButton(R.string.color_selector_dialog_done, (dialog, which) -> {
.setPositiveButton(R.string.color_selector_dialog_save, (dialog, which) -> {
mCurrentColor = colorSelectorLayout.getCheckedColor(DEFAULT_SCREEN_FLASH_COLOR);
mConsumer.accept(mCurrentColor);
Settings.System.putInt(getContext().getContentResolver(),
Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, mCurrentColor);
})
.create();
createdDialog.setOnShowListener(

View File

@@ -21,10 +21,17 @@ import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import static com.android.settings.accessibility.FlashNotificationsUtil.DEFAULT_SCREEN_FLASH_COLOR;
import android.content.Context;
import android.database.ContentObserver;
import android.graphics.Color;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -32,24 +39,37 @@ import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.overlay.FeatureFactory;
import java.util.function.Consumer;
/**
* Controller for Screen flash notification.
*/
public class ScreenFlashNotificationPreferenceController extends TogglePreferenceController {
public class ScreenFlashNotificationPreferenceController extends
TogglePreferenceController implements DefaultLifecycleObserver {
private final FlashNotificationColorContentObserver mFlashNotificationColorContentObserver;
private Fragment mParentFragment;
private Preference mPreference;
public ScreenFlashNotificationPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mFlashNotificationColorContentObserver = new FlashNotificationColorContentObserver(
new Handler(mContext.getMainLooper()));
}
public void setParentFragment(Fragment parentFragment) {
this.mParentFragment = parentFragment;
}
@Override
public void onStart(@NonNull LifecycleOwner owner) {
mFlashNotificationColorContentObserver.register(mContext);
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
mFlashNotificationColorContentObserver.unregister(mContext);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
@@ -100,14 +120,8 @@ public class ScreenFlashNotificationPreferenceController extends TogglePreferenc
Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
DEFAULT_SCREEN_FLASH_COLOR);
final Consumer<Integer> consumer = color -> {
Settings.System.putInt(mContext.getContentResolver(),
Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, color);
refreshColorSummary();
};
ScreenFlashNotificationColorDialogFragment
.getInstance(initialColor, consumer)
.getInstance(initialColor)
.show(mParentFragment.getParentFragmentManager(),
ScreenFlashNotificationColorDialogFragment.class.getSimpleName());
return true;
@@ -128,4 +142,37 @@ public class ScreenFlashNotificationPreferenceController extends TogglePreferenc
private void refreshColorSummary() {
if (mPreference != null) mPreference.setSummary(getSummary());
}
private final class FlashNotificationColorContentObserver extends ContentObserver {
private final Uri mColorUri = Settings.System.getUriFor(
Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR);
FlashNotificationColorContentObserver(Handler handler) {
super(handler);
}
/**
* Register this observer to given {@link Context}, to be called from lifecycle
* {@code onStart} method.
*/
public void register(@NonNull Context context) {
context.getContentResolver().registerContentObserver(
mColorUri, /* notifyForDescendants= */ false, this);
}
/**
* Unregister this observer from given {@link Context}, to be called from lifecycle
* {@code onStop} method.
*/
public void unregister(@NonNull Context context) {
context.getContentResolver().unregisterContentObserver(this);
}
@Override
public void onChange(boolean selfChange, @Nullable Uri uri) {
if (mColorUri.equals(uri)) {
refreshColorSummary();
}
}
}
}

View File

@@ -40,7 +40,6 @@ import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.widget.CompoundButton;
@@ -411,17 +410,6 @@ public class ToggleAccessibilityServicePreferenceFragment extends
}
}
private void onDialogButtonFromEnableToggleClicked(View view) {
final int viewId = view.getId();
if (viewId == R.id.permission_enable_allow_button) {
onAllowButtonFromEnableToggleClicked();
} else if (viewId == R.id.permission_enable_deny_button) {
onDenyButtonFromEnableToggleClicked();
} else {
throw new IllegalArgumentException("Unexpected view id");
}
}
private void onDialogButtonFromUninstallClicked() {
mWarningDialog.dismiss();
final Intent uninstallIntent = createUninstallPackageActivityIntent();
@@ -491,17 +479,6 @@ public class ToggleAccessibilityServicePreferenceFragment extends
mWarningDialog.dismiss();
}
void onDialogButtonFromShortcutToggleClicked(View view) {
final int viewId = view.getId();
if (viewId == R.id.permission_enable_allow_button) {
onAllowButtonFromShortcutToggleClicked();
} else if (viewId == R.id.permission_enable_deny_button) {
onDenyButtonFromShortcutToggleClicked();
} else {
throw new IllegalArgumentException("Unexpected view id");
}
}
void onAllowButtonFromShortcutToggleClicked() {
mShortcutPreference.setChecked(true);
@@ -526,17 +503,13 @@ public class ToggleAccessibilityServicePreferenceFragment extends
private void onAllowButtonFromShortcutClicked() {
mIsDialogShown.set(false);
if (Flags.editShortcutsInFullScreen()) {
EditShortcutsPreferenceFragment.showEditShortcutScreen(
getContext(),
getMetricsCategory(),
getShortcutTitle(),
mComponentName,
getIntent()
);
} else {
showPopupDialog(DialogEnums.EDIT_SHORTCUT);
}
EditShortcutsPreferenceFragment.showEditShortcutScreen(
getContext(),
getMetricsCategory(),
getShortcutTitle(),
mComponentName,
getIntent()
);
if (mWarningDialog != null) {
mWarningDialog.dismiss();

View File

@@ -21,11 +21,10 @@ import static com.android.internal.accessibility.AccessibilityShortcutController
import static com.android.settings.accessibility.AccessibilityStatsLogUtils.logAccessibilityServiceEnabled;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import static com.android.settings.accessibility.DaltonizerPreferenceUtil.isSecureAccessibilityDaltonizerEnabled;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.provider.Settings;
import android.view.LayoutInflater;
@@ -33,14 +32,11 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.SettingsMainSwitchPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
@@ -48,40 +44,18 @@ import java.util.List;
/** Settings for daltonizer. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceFragment
implements DaltonizerRadioButtonPreferenceController.OnChangeListener {
public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceFragment {
private static final String TAG = "ToggleDaltonizerPreferenceFragment";
private static final String ENABLED = Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED;
private static final String KEY_PREVIEW = "daltonizer_preview";
@VisibleForTesting
static final String KEY_DEUTERANOMALY = "daltonizer_mode_deuteranomaly";
@VisibleForTesting
static final String KEY_PROTANOMALY = "daltonizer_mode_protanomaly";
@VisibleForTesting
static final String KEY_TRITANOMEALY = "daltonizer_mode_tritanomaly";
@VisibleForTesting
static final String KEY_GRAYSCALE = "daltonizer_mode_grayscale";
private static final String KEY_DEUTERANOMALY = "daltonizer_mode_deuteranomaly";
private static final String KEY_PROTANOMALY = "daltonizer_mode_protanomaly";
private static final String KEY_TRITANOMEALY = "daltonizer_mode_tritanomaly";
private static final String KEY_GRAYSCALE = "daltonizer_mode_grayscale";
@VisibleForTesting
static final String KEY_SATURATION = "daltonizer_saturation";
private static final List<AbstractPreferenceController> sControllers = new ArrayList<>();
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
Lifecycle lifecycle) {
if (sControllers.size() == 0) {
final Resources resources = context.getResources();
final String[] daltonizerKeys = resources.getStringArray(
R.array.daltonizer_mode_keys);
for (String daltonizerKey : daltonizerKeys) {
sControllers.add(new DaltonizerRadioButtonPreferenceController(
context, lifecycle, daltonizerKey));
}
}
return sControllers;
}
@Override
protected void registerKeysToObserverCallback(
AccessibilitySettingsContentObserver contentObserver) {
@@ -117,13 +91,6 @@ public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceF
}
}
@Override
public void onCheckedChanged(Preference preference) {
for (AbstractPreferenceController controller : sControllers) {
controller.updateState(preference);
}
}
private void updateFooterPreference() {
final String title = getPrefContext()
.getString(R.string.accessibility_daltonizer_about_title);
@@ -155,21 +122,6 @@ public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceF
public void onResume() {
super.onResume();
updateSwitchBarToggleSwitch();
for (AbstractPreferenceController controller :
buildPreferenceControllers(getPrefContext(), getSettingsLifecycle())) {
((DaltonizerRadioButtonPreferenceController) controller).setOnChangeListener(this);
((DaltonizerRadioButtonPreferenceController) controller).displayPreference(
getPreferenceScreen());
}
}
@Override
public void onPause() {
for (AbstractPreferenceController controller :
buildPreferenceControllers(getPrefContext(), getSettingsLifecycle())) {
((DaltonizerRadioButtonPreferenceController) controller).setOnChangeListener(null);
}
super.onPause();
}
@Override
@@ -194,7 +146,8 @@ public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceF
@Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
final boolean isEnabled = Settings.Secure.getInt(getContentResolver(), ENABLED, OFF) == ON;
final boolean isEnabled =
isSecureAccessibilityDaltonizerEnabled(getContentResolver());
if (enabled == isEnabled) {
return;
}

View File

@@ -16,6 +16,10 @@
package com.android.settings.accessibility;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.DEFAULT;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
import android.app.Activity;
@@ -43,7 +47,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
@@ -53,13 +56,13 @@ import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.accessibility.AccessibilityDialogUtils.DialogType;
import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.flags.Flags;
@@ -84,11 +87,9 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
public static final String KEY_GENERAL_CATEGORY = "general_categories";
public static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
public static final int NOT_SET = -1;
protected static final String KEY_TOP_INTRO_PREFERENCE = "top_intro";
protected static final String KEY_USE_SERVICE_PREFERENCE = "use_service";
protected static final String KEY_HTML_DESCRIPTION_PREFERENCE = "html_description";
protected static final String KEY_SAVED_USER_SHORTCUT_TYPE = "shortcut_type";
protected static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow";
protected static final String KEY_SAVED_QS_TOOLTIP_TYPE = "qs_tooltip_type";
protected static final String KEY_ANIMATED_IMAGE = "animated_image";
@@ -101,6 +102,7 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
protected SettingsMainSwitchPreference mToggleServiceSwitchPreference;
protected ShortcutPreference mShortcutPreference;
protected Preference mSettingsPreference;
@Nullable protected AccessibilityFooterPreference mHtmlFooterPreference;
protected AccessibilityFooterPreferenceController mFooterPreferenceController;
protected String mPreferenceKey;
protected Dialog mDialog;
@@ -112,15 +114,10 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
protected Uri mImageUri;
protected CharSequence mHtmlDescription;
protected CharSequence mTopIntroTitle;
// Save user's shortcutType value when savedInstance has value (e.g. device rotated).
protected int mSavedCheckBoxValue = NOT_SET;
private CharSequence mDescription;
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
private AccessibilitySettingsContentObserver mSettingsContentObserver;
private CheckBox mSoftwareTypeCheckBox;
private CheckBox mHardwareTypeCheckBox;
private AccessibilityQuickSettingsTooltipWindow mTooltipWindow;
private boolean mNeedsQSTooltipReshow = false;
private int mNeedsQSTooltipType = QuickSettingsTooltipType.GUIDE_TO_EDIT;
@@ -143,10 +140,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
onProcessArguments(getArguments());
// Restore the user shortcut type and tooltip.
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(KEY_SAVED_USER_SHORTCUT_TYPE)) {
mSavedCheckBoxValue = savedInstanceState.getInt(KEY_SAVED_USER_SHORTCUT_TYPE,
NOT_SET);
}
if (savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_RESHOW)) {
mNeedsQSTooltipReshow = savedInstanceState.getBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW);
}
@@ -204,7 +197,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
updateToggleServiceTitle(mToggleServiceSwitchPreference);
mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
removeDialog(DialogEnums.EDIT_SHORTCUT);
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
};
@@ -215,14 +207,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
@Override
public Dialog onCreateDialog(int dialogId) {
switch (dialogId) {
case DialogEnums.EDIT_SHORTCUT:
final int dialogType = isAnySetupWizard()
? DialogType.EDIT_SHORTCUT_GENERIC_SUW : DialogType.EDIT_SHORTCUT_GENERIC;
mDialog = AccessibilityDialogUtils.showEditShortcutDialog(
getPrefContext(), dialogType, getShortcutTitle(),
this::callOnAlertDialogCheckboxClicked);
setupEditShortcutDialog(mDialog);
return mDialog;
case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
if (isAnySetupWizard()) {
mDialog = AccessibilityShortcutsTutorial
@@ -273,8 +257,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
mSettingsContentObserver.register(getContentResolver());
updateShortcutPreferenceData();
updateShortcutPreference();
updateEditShortcutDialogIfNeeded();
}
@Override
@@ -288,10 +270,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
@Override
public void onSaveInstanceState(Bundle outState) {
final int value = getShortcutTypeCheckBoxValue();
if (value != NOT_SET) {
outState.putInt(KEY_SAVED_USER_SHORTCUT_TYPE, value);
}
final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
if (mNeedsQSTooltipReshow || isTooltipWindowShowing) {
outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true);
@@ -313,8 +291,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
@Override
public int getDialogMetricsCategory(int dialogId) {
switch (dialogId) {
case DialogEnums.EDIT_SHORTCUT:
return SettingsEnums.DIALOG_ACCESSIBILITY_SERVICE_EDIT_SHORTCUT;
case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
return SettingsEnums.DIALOG_ACCESSIBILITY_TUTORIAL;
default:
@@ -589,27 +565,40 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
}
private void initHtmlTextPreference() {
if (TextUtils.isEmpty(mHtmlDescription)) {
if (TextUtils.isEmpty(getCurrentHtmlDescription())) {
return;
}
final PreferenceScreen screen = getPreferenceScreen();
final CharSequence htmlDescription = Html.fromHtml(mHtmlDescription.toString(),
Html.FROM_HTML_MODE_COMPACT, mImageGetter, /* tagHandler= */ null);
final AccessibilityFooterPreference htmlFooterPreference =
mHtmlFooterPreference =
new AccessibilityFooterPreference(screen.getContext());
htmlFooterPreference.setKey(KEY_HTML_DESCRIPTION_PREFERENCE);
htmlFooterPreference.setSummary(htmlDescription);
screen.addPreference(htmlFooterPreference);
mHtmlFooterPreference.setKey(KEY_HTML_DESCRIPTION_PREFERENCE);
updateHtmlTextPreference();
screen.addPreference(mHtmlFooterPreference);
// TODO(b/171272809): Migrate to DashboardFragment.
final String title = getString(R.string.accessibility_introduction_title, mPackageName);
mFooterPreferenceController = new AccessibilityFooterPreferenceController(
screen.getContext(), htmlFooterPreference.getKey());
screen.getContext(), mHtmlFooterPreference.getKey());
mFooterPreferenceController.setIntroductionTitle(title);
mFooterPreferenceController.displayPreference(screen);
}
protected void updateHtmlTextPreference() {
if (mHtmlFooterPreference == null) {
return;
}
String description = getCurrentHtmlDescription().toString();
final CharSequence htmlDescription = Html.fromHtml(description,
Html.FROM_HTML_MODE_COMPACT, mImageGetter, /* tagHandler= */ null);
mHtmlFooterPreference.setSummary(htmlDescription);
}
CharSequence getCurrentHtmlDescription() {
return mHtmlDescription;
}
private void initFooterPreference() {
if (!TextUtils.isEmpty(mDescription)) {
createFooterPreference(getPreferenceScreen(), mDescription,
@@ -617,7 +606,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
}
}
/**
* Creates {@link AccessibilityFooterPreference} and append into {@link PreferenceScreen}
*
@@ -639,69 +627,10 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
mFooterPreferenceController.displayPreference(screen);
}
@VisibleForTesting
void setupEditShortcutDialog(Dialog dialog) {
final View dialogSoftwareView = dialog.findViewById(R.id.software_shortcut);
mSoftwareTypeCheckBox = dialogSoftwareView.findViewById(R.id.checkbox);
setDialogTextAreaClickListener(dialogSoftwareView, mSoftwareTypeCheckBox);
final View dialogHardwareView = dialog.findViewById(R.id.hardware_shortcut);
mHardwareTypeCheckBox = dialogHardwareView.findViewById(R.id.checkbox);
setDialogTextAreaClickListener(dialogHardwareView, mHardwareTypeCheckBox);
updateEditShortcutDialogCheckBox();
}
private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) {
final View dialogTextArea = dialogView.findViewById(R.id.container);
dialogTextArea.setOnClickListener(v -> checkBox.toggle());
}
private void updateEditShortcutDialogCheckBox() {
// If it is during onConfigChanged process then restore the value, or get the saved value
// when shortcutPreference is checked.
int value = restoreOnConfigChangedValue();
if (value == NOT_SET) {
final int lastNonEmptyUserShortcutType = getUserPreferredShortcutTypes();
value = mShortcutPreference.isChecked() ? lastNonEmptyUserShortcutType
: UserShortcutType.EMPTY;
}
mSoftwareTypeCheckBox.setChecked(
hasShortcutType(value, UserShortcutType.SOFTWARE));
mHardwareTypeCheckBox.setChecked(
hasShortcutType(value, UserShortcutType.HARDWARE));
}
private int restoreOnConfigChangedValue() {
final int savedValue = mSavedCheckBoxValue;
mSavedCheckBoxValue = NOT_SET;
return savedValue;
}
private boolean hasShortcutType(int value, @UserShortcutType int type) {
return (value & type) == type;
}
/**
* Returns accumulated {@link UserShortcutType} checkbox value or {@code NOT_SET} if checkboxes
* did not exist.
*/
protected int getShortcutTypeCheckBoxValue() {
if (mSoftwareTypeCheckBox == null || mHardwareTypeCheckBox == null) {
return NOT_SET;
}
int value = UserShortcutType.EMPTY;
if (mSoftwareTypeCheckBox.isChecked()) {
value |= UserShortcutType.SOFTWARE;
}
if (mHardwareTypeCheckBox.isChecked()) {
value |= UserShortcutType.HARDWARE;
}
return value;
}
protected CharSequence getShortcutTypeSummary(Context context) {
if (!mShortcutPreference.isSettingsEditable()) {
return context.getText(R.string.accessibility_shortcut_edit_dialog_title_hardware);
@@ -717,16 +646,16 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
final List<CharSequence> list = new ArrayList<>();
if (android.view.accessibility.Flags.a11yQsShortcut()) {
if (hasShortcutType(shortcutTypes, UserShortcutType.QUICK_SETTINGS)) {
if (hasShortcutType(shortcutTypes, QUICK_SETTINGS)) {
final CharSequence qsTitle = context.getText(
R.string.accessibility_feature_shortcut_setting_summary_quick_settings);
list.add(qsTitle);
}
}
if (hasShortcutType(shortcutTypes, UserShortcutType.SOFTWARE)) {
if (hasShortcutType(shortcutTypes, SOFTWARE)) {
list.add(getSoftwareShortcutTypeSummary(context));
}
if (hasShortcutType(shortcutTypes, UserShortcutType.HARDWARE)) {
if (hasShortcutType(shortcutTypes, HARDWARE)) {
final CharSequence hardwareTitle = context.getText(
R.string.accessibility_shortcut_hardware_keyword);
list.add(hardwareTitle);
@@ -765,35 +694,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
showQuickSettingsTooltipIfNeeded();
}
/**
* This method will be invoked when a button in the edit shortcut dialog is clicked.
*
* @param dialog The dialog that received the click
* @param which The button that was clicked
*/
protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
if (mComponentName == null) {
return;
}
final int value = getShortcutTypeCheckBoxValue();
saveNonEmptyUserShortcutType(value);
AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), value, mComponentName);
AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), ~value, mComponentName);
final boolean shortcutAssigned = value != UserShortcutType.EMPTY;
mShortcutPreference.setChecked(shortcutAssigned);
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
if (mHardwareTypeCheckBox.isChecked()) {
AccessibilityUtil.skipVolumeShortcutDialogTimeoutRestriction(getPrefContext());
}
// Show the quick setting tooltip if the shortcut assigned in the first time
if (shortcutAssigned) {
showQuickSettingsTooltipIfNeeded();
}
}
protected void updateShortcutPreferenceData() {
if (mComponentName == null) {
return;
@@ -801,7 +701,7 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
final int shortcutTypes = AccessibilityUtil.getUserShortcutTypesFromSettings(
getPrefContext(), mComponentName);
if (shortcutTypes != UserShortcutType.EMPTY) {
if (shortcutTypes != DEFAULT) {
final PreferredShortcut shortcut = new PreferredShortcut(
mComponentName.flattenToString(), shortcutTypes);
PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
@@ -844,13 +744,9 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
@Override
public void onSettingsClicked(ShortcutPreference preference) {
if (com.android.settings.accessibility.Flags.editShortcutsInFullScreen()) {
EditShortcutsPreferenceFragment.showEditShortcutScreen(
requireContext(), getMetricsCategory(), getShortcutTitle(),
mComponentName, getIntent());
} else {
showDialog(DialogEnums.EDIT_SHORTCUT);
}
EditShortcutsPreferenceFragment.showEditShortcutScreen(
requireContext(), getMetricsCategory(), getShortcutTitle(),
mComponentName, getIntent());
}
/**
@@ -887,24 +783,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
}
}
private void updateEditShortcutDialogIfNeeded() {
if (mDialog == null || !mDialog.isShowing()) {
return;
}
AccessibilityDialogUtils.updateShortcutInDialog(getContext(), mDialog);
}
@VisibleForTesting
void saveNonEmptyUserShortcutType(int type) {
if (type == UserShortcutType.EMPTY) {
return;
}
final PreferredShortcut shortcut = new PreferredShortcut(
mComponentName.flattenToString(), type);
PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
}
/**
* Shows the quick settings tooltip if the quick settings feature is assigned. The tooltip only
* shows once.
@@ -983,7 +861,7 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
*/
@ShortcutConstants.UserShortcutType
protected int getDefaultShortcutTypes() {
return ShortcutConstants.UserShortcutType.SOFTWARE;
return SOFTWARE;
}
/**
@@ -994,4 +872,12 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
return PreferredShortcuts.retrieveUserShortcutType(
getPrefContext(), mComponentName.flattenToString(), getDefaultShortcutTypes());
}
@Override
public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
RecyclerView recyclerView =
super.onCreateRecyclerView(inflater, parent, savedInstanceState);
return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(recyclerView);
}
}

View File

@@ -18,6 +18,12 @@ package com.android.settings.accessibility;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.DEFAULT;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TWOFINGER_DOUBLETAP;
import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
@@ -27,7 +33,6 @@ import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.icu.text.CaseMap;
import android.icu.text.MessageFormat;
@@ -42,21 +47,20 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
import android.widget.CheckBox;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.SwitchPreferenceCompat;
import androidx.preference.TwoStatePreference;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.accessibility.Flags;
import com.android.settings.DialogCreatable;
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityDialogUtils.DialogType;
import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment;
import com.android.settings.utils.LocaleUtils;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -85,12 +89,11 @@ public class ToggleScreenMagnificationPreferenceFragment extends
// TODO(b/147021230): Move duplicated functions with android/internal/accessibility into util.
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
private CheckBox mSoftwareTypeCheckBox;
private CheckBox mHardwareTypeCheckBox;
private CheckBox mTripleTapTypeCheckBox;
@Nullable private CheckBox mTwoFingerTripleTapTypeCheckBox;
private DialogCreatable mDialogDelegate;
@Nullable
MagnificationOneFingerPanningPreferenceController mOneFingerPanningPreferenceController;
private boolean mInSetupWizard;
@Override
@@ -109,7 +112,6 @@ public class ToggleScreenMagnificationPreferenceFragment extends
.appendPath(String.valueOf(R.raw.a11y_magnification_banner))
.build();
mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
removeDialog(DialogEnums.EDIT_SHORTCUT);
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
};
@@ -174,15 +176,6 @@ public class ToggleScreenMagnificationPreferenceFragment extends
case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
return AccessibilityShortcutsTutorial
.showAccessibilityGestureTutorialDialog(getPrefContext());
case DialogEnums.MAGNIFICATION_EDIT_SHORTCUT:
final CharSequence dialogTitle = getShortcutTitle();
final int dialogType = mInSetupWizard
? DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW
: DialogType.EDIT_SHORTCUT_MAGNIFICATION;
mDialog = AccessibilityDialogUtils.showEditShortcutDialog(getPrefContext(),
dialogType, dialogTitle, this::callOnAlertDialogCheckboxClicked);
setupMagnificationEditShortcutDialog(mDialog);
return mDialog;
default:
return super.onCreateDialog(dialogId);
}
@@ -236,7 +229,8 @@ public class ToggleScreenMagnificationPreferenceFragment extends
context.getString(R.string.accessibility_screen_magnification_intro_text));
}
if (!arguments.containsKey(AccessibilitySettings.EXTRA_HTML_DESCRIPTION)) {
if (!arguments.containsKey(AccessibilitySettings.EXTRA_HTML_DESCRIPTION)
&& !Flags.enableMagnificationOneFingerPanningGesture()) {
String summary = MessageFormat.format(
context.getString(R.string.accessibility_screen_magnification_summary),
new Object[]{1, 2, 3, 4, 5});
@@ -308,12 +302,12 @@ public class ToggleScreenMagnificationPreferenceFragment extends
MagnificationOneFingerPanningPreferenceController.PREF_KEY);
generalCategory.addPreference(oneFingerPanningPreference);
var oneFingerPanningPreferenceController =
mOneFingerPanningPreferenceController =
new MagnificationOneFingerPanningPreferenceController(getContext());
oneFingerPanningPreferenceController.setInSetupWizard(mInSetupWizard);
getSettingsLifecycle().addObserver(oneFingerPanningPreferenceController);
oneFingerPanningPreferenceController.displayPreference(getPreferenceScreen());
addPreferenceController(oneFingerPanningPreferenceController);
mOneFingerPanningPreferenceController.setInSetupWizard(mInSetupWizard);
getSettingsLifecycle().addObserver(mOneFingerPanningPreferenceController);
mOneFingerPanningPreferenceController.displayPreference(getPreferenceScreen());
addPreferenceController(mOneFingerPanningPreferenceController);
}
private void addJoystickSetting(PreferenceCategory generalCategory) {
@@ -354,95 +348,6 @@ public class ToggleScreenMagnificationPreferenceFragment extends
mDialogDelegate = delegate;
}
@Override
protected int getShortcutTypeCheckBoxValue() {
if (mSoftwareTypeCheckBox == null || mHardwareTypeCheckBox == null) {
return NOT_SET;
}
int value = UserShortcutType.EMPTY;
if (mSoftwareTypeCheckBox.isChecked()) {
value |= UserShortcutType.SOFTWARE;
}
if (mHardwareTypeCheckBox.isChecked()) {
value |= UserShortcutType.HARDWARE;
}
if (mTripleTapTypeCheckBox.isChecked()) {
value |= UserShortcutType.TRIPLETAP;
}
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
if (mTwoFingerTripleTapTypeCheckBox.isChecked()) {
value |= UserShortcutType.TWOFINGER_DOUBLETAP;
}
}
return value;
}
@VisibleForTesting
void setupMagnificationEditShortcutDialog(Dialog dialog) {
final View dialogSoftwareView = dialog.findViewById(R.id.software_shortcut);
mSoftwareTypeCheckBox = dialogSoftwareView.findViewById(R.id.checkbox);
setDialogTextAreaClickListener(dialogSoftwareView, mSoftwareTypeCheckBox);
final View dialogHardwareView = dialog.findViewById(R.id.hardware_shortcut);
mHardwareTypeCheckBox = dialogHardwareView.findViewById(R.id.checkbox);
setDialogTextAreaClickListener(dialogHardwareView, mHardwareTypeCheckBox);
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
final View dialogTwoFignerTripleTapView =
dialog.findViewById(R.id.two_finger_triple_tap_shortcut);
mTwoFingerTripleTapTypeCheckBox = dialogTwoFignerTripleTapView.findViewById(
R.id.checkbox);
setDialogTextAreaClickListener(
dialogTwoFignerTripleTapView, mTwoFingerTripleTapTypeCheckBox);
}
final View dialogTripleTapView = dialog.findViewById(R.id.triple_tap_shortcut);
mTripleTapTypeCheckBox = dialogTripleTapView.findViewById(R.id.checkbox);
setDialogTextAreaClickListener(dialogTripleTapView, mTripleTapTypeCheckBox);
final View advancedView = dialog.findViewById(R.id.advanced_shortcut);
if (mTripleTapTypeCheckBox.isChecked()) {
advancedView.setVisibility(View.GONE);
dialogTripleTapView.setVisibility(View.VISIBLE);
}
updateMagnificationEditShortcutDialogCheckBox();
}
private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) {
final View dialogTextArea = dialogView.findViewById(R.id.container);
dialogTextArea.setOnClickListener(v -> checkBox.toggle());
}
private void updateMagnificationEditShortcutDialogCheckBox() {
// If it is during onConfigChanged process then restore the value, or get the saved value
// when shortcutPreference is checked.
int value = restoreOnConfigChangedValue();
if (value == NOT_SET) {
final int lastNonEmptyUserShortcutType = getUserPreferredShortcutTypes();
value = mShortcutPreference.isChecked() ? lastNonEmptyUserShortcutType
: UserShortcutType.EMPTY;
}
mSoftwareTypeCheckBox.setChecked(
hasShortcutType(value, UserShortcutType.SOFTWARE));
mHardwareTypeCheckBox.setChecked(
hasShortcutType(value, UserShortcutType.HARDWARE));
mTripleTapTypeCheckBox.setChecked(
hasShortcutType(value, UserShortcutType.TRIPLETAP));
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
mTwoFingerTripleTapTypeCheckBox.setChecked(
hasShortcutType(value, UserShortcutType.TWOFINGER_DOUBLETAP));
}
}
private int restoreOnConfigChangedValue() {
final int savedValue = mSavedCheckBoxValue;
mSavedCheckBoxValue = NOT_SET;
return savedValue;
}
private boolean hasShortcutType(int value, @UserShortcutType int type) {
return (value & type) == type;
}
@@ -471,6 +376,12 @@ public class ToggleScreenMagnificationPreferenceFragment extends
);
contentObserver.registerKeysToObserverCallback(keysToObserve,
key -> updatePreferencesState());
if (Flags.enableMagnificationOneFingerPanningGesture()) {
contentObserver.registerKeysToObserverCallback(
List.of(Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED),
key -> updateHtmlTextPreference());
}
}
private void updatePreferencesState() {
@@ -480,6 +391,25 @@ public class ToggleScreenMagnificationPreferenceFragment extends
findPreference(controller.getPreferenceKey())));
}
@Override
CharSequence getCurrentHtmlDescription() {
CharSequence origin = super.getCurrentHtmlDescription();
if (!TextUtils.isEmpty(origin)) {
// If in ToggleFeaturePreferenceFragment we already have a fixed html description, we
// should use the fixed one, otherwise we'll dynamically decide the description.
return origin;
}
Context context = getContext();
if (mOneFingerPanningPreferenceController != null && context != null) {
@StringRes int resId = mOneFingerPanningPreferenceController.isChecked()
? R.string.accessibility_screen_magnification_summary_one_finger_panning_on
: R.string.accessibility_screen_magnification_summary_one_finger_panning_off;
return MessageFormat.format(context.getString(resId), new Object[]{1, 2, 3, 4, 5});
}
return "";
}
@Override
protected List<String> getShortcutFeatureSettingsKeys() {
final List<String> shortcutKeys = super.getShortcutFeatureSettingsKeys();
@@ -499,28 +429,28 @@ public class ToggleScreenMagnificationPreferenceFragment extends
// LINT.IfChange(shortcut_type_ui_order)
final List<CharSequence> list = new ArrayList<>();
if (android.view.accessibility.Flags.a11yQsShortcut()) {
if (hasShortcutType(shortcutTypes, UserShortcutType.QUICK_SETTINGS)) {
if (hasShortcutType(shortcutTypes, QUICK_SETTINGS)) {
final CharSequence qsTitle = context.getText(
R.string.accessibility_feature_shortcut_setting_summary_quick_settings);
list.add(qsTitle);
}
}
if (hasShortcutType(shortcutTypes, UserShortcutType.SOFTWARE)) {
if (hasShortcutType(shortcutTypes, SOFTWARE)) {
list.add(getSoftwareShortcutTypeSummary(context));
}
if (hasShortcutType(shortcutTypes, UserShortcutType.HARDWARE)) {
if (hasShortcutType(shortcutTypes, HARDWARE)) {
final CharSequence hardwareTitle = context.getText(
R.string.accessibility_shortcut_hardware_keyword);
list.add(hardwareTitle);
}
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
if (hasShortcutType(shortcutTypes, UserShortcutType.TWOFINGER_DOUBLETAP)) {
if (hasShortcutType(shortcutTypes, TWOFINGER_DOUBLETAP)) {
final CharSequence twoFingerDoubleTapTitle = context.getString(
R.string.accessibility_shortcut_two_finger_double_tap_keyword, 2);
list.add(twoFingerDoubleTapTitle);
}
}
if (hasShortcutType(shortcutTypes, UserShortcutType.TRIPLETAP)) {
if (hasShortcutType(shortcutTypes, TRIPLETAP)) {
final CharSequence tripleTapTitle = context.getText(
R.string.accessibility_shortcut_triple_tap_keyword);
list.add(tripleTapTitle);
@@ -536,22 +466,6 @@ public class ToggleScreenMagnificationPreferenceFragment extends
null, LocaleUtils.getConcatenatedString(list));
}
@Override
protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
final int value = getShortcutTypeCheckBoxValue();
saveNonEmptyUserShortcutType(value);
optInAllMagnificationValuesToSettings(getPrefContext(), value);
optOutAllMagnificationValuesFromSettings(getPrefContext(), ~value);
mShortcutPreference.setChecked(value != UserShortcutType.EMPTY);
mShortcutPreference.setSummary(
getShortcutTypeSummary(getPrefContext()));
if (mHardwareTypeCheckBox.isChecked()) {
AccessibilityUtil.skipVolumeShortcutDialogTimeoutRestriction(getPrefContext());
}
}
@Override
public int getHelpResource() {
return R.string.help_url_magnification;
@@ -577,8 +491,6 @@ public class ToggleScreenMagnificationPreferenceFragment extends
return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION;
case DialogEnums.ACCESSIBILITY_BUTTON_TUTORIAL:
return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_ACCESSIBILITY_BUTTON;
case DialogEnums.MAGNIFICATION_EDIT_SHORTCUT:
return SettingsEnums.DIALOG_MAGNIFICATION_EDIT_SHORTCUT;
default:
return super.getDialogMetricsCategory(dialogId);
}
@@ -628,22 +540,18 @@ public class ToggleScreenMagnificationPreferenceFragment extends
@Override
public void onSettingsClicked(ShortcutPreference preference) {
if (com.android.settings.accessibility.Flags.editShortcutsInFullScreen()) {
EditShortcutsPreferenceFragment.showEditShortcutScreen(
requireContext(),
getMetricsCategory(),
getShortcutTitle(),
MAGNIFICATION_COMPONENT_NAME,
getIntent());
} else {
showDialog(DialogEnums.MAGNIFICATION_EDIT_SHORTCUT);
}
EditShortcutsPreferenceFragment.showEditShortcutScreen(
requireContext(),
getMetricsCategory(),
getShortcutTitle(),
MAGNIFICATION_COMPONENT_NAME,
getIntent());
}
@Override
protected void updateShortcutPreferenceData() {
final int shortcutTypes = getUserShortcutTypeFromSettings(getPrefContext());
if (shortcutTypes != UserShortcutType.EMPTY) {
if (shortcutTypes != DEFAULT) {
final PreferredShortcut shortcut = new PreferredShortcut(
MAGNIFICATION_CONTROLLER_NAME, shortcutTypes);
PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
@@ -676,38 +584,27 @@ public class ToggleScreenMagnificationPreferenceFragment extends
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
}
@VisibleForTesting
void saveNonEmptyUserShortcutType(int type) {
if (type == UserShortcutType.EMPTY) {
return;
}
final PreferredShortcut shortcut = new PreferredShortcut(
MAGNIFICATION_CONTROLLER_NAME, type);
PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
}
@VisibleForTesting
static void optInAllMagnificationValuesToSettings(Context context, int shortcutTypes) {
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
optInMagnificationValueToSettings(context, UserShortcutType.SOFTWARE);
if ((shortcutTypes & SOFTWARE) == SOFTWARE) {
optInMagnificationValueToSettings(context, SOFTWARE);
}
if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
optInMagnificationValueToSettings(context, UserShortcutType.HARDWARE);
if (((shortcutTypes & HARDWARE) == HARDWARE)) {
optInMagnificationValueToSettings(context, HARDWARE);
}
if (((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP)) {
optInMagnificationValueToSettings(context, UserShortcutType.TRIPLETAP);
if (((shortcutTypes & TRIPLETAP) == TRIPLETAP)) {
optInMagnificationValueToSettings(context, TRIPLETAP);
}
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
if (((shortcutTypes & UserShortcutType.TWOFINGER_DOUBLETAP)
== UserShortcutType.TWOFINGER_DOUBLETAP)) {
optInMagnificationValueToSettings(context, UserShortcutType.TWOFINGER_DOUBLETAP);
if (((shortcutTypes & TWOFINGER_DOUBLETAP)
== TWOFINGER_DOUBLETAP)) {
optInMagnificationValueToSettings(context, TWOFINGER_DOUBLETAP);
}
}
if (android.view.accessibility.Flags.a11yQsShortcut()) {
if (((shortcutTypes & UserShortcutType.QUICK_SETTINGS)
== UserShortcutType.QUICK_SETTINGS)) {
optInMagnificationValueToSettings(context, UserShortcutType.QUICK_SETTINGS);
if (((shortcutTypes & QUICK_SETTINGS)
== QUICK_SETTINGS)) {
optInMagnificationValueToSettings(context, QUICK_SETTINGS);
}
}
}
@@ -727,14 +624,14 @@ public class ToggleScreenMagnificationPreferenceFragment extends
return;
}
if (shortcutType == UserShortcutType.TRIPLETAP) {
if (shortcutType == TRIPLETAP) {
Settings.Secure.putInt(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, ON);
return;
}
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
if (shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) {
if (shortcutType == TWOFINGER_DOUBLETAP) {
Settings.Secure.putInt(
context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
@@ -760,7 +657,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends
Settings.Secure.putString(context.getContentResolver(), targetKey, joiner.toString());
// The size setting defaults to unknown. If the user has ever manually changed the size
// before, we do not automatically change it.
if (shortcutType == UserShortcutType.SOFTWARE
if (shortcutType == SOFTWARE
&& Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
FloatingMenuSizePreferenceController.Size.UNKNOWN)
@@ -774,25 +671,25 @@ public class ToggleScreenMagnificationPreferenceFragment extends
@VisibleForTesting
static void optOutAllMagnificationValuesFromSettings(Context context,
int shortcutTypes) {
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
optOutMagnificationValueFromSettings(context, UserShortcutType.SOFTWARE);
if ((shortcutTypes & SOFTWARE) == SOFTWARE) {
optOutMagnificationValueFromSettings(context, SOFTWARE);
}
if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
optOutMagnificationValueFromSettings(context, UserShortcutType.HARDWARE);
if (((shortcutTypes & HARDWARE) == HARDWARE)) {
optOutMagnificationValueFromSettings(context, HARDWARE);
}
if (((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP)) {
optOutMagnificationValueFromSettings(context, UserShortcutType.TRIPLETAP);
if (((shortcutTypes & TRIPLETAP) == TRIPLETAP)) {
optOutMagnificationValueFromSettings(context, TRIPLETAP);
}
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
if (((shortcutTypes & UserShortcutType.TWOFINGER_DOUBLETAP)
== UserShortcutType.TWOFINGER_DOUBLETAP)) {
optOutMagnificationValueFromSettings(context, UserShortcutType.TWOFINGER_DOUBLETAP);
if (((shortcutTypes & TWOFINGER_DOUBLETAP)
== TWOFINGER_DOUBLETAP)) {
optOutMagnificationValueFromSettings(context, TWOFINGER_DOUBLETAP);
}
}
if (android.view.accessibility.Flags.a11yQsShortcut()) {
if (((shortcutTypes & UserShortcutType.QUICK_SETTINGS)
== UserShortcutType.QUICK_SETTINGS)) {
optOutMagnificationValueFromSettings(context, UserShortcutType.QUICK_SETTINGS);
if (((shortcutTypes & QUICK_SETTINGS)
== QUICK_SETTINGS)) {
optOutMagnificationValueFromSettings(context, QUICK_SETTINGS);
}
}
}
@@ -812,14 +709,14 @@ public class ToggleScreenMagnificationPreferenceFragment extends
return;
}
if (shortcutType == UserShortcutType.TRIPLETAP) {
if (shortcutType == TRIPLETAP) {
Settings.Secure.putInt(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF);
return;
}
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
if (shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) {
if (shortcutType == TWOFINGER_DOUBLETAP) {
Settings.Secure.putInt(
context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
@@ -854,20 +751,20 @@ public class ToggleScreenMagnificationPreferenceFragment extends
static boolean hasMagnificationValuesInSettings(Context context, int shortcutTypes) {
boolean exist = false;
if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
exist = hasMagnificationValueInSettings(context, UserShortcutType.SOFTWARE);
if ((shortcutTypes & SOFTWARE) == SOFTWARE) {
exist = hasMagnificationValueInSettings(context, SOFTWARE);
}
if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
exist |= hasMagnificationValueInSettings(context, UserShortcutType.HARDWARE);
if (((shortcutTypes & HARDWARE) == HARDWARE)) {
exist |= hasMagnificationValueInSettings(context, HARDWARE);
}
if (((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP)) {
exist |= hasMagnificationValueInSettings(context, UserShortcutType.TRIPLETAP);
if (((shortcutTypes & TRIPLETAP) == TRIPLETAP)) {
exist |= hasMagnificationValueInSettings(context, TRIPLETAP);
}
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
if (((shortcutTypes & UserShortcutType.TWOFINGER_DOUBLETAP)
== UserShortcutType.TWOFINGER_DOUBLETAP)) {
if (((shortcutTypes & TWOFINGER_DOUBLETAP)
== TWOFINGER_DOUBLETAP)) {
exist |= hasMagnificationValueInSettings(context,
UserShortcutType.TWOFINGER_DOUBLETAP);
TWOFINGER_DOUBLETAP);
}
}
return exist;
@@ -875,13 +772,13 @@ public class ToggleScreenMagnificationPreferenceFragment extends
private static boolean hasMagnificationValueInSettings(Context context,
@UserShortcutType int shortcutType) {
if (shortcutType == UserShortcutType.TRIPLETAP) {
if (shortcutType == TRIPLETAP) {
return Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF) == ON;
}
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
if (shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) {
if (shortcutType == TWOFINGER_DOUBLETAP) {
return Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
OFF) == ON;
@@ -907,19 +804,19 @@ public class ToggleScreenMagnificationPreferenceFragment extends
}
private static int getUserShortcutTypeFromSettings(Context context) {
int shortcutTypes = UserShortcutType.EMPTY;
if (hasMagnificationValuesInSettings(context, UserShortcutType.SOFTWARE)) {
shortcutTypes |= UserShortcutType.SOFTWARE;
int shortcutTypes = DEFAULT;
if (hasMagnificationValuesInSettings(context, SOFTWARE)) {
shortcutTypes |= SOFTWARE;
}
if (hasMagnificationValuesInSettings(context, UserShortcutType.HARDWARE)) {
shortcutTypes |= UserShortcutType.HARDWARE;
if (hasMagnificationValuesInSettings(context, HARDWARE)) {
shortcutTypes |= HARDWARE;
}
if (hasMagnificationValuesInSettings(context, UserShortcutType.TRIPLETAP)) {
shortcutTypes |= UserShortcutType.TRIPLETAP;
if (hasMagnificationValuesInSettings(context, TRIPLETAP)) {
shortcutTypes |= TRIPLETAP;
}
if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
if (hasMagnificationValuesInSettings(context, UserShortcutType.TWOFINGER_DOUBLETAP)) {
shortcutTypes |= UserShortcutType.TWOFINGER_DOUBLETAP;
if (hasMagnificationValuesInSettings(context, TWOFINGER_DOUBLETAP)) {
shortcutTypes |= TWOFINGER_DOUBLETAP;
}
}
return shortcutTypes;
@@ -934,11 +831,12 @@ public class ToggleScreenMagnificationPreferenceFragment extends
// Get the user shortcut type from settings provider.
final int userShortcutType = getUserShortcutTypeFromSettings(context);
final CharSequence featureState =
(userShortcutType != AccessibilityUtil.UserShortcutType.EMPTY)
(userShortcutType != DEFAULT)
? context.getText(R.string.accessibility_summary_shortcut_enabled)
: context.getText(R.string.generic_accessibility_feature_shortcut_off);
final CharSequence featureSummary = context.getText(R.string.magnification_feature_summary);
return context.getString(R.string.preference_summary_default_combination,
return context.getString(
com.android.settingslib.R.string.preference_summary_default_combination,
featureState, featureSummary);
}

View File

@@ -79,7 +79,8 @@ public class ToggleScreenMagnificationPreferenceFragmentForSetupWizard
Bundle savedInstanceState) {
if (parent instanceof GlifPreferenceLayout) {
final GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
return layout.onCreateRecyclerView(inflater, parent, savedInstanceState);
return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(
layout.onCreateRecyclerView(inflater, parent, savedInstanceState));
}
return super.onCreateRecyclerView(inflater, parent, savedInstanceState);
}

View File

@@ -68,7 +68,8 @@ public class ToggleScreenReaderPreferenceFragmentForSetupWizard
Bundle savedInstanceState) {
if (parent instanceof GlifPreferenceLayout) {
final GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
return layout.onCreateRecyclerView(inflater, parent, savedInstanceState);
return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(
layout.onCreateRecyclerView(inflater, parent, savedInstanceState));
}
return super.onCreateRecyclerView(inflater, parent, savedInstanceState);
}

View File

@@ -68,7 +68,8 @@ public class ToggleSelectToSpeakPreferenceFragmentForSetupWizard
Bundle savedInstanceState) {
if (parent instanceof GlifPreferenceLayout) {
final GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
return layout.onCreateRecyclerView(inflater, parent, savedInstanceState);
return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(
layout.onCreateRecyclerView(inflater, parent, savedInstanceState));
}
return super.onCreateRecyclerView(inflater, parent, savedInstanceState);
}

View File

@@ -23,6 +23,7 @@ import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.VibrationAttributes;
import android.os.Vibrator;
import android.provider.Settings;
@@ -49,7 +50,8 @@ public class VibrationMainSwitchPreferenceController extends SettingsMainSwitchP
public VibrationMainSwitchPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mVibrator = context.getSystemService(Vibrator.class);
mSettingObserver = new ContentObserver(new Handler(/* async= */ true)) {
Handler handler = Looper.myLooper() != null ? new Handler(/* async= */ true) : null;
mSettingObserver = new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
updateState(mSwitchPreference);

View File

@@ -27,6 +27,7 @@ import android.database.ContentObserver;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -68,19 +69,8 @@ public abstract class VibrationPreferenceConfig {
/** Play a vibration effect with intensity just selected by the user. */
public static void playVibrationPreview(Vibrator vibrator,
@VibrationAttributes.Usage int vibrationUsage) {
playVibrationPreview(vibrator, createPreviewVibrationAttributes(vibrationUsage));
}
/**
* Play a vibration effect with intensity just selected by the user.
*
* @param vibrator The {@link Vibrator} used to play the vibration.
* @param vibrationAttributes The {@link VibrationAttributes} to indicate the
* vibration information.
*/
public static void playVibrationPreview(Vibrator vibrator,
VibrationAttributes vibrationAttributes) {
vibrator.vibrate(PREVIEW_VIBRATION_EFFECT, vibrationAttributes);
vibrator.vibrate(PREVIEW_VIBRATION_EFFECT,
createPreviewVibrationAttributes(vibrationUsage));
}
public VibrationPreferenceConfig(Context context, String settingKey,
@@ -176,7 +166,7 @@ public abstract class VibrationPreferenceConfig {
/** Creates observer for given preference. */
public SettingObserver(VibrationPreferenceConfig preferenceConfig) {
super(new Handler(/* async= */ true));
super(Looper.myLooper() != null ? new Handler(/* async= */ true) : null);
mUri = Settings.System.getUriFor(preferenceConfig.getSettingKey());
if (preferenceConfig.isRestrictedByRingerModeSilent()) {

View File

@@ -21,6 +21,7 @@ import android.database.ContentObserver;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Vibrator;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -74,7 +75,8 @@ public class VibrationRampingRingerTogglePreferenceController
mRingVibrationPreferenceConfig = new RingVibrationPreferenceConfig(context);
mRingSettingObserver = new VibrationPreferenceConfig.SettingObserver(
mRingVibrationPreferenceConfig);
mSettingObserver = new ContentObserver(new Handler(/* async= */ true)) {
Handler handler = Looper.myLooper() != null ? new Handler(/* async= */ true) : null;
mSettingObserver = new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
updateState(mPreference);

View File

@@ -26,6 +26,7 @@ import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
/** Preference controller for all bluetooth device preference. */
public class ViewAllBluetoothDevicesPreferenceController extends BasePreferenceController {
@@ -52,6 +53,8 @@ public class ViewAllBluetoothDevicesPreferenceController extends BasePreferenceC
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().clicked(
getMetricsCategory(), getPreferenceKey());
launchConnectedDevicePage();
return true;
}

View File

@@ -16,12 +16,14 @@
package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.os.Bundle;
import android.view.View;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import com.android.settings.R;
/**
@@ -44,7 +46,7 @@ public class VolumeShortcutToggleAccessibilityServicePreferenceFragment extends
mShortcutPreference.setSummary(hardwareTitle);
mShortcutPreference.setSettingsEditable(false);
setAllowedPreferredShortcutType(UserShortcutType.HARDWARE);
setAllowedPreferredShortcutType(HARDWARE);
}
@Override
@@ -56,9 +58,9 @@ public class VolumeShortcutToggleAccessibilityServicePreferenceFragment extends
final boolean hasRequestAccessibilityButtonFlag =
(info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
if (hasRequestAccessibilityButtonFlag && isServiceOn) {
shortcutTypes |= UserShortcutType.SOFTWARE;
shortcutTypes |= SOFTWARE;
} else {
shortcutTypes &= (~UserShortcutType.SOFTWARE);
shortcutTypes &= (~SOFTWARE);
}
return shortcutTypes;

View File

@@ -20,6 +20,7 @@ import static android.app.Activity.RESULT_CANCELED;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
import static android.provider.Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED;
import static android.provider.Settings.Secure.ACCESSIBILITY_QS_TARGETS;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
@@ -60,7 +61,6 @@ import com.android.internal.accessibility.dialog.AccessibilityTargetHelper;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.accessibility.AccessibilitySetupWizardUtils;
import com.android.settings.accessibility.Flags;
import com.android.settings.accessibility.PreferredShortcuts;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
@@ -94,7 +94,8 @@ public class EditShortcutsPreferenceFragment extends DashboardFragment {
Settings.Secure.getUriFor(ACCESSIBILITY_BUTTON_MODE);
private static final Uri BUTTON_SHORTCUT_SETTING =
Settings.Secure.getUriFor(ACCESSIBILITY_BUTTON_TARGETS);
private static final Uri GESTURE_SHORTCUT_SETTING =
Settings.Secure.getUriFor(ACCESSIBILITY_GESTURE_TARGETS);
private static final Uri TRIPLE_TAP_SHORTCUT_SETTING =
Settings.Secure.getUriFor(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
private static final Uri TWO_FINGERS_DOUBLE_TAP_SHORTCUT_SETTING =
@@ -108,6 +109,7 @@ public class EditShortcutsPreferenceFragment extends DashboardFragment {
VOLUME_KEYS_SHORTCUT_SETTING,
BUTTON_SHORTCUT_MODE_SETTING,
BUTTON_SHORTCUT_SETTING,
GESTURE_SHORTCUT_SETTING,
TRIPLE_TAP_SHORTCUT_SETTING,
TWO_FINGERS_DOUBLE_TAP_SHORTCUT_SETTING,
QUICK_SETTINGS_SHORTCUT_SETTING,
@@ -174,6 +176,8 @@ public class EditShortcutsPreferenceFragment extends DashboardFragment {
} else if (BUTTON_SHORTCUT_MODE_SETTING.equals(uri)
|| BUTTON_SHORTCUT_SETTING.equals(uri)) {
refreshSoftwareShortcutControllers();
} else if (GESTURE_SHORTCUT_SETTING.equals(uri)) {
refreshPreferenceController(GestureShortcutOptionController.class);
} else if (TRIPLE_TAP_SHORTCUT_SETTING.equals(uri)) {
refreshPreferenceController(TripleTapShortcutOptionController.class);
} else if (TWO_FINGERS_DOUBLE_TAP_SHORTCUT_SETTING.equals(uri)) {
@@ -199,8 +203,7 @@ public class EditShortcutsPreferenceFragment extends DashboardFragment {
Activity activity = getActivity();
if (!activity.getIntent().getAction().equals(
Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS)
|| !Flags.editShortcutsInFullScreen()) {
Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS)) {
return;
}

View File

@@ -50,7 +50,14 @@ public class FloatingButtonShortcutOptionController
@Override
protected boolean isShortcutAvailable() {
return AccessibilityUtil.isFloatingMenuEnabled(mContext);
if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
// FAB should be available when in gesture navigation mode,
// or if we're in the FAB button mode while in navbar navigation mode.
return AccessibilityUtil.isGestureNavigateEnabled(mContext)
|| AccessibilityUtil.isFloatingMenuEnabled(mContext);
} else {
return AccessibilityUtil.isFloatingMenuEnabled(mContext);
}
}
@Nullable

View File

@@ -16,6 +16,8 @@
package com.android.settings.accessibility.shortcuts;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import android.content.Context;
import android.text.SpannableStringBuilder;
@@ -51,11 +53,22 @@ public class GestureShortcutOptionController extends SoftwareShortcutOptionPrefe
}
}
@Override
protected int getShortcutType() {
return android.provider.Flags.a11yStandaloneGestureEnabled()
? GESTURE : super.getShortcutType();
}
@Override
protected boolean isShortcutAvailable() {
return !isInSetupWizard()
&& !AccessibilityUtil.isFloatingMenuEnabled(mContext)
&& AccessibilityUtil.isGestureNavigateEnabled(mContext);
if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
return !isInSetupWizard()
&& AccessibilityUtil.isGestureNavigateEnabled(mContext);
} else {
return !isInSetupWizard()
&& AccessibilityUtil.isGestureNavigateEnabled(mContext)
&& !AccessibilityUtil.isFloatingMenuEnabled(mContext);
}
}
@Override
@@ -68,9 +81,8 @@ public class GestureShortcutOptionController extends SoftwareShortcutOptionPrefe
final SpannableStringBuilder sb = new SpannableStringBuilder();
sb.append(instruction);
if (!isInSetupWizard()) {
sb.append("\n\n");
sb.append(getCustomizeAccessibilityButtonLink());
if (!isInSetupWizard() && !android.provider.Flags.a11yStandaloneGestureEnabled()) {
sb.append("\n\n").append(getCustomizeAccessibilityButtonLink());
}
return sb;

View File

@@ -40,10 +40,8 @@ import com.android.settings.SettingsActivity;
import com.android.settings.SubSettings;
import com.android.settings.biometrics.face.FaceEnrollIntroduction;
import com.android.settings.biometrics.face.FaceEnrollIntroductionInternal;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollActivityClassProvider;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal;
import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity;
import com.android.settings.core.FeatureFlags;
import com.android.settings.homepage.DeepLinkHomepageActivity;
import com.android.settings.homepage.DeepLinkHomepageActivityInternal;
@@ -51,6 +49,7 @@ import com.android.settings.homepage.SettingsHomepageActivity;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockPattern;
import com.android.settings.privatespace.PrivateSpaceSetupActivity;
import com.android.settings.privatespace.delete.PrivateSpaceDeleteActivity;
import com.android.settings.remoteauth.RemoteAuthActivity;
import com.android.settings.remoteauth.RemoteAuthActivityInternal;
@@ -255,10 +254,12 @@ public class ActivityEmbeddingRulesController {
.buildSearchIntent(mContext, SettingsEnums.SETTINGS_HOMEPAGE);
addActivityFilter(activityFilters, searchIntent);
}
addActivityFilter(activityFilters, FingerprintEnrollmentActivity.class);
addActivityFilter(activityFilters, FingerprintEnrollmentActivity.InternalActivity.class);
addActivityFilter(activityFilters, FingerprintEnrollIntroduction.class);
addActivityFilter(activityFilters, FingerprintEnrollIntroductionInternal.class);
final FingerprintEnrollActivityClassProvider fpClassProvider = FeatureFactory
.getFeatureFactory()
.getFingerprintFeatureProvider()
.getEnrollActivityClassProvider();
addActivityFilter(activityFilters, fpClassProvider.getDefault());
addActivityFilter(activityFilters, fpClassProvider.getInternal());
addActivityFilter(activityFilters, FingerprintEnrollEnrolling.class);
addActivityFilter(activityFilters, FaceEnrollIntroductionInternal.class);
addActivityFilter(activityFilters, FaceEnrollIntroduction.class);
@@ -266,6 +267,9 @@ public class ActivityEmbeddingRulesController {
addActivityFilter(activityFilters, RemoteAuthActivityInternal.class);
addActivityFilter(activityFilters, ChooseLockPattern.class);
addActivityFilter(activityFilters, PrivateSpaceSetupActivity.class);
if (android.multiuser.Flags.fixLargeDisplayPrivateSpaceSettings()) {
addActivityFilter(activityFilters, PrivateSpaceDeleteActivity.class);
}
String action = mContext.getString(R.string.config_avatar_picker_action);
addActivityFilter(activityFilters, new Intent(action));

View File

@@ -34,7 +34,7 @@ object EmbeddedDeepLinkUtils {
private const val TAG = "EmbeddedDeepLinkUtils"
@JvmStatic
fun Activity.tryStartMultiPaneDeepLink(
fun Context.tryStartMultiPaneDeepLink(
intent: Intent,
highlightMenuKey: String? = null,
): Boolean {

View File

@@ -18,6 +18,7 @@ package com.android.settings.applications;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.Manifest;
import android.app.Activity;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
@@ -39,6 +40,7 @@ import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
@@ -135,8 +137,13 @@ public abstract class AppInfoBase extends SettingsPreferenceFragment
}
}
if (intent != null && intent.hasExtra(Intent.EXTRA_USER_HANDLE)) {
mUserId = ((UserHandle) intent.getParcelableExtra(
Intent.EXTRA_USER_HANDLE)).getIdentifier();
mUserId = ((UserHandle) intent.getParcelableExtra(Intent.EXTRA_USER_HANDLE))
.getIdentifier();
if (mUserId != UserHandle.myUserId() && !hasInteractAcrossUsersFullPermission()) {
Log.w(TAG, "Intent not valid.");
finish();
return "";
}
} else {
mUserId = UserHandle.myUserId();
}
@@ -163,6 +170,28 @@ public abstract class AppInfoBase extends SettingsPreferenceFragment
return mPackageName;
}
@VisibleForTesting
protected boolean hasInteractAcrossUsersFullPermission() {
Activity activity = getActivity();
if (!(activity instanceof SettingsActivity)) {
return false;
}
final String callingPackageName =
((SettingsActivity) activity).getInitialCallingPackage();
if (TextUtils.isEmpty(callingPackageName)) {
Log.w(TAG, "Not able to get calling package name for permission check");
return false;
}
if (mPm.checkPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingPackageName)
!= PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "Package " + callingPackageName + " does not have required permission "
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL);
return false;
}
return true;
}
protected void setIntentAndFinish(boolean appChanged) {
Log.i(TAG, "appChanged=" + appChanged);
Intent intent = new Intent();

View File

@@ -324,9 +324,11 @@ public class AppStorageSettings extends AppInfoWithHeader
private void initMoveDialog() {
final Context context = getActivity();
final StorageManager storage = context.getSystemService(StorageManager.class);
final List<VolumeInfo> candidates = context.getPackageManager()
.getPackageCandidateVolumes(mAppEntry.info);
final PackageManager pm = context.getPackageManager();
final List<VolumeInfo> candidates =
mAppEntry != null && pm != null
? pm.getPackageCandidateVolumes(mAppEntry.info)
: Collections.emptyList();
if (candidates.size() > 1) {
Collections.sort(candidates, VolumeInfo.getDescriptionComparator());

View File

@@ -21,16 +21,24 @@ import android.content.pm.PackageManager;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settingslib.widget.AppPreference;
public class ProcessStatsPreference extends AppPreference {
static final String TAG = "ProcessStatsPreference";
private ProcStatsPackageEntry mEntry;
private int mProgress;
private boolean mProgressVisible;
public ProcessStatsPreference(Context context) {
super(context, null);
setLayoutResource(R.layout.preference_process_stats);
}
public void init(ProcStatsPackageEntry entry, PackageManager pm, double maxMemory,
@@ -56,4 +64,29 @@ public class ProcessStatsPreference extends AppPreference {
public ProcStatsPackageEntry getEntry() {
return mEntry;
}
/**
* Sets the current progress.
* @param amount the current progress
*
* @see ProgressBar#setProgress(int)
*/
public void setProgress(int amount) {
mProgress = amount;
mProgressVisible = true;
notifyChanged();
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
final ProgressBar progress = (ProgressBar) view.findViewById(android.R.id.progress);
if (mProgressVisible) {
progress.setProgress(mProgress);
progress.setVisibility(View.VISIBLE);
} else {
progress.setVisibility(View.GONE);
}
}
}

View File

@@ -33,6 +33,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.android.internal.app.LocaleHelper;
@@ -62,6 +63,7 @@ public class AppLocaleDetails extends SettingsPreferenceFragment {
private LayoutPreference mPrefOfDescription;
private Preference mPrefOfDisclaimer;
private ApplicationInfo mApplicationInfo;
@Nullable private String mParentLocale;
/**
* Create a instance of AppLocaleDetails.
@@ -111,6 +113,12 @@ public class AppLocaleDetails extends SettingsPreferenceFragment {
public void onResume() {
super.onResume();
refreshUi();
final Activity activity = getActivity();
if (mParentLocale != null) {
activity.setTitle(mParentLocale);
} else {
activity.setTitle(R.string.app_locale_picker_title);
}
}
private void refreshUi() {
@@ -215,4 +223,8 @@ public class AppLocaleDetails extends SettingsPreferenceFragment {
return LocaleHelper.getDisplayName(appLocale.stripExtensions(), appLocale, true);
}
}
public void setParentLocale(@Nullable String localeName) {
mParentLocale = localeName;
}
}

View File

@@ -20,6 +20,7 @@ import static android.content.pm.verify.domain.DomainVerificationUserState.DOMAI
import static android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_SELECTED;
import static android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_VERIFIED;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.appwidget.AppWidgetManager;
@@ -35,10 +36,9 @@ import android.util.ArraySet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
@@ -51,7 +51,7 @@ import com.android.settings.applications.ClearDefaultsPreference;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.MainSwitchPreference;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import java.util.HashMap;
import java.util.List;
@@ -62,10 +62,11 @@ import java.util.UUID;
/** The page of the Open by default */
public class AppLaunchSettings extends AppInfoBase implements
Preference.OnPreferenceChangeListener, OnCheckedChangeListener {
Preference.OnPreferenceChangeListener, SelectorWithWidgetPreference.OnClickListener {
private static final String TAG = "AppLaunchSettings";
// Preference keys
private static final String MAIN_SWITCH_PREF_KEY = "open_by_default_supported_links";
private static final String OPEN_IN_APP_PREF_KEY = "app_launch_open_in_app";
private static final String OPEN_IN_BROWSER_PREF_KEY = "app_launch_open_in_browser";
private static final String VERIFIED_LINKS_PREF_KEY = "open_by_default_verified_links";
private static final String ADD_LINK_PREF_KEY = "open_by_default_add_link";
private static final String CLEAR_DEFAULTS_PREF_KEY = "app_launch_clear_defaults";
@@ -86,7 +87,10 @@ public class AppLaunchSettings extends AppInfoBase implements
public static final String APP_PACKAGE_KEY = "app_package";
private ClearDefaultsPreference mClearDefaultsPreference;
private MainSwitchPreference mMainSwitchPreference;
@Nullable
private SelectorWithWidgetPreference mOpenInAppSelector;
@Nullable
private SelectorWithWidgetPreference mOpenInBrowserSelector;
private Preference mAddLinkPreference;
private PreferenceCategory mMainPreferenceCategory;
private PreferenceCategory mSelectedLinksPreferenceCategory;
@@ -168,20 +172,20 @@ public class AppLaunchSettings extends AppInfoBase implements
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
IntentPickerUtils.logd("onSwitchChanged: isChecked=" + isChecked);
if (mMainSwitchPreference != null) { //mMainSwitchPreference synced with Switch
mMainSwitchPreference.setChecked(isChecked);
}
public void onRadioButtonClicked(@NonNull SelectorWithWidgetPreference selected) {
final boolean openSupportedLinks = selected.getKey().equals(OPEN_IN_APP_PREF_KEY);
IntentPickerUtils.logd("onRadioButtonClicked: openInApp =" + openSupportedLinks);
setOpenByDefaultPreference(openSupportedLinks /* openInApp */);
if (mMainPreferenceCategory != null) {
mMainPreferenceCategory.setVisible(isChecked);
mMainPreferenceCategory.setVisible(openSupportedLinks);
}
if (mDomainVerificationManager != null) {
try {
mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(mPackageName,
isChecked);
openSupportedLinks);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "onSwitchChanged: " + e.getMessage());
Log.w(TAG, "onRadioButtonClicked: " + e.getMessage());
}
}
}
@@ -224,7 +228,8 @@ public class AppLaunchSettings extends AppInfoBase implements
}
private void initMainSwitchAndCategories() {
mMainSwitchPreference = (MainSwitchPreference) findPreference(MAIN_SWITCH_PREF_KEY);
mOpenInAppSelector = findPreference(OPEN_IN_APP_PREF_KEY);
mOpenInBrowserSelector = findPreference(OPEN_IN_BROWSER_PREF_KEY);
mMainPreferenceCategory = findPreference(MAIN_PREF_CATEGORY_KEY);
mSelectedLinksPreferenceCategory = findPreference(SELECTED_LINKS_CATEGORY_KEY);
// Initialize the "Other Default Category" section
@@ -235,14 +240,15 @@ public class AppLaunchSettings extends AppInfoBase implements
final DomainVerificationUserState userState =
IntentPickerUtils.getDomainVerificationUserState(mDomainVerificationManager,
mPackageName);
if (userState == null) {
if (userState == null || mOpenInAppSelector == null || mOpenInBrowserSelector == null) {
disabledPreference();
return false;
}
IntentPickerUtils.logd("isLinkHandlingAllowed() : " + userState.isLinkHandlingAllowed());
mMainSwitchPreference.updateStatus(userState.isLinkHandlingAllowed());
mMainSwitchPreference.addOnSwitchChangeListener(this);
setOpenByDefaultPreference(userState.isLinkHandlingAllowed());
mOpenInAppSelector.setOnClickListener(this);
mOpenInBrowserSelector.setOnClickListener(this);
mMainPreferenceCategory.setVisible(userState.isLinkHandlingAllowed());
return true;
}
@@ -260,6 +266,12 @@ public class AppLaunchSettings extends AppInfoBase implements
verifiedLinksPreference.setEnabled(verifiedLinksNo > 0);
}
private void setOpenByDefaultPreference(boolean openInApp) {
if (mOpenInBrowserSelector == null || mOpenInAppSelector == null) return;
mOpenInAppSelector.setChecked(openInApp);
mOpenInBrowserSelector.setChecked(!openInApp);
}
private void showVerifiedLinksDialog() {
final int linksNo = getLinksNumber(DOMAIN_STATE_VERIFIED);
if (linksNo == 0) {
@@ -360,9 +372,12 @@ public class AppLaunchSettings extends AppInfoBase implements
}
private void disabledPreference() {
mMainSwitchPreference.updateStatus(false);
mMainSwitchPreference.setSelectable(false);
mMainSwitchPreference.setEnabled(false);
if (mOpenInAppSelector == null ||mOpenInBrowserSelector == null) return;
setOpenByDefaultPreference(false /* openInApp */);
mOpenInAppSelector.setSelectable(false);
mOpenInAppSelector.setEnabled(false);
mOpenInBrowserSelector.setSelectable(false);
mOpenInBrowserSelector.setEnabled(false);
mMainPreferenceCategory.setVisible(false);
}

View File

@@ -72,9 +72,9 @@ import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.widget.LayoutPreference;
import java.util.Collections;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
public class InteractAcrossProfilesDetails extends AppInfoBase
implements Preference.OnPreferenceClickListener {
@@ -571,8 +571,8 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
String disallowedPackagesString = Settings.Global.getString(getContentResolver(),
CONNECTED_APPS_DISALLOWED_PACKAGES);
Set<String> allowedPackagesSet = getSetFromString(allowedPackagesString);
Set<String> disallowedPackagesSet = getSetFromString(disallowedPackagesString);
HashSet<String> allowedPackagesSet = getSetFromString(allowedPackagesString);
HashSet<String> disallowedPackagesSet = getSetFromString(disallowedPackagesString);
if (enabled) {
allowedPackagesSet.add(crossProfilePackage);
@@ -592,9 +592,9 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
String.join(",", disallowedPackagesSet));
}
private Set<String> getSetFromString(String packages) {
private HashSet<String> getSetFromString(String packages) {
return Optional.ofNullable(packages)
.map(pkg -> Set.of(pkg.split(",")))
.orElse(Collections.emptySet());
.map(pkg -> new HashSet<>(Arrays.asList(pkg.split(","))))
.orElseGet(HashSet::new);
}
}

View File

@@ -16,6 +16,7 @@
package com.android.settings.applications.specialaccess.notificationaccess;
import android.app.Dialog;
import android.app.Flags;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.DialogInterface;
@@ -55,7 +56,10 @@ public class FriendlyWarningDialogFragment extends InstrumentedDialogFragment {
NotificationAccessDetails parent = (NotificationAccessDetails) getTargetFragment();
final String summary = getResources().getString(
R.string.notification_listener_disable_warning_summary, label);
Flags.modesApi() && Flags.modesUi()
? R.string.notification_listener_disable_modes_warning_summary
: R.string.notification_listener_disable_warning_summary,
label);
return new AlertDialog.Builder(getContext())
.setMessage(summary)
.setCancelable(true)

View File

@@ -16,6 +16,7 @@
package com.android.settings.applications.specialaccess.notificationaccess;
import android.app.Dialog;
import android.app.Flags;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
@@ -96,6 +97,11 @@ public class ScaryWarningDialogFragment extends InstrumentedDialogFragment {
R.string.nls_warning_prompt, label);
((TextView) content.findViewById(R.id.prompt)).setText(prompt);
((TextView) content.findViewById(R.id.settings_description)).setText(
Flags.modesApi() && Flags.modesUi()
? R.string.nls_feature_modes_settings_summary
: R.string.nls_feature_settings_summary);
Button allowButton = content.findViewById(R.id.allow_button);
allowButton.setOnClickListener((view) -> {
parent.enable(cn);

View File

@@ -17,6 +17,7 @@
package com.android.settings.applications.specialaccess.zenaccess;
import android.app.Dialog;
import android.app.Flags;
import android.app.settings.SettingsEnums;
import android.os.Bundle;
import android.text.TextUtils;
@@ -58,9 +59,14 @@ public class FriendlyWarningDialogFragment extends InstrumentedDialogFragment {
final String label = args.getString(KEY_LABEL);
final String title = getResources().getString(
R.string.zen_access_revoke_warning_dialog_title, label);
Flags.modesApi() && Flags.modesUi()
? R.string.zen_modes_access_revoke_warning_dialog_title
: R.string.zen_access_revoke_warning_dialog_title,
label);
final String summary = getResources()
.getString(R.string.zen_access_revoke_warning_dialog_summary);
.getString(Flags.modesApi() && Flags.modesUi()
? R.string.zen_modes_access_revoke_warning_dialog_summary
: R.string.zen_access_revoke_warning_dialog_summary);
ZenAccessDetails parent = (ZenAccessDetails) getTargetFragment();
return new AlertDialog.Builder(getContext())

View File

@@ -17,6 +17,7 @@
package com.android.settings.applications.specialaccess.zenaccess;
import android.app.Dialog;
import android.app.Flags;
import android.app.settings.SettingsEnums;
import android.os.Bundle;
import android.text.TextUtils;
@@ -55,10 +56,15 @@ public class ScaryWarningDialogFragment extends InstrumentedDialogFragment {
final String pkg = args.getString(KEY_PKG);
final String label = args.getString(KEY_LABEL);
final String title = getResources().getString(R.string.zen_access_warning_dialog_title,
final String title = getResources().getString(
Flags.modesApi() && Flags.modesUi()
? R.string.zen_modes_access_warning_dialog_title
: R.string.zen_access_warning_dialog_title,
label);
final String summary = getResources()
.getString(R.string.zen_access_warning_dialog_summary);
.getString(Flags.modesApi() && Flags.modesUi()
? R.string.zen_modes_access_warning_dialog_summary
: R.string.zen_access_warning_dialog_summary);
ZenAccessDetails parent = (ZenAccessDetails) getTargetFragment();

View File

@@ -18,6 +18,7 @@ package com.android.settings.applications.specialaccess.zenaccess;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.Flags;
import android.app.NotificationManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
@@ -28,7 +29,10 @@ import android.util.ArraySet;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.overlay.FeatureFactory;
@@ -48,6 +52,16 @@ public class ZenAccessController extends BasePreferenceController {
return AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
Preference preference = screen.findPreference(getPreferenceKey());
if (preference != null) {
preference.setTitle(Flags.modesApi() && Flags.modesUi()
? R.string.manage_zen_modes_access_title
: R.string.manage_zen_access_title);
}
}
public static Set<String> getPackagesRequestingNotificationPolicyAccess() {
final String[] PERM = {
android.Manifest.permission.ACCESS_NOTIFICATION_POLICY

View File

@@ -16,9 +16,11 @@
package com.android.settings.applications.specialaccess.zenaccess;
import android.app.Flags;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.os.UserManager;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.TwoStatePreference;
@@ -42,6 +44,9 @@ public class ZenAccessDetails extends AppInfoWithHeader implements
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.zen_access_permission_details);
requireActivity().setTitle(Flags.modesApi() && Flags.modesUi()
? R.string.manage_zen_modes_access_title
: R.string.manage_zen_access_title);
getSettingsLifecycle().addObserver(
new ZenAccessSettingObserverMixin(getContext(), this /* listener */));
}
@@ -49,6 +54,11 @@ public class ZenAccessDetails extends AppInfoWithHeader implements
@Override
protected boolean refreshUi() {
final Context context = getContext();
// don't show for managed profiles
if (UserManager.get(context).isManagedProfile(context.getUserId())
&& !ZenAccessController.hasAccess(context, mPackageName)) {
finish();
}
// If this app didn't declare this permission in their manifest, don't bother showing UI.
final Set<String> needAccessApps =
ZenAccessController.getPackagesRequestingNotificationPolicyAccess();
@@ -74,6 +84,9 @@ public class ZenAccessDetails extends AppInfoWithHeader implements
preference.setSummary(getString(R.string.zen_access_disabled_package_warning));
return;
}
preference.setTitle(Flags.modesApi() && Flags.modesUi()
? R.string.zen_modes_access_detail_switch
: R.string.zen_access_detail_switch);
preference.setChecked(ZenAccessController.hasAccess(context, mPackageName));
preference.setOnPreferenceChangeListener((p, newValue) -> {
final boolean access = (Boolean) newValue;

View File

@@ -16,37 +16,54 @@
package com.android.settings.backup;
import android.app.backup.BackupAgentHelper;
import android.util.Log;
import com.android.settings.flags.Flags;
import com.android.settings.onboarding.OnboardingFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.shortcut.CreateShortcutPreferenceController;
import com.android.settings.shortcut.ShortcutsUpdater;
import com.android.settingslib.datastore.BackupRestoreStorageManager;
/** Backup agent for Settings APK */
public class SettingsBackupHelper extends BackupAgentHelper {
private static final String TAG = "SettingsBackupHelper";
public static final String SOUND_BACKUP_HELPER = "SoundSettingsBackup";
public static final String ACCESSIBILITY_APPEARANCE_BACKUP_HELPER =
"AccessibilityAppearanceSettingsBackup";
@Override
public void onCreate() {
super.onCreate();
BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this);
OnboardingFeatureProvider onboardingFeatureProvider =
FeatureFactory.getFeatureFactory().getOnboardingFeatureProvider();
if (Flags.enableSoundBackup()) {
OnboardingFeatureProvider onboardingFeatureProvider =
FeatureFactory.getFeatureFactory().getOnboardingFeatureProvider();
if (onboardingFeatureProvider != null) {
addHelper(SOUND_BACKUP_HELPER, onboardingFeatureProvider.
getSoundBackupHelper(this, this.getBackupRestoreEventLogger()));
}
}
if (Flags.accessibilityAppearanceSettingsBackupEnabled()) {
if (onboardingFeatureProvider != null) {
addHelper(ACCESSIBILITY_APPEARANCE_BACKUP_HELPER,
onboardingFeatureProvider.getAccessibilityAppearanceBackupHelper(
this, this.getBackupRestoreEventLogger()));
}
}
}
@Override
public void onRestoreFinished() {
super.onRestoreFinished();
BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
CreateShortcutPreferenceController.updateRestoredShortcuts(this);
try {
ShortcutsUpdater.updatePinnedShortcuts(this);
} catch (Exception e) {
Log.e(TAG, "Error updating shortcuts after restoring backup", e);
}
}
}

View File

@@ -19,6 +19,7 @@ package com.android.settings.biometrics;
import static android.provider.Settings.ACTION_BIOMETRIC_ENROLL;
import static android.provider.Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED;
import static com.android.settings.biometrics.BiometricEnrollBase.BIOMETRIC_AUTH_REQUEST;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_DENIED;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_GRANTED;
@@ -51,11 +52,13 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.Utils;
import com.android.settings.core.InstrumentedActivity;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockPattern;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.google.android.setupcompat.util.WizardManagerHelper;
import com.google.android.setupdesign.transition.TransitionHelper;
@@ -442,6 +445,18 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
if (!mParentalConsentHelper.launchNext(this, REQUEST_CHOOSE_OPTIONS)) {
Log.e(TAG, "Nothing to prompt for consent (no modalities enabled)!");
finish();
} else {
final Utils.BiometricStatus biometricStatus =
Utils.requestBiometricAuthenticationForMandatoryBiometrics(this,
false /* biometricsAuthenticationRequested */, mUserId);
if (biometricStatus == Utils.BiometricStatus.OK) {
Utils.launchBiometricPromptForMandatoryBiometrics(this,
BIOMETRIC_AUTH_REQUEST, mUserId, true /* hideBackground */);
} else if (biometricStatus != Utils.BiometricStatus.NOT_ACTIVE) {
IdentityCheckBiometricErrorDialog
.showBiometricErrorDialogAndFinishActivityOnDismiss(this,
biometricStatus);
}
}
} else {
Log.d(TAG, "Unknown result for set/choose lock: " + resultCode);
@@ -473,6 +488,14 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
finish();
}
break;
case BIOMETRIC_AUTH_REQUEST:
if (resultCode == ConfirmDeviceCredentialActivity.BIOMETRIC_LOCKOUT_ERROR_RESULT) {
IdentityCheckBiometricErrorDialog
.showBiometricErrorDialogAndFinishActivityOnDismiss(this,
Utils.BiometricStatus.LOCKOUT);
} else if (resultCode != RESULT_OK) {
finish();
}
default:
Log.w(TAG, "Unknown consenting requestCode: " + requestCode + ", finishing");
finish();

View File

@@ -68,6 +68,8 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
public static final String EXTRA_FINISHED_ENROLL_FACE = "finished_enrolling_face";
public static final String EXTRA_FINISHED_ENROLL_FINGERPRINT = "finished_enrolling_fingerprint";
public static final String EXTRA_LAUNCHED_POSTURE_GUIDANCE = "launched_posture_guidance";
public static final String EXTRA_BIOMETRICS_AUTHENTICATED_SUCCESSFULLY =
"biometrics_authenticated_successfully";
/**
* Used by the choose fingerprint wizard to indicate the wizard is
@@ -122,6 +124,7 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
*/
public static final int ENROLL_NEXT_BIOMETRIC_REQUEST = 6;
public static final int REQUEST_POSTURE_GUIDANCE = 7;
public static final int BIOMETRIC_AUTH_REQUEST = 8;
protected boolean mLaunchedConfirmLock;
protected boolean mLaunchedPostureGuidance;

View File

@@ -36,8 +36,10 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.Utils;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settings.password.SetupSkipDialog;
import com.google.android.setupcompat.template.FooterBarMixin;
@@ -417,6 +419,17 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
getNextButton().setEnabled(true);
}));
}
final Utils.BiometricStatus biometricStatus =
Utils.requestBiometricAuthenticationForMandatoryBiometrics(this,
false /* biometricsAuthenticationRequested */, mUserId);
if (biometricStatus == Utils.BiometricStatus.OK) {
Utils.launchBiometricPromptForMandatoryBiometrics(this,
BIOMETRIC_AUTH_REQUEST, mUserId, true /* hideBackground */);
} else if (biometricStatus != Utils.BiometricStatus.NOT_ACTIVE) {
IdentityCheckBiometricErrorDialog
.showBiometricErrorDialogAndFinishActivityOnDismiss(this,
biometricStatus);
}
} else {
setResult(resultCode, data);
finish();
@@ -445,6 +458,16 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
setResult(resultCode, data);
finish();
}
} else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
if (resultCode != RESULT_OK) {
if (resultCode == ConfirmDeviceCredentialActivity.BIOMETRIC_LOCKOUT_ERROR_RESULT) {
IdentityCheckBiometricErrorDialog
.showBiometricErrorDialogAndFinishActivityOnDismiss(this,
Utils.BiometricStatus.LOCKOUT);
} else {
finish();
}
}
}
super.onActivityResult(requestCode, resultCode, data);
}

View File

@@ -16,7 +16,6 @@
package com.android.settings.biometrics;
import static android.util.FeatureFlagUtils.SETTINGS_BIOMETRICS2_ENROLLMENT;
import android.annotation.IntDef;
import android.app.Activity;
@@ -33,7 +32,6 @@ import android.os.Bundle;
import android.os.storage.StorageManager;
import android.text.BidiFormatter;
import android.text.SpannableStringBuilder;
import android.util.FeatureFlagUtils;
import android.util.Log;
import android.view.Surface;
@@ -46,11 +44,9 @@ import com.android.internal.widget.VerifyCredentialResponse;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.biometrics.face.FaceEnrollIntroduction;
import com.android.settings.biometrics.fingerprint.FingerprintEnroll;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollFindSensor;
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction;
import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockSettingsHelper;
@@ -254,17 +250,8 @@ public class BiometricUtils {
public static Intent getFingerprintFindSensorIntent(@NonNull Context context,
@NonNull Intent activityIntent) {
final boolean isSuw = WizardManagerHelper.isAnySetupWizard(activityIntent);
final Intent intent;
if (FeatureFlagUtils.isEnabled(context, SETTINGS_BIOMETRICS2_ENROLLMENT)) {
intent = new Intent(context, isSuw
? FingerprintEnrollmentActivity.SetupActivity.class
: FingerprintEnrollmentActivity.class);
intent.putExtra(BiometricEnrollActivity.EXTRA_SKIP_INTRO, true);
} else {
intent = new Intent(context, isSuw
? SetupFingerprintEnrollFindSensor.class
: FingerprintEnrollFindSensor.class);
}
final Intent intent = new Intent(context, isSuw
? SetupFingerprintEnrollFindSensor.class : FingerprintEnrollFindSensor.class);
if (isSuw) {
SetupWizardUtils.copySetupExtras(activityIntent, intent);
}
@@ -274,21 +261,13 @@ public class BiometricUtils {
/**
* @param context caller's context
* @param activityIntent The intent that started the caller's activity
* @return Intent for starting FingerprintEnrollIntroduction
* @return Intent for starting FingerprintEnroll
*/
public static Intent getFingerprintIntroIntent(@NonNull Context context,
@NonNull Intent activityIntent) {
final boolean isSuw = WizardManagerHelper.isAnySetupWizard(activityIntent);
final Intent intent;
if (FeatureFlagUtils.isEnabled(context, SETTINGS_BIOMETRICS2_ENROLLMENT)) {
intent = new Intent(context, isSuw
? FingerprintEnrollmentActivity.SetupActivity.class
: FingerprintEnrollmentActivity.class);
} else {
intent = new Intent(context, isSuw
? SetupFingerprintEnrollIntroduction.class
: FingerprintEnrollIntroduction.class);
}
final Intent intent = new Intent(context, isSuw
? FingerprintEnroll.SetupActivity.class : FingerprintEnroll.class);
if (isSuw) {
WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
}

View File

@@ -0,0 +1,257 @@
/*
* Copyright (C) 2024 The Android Open 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.biometrics;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.provider.Settings;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.fragment.app.FragmentActivity;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
/** Initializes and shows biometric error dialogs related to identity check. */
public class IdentityCheckBiometricErrorDialog extends InstrumentedDialogFragment {
private static final String TAG = "BiometricErrorDialog";
private static final String KEY_ERROR_CODE = "key_error_code";
private static final String KEY_TWO_FACTOR_AUTHENTICATION = "key_two_factor_authentication";
private static final String KEY_FINISH_ACTIVITY = "key_finish_activity";
private String mActionIdentityCheckSettings = Settings.ACTION_SETTINGS;
private String mIdentityCheckSettingsPackageName;
@Nullable private BroadcastReceiver mBroadcastReceiver;
private boolean mShouldFinishActivity = false;
@NonNull
@Override
public Dialog onCreateDialog(
@Nullable Bundle savedInstanceState) {
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
final LayoutInflater inflater = getActivity().getLayoutInflater();
final boolean isLockoutError = getArguments().getString(KEY_ERROR_CODE).equals(
Utils.BiometricStatus.LOCKOUT.name());
final View customView = inflater.inflate(R.layout.biometric_lockout_error_dialog,
null);
final boolean twoFactorAuthentication = getArguments().getBoolean(
KEY_TWO_FACTOR_AUTHENTICATION);
final String identityCheckSettingsAction = getActivity().getString(
com.android.internal.R.string.identity_check_settings_action);
mActionIdentityCheckSettings = identityCheckSettingsAction.isEmpty()
? mActionIdentityCheckSettings : identityCheckSettingsAction;
mIdentityCheckSettingsPackageName = getActivity().getString(
com.android.internal.R.string.identity_check_settings_package_name);
mShouldFinishActivity = getArguments().getBoolean(
KEY_FINISH_ACTIVITY);
setTitle(customView, isLockoutError);
setBody(customView, isLockoutError, twoFactorAuthentication);
alertDialogBuilder.setView(customView);
setPositiveButton(alertDialogBuilder, isLockoutError, twoFactorAuthentication);
if (!isLockoutError || !twoFactorAuthentication) {
setNegativeButton(alertDialogBuilder, isLockoutError);
}
if (isLockoutError) {
mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
dismiss();
}
}
};
getContext().registerReceiver(mBroadcastReceiver,
new IntentFilter(Intent.ACTION_SCREEN_OFF));
}
return alertDialogBuilder.create();
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (mBroadcastReceiver != null) {
getContext().unregisterReceiver(mBroadcastReceiver);
mBroadcastReceiver = null;
}
if (mShouldFinishActivity && getActivity() != null) {
getActivity().finish();
}
}
/**
* Shows an error dialog to prompt the user to resolve biometric errors for identity check.
* @param fragmentActivity calling activity
* @param errorCode refers to the biometric error
* @param twoFactorAuthentication if the surface requests LSKF before identity check auth
*/
public static void showBiometricErrorDialog(FragmentActivity fragmentActivity,
Utils.BiometricStatus errorCode, boolean twoFactorAuthentication) {
showBiometricErrorDialog(fragmentActivity, errorCode, twoFactorAuthentication,
false /* finishActivityOnDismiss */);
}
/**
* Shows an error dialog to prompt the user to resolve biometric errors for identity check.
* Finishes the activity once the dialog is dismissed.
* @param fragmentActivity calling activity
* @param errorCode refers to the biometric error
*/
public static void showBiometricErrorDialogAndFinishActivityOnDismiss(
FragmentActivity fragmentActivity, Utils.BiometricStatus errorCode) {
showBiometricErrorDialog(fragmentActivity, errorCode, true /* twoFactorAuthentication */,
true /* finishActivityOnDismiss */);
}
private static void showBiometricErrorDialog(FragmentActivity fragmentActivity,
Utils.BiometricStatus errorCode, boolean twoFactorAuthentication,
boolean finishActivityOnDismiss) {
final IdentityCheckBiometricErrorDialog identityCheckBiometricErrorDialog =
new IdentityCheckBiometricErrorDialog();
final Bundle args = new Bundle();
args.putCharSequence(KEY_ERROR_CODE, errorCode.name());
args.putBoolean(KEY_TWO_FACTOR_AUTHENTICATION, twoFactorAuthentication);
args.putBoolean(KEY_FINISH_ACTIVITY, finishActivityOnDismiss);
identityCheckBiometricErrorDialog.setArguments(args);
identityCheckBiometricErrorDialog.show(fragmentActivity.getSupportFragmentManager(),
IdentityCheckBiometricErrorDialog.class.getName());
}
private void setTitle(View view, boolean lockout) {
final TextView titleTextView = view.findViewById(R.id.title);
if (lockout) {
titleTextView.setText(R.string.identity_check_lockout_error_title);
} else {
titleTextView.setText(R.string.identity_check_general_error_title);
}
}
private void setBody(View view, boolean lockout, boolean twoFactorAuthentication) {
final TextView textView1 = view.findViewById(R.id.description_1);
final TextView textView2 = view.findViewById(R.id.description_2);
if (lockout) {
if (twoFactorAuthentication) {
textView1.setText(
R.string.identity_check_lockout_error_two_factor_auth_description_1);
} else {
textView1.setText(R.string.identity_check_lockout_error_description_1);
}
textView2.setText(getClickableDescriptionForLockoutError());
textView2.setMovementMethod(LinkMovementMethod.getInstance());
} else {
textView1.setText(R.string.identity_check_general_error_description_1);
textView2.setVisibility(View.GONE);
}
}
private SpannableString getClickableDescriptionForLockoutError() {
final String description = getResources().getString(
R.string.identity_check_lockout_error_description_2);
final SpannableString spannableString = new SpannableString(description);
final ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(View textView) {
dismiss();
final Intent autoLockSettingsIntent = new Intent(mActionIdentityCheckSettings);
final ResolveInfo autoLockSettingsInfo = getActivity().getPackageManager()
.resolveActivity(autoLockSettingsIntent, 0 /* flags */);
if (autoLockSettingsInfo != null) {
startActivity(autoLockSettingsIntent);
} else {
Log.e(TAG, "Auto lock settings intent could not be resolved.");
}
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(true);
}
};
final String goToSettings = getActivity().getString(R.string.go_to_settings);
spannableString.setSpan(clickableSpan, description.indexOf(goToSettings),
description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannableString;
}
private void setPositiveButton(AlertDialog.Builder alertDialogBuilder, boolean lockout,
boolean twoFactorAuthentication) {
if (lockout) {
if (twoFactorAuthentication) {
alertDialogBuilder.setPositiveButton((R.string.okay),
(dialog, which) -> dialog.dismiss());
} else {
DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
alertDialogBuilder.setPositiveButton(
R.string.identity_check_lockout_error_lock_screen,
(dialog, which) -> {
dialog.dismiss();
devicePolicyManager.lockNow();
});
}
} else {
alertDialogBuilder.setPositiveButton(R.string.identity_check_biometric_error_ok,
(dialog, which) -> dialog.dismiss());
}
}
private void setNegativeButton(AlertDialog.Builder alertDialogBuilder, boolean lockout) {
if (lockout) {
alertDialogBuilder.setNegativeButton(R.string.identity_check_biometric_error_cancel,
(dialog, which) -> dialog.dismiss());
} else {
alertDialogBuilder.setNegativeButton(R.string.go_to_identity_check,
(dialog, which) -> {
final Intent autoLockSettingsIntent = new Intent(
mActionIdentityCheckSettings);
final ResolveInfo autoLockSettingsInfo = getActivity().getPackageManager()
.resolveActivity(autoLockSettingsIntent, 0 /* flags */);
if (autoLockSettingsInfo != null) {
startActivity(autoLockSettingsIntent);
} else {
Log.e(TAG, "Identity check settings intent could not be resolved.");
}
});
}
}
@Override
public int getMetricsCategory() {
return 0;
}
}

View File

@@ -46,10 +46,12 @@ import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.BiometricStatusPreferenceController;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.biometrics.IdentityCheckBiometricErrorDialog;
import com.android.settings.core.SettingsBaseActivity;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.transition.SettingsTransitionHelper;
@@ -65,6 +67,8 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
static final int CONFIRM_REQUEST = 2001;
private static final int CHOOSE_LOCK_REQUEST = 2002;
protected static final int ACTIVE_UNLOCK_REQUEST = 2003;
@VisibleForTesting
static final int BIOMETRIC_AUTH_REQUEST = 2004;
private static final String SAVE_STATE_CONFIRM_CREDETIAL = "confirm_credential";
private static final String DO_NOT_FINISH_ACTIVITY = "do_not_finish_activity";
@@ -72,10 +76,12 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
static final String RETRY_PREFERENCE_KEY = "retry_preference_key";
@VisibleForTesting
static final String RETRY_PREFERENCE_BUNDLE = "retry_preference_bundle";
private static final String BIOMETRICS_AUTH_REQUESTED = "biometrics_auth_requested";
protected int mUserId;
protected long mGkPwHandle;
private boolean mConfirmCredential;
private boolean mBiometricsAuthenticationRequested;
@Nullable private FaceManager mFaceManager;
@Nullable private FingerprintManager mFingerprintManager;
// Do not finish() if choosing/confirming credential, showing fp/face settings, or launching
@@ -123,13 +129,14 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
mGkPwHandle = savedInstanceState.getLong(
ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE);
}
mBiometricsAuthenticationRequested = savedInstanceState.getBoolean(
BIOMETRICS_AUTH_REQUESTED);
}
if (mGkPwHandle == 0L && !mConfirmCredential) {
mConfirmCredential = true;
launchChooseOrConfirmLock();
}
updateUnlockPhonePreferenceSummary();
final Preference useInAppsPreference = findPreference(getUseInAppsPreferenceKey());
@@ -288,6 +295,8 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
outState.putString(RETRY_PREFERENCE_KEY, mRetryPreferenceKey);
outState.putBundle(RETRY_PREFERENCE_BUNDLE, mRetryPreferenceExtra);
}
outState.putBoolean(BIOMETRICS_AUTH_REQUESTED,
mBiometricsAuthenticationRequested);
}
@Override
@@ -305,6 +314,22 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
com.google.android.setupdesign.R.anim.sud_slide_next_out);
retryPreferenceKey(mRetryPreferenceKey, mRetryPreferenceExtra);
}
final Utils.BiometricStatus biometricAuthStatus =
Utils.requestBiometricAuthenticationForMandatoryBiometrics(
getActivity(),
mBiometricsAuthenticationRequested,
mUserId);
if (biometricAuthStatus == Utils.BiometricStatus.OK) {
mBiometricsAuthenticationRequested = true;
Utils.launchBiometricPromptForMandatoryBiometrics(this,
BIOMETRIC_AUTH_REQUEST,
mUserId, true /* hideBackground */);
} else if (biometricAuthStatus != Utils.BiometricStatus.NOT_ACTIVE) {
IdentityCheckBiometricErrorDialog
.showBiometricErrorDialogAndFinishActivityOnDismiss(getActivity(),
biometricAuthStatus);
return;
}
} else {
Log.d(getLogTag(), "Data null or GK PW missing.");
finish();
@@ -315,6 +340,17 @@ public abstract class BiometricsSettingsBase extends DashboardFragment {
}
mRetryPreferenceKey = null;
mRetryPreferenceExtra = null;
} else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
mBiometricsAuthenticationRequested = false;
if (resultCode != RESULT_OK) {
if (resultCode == ConfirmDeviceCredentialActivity.BIOMETRIC_LOCKOUT_ERROR_RESULT) {
IdentityCheckBiometricErrorDialog
.showBiometricErrorDialogAndFinishActivityOnDismiss(getActivity(),
Utils.BiometricStatus.LOCKOUT);
} else {
finish();
}
}
}
}

View File

@@ -24,14 +24,19 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.activeunlock.ActiveUnlockContentListener.OnContentChangedListener;
import com.android.settings.biometrics.activeunlock.ActiveUnlockDeviceNameListener;
import com.android.settings.biometrics.activeunlock.ActiveUnlockRequireBiometricSetup;
import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils;
import com.android.settings.flags.Flags;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
@@ -52,6 +57,10 @@ public class CombinedBiometricSettings extends BiometricsSettingsBase {
private CombinedBiometricStatusUtils mCombinedBiometricStatusUtils;
@Nullable private ActiveUnlockDeviceNameListener mActiveUnlockDeviceNameListener;
private final ActivityResultLauncher<Intent> mActiveUnlockPreferenceLauncher =
registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
this::onActiveUnlockPreferenceResult);
@Override
public void onAttach(Context context) {
super.onAttach(context);
@@ -158,12 +167,25 @@ public class CombinedBiometricSettings extends BiometricsSettingsBase {
intent = mActiveUnlockStatusUtils.getIntent();
}
if (intent != null) {
startActivityForResult(intent, ACTIVE_UNLOCK_REQUEST);
if (Flags.activeUnlockFinishParent()) {
mActiveUnlockPreferenceLauncher.launch(intent);
} else {
startActivityForResult(intent, ACTIVE_UNLOCK_REQUEST);
}
}
return true;
}
private void onActiveUnlockPreferenceResult(@Nullable ActivityResult result) {
if (result != null && result.getResultCode() == BiometricEnrollBase.RESULT_TIMEOUT) {
mDoNotFinishActivity = false;
// When "Watch Unlock" is closed due to entering onStop(),
// "Face & Fingerprint Unlock" shall also close itself and back to "Security" page.
finish();
}
}
@Override
protected String getUseAnyBiometricSummary() {
// either Active Unlock is not enabled or no device is enrolled.

View File

@@ -20,6 +20,7 @@ import static android.app.Activity.RESULT_OK;
import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_SETTINGS_FOR_WORK_TITLE;
import static com.android.settings.Utils.isPrivateProfile;
import static com.android.settings.biometrics.BiometricEnrollBase.BIOMETRIC_AUTH_REQUEST;
import static com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST;
import static com.android.settings.biometrics.BiometricEnrollBase.ENROLL_REQUEST;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED;
@@ -44,9 +45,11 @@ import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.biometrics.IdentityCheckBiometricErrorDialog;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -66,6 +69,8 @@ public class FaceSettings extends DashboardFragment {
private static final String TAG = "FaceSettings";
private static final String KEY_TOKEN = "hw_auth_token";
private static final String KEY_RE_ENROLL_FACE = "re_enroll_face_unlock";
private static final String KEY_BIOMETRICS_SUCCESSFULLY_AUTHENTICATED =
"biometrics_successfully_authenticated";
private static final String PREF_KEY_DELETE_FACE_DATA =
"security_settings_face_delete_faces_container";
@@ -93,6 +98,7 @@ public class FaceSettings extends DashboardFragment {
private FaceFeatureProvider mFaceFeatureProvider;
private boolean mConfirmingPassword;
private boolean mBiometricsAuthenticationRequested;
private final FaceSettingsRemoveButtonPreferenceController.Listener mRemovalListener = () -> {
@@ -312,12 +318,36 @@ public class FaceSettings extends DashboardFragment {
final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId);
mEnrollButton.setVisible(!hasEnrolled);
mRemoveButton.setVisible(hasEnrolled);
final Utils.BiometricStatus biometricAuthStatus =
Utils.requestBiometricAuthenticationForMandatoryBiometrics(getActivity(),
mBiometricsAuthenticationRequested,
mUserId);
if (biometricAuthStatus == Utils.BiometricStatus.OK) {
Utils.launchBiometricPromptForMandatoryBiometrics(this,
BIOMETRIC_AUTH_REQUEST,
mUserId, true /* hideBackground */);
} else if (biometricAuthStatus != Utils.BiometricStatus.NOT_ACTIVE) {
IdentityCheckBiometricErrorDialog
.showBiometricErrorDialogAndFinishActivityOnDismiss(getActivity(),
biometricAuthStatus);
}
}
} else if (requestCode == ENROLL_REQUEST) {
if (resultCode == RESULT_TIMEOUT) {
setResult(resultCode, data);
finish();
}
} else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
mBiometricsAuthenticationRequested = false;
if (resultCode != RESULT_OK) {
if (resultCode == ConfirmDeviceCredentialActivity.BIOMETRIC_LOCKOUT_ERROR_RESULT) {
IdentityCheckBiometricErrorDialog
.showBiometricErrorDialogAndFinishActivityOnDismiss(getActivity(),
Utils.BiometricStatus.LOCKOUT);
} else {
finish();
}
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2024 The Android Open 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.biometrics.fingerprint
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
/**
* Default class for handling fingerprint enrollment, designed to launch a subsequent activity and
* forward the result, then finish itself.
*/
open class FingerprintEnroll: AppCompatActivity() {
/** Inner class representing enrolling fingerprint enrollment in SetupWizard environment */
class SetupActivity : FingerprintEnroll() {
override val nextActivityClass: Class<*>
get() = enrollActivityProvider.setup
}
/** Inner class representing enrolling fingerprint enrollment from FingerprintSettings */
class InternalActivity : FingerprintEnroll() {
override val nextActivityClass: Class<*>
get() = enrollActivityProvider.internal
}
/**
* The class of the next activity to launch. This is open to allow subclasses to provide their
* own behavior. Defaults to the default activity class provided by the
* enrollActivityClassProvider.
*/
open val nextActivityClass: Class<*>
get() = enrollActivityProvider.default
protected val enrollActivityProvider: FingerprintEnrollActivityClassProvider
get() = featureFactory.fingerprintFeatureProvider.enrollActivityClassProvider
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/**
* Logs the next activity to be launched, creates an intent for that activity,
* adds flags to forward the result, includes any existing extras from the current intent,
* starts the new activity and then finishes the current one
*/
Log.d("FingerprintEnroll", "forward to $nextActivityClass")
val nextIntent = Intent(this, nextActivityClass)
nextIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
nextIntent.putExtras(intent)
startActivity(nextIntent)
finish()
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2024 The Android Open 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.biometrics.fingerprint
import android.app.Activity
open class FingerprintEnrollActivityClassProvider {
open val default: Class<out Activity>
get() = FingerprintEnrollIntroduction::class.java
open val setup: Class<out Activity>
get() = SetupFingerprintEnrollIntroduction::class.java
open val internal: Class<out Activity>
get() = FingerprintEnrollIntroductionInternal::class.java
companion object {
@JvmStatic
val instance = FingerprintEnrollActivityClassProvider()
}
}

View File

@@ -74,6 +74,7 @@ import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeature
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.display.DisplayDensityUtils;
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
import com.airbnb.lottie.LottieAnimationView;
import com.airbnb.lottie.LottieComposition;
@@ -89,7 +90,6 @@ import com.google.android.setupdesign.template.HeaderMixin;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.function.Function;
/**
* Activity which handles the actual enrolling for fingerprint.
@@ -197,8 +197,10 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
private OrientationEventListener mOrientationEventListener;
private int mPreviousRotation = 0;
@NonNull
private SfpsEnrollmentFeature mSfpsEnrollmentFeature = new EmptySfpsEnrollmentFeature();
private boolean mIsFolded = false;
private SfpsEnrollmentFeature mSfpsEnrollmentFeature;
@Nullable
private UdfpsEnrollCalibrator mCalibrator;
@@ -386,6 +388,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
@Override
protected void onStart() {
super.onStart();
setupScreenFoldCallbackWhenNecessary();
updateProgress(false /* animate */);
updateTitleAndDescription(true);
if (mRestoring) {
@@ -393,6 +396,19 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
}
}
private void setupScreenFoldCallbackWhenNecessary() {
if (mCanAssumeSfps) {
// These two fields will be cleaned up in BiometricEnrollBase#onStop.
mScreenSizeFoldProvider = new ScreenSizeFoldProvider(getApplicationContext());
mFoldCallback = isFolded -> {
mIsFolded = isFolded;
maybeHideSfpsText(getResources().getConfiguration());
};
// The callback will be unregistered in BiometricEnrollBase#onStop.
mScreenSizeFoldProvider.registerCallback(mFoldCallback, getMainExecutor());
}
}
@Override
public void onEnterAnimationComplete() {
super.onEnterAnimationComplete();
@@ -1156,13 +1172,12 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
private void maybeHideSfpsText(@NonNull Configuration newConfig) {
final HeaderMixin headerMixin = getLayout().getMixin(HeaderMixin.class);
final DescriptionMixin descriptionMixin = getLayout().getMixin(DescriptionMixin.class);
final boolean isLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
if (mCanAssumeSfps) {
// hide the description
descriptionMixin.getTextView().setVisibility(View.GONE);
headerMixin.getTextView().setHyphenationFrequency(HYPHENATION_FREQUENCY_NONE);
if (isLandscape) {
if (mSfpsEnrollmentFeature.shouldAdjustHeaderText(newConfig, mIsFolded)) {
headerMixin.setAutoTextSizeEnabled(true);
headerMixin.getTextView().setMinLines(0);
headerMixin.getTextView().setMaxLines(10);
@@ -1209,32 +1224,4 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
}
}
private static class EmptySfpsEnrollmentFeature implements SfpsEnrollmentFeature {
private final String exceptionStr = "Assume sfps but no SfpsEnrollmentFeature impl.";
@Override
public int getCurrentSfpsEnrollStage(int progressSteps, Function<Integer, Integer> mapper) {
throw new IllegalStateException(exceptionStr);
}
@Override
public int getFeaturedStageHeaderResource(int stage) {
throw new IllegalStateException(exceptionStr);
}
@Override
public int getSfpsEnrollLottiePerStage(int stage) {
throw new IllegalStateException(exceptionStr);
}
@Override
public float getEnrollStageThreshold(@NonNull Context context, int index) {
throw new IllegalStateException(exceptionStr);
}
@Override
public Animator getHelpAnimator(@NonNull View target) {
throw new IllegalStateException(exceptionStr);
}
}
}

View File

@@ -33,7 +33,6 @@ public interface FingerprintFeatureProvider {
*/
SfpsEnrollmentFeature getSfpsEnrollmentFeature();
/**
* Gets calibrator for udfps pre-enroll
* @param appContext application context
@@ -52,4 +51,13 @@ public interface FingerprintFeatureProvider {
* @return the feature implementation
*/
SfpsRestToUnlockFeature getSfpsRestToUnlockFeature(@NonNull Context context);
/**
* Gets the provider for current fingerprint enrollment activity classes
* @return the provider
*/
@NonNull
default FingerprintEnrollActivityClassProvider getEnrollActivityClassProvider() {
return FingerprintEnrollActivityClassProvider.getInstance();
}
}

View File

@@ -23,6 +23,7 @@ import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.settings.core.InstrumentedFragment;
@@ -51,7 +52,8 @@ public class FingerprintRemoveSidecar extends InstrumentedFragment {
}
}
private FingerprintManager.RemovalCallback
@VisibleForTesting
FingerprintManager.RemovalCallback
mRemoveCallback = new FingerprintManager.RemovalCallback() {
@Override
public void onRemovalSucceeded(Fingerprint fingerprint, int remaining) {

View File

@@ -23,6 +23,7 @@ import static android.app.admin.DevicePolicyResources.UNDEFINED;
import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
import static com.android.settings.Utils.isPrivateProfile;
import static com.android.settings.biometrics.BiometricEnrollBase.BIOMETRIC_AUTH_REQUEST;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE;
@@ -41,10 +42,11 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.text.InputFilter;
import android.text.Spanned;
import android.text.TextUtils;
import android.util.FeatureFlagUtils;
import android.util.Log;
import android.view.View;
import android.widget.ImeAwareEditText;
@@ -69,14 +71,14 @@ import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.biometrics.GatekeeperPasswordProvider;
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
import com.android.settings.biometrics2.ui.view.FingerprintEnrollmentActivity;
import com.android.settings.biometrics.IdentityCheckBiometricErrorDialog;
import com.android.settings.core.SettingsBaseActivity;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.RestrictedLockUtils;
@@ -111,6 +113,9 @@ public class FingerprintSettings extends SubSettings {
private static final int RESULT_FINISHED = BiometricEnrollBase.RESULT_FINISHED;
private static final int RESULT_SKIP = BiometricEnrollBase.RESULT_SKIP;
private static final int RESULT_TIMEOUT = BiometricEnrollBase.RESULT_TIMEOUT;
@VisibleForTesting
static final VibrationEffect SUCCESS_VIBRATION_EFFECT =
VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
@Override
public Intent getIntent() {
@@ -161,11 +166,27 @@ public class FingerprintSettings extends SubSettings {
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.security_settings_fingerprint) {
@Override
protected boolean isPageSearchEnabled(Context context) {
return super.isPageSearchEnabled(context) &&
hasEnrolledFingerprints(context);
}
@Override
public List<AbstractPreferenceController>
createPreferenceControllers(Context context) {
return createThePreferenceControllers(context);
}
private boolean hasEnrolledFingerprints(Context context) {
final FingerprintManager fingerprintManager =
Utils.getFingerprintManagerOrNull(context);
if (fingerprintManager != null) {
return fingerprintManager.hasEnrolledTemplates(UserHandle.myUserId());
}
return false;
}
};
private static List<AbstractPreferenceController> createThePreferenceControllers(Context
@@ -218,12 +239,15 @@ public class FingerprintSettings extends SubSettings {
"security_settings_fingerprint_unlock_category";
private static final String KEY_FINGERPRINT_UNLOCK_FOOTER =
"security_settings_fingerprint_footer";
private static final String KEY_BIOMETRICS_AUTHENTICATION_REQUESTED =
"biometrics_authentication_requested";
private static final int MSG_REFRESH_FINGERPRINT_TEMPLATES = 1000;
private static final int MSG_FINGER_AUTH_SUCCESS = 1001;
private static final int MSG_FINGER_AUTH_FAIL = 1002;
private static final int MSG_FINGER_AUTH_ERROR = 1003;
private static final int MSG_FINGER_AUTH_HELP = 1004;
private static final int MSG_RELOAD_FINGERPRINT_TEMPLATES = 1005;
private static final int CONFIRM_REQUEST = 101;
@VisibleForTesting
@@ -251,6 +275,7 @@ public class FingerprintSettings extends SubSettings {
private boolean mInFingerprintLockout;
private byte[] mToken;
private boolean mLaunchedConfirm;
private boolean mBiometricsAuthenticationRequested;
private boolean mHasFirstEnrolled = true;
private Drawable mHighlightDrawable;
private int mUserId;
@@ -264,6 +289,7 @@ public class FingerprintSettings extends SubSettings {
private FingerprintAuthenticateSidecar mAuthenticateSidecar;
private FingerprintRemoveSidecar mRemovalSidecar;
private HashMap<Integer, String> mFingerprintsRenaming;
private Vibrator mVibrator;
@Nullable
private UdfpsEnrollCalibrator mCalibrator;
@@ -309,6 +335,8 @@ public class FingerprintSettings extends SubSettings {
if (activity != null) {
Toast.makeText(activity, errString, Toast.LENGTH_SHORT);
}
mHandler.obtainMessage(MSG_RELOAD_FINGERPRINT_TEMPLATES)
.sendToTarget();
updateDialog();
}
@@ -327,11 +355,7 @@ public class FingerprintSettings extends SubSettings {
switch (msg.what) {
case MSG_REFRESH_FINGERPRINT_TEMPLATES:
removeFingerprintPreference(msg.arg1);
updateAddPreference();
if (isSfps()) {
updateFingerprintUnlockCategoryVisibility();
}
updatePreferences();
updatePreferencesAfterFingerprintRemoved();
break;
case MSG_FINGER_AUTH_SUCCESS:
highlightFingerprintItem(msg.arg1);
@@ -343,10 +367,13 @@ public class FingerprintSettings extends SubSettings {
case MSG_FINGER_AUTH_ERROR:
handleError(msg.arg1 /* errMsgId */, (CharSequence) msg.obj /* errStr */);
break;
case MSG_RELOAD_FINGERPRINT_TEMPLATES:
updatePreferencesAfterFingerprintRemoved();
break;
case MSG_FINGER_AUTH_HELP: {
// Not used
break;
}
break;
}
}
};
@@ -464,6 +491,8 @@ public class FingerprintSettings extends SubSettings {
mIsEnrolling = savedInstanceState.getBoolean(KEY_IS_ENROLLING, mIsEnrolling);
mHasFirstEnrolled = savedInstanceState.getBoolean(KEY_HAS_FIRST_ENROLLED,
mHasFirstEnrolled);
mBiometricsAuthenticationRequested = savedInstanceState.getBoolean(
KEY_BIOMETRICS_AUTHENTICATION_REQUESTED);
}
// (mLaunchedConfirm or mIsEnrolling) means that we are waiting an activity result.
@@ -477,6 +506,7 @@ public class FingerprintSettings extends SubSettings {
addFirstFingerprint(null);
}
}
mVibrator = getContext().getSystemService(Vibrator.class);
final PreferenceScreen root = getPreferenceScreen();
root.removeAll();
addPreferencesFromResource(getPreferenceScreenResId());
@@ -554,6 +584,7 @@ public class FingerprintSettings extends SubSettings {
protected void removeFingerprintPreference(int fingerprintId) {
String name = genKey(fingerprintId);
Log.e(TAG, "removeFingerprintPreference : " + fingerprintId);
Preference prefToRemove = findPreference(name);
if (prefToRemove != null) {
if (!getPreferenceScreen().removePreference(prefToRemove)) {
@@ -678,6 +709,14 @@ public class FingerprintSettings extends SubSettings {
});
}
private void updatePreferencesAfterFingerprintRemoved() {
updateAddPreference();
if (isSfps()) {
updateFingerprintUnlockCategoryVisibility();
}
updatePreferences();
}
private void updateAddPreference() {
if (getActivity() == null) {
return; // Activity went away
@@ -798,6 +837,8 @@ public class FingerprintSettings extends SubSettings {
outState.putSerializable("mFingerprintsRenaming", mFingerprintsRenaming);
outState.putBoolean(KEY_IS_ENROLLING, mIsEnrolling);
outState.putBoolean(KEY_HAS_FIRST_ENROLLED, mHasFirstEnrolled);
outState.putBoolean(KEY_BIOMETRICS_AUTHENTICATION_REQUESTED,
mBiometricsAuthenticationRequested);
}
@Override
@@ -806,15 +847,8 @@ public class FingerprintSettings extends SubSettings {
if (KEY_FINGERPRINT_ADD.equals(key)) {
mIsEnrolling = true;
Intent intent = new Intent();
if (FeatureFlagUtils.isEnabled(getContext(),
FeatureFlagUtils.SETTINGS_BIOMETRICS2_ENROLLMENT)) {
intent.setClassName(SETTINGS_PACKAGE_NAME,
FingerprintEnrollmentActivity.InternalActivity.class.getName());
intent.putExtra(EnrollmentRequest.EXTRA_SKIP_FIND_SENSOR, true);
} else {
intent.setClassName(SETTINGS_PACKAGE_NAME,
FingerprintEnrollEnrolling.class.getName());
}
intent.setClassName(SETTINGS_PACKAGE_NAME,
FingerprintEnrollEnrolling.class.getName());
intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken);
if (mCalibrator != null) {
@@ -968,6 +1002,21 @@ public class FingerprintSettings extends SubSettings {
updateAddPreference();
});
}
final Utils.BiometricStatus biometricAuthStatus =
Utils.requestBiometricAuthenticationForMandatoryBiometrics(
getActivity(),
mBiometricsAuthenticationRequested,
mUserId);
if (biometricAuthStatus == Utils.BiometricStatus.OK) {
Utils.launchBiometricPromptForMandatoryBiometrics(this,
BIOMETRIC_AUTH_REQUEST,
mUserId, true /* hideBackground */);
} else if (biometricAuthStatus != Utils.BiometricStatus.NOT_ACTIVE) {
IdentityCheckBiometricErrorDialog
.showBiometricErrorDialogAndFinishActivityOnDismiss(
getActivity(),
biometricAuthStatus);
}
} else {
Log.d(TAG, "Data null or GK PW missing");
finish();
@@ -1018,6 +1067,17 @@ public class FingerprintSettings extends SubSettings {
mIsEnrolling = false;
mHasFirstEnrolled = true;
updateAddPreference();
} else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
mBiometricsAuthenticationRequested = false;
if (resultCode != RESULT_OK) {
if (resultCode == ConfirmDeviceCredentialActivity.BIOMETRIC_LOCKOUT_ERROR_RESULT) {
IdentityCheckBiometricErrorDialog
.showBiometricErrorDialogAndFinishActivityOnDismiss(getActivity(),
Utils.BiometricStatus.LOCKOUT);
} else {
finish();
}
}
}
}
@@ -1040,6 +1100,7 @@ public class FingerprintSettings extends SubSettings {
}
private void highlightFingerprintItem(int fpId) {
mVibrator.vibrate(SUCCESS_VIBRATION_EFFECT);
String prefName = genKey(fpId);
FingerprintPreference fpref = (FingerprintPreference) findPreference(prefName);
final Drawable highlight = getHighlightDrawable();
@@ -1092,12 +1153,7 @@ public class FingerprintSettings extends SubSettings {
private void addFirstFingerprint(@Nullable Long gkPwHandle) {
Intent intent = new Intent();
intent.setClassName(SETTINGS_PACKAGE_NAME,
FeatureFlagUtils.isEnabled(getActivity(),
FeatureFlagUtils.SETTINGS_BIOMETRICS2_ENROLLMENT)
? FingerprintEnrollmentActivity.InternalActivity.class.getName()
: FingerprintEnrollIntroductionInternal.class.getName()
);
FingerprintEnroll.InternalActivity.class.getName());
intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, true);
intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE);
@@ -1163,7 +1219,7 @@ public class FingerprintSettings extends SubSettings {
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_FINGERPINT_EDIT;
return SettingsEnums.DIALOG_FINGERPRINT_DELETE;
}
@Override
@@ -1325,7 +1381,7 @@ public class FingerprintSettings extends SubSettings {
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_FINGERPINT_EDIT;
return SettingsEnums.DIALOG_FINGERPRINT_RENAME;
}
}

View File

@@ -93,6 +93,7 @@ public class UdfpsEnrollEnrollingView extends GlifLayout {
} else if (mShouldUseReverseLandscape) {
swapHeaderAndContent();
}
mUdfpsEnrollView.setVisibility(View.VISIBLE);
setOnHoverListener();
}

View File

@@ -18,6 +18,7 @@ package com.android.settings.biometrics.fingerprint.feature;
import android.animation.Animator;
import android.content.Context;
import android.content.res.Configuration;
import android.view.View;
import androidx.annotation.NonNull;
@@ -114,4 +115,13 @@ public interface SfpsEnrollmentFeature {
* @param remaining remaining
*/
default void handleOnEnrollmentProgressChange(int steps, int remaining) {}
/**
* Indicates if the properties of header text view like auto text size or min / max lines
* should be adjusted.
* @param conf the current configuration
* @param isFolded is the device folded
* @return true if should adjust auto size and max lines of header; otherwise false
*/
boolean shouldAdjustHeaderText(@NonNull Configuration conf, boolean isFolded);
}

View File

@@ -26,6 +26,7 @@ import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrol
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Configuration;
import android.hardware.fingerprint.FingerprintManager;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
@@ -107,4 +108,9 @@ public class SfpsEnrollmentFeatureImpl implements SfpsEnrollmentFeature {
help.setAutoCancel(false);
return help;
}
@Override
public boolean shouldAdjustHeaderText(@NonNull Configuration conf, boolean isFolded) {
return conf.orientation == Configuration.ORIENTATION_LANDSCAPE;
}
}

View File

@@ -19,7 +19,6 @@ package com.android.settings.biometrics.fingerprint2
import android.hardware.fingerprint.FingerprintManager
import android.view.MotionEvent
import android.view.accessibility.AccessibilityManager
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import com.android.internal.widget.LockPatternUtils
@@ -27,33 +26,47 @@ import com.android.settings.SettingsApplication
import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepository
import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepositoryImpl
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepositoryImpl
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSettingsRepositoryImpl
import com.android.settings.biometrics.fingerprint2.data.repository.UserRepoImpl
import com.android.settings.biometrics.fingerprint2.debug.data.repository.UdfpsEnrollDebugRepositoryImpl
import com.android.settings.biometrics.fingerprint2.debug.domain.interactor.DebugTouchEventInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.AuthenticateInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.CanEnrollFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollFingerprintInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrolledFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintSensorInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintSensorInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.GenerateChallengeInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.RemoveFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.RenameFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.SensorInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.TouchEventInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractorImpl
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.Settings
import java.util.concurrent.Executors
import kotlinx.coroutines.MainScope
@@ -68,36 +81,53 @@ import kotlinx.coroutines.flow.flowOf
* This code is instantiated within the [SettingsApplication], all repos should be private &
* immutable and all interactors should public and immutable
*/
class BiometricsEnvironment(context: SettingsApplication) : ViewModelStoreOwner {
class BiometricsEnvironment(
val context: SettingsApplication,
private val fingerprintManager: FingerprintManager,
) : ViewModelStoreOwner {
private val executorService = Executors.newSingleThreadExecutor()
private val backgroundDispatcher = executorService.asCoroutineDispatcher()
private val applicationScope = MainScope()
private val gateKeeperPasswordProvider = GatekeeperPasswordProvider(LockPatternUtils(context))
private val fingerprintManager =
context.getSystemService(FragmentActivity.FINGERPRINT_SERVICE) as FingerprintManager?
private val userRepo = UserRepoImpl(context.userId)
private val fingerprintSettingsRepository =
FingerprintSettingsRepositoryImpl(
context.resources.getInteger(
com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser
)
)
private val fingerprintEnrollmentRepository =
FingerprintEnrollmentRepositoryImpl(fingerprintManager, userRepo, fingerprintSettingsRepository,
backgroundDispatcher, applicationScope)
private val fingerprintSensorRepository: FingerprintSensorRepository =
FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, applicationScope)
private val debuggingRepository: DebuggingRepository = DebuggingRepositoryImpl()
private val udfpsDebugRepo = UdfpsEnrollDebugRepositoryImpl()
/** For now, interactors are public to those with access to the [BiometricsEnvironment] class */
val fingerprintEnrollInteractor: FingerprintEnrollInteractor by lazy {
FingerprintEnrollInteractorImpl(context, fingerprintManager, Settings)
}
fun createSensorPropertiesInteractor(): SensorInteractor =
SensorInteractorImpl(fingerprintSensorRepository)
/** [FingerprintManagerInteractor] to be used to construct view models */
val fingerprintManagerInteractor: FingerprintManagerInteractor by lazy {
FingerprintManagerInteractorImpl(
context,
backgroundDispatcher,
fingerprintManager,
fingerprintSensorRepository,
gateKeeperPasswordProvider,
fingerprintEnrollInteractor,
)
}
fun createCanEnrollFingerprintsInteractor(): CanEnrollFingerprintsInteractor =
CanEnrollFingerprintsInteractorImpl(fingerprintEnrollmentRepository)
fun createGenerateChallengeInteractor(): GenerateChallengeInteractor =
GenerateChallengeInteractorImpl(fingerprintManager, context.userId, gateKeeperPasswordProvider)
fun createFingerprintEnrollInteractor(): EnrollFingerprintInteractor =
EnrollFingerprintInteractorImpl(context.userId, fingerprintManager, Settings)
fun createFingerprintsEnrolledInteractor(): EnrolledFingerprintsInteractorImpl =
EnrolledFingerprintsInteractorImpl(fingerprintManager, context.userId)
fun createAuthenticateInteractor(): AuthenitcateInteractor =
AuthenticateInteractorImpl(fingerprintManager, context.userId)
fun createRemoveFingerprintInteractor(): RemoveFingerprintInteractor =
RemoveFingerprintsInteractorImpl(fingerprintManager, context.userId)
fun createRenameFingerprintInteractor(): RenameFingerprintInteractor =
RenameFingerprintsInteractorImpl(fingerprintManager, context.userId, backgroundDispatcher)
val accessibilityInteractor: AccessibilityInteractor by lazy {
AccessibilityInteractorImpl(

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2024 The Android Open 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.biometrics.fingerprint2.data.repository
import android.hardware.biometrics.BiometricStateListener
import android.hardware.fingerprint.FingerprintManager
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
/** Repository that contains information about fingerprint enrollments. */
interface FingerprintEnrollmentRepository {
/** The current enrollments of the user */
val currentEnrollments: Flow<List<FingerprintData>?>
/** Indicates if a user can enroll another fingerprint */
val canEnrollUser: Flow<Boolean>
fun maxFingerprintsEnrollable(): Int
}
class FingerprintEnrollmentRepositoryImpl(
fingerprintManager: FingerprintManager,
userRepo: UserRepo,
private val settingsRepository: FingerprintSettingsRepository,
backgroundDispatcher: CoroutineDispatcher,
applicationScope: CoroutineScope,
) : FingerprintEnrollmentRepository {
private val enrollmentChangedFlow: Flow<Int?> =
callbackFlow {
val callback =
object : BiometricStateListener() {
override fun onEnrollmentsChanged(userId: Int, sensorId: Int, hasEnrollments: Boolean) {
trySend(userId)
}
}
withContext(backgroundDispatcher) {
fingerprintManager.registerBiometricStateListener(callback)
}
awaitClose {
// no way to unregister
}
}
.stateIn(applicationScope, started = SharingStarted.Eagerly, initialValue = null)
override val currentEnrollments: Flow<List<FingerprintData>> =
userRepo.currentUser
.distinctUntilChanged()
.flatMapLatest { currentUser ->
enrollmentChangedFlow.map { enrollmentChanged ->
if (enrollmentChanged == null || enrollmentChanged == currentUser) {
fingerprintManager
.getEnrolledFingerprints(currentUser)
?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
?.toList()
} else {
null
}
}
}
.filterNotNull()
.flowOn(backgroundDispatcher)
override val canEnrollUser: Flow<Boolean> =
currentEnrollments.map {
it?.size?.let { it < settingsRepository.maxEnrollableFingerprints() } ?: false
}
override fun maxFingerprintsEnrollable(): Int {
return settingsRepository.maxEnrollableFingerprints()
}
}

View File

@@ -31,6 +31,8 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.withContext
@@ -43,10 +45,13 @@ import kotlinx.coroutines.withContext
interface FingerprintSensorRepository {
/** Get the [FingerprintSensor] */
val fingerprintSensor: Flow<FingerprintSensor>
/** Indicates if this device supports the side fingerprint sensor */
val hasSideFps: Flow<Boolean>
}
class FingerprintSensorRepositoryImpl(
fingerprintManager: FingerprintManager?,
private val fingerprintManager: FingerprintManager,
backgroundDispatcher: CoroutineDispatcher,
activityScope: CoroutineScope,
) : FingerprintSensorRepository {
@@ -66,7 +71,7 @@ class FingerprintSensorRepositoryImpl(
}
}
withContext(backgroundDispatcher) {
fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
fingerprintManager.addAuthenticatorsRegisteredCallback(callback)
}
awaitClose {}
}
@@ -75,6 +80,9 @@ class FingerprintSensorRepositoryImpl(
override val fingerprintSensor: Flow<FingerprintSensor> =
fingerprintPropsInternal.transform { emit(it.toFingerprintSensor()) }
override val hasSideFps: Flow<Boolean> =
fingerprintSensor.flatMapLatest { flow { emit(fingerprintManager.isPowerbuttonFps()) } }
companion object {
private val DEFAULT_PROPS =

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2024 The Android Open 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.biometrics.fingerprint2.data.repository
/**
* Repository for storing metadata about fingerprint enrollments.
*/
interface FingerprintSettingsRepository {
/**
* Indicates the maximum number of fingerprints enrollable
*/
fun maxEnrollableFingerprints(): Int
}
class FingerprintSettingsRepositoryImpl(private val maxFingerprintsEnrollable: Int) :
FingerprintSettingsRepository {
override fun maxEnrollableFingerprints() = maxFingerprintsEnrollable
}

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