Merge 24Q4 (ab/12406339) into aosp-main-future
Bug: 370570306 Merged-In: Ie90e7495dd4a134538bae6e3e08eea0d02134b14 Change-Id: I20517e9ee410e95f2cbeb1247c0c0288ed9f006f
This commit is contained in:
@@ -179,6 +179,8 @@ public class FallbackHome extends Activity {
|
||||
SystemClock.uptimeMillis(), false);
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "User not yet unlocked");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
94
src/com/android/settings/SettingsPreferenceFragmentBase.java
Normal file
94
src/com/android/settings/SettingsPreferenceFragmentBase.java
Normal 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.
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ object EmbeddedDeepLinkUtils {
|
||||
private const val TAG = "EmbeddedDeepLinkUtils"
|
||||
|
||||
@JvmStatic
|
||||
fun Activity.tryStartMultiPaneDeepLink(
|
||||
fun Context.tryStartMultiPaneDeepLink(
|
||||
intent: Intent,
|
||||
highlightMenuKey: String? = null,
|
||||
): Boolean {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ public class UdfpsEnrollEnrollingView extends GlifLayout {
|
||||
} else if (mShouldUseReverseLandscape) {
|
||||
swapHeaderAndContent();
|
||||
}
|
||||
mUdfpsEnrollView.setVisibility(View.VISIBLE);
|
||||
setOnHoverListener();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user