Merge rvc-qpr-dev-plus-aosp-without-vendor@6881855

Bug: 172690556
Merged-In: Iafcefc2aa64cf3c50b1d139ec0204a315be29da7
Change-Id: I5d4e70fe723d890b5694c3490d6ec841b1ac596e
This commit is contained in:
Xin Li
2020-12-02 00:29:15 -08:00
393 changed files with 32291 additions and 47011 deletions

View File

@@ -21,20 +21,26 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.app.admin.DevicePolicyManager;
import android.app.admin.FactoryResetProtectionPolicy;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.oemlock.OemLockManager;
import android.service.persistentdata.PersistentDataBlockManager;
import android.telephony.TelephonyManager;
import android.telephony.UiccSlotInfo;
import android.telephony.euicc.EuiccManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -54,6 +60,8 @@ import com.google.android.setupcompat.template.FooterButton.ButtonType;
import com.google.android.setupcompat.util.WizardManagerHelper;
import com.google.android.setupdesign.GlifLayout;
import java.util.Arrays;
/**
* Confirm and execute a reset of the device to a clean "just out of the box"
* state. Multiple confirmations are required: first, a general "are you sure
@@ -83,6 +91,89 @@ public class MasterClearConfirm extends InstrumentedFragment {
return;
}
// If the eSIM slot is in an error state, display a dialog to warn users that their eSIM
// profiles may not be fully deleted during FDR.
if (shouldShowEsimEraseFailureDialog()) {
Log.e(TAG, "eUICC card is in an error state. Display a dialog to warn the user.");
showEsimErrorDialog();
return;
}
performFactoryReset();
}
/**
* Returns true if the user choose to erase eSIM profile but the eUICC card is in an error
* state.
*/
private boolean shouldShowEsimEraseFailureDialog() {
EuiccManager euiccManager = getActivity().getSystemService(EuiccManager.class);
TelephonyManager telephonyManager =
getActivity().getSystemService(TelephonyManager.class);
if (euiccManager == null || !euiccManager.isEnabled()) {
Log.i(
TAG,
"eSIM manager is disabled. No need to check eSIM slot before FDR.");
return false;
}
if (!mEraseEsims) {
Log.i(
TAG,
"eSIM does not need to be reset. No need to check eSIM slot before FDR.");
return false;
}
UiccSlotInfo[] slotInfos = telephonyManager.getUiccSlotsInfo();
if (slotInfos == null) {
Log.i(TAG, "Unable to get UICC slots.");
return false;
}
// If getIsEuicc() returns false for an eSIM slot, it means the eSIM is in the error
// state.
return Arrays.stream(slotInfos).anyMatch(
slot -> slot != null && !slot.isRemovable() && !slot.getIsEuicc());
}
private void showEsimErrorDialog() {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.fdr_esim_failure_title)
.setMessage(R.string.fdr_esim_failure_text)
.setNeutralButton(R.string.dlg_cancel,
(DialogInterface.OnClickListener) (dialog, which) -> {
dialog.dismiss();
})
.setNegativeButton(R.string.fdr_esim_failure_reboot_btn,
(DialogInterface.OnClickListener) (dialog, which) -> {
dialog.dismiss();
PowerManager pm = (PowerManager) getActivity()
.getSystemService(Context.POWER_SERVICE);
pm.reboot(null);
})
.setPositiveButton(R.string.lockpassword_continue_label,
(DialogInterface.OnClickListener) (dialog, which) -> {
dialog.dismiss();
showContinueFdrDialog();
})
.show();
}
private void showContinueFdrDialog() {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.fdr_continue_title)
.setMessage(R.string.fdr_continue_text)
.setNegativeButton(R.string.dlg_cancel,
(DialogInterface.OnClickListener) (dialog, which) -> {
dialog.dismiss();
})
.setPositiveButton(R.string.fdr_continue_btn,
(DialogInterface.OnClickListener) (dialog, which) -> {
dialog.dismiss();
performFactoryReset();
})
.show();
}
private void performFactoryReset() {
final PersistentDataBlockManager pdbManager = (PersistentDataBlockManager)
getActivity().getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);

View File

@@ -80,6 +80,10 @@ public class TetherSettings extends RestrictedSettingsFragment
@VisibleForTesting
static final String KEY_TETHER_PREFS_FOOTER = "tether_prefs_footer";
@VisibleForTesting
static final String BLUETOOTH_TETHERING_STATE_CHANGED =
"android.bluetooth.pan.profile.action.TETHERING_STATE_CHANGED";
private static final String TAG = "TetheringSettings";
private SwitchPreference mUsbTether;
@@ -154,9 +158,7 @@ public class TetherSettings extends RestrictedSettingsFragment
BluetoothProfile.PAN);
}
mUsbTether = (SwitchPreference) findPreference(KEY_USB_TETHER_SETTINGS);
mBluetoothTether = (SwitchPreference) findPreference(KEY_ENABLE_BLUETOOTH_TETHERING);
mEthernetTether = (SwitchPreference) findPreference(KEY_ENABLE_ETHERNET_TETHERING);
setupTetherPreference();
setFooterPreferenceTitle();
mDataSaverBackend.addListener(this);
@@ -208,6 +210,13 @@ public class TetherSettings extends RestrictedSettingsFragment
super.onDestroy();
}
@VisibleForTesting
void setupTetherPreference() {
mUsbTether = (SwitchPreference) findPreference(KEY_USB_TETHER_SETTINGS);
mBluetoothTether = (SwitchPreference) findPreference(KEY_ENABLE_BLUETOOTH_TETHERING);
mEthernetTether = (SwitchPreference) findPreference(KEY_ENABLE_ETHERNET_TETHERING);
}
@Override
public void onDataSaverChanged(boolean isDataSaving) {
mDataSaverEnabled = isDataSaving;
@@ -281,6 +290,8 @@ public class TetherSettings extends RestrictedSettingsFragment
}
}
updateState();
} else if (action.equals(BLUETOOTH_TETHERING_STATE_CHANGED)) {
updateState();
}
}
}
@@ -297,32 +308,13 @@ public class TetherSettings extends RestrictedSettingsFragment
return;
}
final Activity activity = getActivity();
mStartTetheringCallback = new OnStartTetheringCallback(this);
mTetheringEventCallback = new TetheringEventCallback();
mTm.registerTetheringEventCallback(new HandlerExecutor(mHandler), mTetheringEventCallback);
mMassStorageActive = Environment.MEDIA_SHARED.equals(Environment.getExternalStorageState());
mTetherChangeReceiver = new TetherChangeReceiver();
IntentFilter filter = new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
Intent intent = activity.registerReceiver(mTetherChangeReceiver, filter);
filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_STATE);
activity.registerReceiver(mTetherChangeReceiver, filter);
filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_SHARED);
filter.addAction(Intent.ACTION_MEDIA_UNSHARED);
filter.addDataScheme("file");
activity.registerReceiver(mTetherChangeReceiver, filter);
filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
activity.registerReceiver(mTetherChangeReceiver, filter);
if (intent != null) mTetherChangeReceiver.onReceive(activity, intent);
registerReceiver();
mEthernetListener = new EthernetListener();
if (mEm != null)
@@ -348,10 +340,38 @@ public class TetherSettings extends RestrictedSettingsFragment
mEthernetListener = null;
}
@VisibleForTesting
void registerReceiver() {
final Activity activity = getActivity();
mTetherChangeReceiver = new TetherChangeReceiver();
IntentFilter filter = new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
final Intent intent = activity.registerReceiver(mTetherChangeReceiver, filter);
filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_STATE);
activity.registerReceiver(mTetherChangeReceiver, filter);
filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_SHARED);
filter.addAction(Intent.ACTION_MEDIA_UNSHARED);
filter.addDataScheme("file");
activity.registerReceiver(mTetherChangeReceiver, filter);
filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BLUETOOTH_TETHERING_STATE_CHANGED);
activity.registerReceiver(mTetherChangeReceiver, filter);
if (intent != null) mTetherChangeReceiver.onReceive(activity, intent);
}
private void updateState() {
String[] available = mCm.getTetherableIfaces();
String[] tethered = mCm.getTetheredIfaces();
String[] errored = mCm.getTetheringErroredIfaces();
final ConnectivityManager cm =
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
final String[] available = cm.getTetherableIfaces();
final String[] tethered = cm.getTetheredIfaces();
final String[] errored = cm.getTetheringErroredIfaces();
updateState(available, tethered, errored);
}
@@ -362,7 +382,8 @@ public class TetherSettings extends RestrictedSettingsFragment
updateEthernetState(available, tethered);
}
private void updateUsbState(String[] available, String[] tethered,
@VisibleForTesting
void updateUsbState(String[] available, String[] tethered,
String[] errored) {
boolean usbAvailable = mUsbConnected && !mMassStorageActive;
int usbError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
@@ -400,20 +421,33 @@ public class TetherSettings extends RestrictedSettingsFragment
}
}
private void updateBluetoothState() {
@VisibleForTesting
int getBluetoothState() {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
return BluetoothAdapter.ERROR;
}
return adapter.getState();
}
@VisibleForTesting
boolean isBluetoothTetheringOn() {
final BluetoothPan bluetoothPan = mBluetoothPan.get();
return bluetoothPan != null && bluetoothPan.isTetheringOn();
}
private void updateBluetoothState() {
final int btState = getBluetoothState();
if (btState == BluetoothAdapter.ERROR) {
return;
}
int btState = adapter.getState();
if (btState == BluetoothAdapter.STATE_TURNING_OFF) {
mBluetoothTether.setEnabled(false);
} else if (btState == BluetoothAdapter.STATE_TURNING_ON) {
mBluetoothTether.setEnabled(false);
} else {
BluetoothPan bluetoothPan = mBluetoothPan.get();
if (btState == BluetoothAdapter.STATE_ON && bluetoothPan != null
&& bluetoothPan.isTetheringOn()) {
if (btState == BluetoothAdapter.STATE_ON && isBluetoothTetheringOn()) {
mBluetoothTether.setChecked(true);
mBluetoothTether.setEnabled(!mDataSaverEnabled);
} else {
@@ -423,7 +457,8 @@ public class TetherSettings extends RestrictedSettingsFragment
}
}
private void updateEthernetState(String[] available, String[] tethered) {
@VisibleForTesting
void updateEthernetState(String[] available, String[] tethered) {
boolean isAvailable = false;
boolean isTethered = false;

View File

@@ -18,6 +18,10 @@ package com.android.settings;
import static android.content.Intent.EXTRA_USER;
import static android.content.Intent.EXTRA_USER_ID;
import static android.media.MediaRoute2Info.TYPE_GROUP;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
@@ -53,6 +57,8 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
@@ -1137,4 +1143,31 @@ public final class Utils extends com.android.settingslib.Utils {
drawable.draw(canvas);
return roundedBitmap;
}
/**
* Returns {@code true} if needed to disable media output, otherwise returns {@code false}.
*/
public static boolean isMediaOutputDisabled(
MediaRouter2Manager router2Manager, String packageName) {
boolean isMediaOutputDisabled = false;
if (!TextUtils.isEmpty(packageName)) {
final List<MediaRoute2Info> infos = router2Manager.getAvailableRoutes(packageName);
if (infos.size() == 1) {
final MediaRoute2Info info = infos.get(0);
final int deviceType = info.getType();
switch (deviceType) {
case TYPE_UNKNOWN:
case TYPE_REMOTE_TV:
case TYPE_REMOTE_SPEAKER:
case TYPE_GROUP:
isMediaOutputDisabled = true;
break;
default:
isMediaOutputDisabled = false;
break;
}
}
}
return isMediaOutputDisabled;
}
}

View File

@@ -58,12 +58,12 @@ public class AppFilterRegistry {
// Filters will appear sorted based on their value defined here.
public static final int FILTER_APPS_POWER_WHITELIST = 0;
public static final int FILTER_APPS_POWER_WHITELIST_ALL = 1;
public static final int FILTER_APPS_ALL = 2;
public static final int FILTER_APPS_ENABLED = 3;
public static final int FILTER_APPS_INSTANT = 4;
public static final int FILTER_APPS_DISABLED = 5;
public static final int FILTER_APPS_RECENT = 6;
public static final int FILTER_APPS_FREQUENT = 7;
public static final int FILTER_APPS_RECENT = 2;
public static final int FILTER_APPS_FREQUENT = 3;
public static final int FILTER_APPS_ALL = 4;
public static final int FILTER_APPS_ENABLED = 5;
public static final int FILTER_APPS_INSTANT = 6;
public static final int FILTER_APPS_DISABLED = 7;
public static final int FILTER_APPS_PERSONAL = 8;
public static final int FILTER_APPS_WORK = 9;
public static final int FILTER_APPS_USAGE_ACCESS = 10;

View File

@@ -476,7 +476,7 @@ public class ManageApplications extends InstrumentedFragment
mFilterAdapter.enableFilter(FILTER_APPS_RECENT);
mFilterAdapter.enableFilter(FILTER_APPS_FREQUENT);
mFilterAdapter.enableFilter(FILTER_APPS_BLOCKED);
mFilterAdapter.disableFilter(FILTER_APPS_ALL);
mFilterAdapter.enableFilter(FILTER_APPS_ALL);
}
if (mListType == LIST_TYPE_HIGH_POWER) {
mFilterAdapter.enableFilter(FILTER_APPS_POWER_WHITELIST_ALL);
@@ -1089,12 +1089,16 @@ public class ManageApplications extends InstrumentedFragment
mAppFilter = appFilter;
// Notification filters require resorting the list
if (FILTER_APPS_FREQUENT == appFilter.getFilterType()) {
rebuild(R.id.sort_order_frequent_notification);
} else if (FILTER_APPS_RECENT == appFilter.getFilterType()) {
rebuild(R.id.sort_order_recent_notification);
} else if (FILTER_APPS_BLOCKED == appFilter.getFilterType()) {
rebuild(R.id.sort_order_alpha);
if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
if (FILTER_APPS_FREQUENT == appFilter.getFilterType()) {
rebuild(R.id.sort_order_frequent_notification);
} else if (FILTER_APPS_RECENT == appFilter.getFilterType()) {
rebuild(R.id.sort_order_recent_notification);
} else if (FILTER_APPS_BLOCKED == appFilter.getFilterType()) {
rebuild(R.id.sort_order_alpha);
} else {
rebuild(R.id.sort_order_alpha);
}
} else {
rebuild();
}

View File

@@ -40,20 +40,13 @@ public class ZenAccessController extends BasePreferenceController {
private static final String TAG = "ZenAccessController";
private final ActivityManager mActivityManager;
public ZenAccessController(Context context, String preferenceKey) {
super(context, preferenceKey);
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
}
@Override
public int getAvailabilityStatus() {
return isSupported(mActivityManager) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
public static boolean isSupported(ActivityManager activityManager) {
return !activityManager.isLowRamDevice();
return AVAILABLE;
}
public static Set<String> getPackagesRequestingNotificationPolicyAccess() {

View File

@@ -50,9 +50,6 @@ public class ZenAccessDetails extends AppInfoWithHeader implements
@Override
protected boolean refreshUi() {
final Context context = getContext();
if (!ZenAccessController.isSupported(context.getSystemService(ActivityManager.class))) {
return false;
}
// If this app didn't declare this permission in their manifest, don't bother showing UI.
final Set<String> needAccessApps =
ZenAccessController.getPackagesRequestingNotificationPolicyAccess();

View File

@@ -53,9 +53,6 @@ public class ZenAccessSettingObserverMixin extends ContentObserver implements Li
@Override
public void onStart() {
if (!ZenAccessController.isSupported(mContext.getSystemService(ActivityManager.class))) {
return;
}
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(
Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES),
@@ -69,9 +66,6 @@ public class ZenAccessSettingObserverMixin extends ContentObserver implements Li
@Override
public void onStop() {
if (!ZenAccessController.isSupported(mContext.getSystemService(ActivityManager.class))) {
return;
}
mContext.getContentResolver().unregisterContentObserver(this /* observer */);
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.biometrics.face;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.os.Bundle;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
/**
* Confirmation dialog shown to users with accessibility enabled who are trying to start the
* non-accessibility enrollment flow.
*/
public class FaceEnrollAccessibilityDialog extends InstrumentedDialogFragment {
private AlertDialog.OnClickListener mPositiveButtonListener;
/**
* @return new instance of the dialog
*/
public static FaceEnrollAccessibilityDialog newInstance() {
return new FaceEnrollAccessibilityDialog();
}
public void setPositiveButtonListener(AlertDialog.OnClickListener listener) {
mPositiveButtonListener = listener;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
final int titleResId =
R.string.security_settings_face_enroll_education_accessibility_dialog_message;
final int negativeButtonResId =
R.string.security_settings_face_enroll_education_accessibility_dialog_negative;
final int positiveButtonResId =
R.string.security_settings_face_enroll_education_accessibility_dialog_positive;
builder.setMessage(titleResId)
.setNegativeButton(negativeButtonResId, (dialog, which) -> {
dialog.cancel();
})
.setPositiveButton(positiveButtonResId, (dialog, which) -> {
mPositiveButtonListener.onClick(dialog, which);
});
return builder.create();
}
@Override
public int getMetricsCategory() {
return SettingsEnums.FACE_ENROLL_INTRO;
}
}

View File

@@ -55,6 +55,7 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
private Intent mResultIntent;
private TextView mDescriptionText;
private boolean mNextClicked;
private boolean mAccessibilityEnabled;
private CompoundButton.OnCheckedChangeListener mSwitchDiversityListener =
new CompoundButton.OnCheckedChangeListener() {
@@ -123,13 +124,12 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
.setTheme(R.style.SudGlifButton_Primary)
.build();
boolean accessibilityEnabled = false;
final AccessibilityManager accessibilityManager = getApplicationContext().getSystemService(
AccessibilityManager.class);
if (accessibilityManager != null) {
// Add additional check for touch exploration. This prevents other accessibility
// features such as Live Transcribe from defaulting to the accessibility setup.
accessibilityEnabled = accessibilityManager.isEnabled()
mAccessibilityEnabled = accessibilityManager.isEnabled()
&& accessibilityManager.isTouchExplorationEnabled();
}
mFooterBarMixin.setPrimaryButton(footerButton);
@@ -147,7 +147,7 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
mSwitchDiversity.getSwitch().toggle();
});
if (accessibilityEnabled) {
if (mAccessibilityEnabled) {
accessibilityButton.callOnClick();
}
}
@@ -194,9 +194,20 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
if (mResultIntent != null) {
intent.putExtras(mResultIntent);
}
mNextClicked = true;
intent.putExtra(EXTRA_KEY_REQUIRE_DIVERSITY, !mSwitchDiversity.isChecked());
startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST);
if (!mSwitchDiversity.isChecked() && mAccessibilityEnabled) {
FaceEnrollAccessibilityDialog dialog = FaceEnrollAccessibilityDialog.newInstance();
dialog.setPositiveButtonListener((dialog1, which) -> {
startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST);
mNextClicked = true;
});
dialog.show(getSupportFragmentManager(), FaceEnrollAccessibilityDialog.class.getName());
} else {
startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST);
mNextClicked = true;
}
}
protected void onSkipButtonClick(View view) {

View File

@@ -52,8 +52,7 @@ import java.lang.annotation.RetentionPolicy;
* BluetoothDevicePreference is the preference type used to display each remote
* Bluetooth device in the Bluetooth Settings screen.
*/
public final class BluetoothDevicePreference extends GearPreference implements
CachedBluetoothDevice.Callback {
public final class BluetoothDevicePreference extends GearPreference {
private static final String TAG = "BluetoothDevicePref";
private static int sDimAlpha = Integer.MIN_VALUE;
@@ -77,10 +76,20 @@ public final class BluetoothDevicePreference extends GearPreference implements
private AlertDialog mDisconnectDialog;
private String contentDescription = null;
private boolean mHideSecondTarget = false;
private boolean mIsCallbackRemoved = false;
@VisibleForTesting
boolean mNeedNotifyHierarchyChanged = false;
/* Talk-back descriptions for various BT icons */
Resources mResources;
final BluetoothDevicePreferenceCallback mCallback;
private class BluetoothDevicePreferenceCallback implements CachedBluetoothDevice.Callback {
@Override
public void onDeviceAttributesChanged() {
onPreferenceAttributesChanged();
}
}
public BluetoothDevicePreference(Context context, CachedBluetoothDevice cachedDevice,
boolean showDeviceWithoutNames, @SortType int type) {
@@ -96,11 +105,12 @@ public final class BluetoothDevicePreference extends GearPreference implements
}
mCachedDevice = cachedDevice;
mCachedDevice.registerCallback(this);
mCallback = new BluetoothDevicePreferenceCallback();
mCachedDevice.registerCallback(mCallback);
mCurrentTime = System.currentTimeMillis();
mType = type;
onDeviceAttributesChanged();
onPreferenceAttributesChanged();
}
public void setNeedNotifyHierarchyChanged(boolean needNotifyHierarchyChanged) {
@@ -127,13 +137,35 @@ public final class BluetoothDevicePreference extends GearPreference implements
@Override
protected void onPrepareForRemoval() {
super.onPrepareForRemoval();
mCachedDevice.unregisterCallback(this);
if (!mIsCallbackRemoved) {
mCachedDevice.unregisterCallback(mCallback);
mIsCallbackRemoved = true;
}
if (mDisconnectDialog != null) {
mDisconnectDialog.dismiss();
mDisconnectDialog = null;
}
}
@Override
public void onAttached() {
super.onAttached();
if (mIsCallbackRemoved) {
mCachedDevice.registerCallback(mCallback);
mIsCallbackRemoved = false;
}
onPreferenceAttributesChanged();
}
@Override
public void onDetached() {
super.onDetached();
if (!mIsCallbackRemoved) {
mCachedDevice.unregisterCallback(mCallback);
mIsCallbackRemoved = true;
}
}
public CachedBluetoothDevice getBluetoothDevice() {
return mCachedDevice;
}
@@ -142,7 +174,7 @@ public final class BluetoothDevicePreference extends GearPreference implements
mHideSecondTarget = hideSecondTarget;
}
public void onDeviceAttributesChanged() {
private void onPreferenceAttributesChanged() {
/*
* The preference framework takes care of making sure the value has
* changed before proceeding. It will also call notifyChanged() if

View File

@@ -18,6 +18,7 @@ package com.android.settings.bluetooth;
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
import android.Manifest;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -192,6 +193,6 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment {
if (mLaunchPackage != null && mLaunchClass != null) {
intent.setClassName(mLaunchPackage, mLaunchClass);
}
getActivity().sendBroadcast(intent);
getActivity().sendBroadcast(intent, Manifest.permission.BLUETOOTH_ADMIN);
}
}

View File

@@ -24,6 +24,7 @@ import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.connecteddevice.PreviouslyConnectedDeviceDashboardFragment;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
@@ -42,13 +43,15 @@ public class SavedBluetoothDeviceUpdater extends BluetoothDeviceUpdater
private static final String PREF_KEY = "saved_bt";
private final boolean mDisplayConnected;
@VisibleForTesting
BluetoothAdapter mBluetoothAdapter;
public SavedBluetoothDeviceUpdater(Context context, DashboardFragment fragment,
DevicePreferenceCallback devicePreferenceCallback) {
super(context, fragment, devicePreferenceCallback);
mDisplayConnected = (fragment instanceof PreviouslyConnectedDeviceDashboardFragment);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
@@ -101,7 +104,8 @@ public class SavedBluetoothDeviceUpdater extends BluetoothDeviceUpdater
", is connected : " + device.isConnected() + ", is profile connected : "
+ cachedDevice.isConnected());
}
return device.getBondState() == BluetoothDevice.BOND_BONDED && !device.isConnected();
return device.getBondState() == BluetoothDevice.BOND_BONDED
&& (mDisplayConnected || !device.isConnected());
}
@Override
@@ -109,6 +113,9 @@ public class SavedBluetoothDeviceUpdater extends BluetoothDeviceUpdater
mMetricsFeatureProvider.logClickedPreference(preference, mFragment.getMetricsCategory());
final CachedBluetoothDevice device = ((BluetoothDevicePreference) preference)
.getBluetoothDevice();
if (device.isConnected()) {
return device.setActive();
}
device.connect();
return true;
}

View File

@@ -16,7 +16,11 @@
package com.android.settings.connecteddevice;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
@@ -32,6 +36,9 @@ public class PreviouslyConnectedDeviceDashboardFragment extends DashboardFragmen
private static final String TAG = "PreConnectedDeviceFrag";
static final String KEY_PREVIOUSLY_CONNECTED_DEVICES = "saved_device_list";
@VisibleForTesting
BluetoothAdapter mBluetoothAdapter;
@Override
public int getHelpResource() {
return R.string.help_url_previously_connected_devices;
@@ -52,12 +59,32 @@ public class PreviouslyConnectedDeviceDashboardFragment extends DashboardFragmen
return SettingsEnums.PREVIOUSLY_CONNECTED_DEVICES;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
use(SavedDeviceGroupController.class).init(this);
}
@Override
public void onStart() {
super.onStart();
enableBluetoothIfNecessary();
}
@VisibleForTesting
void enableBluetoothIfNecessary() {
if (mBluetoothAdapter != null && !mBluetoothAdapter.isEnabled()) {
mBluetoothAdapter.enable();
}
}
/**
* For Search.
*/

View File

@@ -15,14 +15,22 @@
*/
package com.android.settings.connecteddevice;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.bluetooth.BluetoothDevicePreference;
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
import com.android.settings.bluetooth.SavedBluetoothDeviceUpdater;
import com.android.settings.connecteddevice.dock.DockUpdater;
@@ -33,21 +41,47 @@ import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import java.util.ArrayList;
import java.util.List;
public class PreviouslyConnectedDevicePreferenceController extends BasePreferenceController
implements LifecycleObserver, OnStart, OnStop, DevicePreferenceCallback {
private static final String TAG = "PreviouslyDevicePreController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int MAX_DEVICE_NUM = 3;
private static final int DOCK_DEVICE_INDEX = 9;
private static final String KEY_SEE_ALL = "previously_connected_devices_see_all";
private final List<Preference> mDevicesList = new ArrayList<>();
private final List<Preference> mDockDevicesList = new ArrayList<>();
private PreferenceGroup mPreferenceGroup;
private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
private DockUpdater mSavedDockUpdater;
private int mPreferenceSize;
private BluetoothAdapter mBluetoothAdapter;
@VisibleForTesting
Preference mSeeAllPreference;
@VisibleForTesting
IntentFilter mIntentFilter;
@VisibleForTesting
BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updatePreferenceVisibility();
}
};
public PreviouslyConnectedDevicePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mSavedDockUpdater = FeatureFactory.getFactory(
context).getDockUpdaterFeatureProvider().getSavedDockUpdater(context, this);
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
@Override
@@ -62,7 +96,8 @@ public class PreviouslyConnectedDevicePreferenceController extends BasePreferenc
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreferenceGroup = screen.findPreference(getPreferenceKey());
mPreferenceGroup.setVisible(false);
mSeeAllPreference = mPreferenceGroup.findPreference(KEY_SEE_ALL);
updatePreferenceVisibility();
if (isAvailable()) {
final Context context = screen.getContext();
@@ -75,12 +110,14 @@ public class PreviouslyConnectedDevicePreferenceController extends BasePreferenc
public void onStart() {
mBluetoothDeviceUpdater.registerCallback();
mSavedDockUpdater.registerCallback();
mContext.registerReceiver(mReceiver, mIntentFilter);
}
@Override
public void onStop() {
mBluetoothDeviceUpdater.unregisterCallback();
mSavedDockUpdater.unregisterCallback();
mContext.unregisterReceiver(mReceiver);
}
public void init(DashboardFragment fragment) {
@@ -90,18 +127,77 @@ public class PreviouslyConnectedDevicePreferenceController extends BasePreferenc
@Override
public void onDeviceAdded(Preference preference) {
mPreferenceSize++;
if (mPreferenceSize <= MAX_DEVICE_NUM) {
mPreferenceGroup.addPreference(preference);
final List<BluetoothDevice> bluetoothDevices =
mBluetoothAdapter.getMostRecentlyConnectedDevices();
final int index = preference instanceof BluetoothDevicePreference
? bluetoothDevices.indexOf(((BluetoothDevicePreference) preference)
.getBluetoothDevice().getDevice()) : DOCK_DEVICE_INDEX;
if (DEBUG) {
Log.d(TAG, "onDeviceAdded() " + preference.getTitle() + ", index of : " + index);
for (BluetoothDevice device : bluetoothDevices) {
Log.d(TAG, "onDeviceAdded() most recently device : " + device.getName());
}
}
updatePreferenceVisiblity();
addPreference(index, preference);
updatePreferenceVisibility();
}
private void addPreference(int index, Preference preference) {
if (preference instanceof BluetoothDevicePreference) {
if (mDevicesList.size() >= index) {
mDevicesList.add(index, preference);
} else {
mDevicesList.add(preference);
}
} else {
mDockDevicesList.add(preference);
}
addPreference();
}
private void addPreference() {
mPreferenceGroup.removeAll();
mPreferenceGroup.addPreference(mSeeAllPreference);
final int size = getDeviceListSize();
for (int i = 0; i < size; i++) {
if (DEBUG) {
Log.d(TAG, "addPreference() add device : " + mDevicesList.get(i).getTitle());
}
mDevicesList.get(i).setOrder(i);
mPreferenceGroup.addPreference(mDevicesList.get(i));
}
if (mDockDevicesList.size() > 0) {
for (int i = 0; i < getDockDeviceListSize(MAX_DEVICE_NUM - size); i++) {
if (DEBUG) {
Log.d(TAG, "addPreference() add dock device : "
+ mDockDevicesList.get(i).getTitle());
}
mDockDevicesList.get(i).setOrder(DOCK_DEVICE_INDEX);
mPreferenceGroup.addPreference(mDockDevicesList.get(i));
}
}
}
private int getDeviceListSize() {
return mDevicesList.size() >= MAX_DEVICE_NUM
? MAX_DEVICE_NUM : mDevicesList.size();
}
private int getDockDeviceListSize(int availableSize) {
return mDockDevicesList.size() >= availableSize
? availableSize : mDockDevicesList.size();
}
@Override
public void onDeviceRemoved(Preference preference) {
mPreferenceSize--;
mPreferenceGroup.removePreference(preference);
updatePreferenceVisiblity();
if (preference instanceof BluetoothDevicePreference) {
mDevicesList.remove(preference);
} else {
mDockDevicesList.remove(preference);
}
addPreference();
updatePreferenceVisibility();
}
@VisibleForTesting
@@ -120,7 +216,12 @@ public class PreviouslyConnectedDevicePreferenceController extends BasePreferenc
}
@VisibleForTesting
void updatePreferenceVisiblity() {
mPreferenceGroup.setVisible(mPreferenceSize > 0);
void updatePreferenceVisibility() {
if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
mSeeAllPreference.setSummary("");
} else {
mSeeAllPreference.setSummary(
mContext.getString(R.string.connected_device_see_all_summary));
}
}
}

View File

@@ -248,7 +248,9 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider {
final Map<String, IContentProvider> providerMap = new ArrayMap<>();
final String titleFromUri = TileUtils.getTextFromUri(
mContext, uri, providerMap, META_DATA_PREFERENCE_TITLE);
ThreadUtils.postOnMainThread(() -> preference.setTitle(titleFromUri));
if (!TextUtils.equals(titleFromUri, preference.getTitle())) {
ThreadUtils.postOnMainThread(() -> preference.setTitle(titleFromUri));
}
});
}
@@ -277,7 +279,9 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider {
final Map<String, IContentProvider> providerMap = new ArrayMap<>();
final String summaryFromUri = TileUtils.getTextFromUri(
mContext, uri, providerMap, META_DATA_PREFERENCE_SUMMARY);
ThreadUtils.postOnMainThread(() -> preference.setSummary(summaryFromUri));
if (!TextUtils.equals(summaryFromUri, preference.getSummary())) {
ThreadUtils.postOnMainThread(() -> preference.setSummary(summaryFromUri));
}
});
}

View File

@@ -16,6 +16,8 @@
package com.android.settings.development;
import static android.service.quicksettings.TileService.ACTION_QS_TILE_PREFERENCES;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothA2dp;
@@ -23,12 +25,14 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.SystemProperties;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -41,6 +45,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.RestrictedDashboardFragment;
import com.android.settings.development.autofill.AutofillLoggingLevelPreferenceController;
import com.android.settings.development.autofill.AutofillResetOptionsPreferenceController;
@@ -52,6 +57,7 @@ import com.android.settings.development.bluetooth.BluetoothCodecDialogPreference
import com.android.settings.development.bluetooth.BluetoothHDAudioPreferenceController;
import com.android.settings.development.bluetooth.BluetoothQualityDialogPreferenceController;
import com.android.settings.development.bluetooth.BluetoothSampleRateDialogPreferenceController;
import com.android.settings.development.qstile.DevelopmentTiles;
import com.android.settings.development.storage.SharedDataPreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.SwitchBar;
@@ -199,11 +205,42 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
// Restore UI state based on whether developer options is enabled
if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(getContext())) {
enableDeveloperOptions();
handleQsTileLongPressActionIfAny();
} else {
disableDeveloperOptions();
}
}
/**
* Long-pressing a developer options quick settings tile will by default (see
* QS_TILE_PREFERENCES in the manifest) take you to the developer options page.
* Some tiles may want to go into their own page within the developer options.
*/
private void handleQsTileLongPressActionIfAny() {
Intent intent = getActivity().getIntent();
if (intent == null || !TextUtils.equals(ACTION_QS_TILE_PREFERENCES, intent.getAction())) {
return;
}
Log.d(TAG, "Developer options started from qstile long-press");
final ComponentName componentName = (ComponentName) intent.getParcelableExtra(
Intent.EXTRA_COMPONENT_NAME);
if (componentName == null) {
return;
}
if (DevelopmentTiles.WirelessDebugging.class.getName().equals(
componentName.getClassName()) && getDevelopmentOptionsController(
WirelessDebuggingPreferenceController.class).isAvailable()) {
Log.d(TAG, "Long press from wireless debugging qstile");
new SubSettingLauncher(getContext())
.setDestination(WirelessDebuggingFragment.class.getName())
.setSourceMetricsCategory(SettingsEnums.SETTINGS_ADB_WIRELESS)
.launch();
}
// Add other qstiles here
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {

View File

@@ -16,8 +16,8 @@
package com.android.settings.development.graphicsdriver;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.GAME_DRIVER_DEFAULT;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.GAME_DRIVER_OFF;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.UPDATABLE_DRIVER_DEFAULT;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.UPDATABLE_DRIVER_OFF;
import android.content.ContentResolver;
import android.content.Context;
@@ -62,7 +62,7 @@ public class GraphicsDriverAppPreferenceController extends BasePreferenceControl
private final ContentResolver mContentResolver;
private final String mPreferenceTitle;
private final String mPreferenceDefault;
private final String mPreferenceGameDriver;
private final String mPreferenceProductionDriver;
private final String mPreferencePrereleaseDriver;
private final String mPreferenceSystem;
@VisibleForTesting
@@ -88,8 +88,8 @@ public class GraphicsDriverAppPreferenceController extends BasePreferenceControl
final Resources resources = context.getResources();
mPreferenceTitle = resources.getString(R.string.graphics_driver_app_preference_title);
mPreferenceDefault = resources.getString(R.string.graphics_driver_app_preference_default);
mPreferenceGameDriver =
resources.getString(R.string.graphics_driver_app_preference_game_driver);
mPreferenceProductionDriver =
resources.getString(R.string.graphics_driver_app_preference_production_driver);
mPreferencePrereleaseDriver =
resources.getString(R.string.graphics_driver_app_preference_prerelease_driver);
mPreferenceSystem = resources.getString(R.string.graphics_driver_app_preference_system);
@@ -101,19 +101,21 @@ public class GraphicsDriverAppPreferenceController extends BasePreferenceControl
mAppInfos = getAppInfos(context);
mDevOptInApps =
getGlobalSettingsString(mContentResolver, Settings.Global.GAME_DRIVER_OPT_IN_APPS);
getGlobalSettingsString(mContentResolver,
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS);
mDevPrereleaseOptInApps = getGlobalSettingsString(
mContentResolver, Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS);
mContentResolver, Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS);
mDevOptOutApps =
getGlobalSettingsString(mContentResolver, Settings.Global.GAME_DRIVER_OPT_OUT_APPS);
getGlobalSettingsString(mContentResolver,
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS);
}
@Override
public int getAvailabilityStatus() {
return DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)
&& (Settings.Global.getInt(mContentResolver,
Settings.Global.GAME_DRIVER_ALL_APPS, GAME_DRIVER_DEFAULT)
!= GAME_DRIVER_OFF)
Settings.Global.UPDATABLE_DRIVER_ALL_APPS, UPDATABLE_DRIVER_DEFAULT)
!= UPDATABLE_DRIVER_OFF)
? AVAILABLE
: CONDITIONALLY_UNAVAILABLE;
}
@@ -157,7 +159,7 @@ public class GraphicsDriverAppPreferenceController extends BasePreferenceControl
mDevOptInApps.remove(packageName);
mDevPrereleaseOptInApps.remove(packageName);
mDevOptOutApps.add(packageName);
} else if (value.equals(mPreferenceGameDriver)) {
} else if (value.equals(mPreferenceProductionDriver)) {
mDevOptInApps.add(packageName);
mDevPrereleaseOptInApps.remove(packageName);
mDevOptOutApps.remove(packageName);
@@ -174,13 +176,15 @@ public class GraphicsDriverAppPreferenceController extends BasePreferenceControl
listPref.setSummary(value);
// Push the updated Sets for stable/prerelease opt-in and opt-out apps to
// corresponding Settings.Global.GAME_DRIVER(_PRERELEASE)?_OPT_(IN|OUT)_APPS
Settings.Global.putString(mContentResolver, Settings.Global.GAME_DRIVER_OPT_IN_APPS,
// corresponding Settings.Global.UPDATABLE_DRIVER_[PRODUCTION|PRERELEASE]_OPT_(IN|OUT)_APPS
Settings.Global.putString(mContentResolver,
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS,
String.join(",", mDevOptInApps));
Settings.Global.putString(mContentResolver,
Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS,
Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS,
String.join(",", mDevPrereleaseOptInApps));
Settings.Global.putString(mContentResolver, Settings.Global.GAME_DRIVER_OPT_OUT_APPS,
Settings.Global.putString(mContentResolver,
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS,
String.join(",", mDevOptOutApps));
return true;
@@ -251,7 +255,7 @@ public class GraphicsDriverAppPreferenceController extends BasePreferenceControl
listPreference.setEntryValues(mEntryList);
// Initialize preference default and summary with the opt in/out choices
// from Settings.Global.GAME_DRIVER(_PRERELEASE)?_OPT_(IN|OUT)_APPS
// from Settings.Global.UPDATABLE_DRIVER_[PRODUCTION|PRERELEASE]_OPT_[IN|OUT]_APPS
if (mDevOptOutApps.contains(packageName)) {
listPreference.setValue(mPreferenceSystem);
listPreference.setSummary(mPreferenceSystem);
@@ -259,8 +263,8 @@ public class GraphicsDriverAppPreferenceController extends BasePreferenceControl
listPreference.setValue(mPreferencePrereleaseDriver);
listPreference.setSummary(mPreferencePrereleaseDriver);
} else if (mDevOptInApps.contains(packageName)) {
listPreference.setValue(mPreferenceGameDriver);
listPreference.setSummary(mPreferenceGameDriver);
listPreference.setValue(mPreferenceProductionDriver);
listPreference.setSummary(mPreferenceProductionDriver);
} else {
listPreference.setValue(mPreferenceDefault);
listPreference.setSummary(mPreferenceDefault);

View File

@@ -52,7 +52,7 @@ public class GraphicsDriverContentObserver extends ContentObserver {
*/
public void register(ContentResolver contentResolver) {
contentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.GAME_DRIVER_ALL_APPS), false, this);
Settings.Global.getUriFor(Settings.Global.UPDATABLE_DRIVER_ALL_APPS), false, this);
}
/**

View File

@@ -30,7 +30,7 @@ import com.android.settingslib.development.DevelopmentSettingsEnabler;
import com.android.settingslib.search.SearchIndexable;
/**
* Dashboard for Game Driver preferences.
* Dashboard for Graphics Driver preferences.
*/
@SearchIndexable
public class GraphicsDriverDashboard extends DashboardFragment {
@@ -39,7 +39,7 @@ public class GraphicsDriverDashboard extends DashboardFragment {
@Override
public int getMetricsCategory() {
return SettingsEnums.SETTINGS_GAME_DRIVER_DASHBOARD;
return SettingsEnums.SETTINGS_GRAPHICS_DRIVER_DASHBOARD;
}
@Override

View File

@@ -46,24 +46,24 @@ import java.util.ArrayList;
import java.util.List;
/**
* Controller of global switch to enable Game Driver for all Apps.
* Controller of global switch to enable updatable driver for all Apps.
*/
public class GraphicsDriverEnableForAllAppsPreferenceController extends BasePreferenceController
implements Preference.OnPreferenceChangeListener,
GraphicsDriverContentObserver.OnGraphicsDriverContentChangedListener,
LifecycleObserver, OnStart, OnStop {
public static final int GAME_DRIVER_DEFAULT = 0;
public static final int GAME_DRIVER_ALL_APPS = 1;
public static final int GAME_DRIVER_PRERELEASE_ALL_APPS = 2;
public static final int GAME_DRIVER_OFF = 3;
public static final String PROPERTY_GFX_DRIVER_GAME = "ro.gfx.driver.0";
public static final int UPDATABLE_DRIVER_DEFAULT = 0;
public static final int UPDATABLE_DRIVER_PRODUCTION_ALL_APPS = 1;
public static final int UPDATABLE_DRIVER_PRERELEASE_ALL_APPS = 2;
public static final int UPDATABLE_DRIVER_OFF = 3;
public static final String PROPERTY_GFX_DRIVER_PRODUCTION = "ro.gfx.driver.0";
public static final String PROPERTY_GFX_DRIVER_PRERELEASE = "ro.gfx.driver.1";
private final Context mContext;
private final ContentResolver mContentResolver;
private final String mPreferenceDefault;
private final String mPreferenceGameDriver;
private final String mPreferenceProductionDriver;
private final String mPreferencePrereleaseDriver;
@VisibleForTesting
CharSequence[] mEntryList;
@@ -79,8 +79,8 @@ public class GraphicsDriverEnableForAllAppsPreferenceController extends BasePref
final Resources resources = context.getResources();
mPreferenceDefault = resources.getString(R.string.graphics_driver_app_preference_default);
mPreferenceGameDriver =
resources.getString(R.string.graphics_driver_app_preference_game_driver);
mPreferenceProductionDriver =
resources.getString(R.string.graphics_driver_app_preference_production_driver);
mPreferencePrereleaseDriver =
resources.getString(R.string.graphics_driver_app_preference_prerelease_driver);
mEntryList = constructEntryList(mContext, false);
@@ -92,8 +92,9 @@ public class GraphicsDriverEnableForAllAppsPreferenceController extends BasePref
public int getAvailabilityStatus() {
return DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)
&& (Settings.Global.getInt(mContentResolver,
Settings.Global.GAME_DRIVER_ALL_APPS, GAME_DRIVER_DEFAULT)
!= GAME_DRIVER_OFF)
Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
UPDATABLE_DRIVER_DEFAULT)
!= UPDATABLE_DRIVER_OFF)
? AVAILABLE
: CONDITIONALLY_UNAVAILABLE;
}
@@ -122,11 +123,12 @@ public class GraphicsDriverEnableForAllAppsPreferenceController extends BasePref
final ListPreference listPref = (ListPreference) preference;
listPref.setVisible(isAvailable());
final int currentChoice = Settings.Global.getInt(
mContentResolver, Settings.Global.GAME_DRIVER_ALL_APPS, GAME_DRIVER_DEFAULT);
if (currentChoice == GAME_DRIVER_ALL_APPS) {
listPref.setValue(mPreferenceGameDriver);
listPref.setSummary(mPreferenceGameDriver);
} else if (currentChoice == GAME_DRIVER_PRERELEASE_ALL_APPS) {
mContentResolver, Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
UPDATABLE_DRIVER_DEFAULT);
if (currentChoice == UPDATABLE_DRIVER_PRODUCTION_ALL_APPS) {
listPref.setValue(mPreferenceProductionDriver);
listPref.setSummary(mPreferenceProductionDriver);
} else if (currentChoice == UPDATABLE_DRIVER_PRERELEASE_ALL_APPS) {
listPref.setValue(mPreferencePrereleaseDriver);
listPref.setSummary(mPreferencePrereleaseDriver);
} else {
@@ -140,21 +142,22 @@ public class GraphicsDriverEnableForAllAppsPreferenceController extends BasePref
final ListPreference listPref = (ListPreference) preference;
final String value = newValue.toString();
final int currentChoice = Settings.Global.getInt(
mContentResolver, Settings.Global.GAME_DRIVER_ALL_APPS, GAME_DRIVER_DEFAULT);
mContentResolver, Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
UPDATABLE_DRIVER_DEFAULT);
final int userChoice;
if (value.equals(mPreferenceGameDriver)) {
userChoice = GAME_DRIVER_ALL_APPS;
if (value.equals(mPreferenceProductionDriver)) {
userChoice = UPDATABLE_DRIVER_PRODUCTION_ALL_APPS;
} else if (value.equals(mPreferencePrereleaseDriver)) {
userChoice = GAME_DRIVER_PRERELEASE_ALL_APPS;
userChoice = UPDATABLE_DRIVER_PRERELEASE_ALL_APPS;
} else {
userChoice = GAME_DRIVER_DEFAULT;
userChoice = UPDATABLE_DRIVER_DEFAULT;
}
listPref.setValue(value);
listPref.setSummary(value);
if (userChoice != currentChoice) {
Settings.Global.putInt(
mContentResolver, Settings.Global.GAME_DRIVER_ALL_APPS, userChoice);
mContentResolver, Settings.Global.UPDATABLE_DRIVER_ALL_APPS, userChoice);
}
return true;
@@ -172,7 +175,8 @@ public class GraphicsDriverEnableForAllAppsPreferenceController extends BasePref
final Resources resources = context.getResources();
final String prereleaseDriverPackageName =
SystemProperties.get(PROPERTY_GFX_DRIVER_PRERELEASE);
final String gameDriverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER_GAME);
final String productionDriverPackageName =
SystemProperties.get(PROPERTY_GFX_DRIVER_PRODUCTION);
List<CharSequence> entryList = new ArrayList<>();
entryList.add(resources.getString(R.string.graphics_driver_app_preference_default));
@@ -182,9 +186,10 @@ public class GraphicsDriverEnableForAllAppsPreferenceController extends BasePref
entryList.add(resources.getString(
R.string.graphics_driver_app_preference_prerelease_driver));
}
if (!TextUtils.isEmpty(gameDriverPackageName)
&& hasDriverPackage(pm, gameDriverPackageName)) {
entryList.add(resources.getString(R.string.graphics_driver_app_preference_game_driver));
if (!TextUtils.isEmpty(productionDriverPackageName)
&& hasDriverPackage(pm, productionDriverPackageName)) {
entryList.add(resources.getString(
R.string.graphics_driver_app_preference_production_driver));
}
if (withSystem) {
entryList.add(resources.getString(R.string.graphics_driver_app_preference_system));

View File

@@ -16,8 +16,8 @@
package com.android.settings.development.graphicsdriver;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.GAME_DRIVER_DEFAULT;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.GAME_DRIVER_OFF;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.UPDATABLE_DRIVER_DEFAULT;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.UPDATABLE_DRIVER_OFF;
import android.content.ContentResolver;
import android.content.Context;
@@ -58,8 +58,9 @@ public class GraphicsDriverFooterPreferenceController extends BasePreferenceCont
@Override
public int getAvailabilityStatus() {
return Settings.Global.getInt(
mContentResolver, Settings.Global.GAME_DRIVER_ALL_APPS, GAME_DRIVER_DEFAULT)
== GAME_DRIVER_OFF
mContentResolver, Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
UPDATABLE_DRIVER_DEFAULT)
== UPDATABLE_DRIVER_OFF
? AVAILABLE_UNSEARCHABLE
: CONDITIONALLY_UNAVAILABLE;
}

View File

@@ -16,10 +16,10 @@
package com.android.settings.development.graphicsdriver;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.GAME_DRIVER_ALL_APPS;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.GAME_DRIVER_DEFAULT;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.GAME_DRIVER_OFF;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.GAME_DRIVER_PRERELEASE_ALL_APPS;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.UPDATABLE_DRIVER_DEFAULT;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.UPDATABLE_DRIVER_OFF;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.UPDATABLE_DRIVER_PRERELEASE_ALL_APPS;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableForAllAppsPreferenceController.UPDATABLE_DRIVER_PRODUCTION_ALL_APPS;
import android.content.ContentResolver;
import android.content.Context;
@@ -36,7 +36,7 @@ import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
/**
* Controller of global switch bar used to fully turn off Game Driver.
* Controller of global switch bar used to fully turn off updatable driver.
*/
public class GraphicsDriverGlobalSwitchBarController
implements SwitchWidgetController.OnSwitchChangeListener,
@@ -61,8 +61,9 @@ public class GraphicsDriverGlobalSwitchBarController
DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context));
mSwitchWidgetController.setChecked(
Settings.Global.getInt(
mContentResolver, Settings.Global.GAME_DRIVER_ALL_APPS, GAME_DRIVER_DEFAULT)
!= GAME_DRIVER_OFF);
mContentResolver, Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
UPDATABLE_DRIVER_DEFAULT)
!= UPDATABLE_DRIVER_OFF);
mSwitchWidgetController.setListener(this);
}
@@ -81,21 +82,22 @@ public class GraphicsDriverGlobalSwitchBarController
@Override
public boolean onSwitchToggled(boolean isChecked) {
final int graphicsDriverGlobalOption = Settings.Global.getInt(
mContentResolver, Settings.Global.GAME_DRIVER_ALL_APPS, GAME_DRIVER_DEFAULT);
mContentResolver, Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
UPDATABLE_DRIVER_DEFAULT);
if (isChecked
&& (graphicsDriverGlobalOption == GAME_DRIVER_DEFAULT
|| graphicsDriverGlobalOption == GAME_DRIVER_ALL_APPS
|| graphicsDriverGlobalOption == GAME_DRIVER_PRERELEASE_ALL_APPS)) {
&& (graphicsDriverGlobalOption == UPDATABLE_DRIVER_DEFAULT
|| graphicsDriverGlobalOption == UPDATABLE_DRIVER_PRODUCTION_ALL_APPS
|| graphicsDriverGlobalOption == UPDATABLE_DRIVER_PRERELEASE_ALL_APPS)) {
return true;
}
if (!isChecked && graphicsDriverGlobalOption == GAME_DRIVER_OFF) {
if (!isChecked && graphicsDriverGlobalOption == UPDATABLE_DRIVER_OFF) {
return true;
}
Settings.Global.putInt(mContentResolver, Settings.Global.GAME_DRIVER_ALL_APPS,
isChecked ? GAME_DRIVER_DEFAULT : GAME_DRIVER_OFF);
Settings.Global.putInt(mContentResolver, Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
isChecked ? UPDATABLE_DRIVER_DEFAULT : UPDATABLE_DRIVER_OFF);
return true;
}
@@ -104,7 +106,8 @@ public class GraphicsDriverGlobalSwitchBarController
public void onGraphicsDriverContentChanged() {
mSwitchWidgetController.setChecked(
Settings.Global.getInt(
mContentResolver, Settings.Global.GAME_DRIVER_ALL_APPS, GAME_DRIVER_DEFAULT)
!= GAME_DRIVER_OFF);
mContentResolver, Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
UPDATABLE_DRIVER_DEFAULT)
!= UPDATABLE_DRIVER_OFF);
}
}

View File

@@ -18,26 +18,29 @@ package com.android.settings.display.darkmode;
import android.content.Context;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Calendar;
import java.util.TimeZone;
/**
* Formats LocalTime to the locale time string format
*/
public class TimeFormatter {
private final Context mContext;
private final DateTimeFormatter mFormatter;
private final java.text.DateFormat mFormatter;
public TimeFormatter(Context context) {
mContext = context;
Locale locale = mContext.getResources().getConfiguration().locale;
if (locale == null) {
locale = Locale.getDefault();
}
mFormatter = DateTimeFormatter.ofPattern("hh:mm a", locale);
mFormatter = android.text.format.DateFormat.getTimeFormat(context);
mFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
}
public String of(LocalTime time) {
return mFormatter.format(time);
final Calendar c = Calendar.getInstance();
c.setTimeZone(mFormatter.getTimeZone());
c.set(Calendar.HOUR_OF_DAY, time.getHour());
c.set(Calendar.MINUTE, time.getMinute());
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
return mFormatter.format(c.getTime());
}
public boolean is24HourFormat() {

View File

@@ -34,6 +34,7 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.Utils;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -49,6 +50,8 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController
@VisibleForTesting
static final String KEY_BATTERY_HEADER = "battery_header";
@VisibleForTesting
BatteryStatusFeatureProvider mBatteryStatusFeatureProvider;
@VisibleForTesting
BatteryMeterView mBatteryMeterView;
@VisibleForTesting
@@ -66,6 +69,8 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController
public BatteryHeaderPreferenceController(Context context, String key) {
super(context, key);
mPowerManager = context.getSystemService(PowerManager.class);
mBatteryStatusFeatureProvider = FeatureFactory.getFactory(context)
.getBatteryStatusFeatureProvider(context);
}
public void setActivity(Activity activity) {
@@ -107,10 +112,12 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController
public void updateHeaderPreference(BatteryInfo info) {
mBatteryPercentText.setText(formatBatteryPercentageText(info.batteryLevel));
if (info.remainingLabel == null) {
mSummary1.setText(info.statusLabel);
} else {
mSummary1.setText(info.remainingLabel);
if (!mBatteryStatusFeatureProvider.triggerBatteryStatusUpdate(this, info)) {
if (info.remainingLabel == null) {
mSummary1.setText(info.statusLabel);
} else {
mSummary1.setText(info.remainingLabel);
}
}
mBatteryMeterView.setBatteryLevel(info.batteryLevel);
@@ -118,6 +125,13 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController
mBatteryMeterView.setPowerSave(mPowerManager.isPowerSaveMode());
}
/**
* Callback which receives text for the summary line.
*/
public void updateBatteryStatus(String statusLabel) {
mSummary1.setText(statusLabel);
}
public void quickUpdateHeaderPreference() {
Intent batteryBroadcast = mContext.registerReceiver(null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge;
/**
* Feature Provider used to retrieve battery status
*/
public interface BatteryStatusFeatureProvider {
/**
* Trigger a battery status update; return false if built-in status should be used.
*/
boolean triggerBatteryStatusUpdate(
BatteryHeaderPreferenceController batteryHeaderPreferenceController, BatteryInfo info);
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge;
import android.content.Context;
/**
* Used to override battery status string in Battery Header.
*/
public class BatteryStatusFeatureProviderImpl implements BatteryStatusFeatureProvider {
protected Context mContext;
public BatteryStatusFeatureProviderImpl(Context context) {
mContext = context.getApplicationContext();
}
@Override
public boolean triggerBatteryStatusUpdate(
BatteryHeaderPreferenceController batteryHeaderPreferenceController, BatteryInfo info) {
return false;
}
}

View File

@@ -24,6 +24,9 @@ public interface ContextualCardFeatureProvider {
/** Get contextual cards from the card provider */
Cursor getContextualCards();
/** Get the default contextual card to display */
ContextualCard getDefaultContextualCard();
/**
* Mark a specific {@link ContextualCard} as dismissed with dismissal signal in the database
* to indicate that the card has been dismissed.

View File

@@ -54,6 +54,11 @@ public class ContextualCardFeatureProviderImpl implements ContextualCardFeatureP
return cursor;
}
@Override
public ContextualCard getDefaultContextualCard() {
return null;
}
@Override
public int markCardAsDismissed(Context context, String cardName) {
final SQLiteDatabase db = CardDatabaseHelper.getInstance(mContext).getWritableDatabase();

View File

@@ -50,7 +50,7 @@ import java.util.stream.Collectors;
public class ContextualCardLoader extends AsyncLoaderCompat<List<ContextualCard>> {
@VisibleForTesting
static final int DEFAULT_CARD_COUNT = 3;
static final int DEFAULT_CARD_COUNT = 1;
@VisibleForTesting
static final String CONTEXTUAL_CARD_COUNT = "contextual_card_count";
static final int CARD_CONTENT_LOADER_ID = 1;
@@ -131,7 +131,7 @@ public class ContextualCardLoader extends AsyncLoaderCompat<List<ContextualCard>
final List<ContextualCard> visibleCards = new ArrayList<>();
final List<ContextualCard> hiddenCards = new ArrayList<>();
final int maxCardCount = getCardCount();
final int maxCardCount = getCardCount(mContext);
eligibleCards.forEach(card -> {
if (card.getCategory() != STICKY_VALUE) {
return;
@@ -164,14 +164,23 @@ public class ContextualCardLoader extends AsyncLoaderCompat<List<ContextualCard>
SettingsEnums.ACTION_CONTEXTUAL_CARD_NOT_SHOW,
ContextualCardLogUtils.buildCardListLog(hiddenCards));
}
// Add a default card if no other visible cards
if (visibleCards.isEmpty() && maxCardCount == 1) {
final ContextualCard defaultCard = FeatureFactory.getFactory(mContext)
.getContextualCardFeatureProvider(mContext).getDefaultContextualCard();
if (defaultCard != null) {
Log.i(TAG, "Default card: " + defaultCard.getSliceUri());
visibleCards.add(defaultCard);
}
}
return visibleCards;
}
@VisibleForTesting
int getCardCount() {
static int getCardCount(Context context) {
// Return the card count if Settings.Global has KEY_CONTEXTUAL_CARD_COUNT key,
// otherwise return the default one.
return Settings.Global.getInt(mContext.getContentResolver(),
return Settings.Global.getInt(context.getContentResolver(),
CONTEXTUAL_CARD_COUNT, DEFAULT_CARD_COUNT);
}

View File

@@ -122,6 +122,10 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo
Log.w(TAG, "Legacy suggestion contextual card enabled, skipping contextual cards.");
return;
}
if (ContextualCardLoader.getCardCount(mContext) <= 0) {
Log.w(TAG, "Card count is zero, skipping contextual cards.");
return;
}
mStartTime = System.currentTimeMillis();
final CardContentLoaderCallbacks cardContentLoaderCallbacks =
new CardContentLoaderCallbacks(mContext);
@@ -239,7 +243,7 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo
final MetricsFeatureProvider metricsFeatureProvider =
FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();
//navigate back to the homepage, screen rotate or after card dismissal
// navigate back to the homepage, screen rotate or after card dismissal
if (!mIsFirstLaunch) {
onContextualCardUpdated(cardsToKeep.stream()
.collect(groupingBy(ContextualCard::getCardType)));
@@ -262,8 +266,17 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo
SettingsEnums.ACTION_CONTEXTUAL_CARD_LOAD_TIMEOUT,
SettingsEnums.SETTINGS_HOMEPAGE,
null /* key */, (int) loadTime /* value */);
// display a card on timeout if the one-card space is pre-allocated
if (!cards.isEmpty() && ContextualCardLoader.getCardCount(mContext) == 1) {
onContextualCardUpdated(cards.stream()
.collect(groupingBy(ContextualCard::getCardType)));
metricsFeatureProvider.action(mContext,
SettingsEnums.ACTION_CONTEXTUAL_CARD_SHOW,
ContextualCardLogUtils.buildCardListLog(cards));
}
}
//only log homepage display upon a fresh launch
// only log homepage display upon a fresh launch
final long totalTime = System.currentTimeMillis() - mStartTime;
metricsFeatureProvider.action(mContext,
SettingsEnums.ACTION_CONTEXTUAL_HOME_SHOW, (int) totalTime);

View File

@@ -16,7 +16,10 @@
package com.android.settings.homepage.contextualcards;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -131,7 +134,22 @@ public class ContextualCardsAdapter extends RecyclerView.Adapter<RecyclerView.Vi
diffResult.dispatchUpdatesTo(this);
}
if (mRecyclerView != null && previouslyEmpty && !nowEmpty) {
if (mRecyclerView == null) {
return;
}
// When no card gets displayed either because a card's condition no longer meets
// or when it's dismissed, the height should be rearranged.
if (mContextualCards.isEmpty()) {
final ViewGroup.LayoutParams params = mRecyclerView.getLayoutParams();
if (params.height != WRAP_CONTENT) {
Log.d(TAG, "mContextualCards is empty. Set the RV to wrap_content");
params.height = WRAP_CONTENT;
mRecyclerView.setLayoutParams(params);
}
}
if (previouslyEmpty && !nowEmpty) {
// Adding items to empty list, should animate.
mRecyclerView.scheduleLayoutAnimation();
}

View File

@@ -16,6 +16,8 @@
package com.android.settings.homepage.contextualcards;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.settings.homepage.contextualcards.ContextualCardsAdapter.SPAN_COUNT;
import android.app.settings.SettingsEnums;
@@ -34,6 +36,7 @@ import androidx.annotation.VisibleForTesting;
import androidx.loader.app.LoaderManager;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
@@ -105,8 +108,20 @@ public class ContextualCardsFragment extends InstrumentedFragment implements
final View rootView = inflater.inflate(R.layout.settings_homepage, container, false);
mCardsContainer = rootView.findViewById(R.id.card_container);
mLayoutManager = new GridLayoutManager(getActivity(), SPAN_COUNT,
GridLayoutManager.VERTICAL, false /* reverseLayout */);
GridLayoutManager.VERTICAL, false /* reverseLayout */) {
@Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
// Once cards finish laying out, make the RV back to wrap content for flexibility.
final ViewGroup.LayoutParams params = mCardsContainer.getLayoutParams();
if (params.height != WRAP_CONTENT) {
params.height = WRAP_CONTENT;
mCardsContainer.setLayoutParams(params);
}
}
};
mCardsContainer.setLayoutManager(mLayoutManager);
preAllocateHeight(context);
mContextualCardsAdapter = new ContextualCardsAdapter(context, this /* lifecycleOwner */,
mContextualCardManager);
mCardsContainer.setItemAnimator(null);
@@ -159,6 +174,25 @@ public class ContextualCardsFragment extends InstrumentedFragment implements
FeatureFactory.getFactory(context).getSlicesFeatureProvider().newUiSession();
}
private void preAllocateHeight(Context context) {
final int cardCount = ContextualCardLoader.getCardCount(context);
if (cardCount != 1) {
// only pre-allocate space when card count is one
Log.d(TAG, "Skip height pre-allocating. card count = " + cardCount);
return;
}
final int preAllocatedHeight = getResources().getDimensionPixelSize(
R.dimen.contextual_card_preallocated_height);
if (preAllocatedHeight == 0) {
return;
}
final ViewGroup.LayoutParams params = mCardsContainer.getLayoutParams();
params.height = preAllocatedHeight;
mCardsContainer.setLayoutParams(params);
}
/**
* Receiver for updating UI session when home key or recent app key is pressed.
*/

View File

@@ -47,7 +47,6 @@ import com.android.settings.homepage.contextualcards.CardContentProvider;
import com.android.settings.homepage.contextualcards.ContextualCard;
import com.android.settings.homepage.contextualcards.ContextualCardRenderer;
import com.android.settings.homepage.contextualcards.ControllerRendererPool;
import com.android.settings.homepage.contextualcards.slices.SliceFullCardRendererHelper.SliceViewHolder;
import com.android.settingslib.utils.ThreadUtils;
import java.util.Map;
@@ -105,7 +104,7 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, Life
// Show cached slice first before slice binding completed to avoid jank.
if (holder.getItemViewType() != VIEW_TYPE_HALF_WIDTH) {
((SliceViewHolder) holder).sliceView.setSlice(card.getSlice());
mFullCardHelper.bindView(holder, card, card.getSlice());
}
LiveData<Slice> sliceLiveData = mSliceLiveDataMap.get(uri);

View File

@@ -27,6 +27,7 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.media.session.MediaController;
import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
@@ -62,8 +63,12 @@ public class MediaOutputIndicatorSlice implements CustomSliceable {
com.android.internal.R.drawable.ic_settings_bluetooth);
final CharSequence title = mContext.getString(R.string.media_output_label_title,
Utils.getApplicationLabel(mContext, getWorker().getPackageName()));
final int requestCode = TextUtils.isEmpty(getWorker().getPackageName())
? 0
: getWorker().getPackageName().hashCode();
final PendingIntent primaryActionIntent = PendingIntent.getActivity(mContext,
0 /* requestCode */, getMediaOutputSliceIntent(), FLAG_UPDATE_CURRENT);
requestCode,
getMediaOutputSliceIntent(), FLAG_UPDATE_CURRENT);
final SliceAction primarySliceAction = SliceAction.createDeeplink(
primaryActionIntent, icon, ListBuilder.ICON_IMAGE, title);
@ColorInt final int color = Utils.getColorAccentDefaultColor(mContext);

View File

@@ -106,10 +106,15 @@ public class MediaOutputSlice implements CustomSliceable {
final MediaDevice connectedDevice = worker.getCurrentConnectedMediaDevice();
if (devices.size() == 1) {
// Zero state
for (MediaDevice device : devices) {
addRow(device, device, listBuilder);
final MediaDevice device = devices.iterator().next();
addRow(device, device, listBuilder);
// Add "pair new" only when local output device exists
final int type = device.getDeviceType();
if (type == MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE
|| type == MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE
|| type == MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE) {
listBuilder.addRow(getPairNewRow());
}
listBuilder.addRow(getPairNewRow());
} else {
final boolean isTouched = worker.getIsTouched();
// Fix the last top device when user press device to transfer.
@@ -252,9 +257,11 @@ public class MediaOutputSlice implements CustomSliceable {
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
getWorker().getPackageName());
final int requestCode = TextUtils.isEmpty(getWorker().getPackageName())
? 0
: getWorker().getPackageName().hashCode();
return SliceAction.createDeeplink(
PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */),
PendingIntent.getActivity(mContext, requestCode, intent, 0 /* flags */),
IconCompat.createWithResource(mContext, R.drawable.ic_add_blue_24dp),
ListBuilder.ICON_IMAGE,
mContext.getText(R.string.add));

View File

@@ -17,6 +17,7 @@
package com.android.settings.media;
import static android.app.slice.Slice.EXTRA_RANGE_VALUE;
import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
import static com.android.settings.slices.CustomSliceRegistry.REMOTE_MEDIA_SLICE_URI;
@@ -24,11 +25,15 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
import android.net.Uri;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice;
import androidx.slice.builders.ListBuilder;
@@ -59,6 +64,9 @@ public class RemoteMediaSlice implements CustomSliceable {
private MediaDeviceUpdateWorker mWorker;
@VisibleForTesting
MediaRouter2Manager mRouterManager;
public RemoteMediaSlice(Context context) {
mContext = context;
}
@@ -80,6 +88,9 @@ public class RemoteMediaSlice implements CustomSliceable {
Log.e(TAG, "Unable to get the slice worker.");
return listBuilder.build();
}
if (mRouterManager == null) {
mRouterManager = MediaRouter2Manager.getInstance(mContext);
}
// Only displaying remote devices
final List<RoutingSessionInfo> infos = getWorker().getActiveRemoteMediaDevice();
if (infos.isEmpty()) {
@@ -91,7 +102,6 @@ public class RemoteMediaSlice implements CustomSliceable {
R.drawable.ic_volume_remote);
// To create an empty icon to indent the row
final IconCompat emptyIcon = createEmptyIcon();
int requestCode = 0;
for (RoutingSessionInfo info : infos) {
final int maxVolume = info.getVolumeMax();
if (maxVolume <= 0) {
@@ -99,20 +109,32 @@ public class RemoteMediaSlice implements CustomSliceable {
+ maxVolume);
continue;
}
final CharSequence appName = Utils.getApplicationLabel(
mContext, info.getClientPackageName());
final CharSequence outputTitle = mContext.getString(R.string.media_output_label_title,
Utils.getApplicationLabel(mContext, info.getClientPackageName()));
appName);
listBuilder.addInputRange(new InputRangeBuilder()
.setTitleItem(icon, ListBuilder.ICON_IMAGE)
.setTitle(castVolume)
.setInputAction(getSliderInputAction(requestCode++, info.getId()))
.setInputAction(getSliderInputAction(info.getId().hashCode(), info.getId()))
.setPrimaryAction(getSoundSettingAction(castVolume, icon, info.getId()))
.setMax(maxVolume)
.setValue(info.getVolume()));
final boolean isMediaOutputDisabled =
Utils.isMediaOutputDisabled(mRouterManager, info.getClientPackageName());
final SpannableString spannableTitle = new SpannableString(
TextUtils.isEmpty(appName) ? "" : appName);
spannableTitle.setSpan(new ForegroundColorSpan(
Utils.getColorAttrDefaultColor(
mContext, android.R.attr.textColorSecondary)), 0,
spannableTitle.length(), SPAN_EXCLUSIVE_EXCLUSIVE);
listBuilder.addRow(new ListBuilder.RowBuilder()
.setTitle(outputTitle)
.setTitle(isMediaOutputDisabled ? spannableTitle : outputTitle)
.setSubtitle(info.getName())
.setTitleItem(emptyIcon, ListBuilder.ICON_IMAGE)
.setPrimaryAction(getMediaOutputSliceAction(info.getClientPackageName())));
.setPrimaryAction(getMediaOutputSliceAction(
info.getClientPackageName(), isMediaOutputDisabled)));
}
return listBuilder.build();
}
@@ -145,15 +167,19 @@ public class RemoteMediaSlice implements CustomSliceable {
return primarySliceAction;
}
private SliceAction getMediaOutputSliceAction(String packageName) {
private SliceAction getMediaOutputSliceAction(
String packageName, boolean isMediaOutputDisabled) {
final Intent intent = new Intent()
.setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
.setAction(isMediaOutputDisabled
? ""
: MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, packageName);
final IconCompat icon = IconCompat.createWithResource(mContext,
R.drawable.ic_volume_remote);
final int requestCode = TextUtils.isEmpty(packageName) ? 0 : packageName.hashCode();
final PendingIntent primaryActionIntent = PendingIntent.getActivity(mContext,
0 /* requestCode */, intent, 0 /* flags */);
requestCode, intent, 0 /* flags */);
final SliceAction primarySliceAction = SliceAction.createDeeplink(
primaryActionIntent, icon, ListBuilder.ICON_IMAGE,
mContext.getString(R.string.media_output_label_title,

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.network;
import android.content.Context;
import android.provider.Settings;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
/**
* {@link BasePreferenceController} that shows Adaptive connectivity on/off state.
*/
public class AdaptiveConnectivityPreferenceController extends BasePreferenceController {
public AdaptiveConnectivityPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
}
@Override
public int getAvailabilityStatus() {
return mContext.getResources().getBoolean(R.bool.config_show_adaptive_connectivity)
? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
public CharSequence getSummary() {
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED, 1) == 1
? mContext.getString(R.string.switch_on_text)
: mContext.getString(R.string.switch_off_text);
}
}

View File

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

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.network;
import android.content.Context;
import android.provider.Settings;
import androidx.preference.PreferenceScreen;
import com.android.settings.core.TogglePreferenceController;
/**
* {@link TogglePreferenceController} that controls whether Adaptive connectivity option is enabled.
*/
public class AdaptiveConnectivityTogglePreferenceController extends TogglePreferenceController {
public AdaptiveConnectivityTogglePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public boolean isChecked() {
return Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED, 1) == 1;
}
@Override
public boolean setChecked(boolean isChecked) {
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED,
isChecked ? 1 : 0);
return true;
}
}

View File

@@ -280,7 +280,8 @@ public class MobileNetworkUtils {
String.format("showEuiccSettings: esimIgnoredDevice: %b, enabledEsimUiByDefault: "
+ "%b, euiccProvisioned: %b, inDeveloperMode: %b.",
esimIgnoredDevice, enabledEsimUiByDefault, euiccProvisioned, inDeveloperMode));
return (inDeveloperMode || euiccProvisioned
return (euiccProvisioned
|| (!esimIgnoredDevice && inDeveloperMode)
|| (!esimIgnoredDevice && enabledEsimUiByDefault
&& isCurrentCountrySupported(context)));
}

View File

@@ -126,10 +126,8 @@ public class NetworkOperatorPreference extends Preference {
final CellSignalStrength signalStrength = getCellSignalStrength(mCellInfo);
final int level = signalStrength != null ? signalStrength.getLevel() : LEVEL_NONE;
if (DBG) Log.d(TAG, "refresh level: " + String.valueOf(level));
if (mLevel != level) {
mLevel = level;
updateIcon(mLevel);
}
mLevel = level;
updateIcon(mLevel);
}
/**

View File

@@ -55,6 +55,13 @@ public class NrDisabledInDsdsFooterPreferenceController extends BasePreferenceCo
super.updateState(preference);
if (preference != null) {
// This is necessary to ensure that setting the title to the spannable string returned
// by getFooterText will be accepted. Internally, setTitle does an equality check on
// the spannable string being set to the text already set on the preference. That
// equality check apparently only takes into account the raw text and not and spannables
// that are part of the text. So we clear the title before applying the spannable
// footer to ensure it is accepted.
preference.setTitle("");
preference.setTitle(getFooterText());
}
}

View File

@@ -18,8 +18,6 @@ package com.android.settings.network.telephony;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyManager;
/**
* Contains hidden constants copied from the platform.
*/
@@ -211,6 +209,12 @@ public class TelephonyConstants {
* Copied from {@link android.telephony.RadioAccessFamily}
*/
public static class RadioAccessFamily {
/**
* TODO: get rid of RAF definition in RadioAccessFamily and
* use {@link TelephonyManager.NetworkTypeBitMask}
* TODO: public definition {@link TelephonyManager.NetworkTypeBitMask} is long.
* TODO: Convert from int * to long everywhere including HAL definitions.
*/
// 2G
public static final int RAF_UNKNOWN = (int) TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN;
public static final int RAF_GSM = (int) TelephonyManager.NETWORK_TYPE_BITMASK_GSM;
@@ -246,9 +250,7 @@ public class TelephonyConstants {
public static final int WCDMA = HS | RAF_UMTS;
// 4G
public static final int LTE = RAF_LTE | RAF_LTE_CA;
// 5G
public static final int NR = RAF_NR;
}
}

View File

@@ -64,6 +64,7 @@ public abstract class AdjustVolumeRestrictedPreferenceController extends
filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
filter.addAction(AudioManager.MASTER_MUTE_CHANGED_ACTION);
filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
return filter;
}
}

View File

@@ -18,6 +18,7 @@ package com.android.settings.notification;
import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -81,7 +82,8 @@ public class BubbleNotificationPreferenceController extends TogglePreferenceCont
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
ActivityManager am = mContext.getSystemService(ActivityManager.class);
return am.isLowRamDevice() ? UNSUPPORTED_ON_DEVICE : AVAILABLE;
}
@Override

View File

@@ -18,6 +18,7 @@ package com.android.settings.notification;
import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
import android.app.ActivityManager;
import android.content.Context;
import android.provider.Settings;
@@ -48,7 +49,8 @@ public class BubbleSummaryNotificationPreferenceController extends BasePreferenc
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
ActivityManager am = mContext.getSystemService(ActivityManager.class);
return am.isLowRamDevice() ? UNSUPPORTED_ON_DEVICE : AVAILABLE;
}
private boolean areBubblesEnabled() {

View File

@@ -18,6 +18,7 @@ package com.android.settings.notification;
import android.content.Context;
import android.content.Intent;
import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
import android.text.TextUtils;
@@ -57,6 +58,8 @@ public class RemoteVolumeGroupController extends BasePreferenceController implem
@VisibleForTesting
LocalMediaManager mLocalMediaManager;
@VisibleForTesting
MediaRouter2Manager mRouterManager;
public RemoteVolumeGroupController(Context context, String preferenceKey) {
super(context, preferenceKey);
@@ -65,6 +68,7 @@ public class RemoteVolumeGroupController extends BasePreferenceController implem
mLocalMediaManager.registerCallback(this);
mLocalMediaManager.startScan();
}
mRouterManager = MediaRouter2Manager.getInstance(context);
}
@Override
@@ -111,8 +115,10 @@ public class RemoteVolumeGroupController extends BasePreferenceController implem
if (mPreferenceCategory.findPreference(info.getId()) != null) {
continue;
}
final CharSequence appName = Utils.getApplicationLabel(
mContext, info.getClientPackageName());
final CharSequence outputTitle = mContext.getString(R.string.media_output_label_title,
Utils.getApplicationLabel(mContext, info.getClientPackageName()));
appName);
// Add slider
final RemoteVolumeSeekBarPreference seekBarPreference =
new RemoteVolumeSeekBarPreference(mContext);
@@ -125,10 +131,13 @@ public class RemoteVolumeGroupController extends BasePreferenceController implem
seekBarPreference.setIcon(R.drawable.ic_volume_remote);
mPreferenceCategory.addPreference(seekBarPreference);
// Add output indicator
final boolean isMediaOutputDisabled = Utils.isMediaOutputDisabled(
mRouterManager, info.getClientPackageName());
final Preference preference = new Preference(mContext);
preference.setKey(SWITCHER_PREFIX + info.getId());
preference.setTitle(outputTitle);
preference.setTitle(isMediaOutputDisabled ? appName : outputTitle);
preference.setSummary(info.getName());
preference.setEnabled(!isMediaOutputDisabled);
mPreferenceCategory.addPreference(preference);
}
}

View File

@@ -76,6 +76,7 @@ public class SoundSettings extends DashboardFragment implements OnActivityResult
private RingtonePreference mRequestPreference;
private UpdatableListPreferenceDialogFragment mDialogFragment;
private String mHfpOutputControllerKey;
private String mVibrationPreferencesKey = "vibration_preference_screen";
@Override
public int getMetricsCategory() {
@@ -127,6 +128,10 @@ public class SoundSettings extends DashboardFragment implements OnActivityResult
@Override
public void onDisplayPreferenceDialog(Preference preference) {
if (TextUtils.equals(mVibrationPreferencesKey, preference.getKey())) {
super.onDisplayPreferenceDialog(preference);
return;
}
final int metricsCategory;
if (mHfpOutputControllerKey.equals(preference.getKey())) {
metricsCategory = SettingsEnums.DIALOG_SWITCH_HFP_DEVICES;
@@ -310,4 +315,4 @@ public class SoundSettings extends DashboardFragment implements OnActivityResult
mDialogFragment.onListPreferenceUpdated(preference);
}
}
}
}

View File

@@ -107,7 +107,10 @@ public abstract class VolumeSeekBarPreferenceController extends
return mHelper.getMinVolume(getAudioStream());
}
protected abstract int getAudioStream();
/**
* @return the audio stream type
*/
public abstract int getAudioStream();
protected abstract int getMuteIcon();

View File

@@ -106,8 +106,11 @@ public class BlockPreferenceController extends NotificationPreferenceController
// It's always safe to override the importance if it's meant to be blocked or if
// it was blocked and we are unblocking it.
if (blocked || originalImportance == IMPORTANCE_NONE) {
final int importance = blocked ? IMPORTANCE_NONE
: isDefaultChannel() ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_DEFAULT;
final int importance = blocked
? IMPORTANCE_NONE
: isDefaultChannel()
? IMPORTANCE_UNSPECIFIED
: mChannel.getOriginalImportance();
mChannel.setImportance(importance);
saveChannel();
}

View File

@@ -45,8 +45,6 @@ public class BubblePreference extends Preference implements View.OnClickListener
private int mSelectedPreference;
private Context mContext;
private Drawable mSelectedBackground;
private Drawable mUnselectedBackground;
private ButtonViewHolder mBubbleAllButton;
private ButtonViewHolder mBubbleSelectedButton;
@@ -72,8 +70,6 @@ public class BubblePreference extends Preference implements View.OnClickListener
mHelper = new RestrictedPreferenceHelper(context, this, attrs);
mHelper.useAdminDisabledSummary(true);
mContext = context;
mSelectedBackground = mContext.getDrawable(R.drawable.button_border_selected);
mUnselectedBackground = mContext.getDrawable(R.drawable.button_border_unselected);
setLayoutResource(R.layout.bubble_preference);
}
@@ -167,7 +163,9 @@ public class BubblePreference extends Preference implements View.OnClickListener
}
void setSelected(Context context, boolean selected) {
mView.setBackground(selected ? mSelectedBackground : mUnselectedBackground);
mView.setBackground(mContext.getDrawable(selected
? R.drawable.button_border_selected
: R.drawable.button_border_unselected));
mView.setSelected(selected);
ColorStateList stateList = selected

View File

@@ -19,6 +19,7 @@ package com.android.settings.notification.app;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
import android.app.ActivityManager;
import android.annotation.Nullable;
import android.content.Context;
import android.provider.Settings;
@@ -77,7 +78,7 @@ public class BubblePreferenceController extends NotificationPreferenceController
if (isDefaultChannel()) {
return true;
} else {
return mAppRow != null;
return mAppRow != null && mAppRow.bubblePreference != BUBBLE_PREFERENCE_NONE;
}
}
return true;
@@ -139,7 +140,8 @@ public class BubblePreferenceController extends NotificationPreferenceController
}
private boolean isGloballyEnabled() {
return Settings.Global.getInt(mContext.getContentResolver(),
ActivityManager am = mContext.getSystemService(ActivityManager.class);
return !am.isLowRamDevice() && Settings.Global.getInt(mContext.getContentResolver(),
NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF) == SYSTEM_WIDE_ON;
}

View File

@@ -20,6 +20,7 @@ import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -99,7 +100,8 @@ public class BubbleSummaryPreferenceController extends NotificationPreferenceCon
}
private boolean isGloballyEnabled() {
return Settings.Global.getInt(mContext.getContentResolver(),
ActivityManager am = mContext.getSystemService(ActivityManager.class);
return !am.isLowRamDevice() && Settings.Global.getInt(mContext.getContentResolver(),
NOTIFICATION_BUBBLES, ON) == ON;
}
}

View File

@@ -207,17 +207,14 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
channelPref.setOnPreferenceChangeListener(
(preference, o) -> {
boolean value = (Boolean) o;
int importance = value ? IMPORTANCE_LOW : IMPORTANCE_NONE;
int importance = value ? channel.getOriginalImportance() : IMPORTANCE_NONE;
channel.setImportance(importance);
channel.lockFields(
NotificationChannel.USER_LOCKED_IMPORTANCE);
channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
MasterSwitchPreference channelPref1 = (MasterSwitchPreference) preference;
channelPref1.setIcon(null);
if (channel.getImportance() > IMPORTANCE_LOW) {
channelPref1.setIcon(getAlertingIcon());
}
toggleBehaviorIconState(channelPref1.getIcon(),
importance != IMPORTANCE_NONE);
mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, channel);
return true;
@@ -234,24 +231,6 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
return icon;
}
private void toggleBehaviorIconState(Drawable icon, boolean enabled) {
if (icon == null) return;
LayerDrawable layerDrawable = (LayerDrawable) icon;
GradientDrawable background =
(GradientDrawable) layerDrawable.findDrawableByLayerId(R.id.back);
if (background == null) return;
if (enabled) {
background.clearColorFilter();
} else {
background.setColorFilter(new BlendModeColorFilter(
mContext.getColor(R.color.material_grey_300),
BlendMode.SRC_IN));
}
}
protected void onGroupBlockStateChanged(NotificationChannelGroup group) {
if (group == null) {
return;

View File

@@ -50,8 +50,6 @@ public class ConversationPriorityPreference extends Preference {
private View mAlertButton;
private View mPriorityButton;
private Context mContext;
Drawable selectedBackground;
Drawable unselectedBackground;
private static final int BUTTON_ANIM_TIME_MS = 100;
public ConversationPriorityPreference(Context context, AttributeSet attrs,
@@ -77,8 +75,6 @@ public class ConversationPriorityPreference extends Preference {
private void init(Context context) {
mContext = context;
selectedBackground = mContext.getDrawable(R.drawable.button_border_selected);
unselectedBackground = mContext.getDrawable(R.drawable.button_border_unselected);
setLayoutResource(R.layout.notif_priority_conversation_preference);
}
@@ -148,86 +144,43 @@ public class ConversationPriorityPreference extends Preference {
TransitionManager.beginDelayedTransition(parent, transition);
}
ColorStateList colorAccent = getAccentTint();
ColorStateList colorNormal = getRegularTint();
ImageView silenceIcon = parent.findViewById(R.id.silence_icon);
TextView silenceLabel = parent.findViewById(R.id.silence_label);
TextView silenceSummary = parent.findViewById(R.id.silence_summary);
ImageView alertIcon = parent.findViewById(R.id.alert_icon);
TextView alertLabel = parent.findViewById(R.id.alert_label);
TextView alertSummary = parent.findViewById(R.id.alert_summary);
ImageView priorityIcon = parent.findViewById(R.id.priority_icon);
TextView priorityLabel = parent.findViewById(R.id.priority_label);
TextView prioritySummary = parent.findViewById(R.id.priority_summary);
if (importance <= IMPORTANCE_LOW && importance > IMPORTANCE_UNSPECIFIED) {
alertSummary.setVisibility(GONE);
alertIcon.setImageTintList(colorNormal);
alertLabel.setTextColor(colorNormal);
prioritySummary.setVisibility(GONE);
priorityIcon.setImageTintList(colorNormal);
priorityLabel.setTextColor(colorNormal);
silenceIcon.setImageTintList(colorAccent);
silenceLabel.setTextColor(colorAccent);
silenceSummary.setVisibility(VISIBLE);
mAlertButton.setBackground(unselectedBackground);
mPriorityButton.setBackground(unselectedBackground);
mSilenceButton.setBackground(selectedBackground);
// a11y service won't always read the newly appearing text in the right order if the
// selection happens too soon (readback happens on a different thread as layout). post
// the selection to make that conflict less likely
parent.post(() -> {
mSilenceButton.setSelected(true);
mAlertButton.setSelected(false);
mPriorityButton.setSelected(false);
});
setSelected(mPriorityButton, false);
setSelected(mAlertButton, false);
setSelected(mSilenceButton, true);
} else {
if (isPriority) {
alertSummary.setVisibility(GONE);
alertIcon.setImageTintList(colorNormal);
alertLabel.setTextColor(colorNormal);
prioritySummary.setVisibility(VISIBLE);
priorityIcon.setImageTintList(colorAccent);
priorityLabel.setTextColor(colorAccent);
silenceIcon.setImageTintList(colorNormal);
silenceLabel.setTextColor(colorNormal);
silenceSummary.setVisibility(GONE);
mAlertButton.setBackground(unselectedBackground);
mPriorityButton.setBackground(selectedBackground);
mSilenceButton.setBackground(unselectedBackground);
parent.post(() -> {
mSilenceButton.setSelected(false);
mAlertButton.setSelected(false);
mPriorityButton.setSelected(true);
});
setSelected(mPriorityButton, true);
setSelected(mAlertButton, false);
setSelected(mSilenceButton, false);
} else {
alertSummary.setVisibility(VISIBLE);
alertIcon.setImageTintList(colorAccent);
alertLabel.setTextColor(colorAccent);
prioritySummary.setVisibility(GONE);
priorityIcon.setImageTintList(colorNormal);
priorityLabel.setTextColor(colorNormal);
silenceIcon.setImageTintList(colorNormal);
silenceLabel.setTextColor(colorNormal);
silenceSummary.setVisibility(GONE);
mAlertButton.setBackground(selectedBackground);
mPriorityButton.setBackground(unselectedBackground);
mSilenceButton.setBackground(unselectedBackground);
parent.post(() -> {
mSilenceButton.setSelected(false);
mAlertButton.setSelected(true);
mPriorityButton.setSelected(false);
});
setSelected(mPriorityButton, false);
setSelected(mAlertButton, true);
setSelected(mSilenceButton, false);
}
}
}
void setSelected(View view, boolean selected) {
ColorStateList colorAccent = getAccentTint();
ColorStateList colorNormal = getRegularTint();
ImageView icon = view.findViewById(R.id.icon);
TextView label = view.findViewById(R.id.label);
TextView summary = view.findViewById(R.id.summary);
icon.setImageTintList(selected ? colorAccent : colorNormal);
label.setTextColor(selected ? colorAccent : colorNormal);
summary.setVisibility(selected ? VISIBLE : GONE);
view.setBackground(mContext.getDrawable(selected
? R.drawable.button_border_selected
: R.drawable.button_border_unselected));
// a11y service won't always read the newly appearing text in the right order if the
// selection happens too soon (readback happens on a different thread as layout). post
// the selection to make that conflict less likely
view.post(() -> {
view.setSelected(selected);
});
}
}

View File

@@ -155,21 +155,22 @@ public class NotificationHistoryActivity extends Activity {
final View container = viewForPackage.findViewById(R.id.notification_list);
container.setVisibility(View.GONE);
ImageButton expand = viewForPackage.findViewById(R.id.expand);
expand.setContentDescription(container.getVisibility() == View.VISIBLE
View header = viewForPackage.findViewById(R.id.app_header);
ImageView expand = viewForPackage.findViewById(R.id.expand);
header.setStateDescription(container.getVisibility() == View.VISIBLE
? getString(R.string.condition_expand_hide)
: getString(R.string.condition_expand_show));
int finalI = i;
expand.setOnClickListener(v -> {
header.setOnClickListener(v -> {
container.setVisibility(container.getVisibility() == View.VISIBLE
? View.GONE : View.VISIBLE);
expand.setImageResource(container.getVisibility() == View.VISIBLE
? R.drawable.ic_expand_less
: com.android.internal.R.drawable.ic_expand_more);
expand.setContentDescription(container.getVisibility() == View.VISIBLE
header.setStateDescription(container.getVisibility() == View.VISIBLE
? getString(R.string.condition_expand_hide)
: getString(R.string.condition_expand_show));
expand.sendAccessibilityEvent(TYPE_VIEW_ACCESSIBILITY_FOCUSED);
header.sendAccessibilityEvent(TYPE_VIEW_ACCESSIBILITY_FOCUSED);
mUiEventLogger.logWithPosition(
(container.getVisibility() == View.VISIBLE)
? NotificationHistoryEvent.NOTIFICATION_HISTORY_PACKAGE_HISTORY_OPEN

View File

@@ -33,7 +33,6 @@ public class NotificationHistoryViewHolder extends RecyclerView.ViewHolder {
NotificationHistoryViewHolder(View itemView) {
super(itemView);
mTime = itemView.findViewById(R.id.timestamp);
mTime.setShowRelativeTime(true);
mTitle = itemView.findViewById(R.id.title);
mSummary = itemView.findViewById(R.id.text);
}

View File

@@ -108,7 +108,7 @@ public class NotificationSbnViewHolder extends RecyclerView.ViewHolder {
: NotificationHistoryActivity.NotificationHistoryEvent
.NOTIFICATION_HISTORY_RECENT_ITEM_CLICK,
uid, pkg, instanceId, position);
if (pi != null) {
if (pi != null && isPendingIntentValid) {
try {
pi.send();
} catch (PendingIntent.CanceledException e) {

View File

@@ -31,6 +31,7 @@ import com.android.settings.bluetooth.BluetoothFeatureProvider;
import com.android.settings.dashboard.DashboardFeatureProvider;
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
import com.android.settings.fuelgauge.BatteryStatusFeatureProvider;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.gestures.AssistGestureFeatureProvider;
import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider;
@@ -103,6 +104,12 @@ public abstract class FeatureFactory {
public abstract PowerUsageFeatureProvider getPowerUsageFeatureProvider(Context context);
/**
* Retrieve implementation for Battery Status feature.
*/
public abstract BatteryStatusFeatureProvider getBatteryStatusFeatureProvider(
Context context);
public abstract DashboardFeatureProvider getDashboardFeatureProvider(Context context);
public abstract DockUpdaterFeatureProvider getDockUpdaterFeatureProvider();

View File

@@ -42,6 +42,8 @@ import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
import com.android.settings.dashboard.suggestions.SuggestionFeatureProviderImpl;
import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
import com.android.settings.enterprise.EnterprisePrivacyFeatureProviderImpl;
import com.android.settings.fuelgauge.BatteryStatusFeatureProvider;
import com.android.settings.fuelgauge.BatteryStatusFeatureProviderImpl;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.fuelgauge.PowerUsageFeatureProviderImpl;
import com.android.settings.gestures.AssistGestureFeatureProvider;
@@ -78,6 +80,7 @@ public class FeatureFactoryImpl extends FeatureFactory {
private SecurityFeatureProvider mSecurityFeatureProvider;
private SuggestionFeatureProvider mSuggestionFeatureProvider;
private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
private BatteryStatusFeatureProvider mBatteryStatusFeatureProvider;
private AssistGestureFeatureProvider mAssistGestureFeatureProvider;
private UserFeatureProvider mUserFeatureProvider;
private SlicesFeatureProvider mSlicesFeatureProvider;
@@ -110,6 +113,15 @@ public class FeatureFactoryImpl extends FeatureFactory {
return mPowerUsageFeatureProvider;
}
@Override
public BatteryStatusFeatureProvider getBatteryStatusFeatureProvider(Context context) {
if (mBatteryStatusFeatureProvider == null) {
mBatteryStatusFeatureProvider = new BatteryStatusFeatureProviderImpl(
context.getApplicationContext());
}
return mBatteryStatusFeatureProvider;
}
@Override
public DashboardFeatureProvider getDashboardFeatureProvider(Context context) {
if (mDashboardFeatureProvider == null) {

View File

@@ -25,11 +25,7 @@ import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_SLICE
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
@@ -109,8 +105,7 @@ public class MediaOutputPanel implements PanelContent, LocalMediaManager.DeviceC
@Override
public IconCompat getIcon() {
if (mMediaController == null) {
return IconCompat.createWithResource(mContext, R.drawable.ic_media_stream).setTint(
Utils.getColorAccentDefaultColor(mContext));
return null;
}
final MediaMetadata metadata = mMediaController.getMetadata();
if (metadata != null) {
@@ -124,25 +119,6 @@ public class MediaOutputPanel implements PanelContent, LocalMediaManager.DeviceC
}
}
Log.d(TAG, "Media meta data does not contain icon information");
return getPackageIcon();
}
private IconCompat getPackageIcon() {
try {
final Drawable drawable = mContext.getPackageManager().getApplicationIcon(mPackageName);
if (drawable instanceof BitmapDrawable) {
return IconCompat.createWithBitmap(((BitmapDrawable) drawable).getBitmap());
}
final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return IconCompat.createWithBitmap(bitmap);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Package is not found. Unable to get package icon.");
}
return null;
}

View File

@@ -93,10 +93,12 @@ public class PanelFragment extends Fragment {
private String mPanelClosedKey;
private LinearLayout mPanelHeader;
private ImageView mTitleIcon;
private LinearLayout mTitleGroup;
private TextView mHeaderTitle;
private TextView mHeaderSubtitle;
private int mMaxHeight;
private View mFooterDivider;
private boolean mPanelCreating;
private final Map<Uri, LiveData<Slice>> mSliceLiveData = new LinkedHashMap<>();
@@ -127,6 +129,7 @@ public class PanelFragment extends Fragment {
if (mPanelSlices != null) {
mPanelSlices.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
mPanelCreating = false;
}
};
@@ -140,6 +143,7 @@ public class PanelFragment extends Fragment {
mLayoutView.getViewTreeObserver()
.addOnGlobalLayoutListener(mPanelLayoutListener);
mMaxHeight = getResources().getDimensionPixelSize(R.dimen.output_switcher_slice_max_height);
mPanelCreating = true;
createPanelContent();
return mLayoutView;
}
@@ -153,6 +157,7 @@ public class PanelFragment extends Fragment {
* Call createPanelContent() once animation end.
*/
void updatePanelWithAnimation() {
mPanelCreating = true;
final View panelContent = mLayoutView.findViewById(R.id.panel_container);
final AnimatorSet animatorSet = buildAnimatorSet(mLayoutView,
0.0f /* startY */, panelContent.getHeight() /* endY */,
@@ -171,6 +176,10 @@ public class PanelFragment extends Fragment {
animatorSet.start();
}
boolean isPanelCreating() {
return mPanelCreating;
}
private void createPanelContent() {
final FragmentActivity activity = getActivity();
if (activity == null) {
@@ -181,6 +190,7 @@ public class PanelFragment extends Fragment {
activity.finish();
return;
}
final ViewGroup.LayoutParams params = mLayoutView.getLayoutParams();
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
mLayoutView.setLayoutParams(params);
@@ -191,6 +201,7 @@ public class PanelFragment extends Fragment {
mTitleView = mLayoutView.findViewById(R.id.panel_title);
mPanelHeader = mLayoutView.findViewById(R.id.panel_header);
mTitleIcon = mLayoutView.findViewById(R.id.title_icon);
mTitleGroup = mLayoutView.findViewById(R.id.title_group);
mHeaderTitle = mLayoutView.findViewById(R.id.header_title);
mHeaderSubtitle = mLayoutView.findViewById(R.id.header_subtitle);
mFooterDivider = mLayoutView.findViewById(R.id.footer_divider);
@@ -228,26 +239,13 @@ public class PanelFragment extends Fragment {
final IconCompat icon = mPanel.getIcon();
final CharSequence title = mPanel.getTitle();
if (icon == null) {
if (icon != null || mPanel.getViewType() == PanelContent.VIEW_TYPE_SLIDER_LARGE_ICON) {
enablePanelHeader(icon, title);
} else {
mTitleView.setVisibility(View.VISIBLE);
mPanelHeader.setVisibility(View.GONE);
mTitleView.setText(title);
} else {
mTitleView.setVisibility(View.GONE);
mPanelHeader.setVisibility(View.VISIBLE);
mPanelHeader.setAccessibilityPaneTitle(title);
mTitleIcon.setImageIcon(icon.toIcon(getContext()));
mHeaderTitle.setText(title);
mHeaderSubtitle.setText(mPanel.getSubTitle());
if (mPanel.getHeaderIconIntent() != null) {
mTitleIcon.setOnClickListener(getHeaderIconListener());
mTitleIcon.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
} else {
final int size = getResources().getDimensionPixelSize(
R.dimen.output_switcher_panel_icon_size);
mTitleIcon.setLayoutParams(new LinearLayout.LayoutParams(size, size));
}
}
if (mPanel.getViewType() == PanelContent.VIEW_TYPE_SLIDER_LARGE_ICON) {
@@ -281,6 +279,29 @@ public class PanelFragment extends Fragment {
0 /* value */);
}
private void enablePanelHeader(IconCompat icon, CharSequence title) {
mTitleView.setVisibility(View.GONE);
mPanelHeader.setVisibility(View.VISIBLE);
mPanelHeader.setAccessibilityPaneTitle(title);
mHeaderTitle.setText(title);
mHeaderSubtitle.setText(mPanel.getSubTitle());
if (icon != null) {
mTitleGroup.setVisibility(View.VISIBLE);
mTitleIcon.setImageIcon(icon.toIcon(getContext()));
if (mPanel.getHeaderIconIntent() != null) {
mTitleIcon.setOnClickListener(getHeaderIconListener());
mTitleIcon.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
} else {
final int size = getResources().getDimensionPixelSize(
R.dimen.output_switcher_panel_icon_size);
mTitleIcon.setLayoutParams(new LinearLayout.LayoutParams(size, size));
}
} else {
mTitleGroup.setVisibility(View.GONE);
}
}
private void loadAllSlices() {
mSliceLiveData.clear();
final List<Uri> sliceUris = mPanel.getSlices();
@@ -477,7 +498,13 @@ public class PanelFragment extends Fragment {
@Override
public void onHeaderChanged() {
ThreadUtils.postOnMainThread(() -> {
mTitleIcon.setImageIcon(mPanel.getIcon().toIcon(getContext()));
final IconCompat icon = mPanel.getIcon();
if (icon != null) {
mTitleIcon.setImageIcon(icon.toIcon(getContext()));
mTitleGroup.setVisibility(View.VISIBLE);
} else {
mTitleGroup.setVisibility(View.GONE);
}
mHeaderTitle.setText(mPanel.getTitle());
mHeaderSubtitle.setText(mPanel.getSubTitle());
});

View File

@@ -21,6 +21,7 @@ import static com.android.settingslib.media.MediaOutputSliceConstants.EXTRA_PACK
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.Window;
@@ -41,12 +42,14 @@ import com.android.settings.core.HideNonSystemOverlayMixin;
*/
public class SettingsPanelActivity extends FragmentActivity {
private final String TAG = "panel_activity";
private static final String TAG = "SettingsPanelActivity";
@VisibleForTesting
final Bundle mBundle = new Bundle();
@VisibleForTesting
boolean mForceCreation = false;
@VisibleForTesting
PanelFragment mPanelFragment;
/**
* Key specifying which Panel the app is requesting.
@@ -87,7 +90,9 @@ public class SettingsPanelActivity extends FragmentActivity {
@Override
protected void onStop() {
super.onStop();
mForceCreation = true;
if (mPanelFragment != null && !mPanelFragment.isPanelCreating()) {
mForceCreation = true;
}
}
@Override
@@ -104,10 +109,10 @@ public class SettingsPanelActivity extends FragmentActivity {
return;
}
final String action = callingIntent.getAction();
// We will use it once media output switch panel support remote device.
final String mediaPackageName = callingIntent.getStringExtra(EXTRA_PACKAGE_NAME);
mBundle.putString(KEY_PANEL_TYPE_ARGUMENT, callingIntent.getAction());
mBundle.putString(KEY_PANEL_TYPE_ARGUMENT, action);
mBundle.putString(KEY_CALLING_PACKAGE_NAME, getCallingPackage());
mBundle.putString(KEY_MEDIA_PACKAGE_NAME, mediaPackageName);
@@ -116,9 +121,21 @@ public class SettingsPanelActivity extends FragmentActivity {
// If fragment already exists and visible, we will need to update panel with animation.
if (!shouldForceCreation && fragment != null && fragment instanceof PanelFragment) {
final PanelFragment panelFragment = (PanelFragment) fragment;
panelFragment.setArguments(mBundle);
panelFragment.updatePanelWithAnimation();
mPanelFragment = (PanelFragment) fragment;
if (mPanelFragment.isPanelCreating()) {
Log.w(TAG, "A panel is creating, skip " + action);
return;
}
final Bundle bundle = fragment.getArguments();
if (bundle != null
&& TextUtils.equals(action, bundle.getString(KEY_PANEL_TYPE_ARGUMENT))) {
Log.w(TAG, "Panel is showing the same action, skip " + action);
return;
}
mPanelFragment.setArguments(new Bundle(mBundle));
mPanelFragment.updatePanelWithAnimation();
} else {
setContentView(R.layout.settings_panel);
@@ -127,9 +144,9 @@ public class SettingsPanelActivity extends FragmentActivity {
window.setGravity(Gravity.BOTTOM);
window.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT);
final PanelFragment panelFragment = new PanelFragment();
panelFragment.setArguments(mBundle);
fragmentManager.beginTransaction().add(R.id.main_content, panelFragment).commit();
mPanelFragment = new PanelFragment();
mPanelFragment.setArguments(new Bundle(mBundle));
fragmentManager.beginTransaction().add(R.id.main_content, mPanelFragment).commit();
}
}
}

View File

@@ -20,6 +20,7 @@ import static android.provider.Settings.ENABLE_MMS_DATA_REQUEST_REASON_INCOMING_
import static android.provider.Settings.ENABLE_MMS_DATA_REQUEST_REASON_OUTGOING_MMS;
import static android.provider.Settings.EXTRA_ENABLE_MMS_DATA_REQUEST_REASON;
import static android.provider.Settings.EXTRA_SUB_ID;
import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE;
import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL;
import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA;
@@ -209,6 +210,7 @@ public class SimSelectNotification extends BroadcastReceiver {
.setContentText(resources.getText(R.string.sim_notification_summary))
.setAutoCancel(true);
Intent resultIntent = new Intent(Settings.ACTION_WIRELESS_SETTINGS);
resultIntent.setPackage(SETTINGS_PACKAGE_NAME);
resultIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent resultPendingIntent = PendingIntent.getActivity(context, 0, resultIntent,
PendingIntent.FLAG_CANCEL_CURRENT);

View File

@@ -216,6 +216,16 @@ public class CustomSliceRegistry {
.appendPath("ring_volume")
.build();
/**
* Full {@link Uri} for the all volume Slices.
*/
public static final Uri VOLUME_SLICES_URI = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(SettingsSliceProvider.SLICE_AUTHORITY)
.appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
.appendPath("volume_slices")
.build();
/**
* Full {@link Uri} for the Wifi Calling Slice.
*/

View File

@@ -47,6 +47,7 @@ import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.bluetooth.BluetoothSliceBuilder;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.notification.VolumeSeekBarPreferenceController;
import com.android.settings.notification.zen.ZenModeSliceBuilder;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.SliceBroadcastRelay;
@@ -184,7 +185,10 @@ public class SettingsSliceProvider extends SliceProvider {
@Override
public void onSliceUnpinned(Uri sliceUri) {
SliceBroadcastRelay.unregisterReceivers(getContext(), sliceUri);
final Context context = getContext();
if (!VolumeSliceHelper.unregisterUri(context, sliceUri)) {
SliceBroadcastRelay.unregisterReceivers(context, sliceUri);
}
ThreadUtils.postOnMainThread(() -> stopBackgroundWorker(sliceUri));
}
@@ -390,7 +394,13 @@ public class SettingsSliceProvider extends SliceProvider {
final IntentFilter filter = controller.getIntentFilter();
if (filter != null) {
registerIntentToUri(filter, uri);
if (controller instanceof VolumeSeekBarPreferenceController) {
// Register volume slices to a broadcast relay to reduce unnecessary UI updates
VolumeSliceHelper.registerIntentToUri(getContext(), filter, uri,
((VolumeSeekBarPreferenceController) controller).getAudioStream());
} else {
registerIntentToUri(filter, uri);
}
}
ThreadUtils.postOnMainThread(() -> startBackgroundWorker(controller, uri));

View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.slices;
import static com.android.settings.slices.CustomSliceRegistry.VOLUME_SLICES_URI;
import android.content.ContentProvider;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.net.Uri;
import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.settingslib.SliceBroadcastRelay;
import java.util.Map;
/**
* This helper is to handle the broadcasts of volume slices
*/
public class VolumeSliceHelper {
private static final String TAG = "VolumeSliceHelper";
@VisibleForTesting
static Map<Uri, Integer> sRegisteredUri = new ArrayMap<>();
@VisibleForTesting
static IntentFilter sIntentFilter;
static void registerIntentToUri(Context context, IntentFilter intentFilter, Uri sliceUri,
int audioStream) {
Log.d(TAG, "Registering uri for broadcast relay: " + sliceUri);
synchronized (sRegisteredUri) {
if (sRegisteredUri.isEmpty()) {
SliceBroadcastRelay.registerReceiver(context, VOLUME_SLICES_URI,
VolumeSliceRelayReceiver.class, intentFilter);
sIntentFilter = intentFilter;
}
sRegisteredUri.put(sliceUri, audioStream);
}
}
static boolean unregisterUri(Context context, Uri sliceUri) {
if (!sRegisteredUri.containsKey(sliceUri)) {
return false;
}
Log.d(TAG, "Unregistering uri broadcast relay: " + sliceUri);
synchronized (sRegisteredUri) {
sRegisteredUri.remove(sliceUri);
if (sRegisteredUri.isEmpty()) {
sIntentFilter = null;
SliceBroadcastRelay.unregisterReceivers(context, VOLUME_SLICES_URI);
}
}
return true;
}
static void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (sIntentFilter == null || action == null || !sIntentFilter.hasAction(action)) {
return;
}
final String uriString = intent.getStringExtra(SliceBroadcastRelay.EXTRA_URI);
if (uriString == null) {
return;
}
final Uri uri = Uri.parse(uriString);
if (!VOLUME_SLICES_URI.equals(ContentProvider.getUriWithoutUserId(uri))) {
Log.w(TAG, "Invalid uri: " + uriString);
return;
}
if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
handleVolumeChanged(context, intent);
} else if (AudioManager.STREAM_MUTE_CHANGED_ACTION.equals(action)
|| AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) {
handleStreamChanged(context, intent);
} else {
notifyAllStreamsChanged(context);
}
}
private static void handleVolumeChanged(Context context, Intent intent) {
final int vol = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
final int prevVol = intent.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
if (vol != prevVol) {
handleStreamChanged(context, intent);
}
}
private static void handleStreamChanged(Context context, Intent intent) {
final int inputType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
for (Map.Entry<Uri, Integer> entry : sRegisteredUri.entrySet()) {
if (entry.getValue() == inputType) {
context.getContentResolver().notifyChange(entry.getKey(), null /* observer */);
break;
}
}
}
private static void notifyAllStreamsChanged(Context context) {
sRegisteredUri.forEach((uri, audioStream) -> {
context.getContentResolver().notifyChange(uri, null /* observer */);
});
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.slices;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* Receives broadcasts to notify that Settings volume Slices are potentially stale.
*/
public class VolumeSliceRelayReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
VolumeSliceHelper.onReceive(context, intent);
}
}

View File

@@ -35,12 +35,12 @@ public class MediaControlsPreferenceController extends TogglePreferenceControlle
@Override
public boolean isChecked() {
int val = Settings.Secure.getInt(mContext.getContentResolver(), MEDIA_CONTROLS_RESUME, 1);
return val == 0;
return val == 1;
}
@Override
public boolean setChecked(boolean isChecked) {
int val = isChecked ? 0 : 1;
int val = isChecked ? 1 : 0;
return Settings.Secure.putInt(mContext.getContentResolver(), MEDIA_CONTROLS_RESUME, val);
}

View File

@@ -37,6 +37,8 @@ import android.os.IBinder;
import android.os.UserManager;
import android.security.Credentials;
import android.security.KeyStore;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.text.Editable;
import android.text.InputType;
import android.text.SpannableString;
@@ -80,7 +82,9 @@ import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
/**
@@ -159,11 +163,11 @@ public class WifiConfigController implements TextWatcher,
private String mMultipleCertSetString;
private String mUseSystemCertsString;
private String mDoNotProvideEapUserCertString;
private String mDoNotValidateEapServerString;
private ScrollView mDialogContainer;
private Spinner mSecuritySpinner;
private Spinner mEapMethodSpinner;
@VisibleForTesting Spinner mEapMethodSpinner;
@VisibleForTesting Spinner mEapSimSpinner; // For EAP-SIM, EAP-AKA and EAP-AKA-PRIME.
private Spinner mEapCaCertSpinner;
private Spinner mEapOcspSpinner;
private TextView mEapDomainView;
@@ -209,6 +213,8 @@ public class WifiConfigController implements TextWatcher,
private final WifiManager mWifiManager;
private final List<SubscriptionInfo> mActiveSubscriptionInfos = new ArrayList<>();
public WifiConfigController(WifiConfigUiBase parent, View view, AccessPoint accessPoint,
int mode) {
this (parent, view, accessPoint, mode, true /* requestFocus */);
@@ -264,8 +270,6 @@ public class WifiConfigController implements TextWatcher,
mUseSystemCertsString = mContext.getString(R.string.wifi_use_system_certs);
mDoNotProvideEapUserCertString =
mContext.getString(R.string.wifi_do_not_provide_eap_user_cert);
mDoNotValidateEapServerString =
mContext.getString(R.string.wifi_do_not_validate_eap_server);
mSsidScanButton = (ImageButton) mView.findViewById(R.id.ssid_scanner_button);
mDialogContainer = mView.findViewById(R.id.dialog_scrollview);
@@ -554,12 +558,10 @@ public class WifiConfigController implements TextWatcher,
// Disallow submit if the user has not selected a CA certificate for an EAP network
// configuration.
enabled = false;
}
if (caCertSelection.equals(mUseSystemCertsString)
&& mEapDomainView != null
} else if (mEapDomainView != null
&& mView.findViewById(R.id.l_domain).getVisibility() != View.GONE
&& TextUtils.isEmpty(mEapDomainView.getText().toString())) {
// Disallow submit if the user chooses to use system certificates for EAP server
// Disallow submit if the user chooses to use a certificate for EAP server
// validation, but does not provide a domain.
enabled = false;
}
@@ -577,7 +579,6 @@ public class WifiConfigController implements TextWatcher,
}
void showWarningMessagesIfAppropriate() {
mView.findViewById(R.id.no_ca_cert_warning).setVisibility(View.GONE);
mView.findViewById(R.id.no_user_cert_warning).setVisibility(View.GONE);
mView.findViewById(R.id.no_domain_warning).setVisibility(View.GONE);
mView.findViewById(R.id.ssid_too_long_warning).setVisibility(View.GONE);
@@ -590,19 +591,11 @@ public class WifiConfigController implements TextWatcher,
}
if (mEapCaCertSpinner != null
&& mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) {
String caCertSelection = (String) mEapCaCertSpinner.getSelectedItem();
if (caCertSelection.equals(mDoNotValidateEapServerString)) {
// Display warning if user chooses not to validate the EAP server with a
// user-supplied CA certificate in an EAP network configuration.
mView.findViewById(R.id.no_ca_cert_warning).setVisibility(View.VISIBLE);
}
if (caCertSelection.equals(mUseSystemCertsString)
&& mEapDomainView != null
if (mEapDomainView != null
&& mView.findViewById(R.id.l_domain).getVisibility() != View.GONE
&& TextUtils.isEmpty(mEapDomainView.getText().toString())) {
// Display warning if user chooses to use pre-installed public CA certificates
// without restricting the server domain that these certificates can be used to
// validate.
// Display warning if user chooses to use a certificate without restricting the
// server domain that these certificates can be used to validate.
mView.findViewById(R.id.no_domain_warning).setVisibility(View.VISIBLE);
}
}
@@ -736,8 +729,7 @@ public class WifiConfigController implements TextWatcher,
config.enterpriseConfig.setCaCertificateAliases(null);
config.enterpriseConfig.setCaPath(null);
config.enterpriseConfig.setDomainSuffixMatch(mEapDomainView.getText().toString());
if (caCert.equals(mUnspecifiedCertString)
|| caCert.equals(mDoNotValidateEapServerString)) {
if (caCert.equals(mUnspecifiedCertString)) {
// ca_cert already set to null, so do nothing.
} else if (caCert.equals(mUseSystemCertsString)) {
config.enterpriseConfig.setCaPath(SYSTEM_CA_STORE_PATH);
@@ -771,8 +763,7 @@ public class WifiConfigController implements TextWatcher,
}
// Only set OCSP option if there is a valid CA certificate.
if (caCert.equals(mUnspecifiedCertString)
|| caCert.equals(mDoNotValidateEapServerString)) {
if (caCert.equals(mUnspecifiedCertString)) {
config.enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_NONE);
} else {
config.enterpriseConfig.setOcsp(mEapOcspSpinner.getSelectedItemPosition());
@@ -825,6 +816,12 @@ public class WifiConfigController implements TextWatcher,
return null;
}
if (config.enterpriseConfig.isAuthenticationSimBased()
&& mActiveSubscriptionInfos.size() > 0) {
config.carrierId = mActiveSubscriptionInfos
.get(mEapSimSpinner.getSelectedItemPosition()).getCarrierId();
}
config.setIpConfiguration(
new IpConfiguration(mIpAssignment, mProxySettings,
mStaticIpConfiguration, mHttpProxy));
@@ -1013,6 +1010,7 @@ public class WifiConfigController implements TextWatcher,
initiateEnterpriseNetworkUi = true;
mEapMethodSpinner = (Spinner) mView.findViewById(R.id.method);
mEapMethodSpinner.setOnItemSelectedListener(this);
mEapSimSpinner = (Spinner) mView.findViewById(R.id.sim);
mPhase2Spinner = (Spinner) mView.findViewById(R.id.phase2);
mPhase2Spinner.setOnItemSelectedListener(this);
mEapCaCertSpinner = (Spinner) mView.findViewById(R.id.ca_cert);
@@ -1049,18 +1047,20 @@ public class WifiConfigController implements TextWatcher,
}
if (refreshCertificates) {
loadSims();
loadCertificates(
mEapCaCertSpinner,
Credentials.CA_CERTIFICATE,
mDoNotValidateEapServerString,
false,
true);
null /* noCertificateString */,
false /* showMultipleCerts */,
true /* showUsePreinstalledCertOption */);
loadCertificates(
mEapUserCertSpinner,
Credentials.USER_PRIVATE_KEY,
mDoNotProvideEapUserCertString,
false,
false);
false /* showMultipleCerts */,
false /* showUsePreinstalledCertOption */);
// To avoid the user connects to a non-secure network unexpectedly,
// request using system trusted certificates by default
// unless the user explicitly chooses "Do not validate" or other
@@ -1070,9 +1070,10 @@ public class WifiConfigController implements TextWatcher,
// Modifying an existing network
if (initiateEnterpriseNetworkUi && mAccessPoint != null && mAccessPoint.isSaved()) {
WifiEnterpriseConfig enterpriseConfig = mAccessPoint.getConfig().enterpriseConfig;
int eapMethod = enterpriseConfig.getEapMethod();
int phase2Method = enterpriseConfig.getPhase2Method();
final WifiConfiguration wifiConfig = mAccessPoint.getConfig();
final WifiEnterpriseConfig enterpriseConfig = wifiConfig.enterpriseConfig;
final int eapMethod = enterpriseConfig.getEapMethod();
final int phase2Method = enterpriseConfig.getPhase2Method();
mEapMethodSpinner.setSelection(eapMethod);
showEapFieldsByMethod(eapMethod);
switch (eapMethod) {
@@ -1120,12 +1121,22 @@ public class WifiConfigController implements TextWatcher,
default:
break;
}
if (enterpriseConfig.isAuthenticationSimBased()) {
for (int i = 0; i < mActiveSubscriptionInfos.size(); i++) {
if (wifiConfig.carrierId == mActiveSubscriptionInfos.get(i).getCarrierId()) {
mEapSimSpinner.setSelection(i);
break;
}
}
}
if (!TextUtils.isEmpty(enterpriseConfig.getCaPath())) {
setSelection(mEapCaCertSpinner, mUseSystemCertsString);
} else {
String[] caCerts = enterpriseConfig.getCaCertificateAliases();
if (caCerts == null) {
setSelection(mEapCaCertSpinner, mDoNotValidateEapServerString);
setSelection(mEapCaCertSpinner, mUnspecifiedCertString);
} else if (caCerts.length == 1) {
setSelection(mEapCaCertSpinner, caCerts[0]);
} else {
@@ -1133,9 +1144,9 @@ public class WifiConfigController implements TextWatcher,
loadCertificates(
mEapCaCertSpinner,
Credentials.CA_CERTIFICATE,
mDoNotValidateEapServerString,
true,
true);
null /* noCertificateString */,
true /* showMultipleCerts */,
true /* showUsePreinstalledCertOption */);
setSelection(mEapCaCertSpinner, mMultipleCertSetString);
}
}
@@ -1208,6 +1219,7 @@ public class WifiConfigController implements TextWatcher,
mView.findViewById(R.id.l_ocsp).setVisibility(View.VISIBLE);
mView.findViewById(R.id.password_layout).setVisibility(View.VISIBLE);
mView.findViewById(R.id.show_password_layout).setVisibility(View.VISIBLE);
mView.findViewById(R.id.l_sim).setVisibility(View.VISIBLE);
Context context = mConfigUi.getContext();
switch (eapMethod) {
@@ -1218,12 +1230,14 @@ public class WifiConfigController implements TextWatcher,
setDomainInvisible();
setAnonymousIdentInvisible();
setUserCertInvisible();
mView.findViewById(R.id.l_sim).setVisibility(View.GONE);
break;
case WIFI_EAP_METHOD_TLS:
mView.findViewById(R.id.l_user_cert).setVisibility(View.VISIBLE);
setPhase2Invisible();
setAnonymousIdentInvisible();
setPasswordInvisible();
mView.findViewById(R.id.l_sim).setVisibility(View.GONE);
break;
case WIFI_EAP_METHOD_PEAP:
// Reset adapter if needed
@@ -1245,6 +1259,7 @@ public class WifiConfigController implements TextWatcher,
mView.findViewById(R.id.l_phase2).setVisibility(View.VISIBLE);
mView.findViewById(R.id.l_anonymous).setVisibility(View.VISIBLE);
setUserCertInvisible();
mView.findViewById(R.id.l_sim).setVisibility(View.GONE);
break;
case WIFI_EAP_METHOD_SIM:
case WIFI_EAP_METHOD_AKA:
@@ -1262,8 +1277,7 @@ public class WifiConfigController implements TextWatcher,
if (mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) {
String eapCertSelection = (String) mEapCaCertSpinner.getSelectedItem();
if (eapCertSelection.equals(mDoNotValidateEapServerString)
|| eapCertSelection.equals(mUnspecifiedCertString)) {
if (eapCertSelection.equals(mUnspecifiedCertString)) {
// Domain suffix matching is not relevant if the user hasn't chosen a CA
// certificate yet, or chooses not to validate the EAP server.
setDomainInvisible();
@@ -1282,11 +1296,13 @@ public class WifiConfigController implements TextWatcher,
mEapIdentityView.setText("");
mView.findViewById(R.id.l_identity).setVisibility(View.GONE);
setPasswordInvisible();
mView.findViewById(R.id.l_sim).setVisibility(View.VISIBLE);
} else {
mView.findViewById(R.id.l_identity).setVisibility(View.VISIBLE);
mView.findViewById(R.id.l_anonymous).setVisibility(View.VISIBLE);
mView.findViewById(R.id.password_layout).setVisibility(View.VISIBLE);
mView.findViewById(R.id.show_password_layout).setVisibility(View.VISIBLE);
mView.findViewById(R.id.l_sim).setVisibility(View.GONE);
}
}
@@ -1449,6 +1465,44 @@ public class WifiConfigController implements TextWatcher,
return KeyStore.getInstance();
}
@VisibleForTesting
void loadSims() {
List<SubscriptionInfo> activeSubscriptionInfos = mContext
.getSystemService(SubscriptionManager.class).getActiveSubscriptionInfoList();
if (activeSubscriptionInfos == null) {
activeSubscriptionInfos = Collections.EMPTY_LIST;
}
mActiveSubscriptionInfos.clear();
// De-duplicates active subscriptions and caches in mActiveSubscriptionInfos.
for (SubscriptionInfo newInfo : activeSubscriptionInfos) {
for (SubscriptionInfo cachedInfo : mActiveSubscriptionInfos) {
if (newInfo.getCarrierId() == cachedInfo.getCarrierId()) {
continue;
}
}
mActiveSubscriptionInfos.add(newInfo);
}
// Shows disabled 'No SIM' when there is no active subscription.
if (mActiveSubscriptionInfos.size() == 0) {
final String[] noSim = new String[]{mContext.getString(R.string.wifi_no_sim_card)};
mEapSimSpinner.setAdapter(getSpinnerAdapter(noSim));
mEapSimSpinner.setSelection(0 /* position */);
mEapSimSpinner.setEnabled(false);
return;
}
// Shows display name of each active subscription.
final String[] displayNames = mActiveSubscriptionInfos.stream().map(
SubscriptionInfo::getDisplayName).toArray(String[]::new);
mEapSimSpinner.setAdapter(getSpinnerAdapter(displayNames));
mEapSimSpinner.setSelection(0 /* position */);
if (displayNames.length == 1) {
mEapSimSpinner.setEnabled(false);
}
}
@VisibleForTesting
void loadCertificates(
Spinner spinner,
@@ -1485,7 +1539,8 @@ public class WifiConfigController implements TextWatcher,
}).collect(Collectors.toList()));
}
if (mAccessPointSecurity != AccessPoint.SECURITY_EAP_SUITE_B) {
if (!TextUtils.isEmpty(noCertificateString)
&& mAccessPointSecurity != AccessPoint.SECURITY_EAP_SUITE_B) {
certs.add(noCertificateString);
}
@@ -1713,7 +1768,8 @@ public class WifiConfigController implements TextWatcher,
mContext.getResources().getStringArray(contentStringArrayResId));
}
private ArrayAdapter<CharSequence> getSpinnerAdapter(
@VisibleForTesting
ArrayAdapter<CharSequence> getSpinnerAdapter(
String[] contentStringArray) {
ArrayAdapter<CharSequence> spinnerAdapter = new ArrayAdapter<>(mContext,
android.R.layout.simple_spinner_item, contentStringArray);

View File

@@ -35,6 +35,8 @@ import android.os.IBinder;
import android.os.UserManager;
import android.security.Credentials;
import android.security.KeyStore;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.text.Editable;
import android.text.InputType;
import android.text.SpannableString;
@@ -80,7 +82,9 @@ import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
/**
@@ -156,11 +160,11 @@ public class WifiConfigController2 implements TextWatcher,
private String mMultipleCertSetString;
private String mUseSystemCertsString;
private String mDoNotProvideEapUserCertString;
private String mDoNotValidateEapServerString;
private ScrollView mDialogContainer;
private Spinner mSecuritySpinner;
private Spinner mEapMethodSpinner;
@VisibleForTesting Spinner mEapMethodSpinner;
@VisibleForTesting Spinner mEapSimSpinner; // For EAP-SIM, EAP-AKA and EAP-AKA-PRIME.
private Spinner mEapCaCertSpinner;
private Spinner mEapOcspSpinner;
private TextView mEapDomainView;
@@ -205,6 +209,8 @@ public class WifiConfigController2 implements TextWatcher,
private final WifiManager mWifiManager;
private final List<SubscriptionInfo> mActiveSubscriptionInfos = new ArrayList<>();
public WifiConfigController2(WifiConfigUiBase2 parent, View view, WifiEntry wifiEntry,
int mode) {
mConfigUi = parent;
@@ -253,8 +259,6 @@ public class WifiConfigController2 implements TextWatcher,
mUseSystemCertsString = mContext.getString(R.string.wifi_use_system_certs);
mDoNotProvideEapUserCertString =
mContext.getString(R.string.wifi_do_not_provide_eap_user_cert);
mDoNotValidateEapServerString =
mContext.getString(R.string.wifi_do_not_validate_eap_server);
mSsidScanButton = (ImageButton) mView.findViewById(R.id.ssid_scanner_button);
mDialogContainer = mView.findViewById(R.id.dialog_scrollview);
@@ -533,12 +537,10 @@ public class WifiConfigController2 implements TextWatcher,
// Disallow submit if the user has not selected a CA certificate for an EAP network
// configuration.
enabled = false;
}
if (caCertSelection.equals(mUseSystemCertsString)
&& mEapDomainView != null
} else if (mEapDomainView != null
&& mView.findViewById(R.id.l_domain).getVisibility() != View.GONE
&& TextUtils.isEmpty(mEapDomainView.getText().toString())) {
// Disallow submit if the user chooses to use system certificates for EAP server
// Disallow submit if the user chooses to use a certificate for EAP server
// validation, but does not provide a domain.
enabled = false;
}
@@ -556,7 +558,6 @@ public class WifiConfigController2 implements TextWatcher,
}
void showWarningMessagesIfAppropriate() {
mView.findViewById(R.id.no_ca_cert_warning).setVisibility(View.GONE);
mView.findViewById(R.id.no_user_cert_warning).setVisibility(View.GONE);
mView.findViewById(R.id.no_domain_warning).setVisibility(View.GONE);
mView.findViewById(R.id.ssid_too_long_warning).setVisibility(View.GONE);
@@ -569,19 +570,11 @@ public class WifiConfigController2 implements TextWatcher,
}
if (mEapCaCertSpinner != null
&& mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) {
String caCertSelection = (String) mEapCaCertSpinner.getSelectedItem();
if (caCertSelection.equals(mDoNotValidateEapServerString)) {
// Display warning if user chooses not to validate the EAP server with a
// user-supplied CA certificate in an EAP network configuration.
mView.findViewById(R.id.no_ca_cert_warning).setVisibility(View.VISIBLE);
}
if (caCertSelection.equals(mUseSystemCertsString)
&& mEapDomainView != null
if (mEapDomainView != null
&& mView.findViewById(R.id.l_domain).getVisibility() != View.GONE
&& TextUtils.isEmpty(mEapDomainView.getText().toString())) {
// Display warning if user chooses to use pre-installed public CA certificates
// without restricting the server domain that these certificates can be used to
// validate.
// Display warning if user chooses to use a certificate without restricting the
// server domain that these certificates can be used to validate.
mView.findViewById(R.id.no_domain_warning).setVisibility(View.VISIBLE);
}
}
@@ -714,12 +707,17 @@ public class WifiConfigController2 implements TextWatcher,
break;
}
if (config.enterpriseConfig.isAuthenticationSimBased()
&& mActiveSubscriptionInfos.size() > 0) {
config.carrierId = mActiveSubscriptionInfos
.get(mEapSimSpinner.getSelectedItemPosition()).getCarrierId();
}
String caCert = (String) mEapCaCertSpinner.getSelectedItem();
config.enterpriseConfig.setCaCertificateAliases(null);
config.enterpriseConfig.setCaPath(null);
config.enterpriseConfig.setDomainSuffixMatch(mEapDomainView.getText().toString());
if (caCert.equals(mUnspecifiedCertString)
|| caCert.equals(mDoNotValidateEapServerString)) {
if (caCert.equals(mUnspecifiedCertString)) {
// ca_cert already set to null, so do nothing.
} else if (caCert.equals(mUseSystemCertsString)) {
config.enterpriseConfig.setCaPath(SYSTEM_CA_STORE_PATH);
@@ -752,8 +750,7 @@ public class WifiConfigController2 implements TextWatcher,
}
// Only set OCSP option if there is a valid CA certificate.
if (caCert.equals(mUnspecifiedCertString)
|| caCert.equals(mDoNotValidateEapServerString)) {
if (caCert.equals(mUnspecifiedCertString)) {
config.enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_NONE);
} else {
config.enterpriseConfig.setOcsp(mEapOcspSpinner.getSelectedItemPosition());
@@ -994,6 +991,7 @@ public class WifiConfigController2 implements TextWatcher,
initiateEnterpriseNetworkUi = true;
mEapMethodSpinner = (Spinner) mView.findViewById(R.id.method);
mEapMethodSpinner.setOnItemSelectedListener(this);
mEapSimSpinner = (Spinner) mView.findViewById(R.id.sim);
mPhase2Spinner = (Spinner) mView.findViewById(R.id.phase2);
mPhase2Spinner.setOnItemSelectedListener(this);
mEapCaCertSpinner = (Spinner) mView.findViewById(R.id.ca_cert);
@@ -1031,18 +1029,20 @@ public class WifiConfigController2 implements TextWatcher,
}
if (refreshCertificates) {
loadSims();
loadCertificates(
mEapCaCertSpinner,
Credentials.CA_CERTIFICATE,
mDoNotValidateEapServerString,
false,
true);
null /* noCertificateString */,
false /* showMultipleCerts */,
true /* showUsePreinstalledCertOption */);
loadCertificates(
mEapUserCertSpinner,
Credentials.USER_PRIVATE_KEY,
mDoNotProvideEapUserCertString,
false,
false);
false /* showMultipleCerts */,
false /* showUsePreinstalledCertOption */);
// To avoid the user connects to a non-secure network unexpectedly,
// request using system trusted certificates by default
// unless the user explicitly chooses "Do not validate" or other
@@ -1052,10 +1052,10 @@ public class WifiConfigController2 implements TextWatcher,
// Modifying an existing network
if (initiateEnterpriseNetworkUi && mWifiEntry != null && mWifiEntry.isSaved()) {
WifiEnterpriseConfig enterpriseConfig = mWifiEntry.getWifiConfiguration()
.enterpriseConfig;
int eapMethod = enterpriseConfig.getEapMethod();
int phase2Method = enterpriseConfig.getPhase2Method();
final WifiConfiguration wifiConfig = mWifiEntry.getWifiConfiguration();
final WifiEnterpriseConfig enterpriseConfig = wifiConfig.enterpriseConfig;
final int eapMethod = enterpriseConfig.getEapMethod();
final int phase2Method = enterpriseConfig.getPhase2Method();
mEapMethodSpinner.setSelection(eapMethod);
showEapFieldsByMethod(eapMethod);
switch (eapMethod) {
@@ -1103,12 +1103,22 @@ public class WifiConfigController2 implements TextWatcher,
default:
break;
}
if (enterpriseConfig.isAuthenticationSimBased()) {
for (int i = 0; i < mActiveSubscriptionInfos.size(); i++) {
if (wifiConfig.carrierId == mActiveSubscriptionInfos.get(i).getCarrierId()) {
mEapSimSpinner.setSelection(i);
break;
}
}
}
if (!TextUtils.isEmpty(enterpriseConfig.getCaPath())) {
setSelection(mEapCaCertSpinner, mUseSystemCertsString);
} else {
String[] caCerts = enterpriseConfig.getCaCertificateAliases();
if (caCerts == null) {
setSelection(mEapCaCertSpinner, mDoNotValidateEapServerString);
setSelection(mEapCaCertSpinner, mUnspecifiedCertString);
} else if (caCerts.length == 1) {
setSelection(mEapCaCertSpinner, caCerts[0]);
} else {
@@ -1116,9 +1126,9 @@ public class WifiConfigController2 implements TextWatcher,
loadCertificates(
mEapCaCertSpinner,
Credentials.CA_CERTIFICATE,
mDoNotValidateEapServerString,
true,
true);
null /* noCertificateString */,
true /* showMultipleCerts */,
true /* showUsePreinstalledCertOption */);
setSelection(mEapCaCertSpinner, mMultipleCertSetString);
}
}
@@ -1191,6 +1201,7 @@ public class WifiConfigController2 implements TextWatcher,
mView.findViewById(R.id.l_ocsp).setVisibility(View.VISIBLE);
mView.findViewById(R.id.password_layout).setVisibility(View.VISIBLE);
mView.findViewById(R.id.show_password_layout).setVisibility(View.VISIBLE);
mView.findViewById(R.id.l_sim).setVisibility(View.VISIBLE);
Context context = mConfigUi.getContext();
switch (eapMethod) {
@@ -1201,12 +1212,14 @@ public class WifiConfigController2 implements TextWatcher,
setDomainInvisible();
setAnonymousIdentInvisible();
setUserCertInvisible();
mView.findViewById(R.id.l_sim).setVisibility(View.GONE);
break;
case WIFI_EAP_METHOD_TLS:
mView.findViewById(R.id.l_user_cert).setVisibility(View.VISIBLE);
setPhase2Invisible();
setAnonymousIdentInvisible();
setPasswordInvisible();
mView.findViewById(R.id.l_sim).setVisibility(View.GONE);
break;
case WIFI_EAP_METHOD_PEAP:
// Reset adapter if needed
@@ -1228,6 +1241,7 @@ public class WifiConfigController2 implements TextWatcher,
mView.findViewById(R.id.l_phase2).setVisibility(View.VISIBLE);
mView.findViewById(R.id.l_anonymous).setVisibility(View.VISIBLE);
setUserCertInvisible();
mView.findViewById(R.id.l_sim).setVisibility(View.GONE);
break;
case WIFI_EAP_METHOD_SIM:
case WIFI_EAP_METHOD_AKA:
@@ -1245,8 +1259,7 @@ public class WifiConfigController2 implements TextWatcher,
if (mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) {
String eapCertSelection = (String) mEapCaCertSpinner.getSelectedItem();
if (eapCertSelection.equals(mDoNotValidateEapServerString)
|| eapCertSelection.equals(mUnspecifiedCertString)) {
if (eapCertSelection.equals(mUnspecifiedCertString)) {
// Domain suffix matching is not relevant if the user hasn't chosen a CA
// certificate yet, or chooses not to validate the EAP server.
setDomainInvisible();
@@ -1265,11 +1278,13 @@ public class WifiConfigController2 implements TextWatcher,
mEapIdentityView.setText("");
mView.findViewById(R.id.l_identity).setVisibility(View.GONE);
setPasswordInvisible();
mView.findViewById(R.id.l_sim).setVisibility(View.VISIBLE);
} else {
mView.findViewById(R.id.l_identity).setVisibility(View.VISIBLE);
mView.findViewById(R.id.l_anonymous).setVisibility(View.VISIBLE);
mView.findViewById(R.id.password_layout).setVisibility(View.VISIBLE);
mView.findViewById(R.id.show_password_layout).setVisibility(View.VISIBLE);
mView.findViewById(R.id.l_sim).setVisibility(View.GONE);
}
}
@@ -1432,6 +1447,44 @@ public class WifiConfigController2 implements TextWatcher,
return KeyStore.getInstance();
}
@VisibleForTesting
void loadSims() {
List<SubscriptionInfo> activeSubscriptionInfos = mContext
.getSystemService(SubscriptionManager.class).getActiveSubscriptionInfoList();
if (activeSubscriptionInfos == null) {
activeSubscriptionInfos = Collections.EMPTY_LIST;
}
mActiveSubscriptionInfos.clear();
// De-duplicates active subscriptions and caches in mActiveSubscriptionInfos.
for (SubscriptionInfo newInfo : activeSubscriptionInfos) {
for (SubscriptionInfo cachedInfo : mActiveSubscriptionInfos) {
if (newInfo.getCarrierId() == cachedInfo.getCarrierId()) {
continue;
}
}
mActiveSubscriptionInfos.add(newInfo);
}
// Shows disabled 'No SIM' when there is no active subscription.
if (mActiveSubscriptionInfos.size() == 0) {
final String[] noSim = new String[]{mContext.getString(R.string.wifi_no_sim_card)};
mEapSimSpinner.setAdapter(getSpinnerAdapter(noSim));
mEapSimSpinner.setSelection(0 /* position */);
mEapSimSpinner.setEnabled(false);
return;
}
// Shows display name of each active subscription.
final String[] displayNames = mActiveSubscriptionInfos.stream().map(
SubscriptionInfo::getDisplayName).toArray(String[]::new);
mEapSimSpinner.setAdapter(getSpinnerAdapter(displayNames));
mEapSimSpinner.setSelection(0 /* position */);
if (displayNames.length == 1) {
mEapSimSpinner.setEnabled(false);
}
}
@VisibleForTesting
void loadCertificates(
Spinner spinner,
@@ -1468,7 +1521,8 @@ public class WifiConfigController2 implements TextWatcher,
}).collect(Collectors.toList()));
}
if (mWifiEntrySecurity != WifiEntry.SECURITY_EAP_SUITE_B) {
if (!TextUtils.isEmpty(noCertificateString)
&& mWifiEntrySecurity != WifiEntry.SECURITY_EAP_SUITE_B) {
certs.add(noCertificateString);
}
@@ -1696,7 +1750,8 @@ public class WifiConfigController2 implements TextWatcher,
mContext.getResources().getStringArray(contentStringArrayResId));
}
private ArrayAdapter<CharSequence> getSpinnerAdapter(
@VisibleForTesting
ArrayAdapter<CharSequence> getSpinnerAdapter(
String[] contentStringArray) {
ArrayAdapter<CharSequence> spinnerAdapter = new ArrayAdapter<>(mContext,
android.R.layout.simple_spinner_item, contentStringArray);

View File

@@ -17,32 +17,20 @@
package com.android.settings.wifi;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkScoreManager;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
import android.os.SimpleClock;
import android.os.SystemClock;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.wifi.details2.WifiNetworkDetailsFragment2;
import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.wifi.WifiEntryPreference;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiPickerTracker;
import java.time.Clock;
import java.time.ZoneOffset;
import com.android.settingslib.wifi.AccessPoint;
import com.android.settingslib.wifi.AccessPointPreference;
import com.android.settingslib.wifi.WifiTracker;
import com.android.settingslib.wifi.WifiTrackerFactory;
// TODO(b/151133650): Replace AbstractPreferenceController with BasePreferenceController.
/**
@@ -50,28 +38,21 @@ import java.time.ZoneOffset;
* controller class when there is a wifi connection present.
*/
public class WifiConnectionPreferenceController extends AbstractPreferenceController implements
WifiPickerTracker.WifiPickerTrackerCallback {
WifiTracker.WifiListener {
private static final String TAG = "WifiConnPrefCtrl";
private static final String KEY = "active_wifi_connection";
// Max age of tracked WifiEntries.
private static final long MAX_SCAN_AGE_MILLIS = 15_000;
// Interval between initiating WifiPickerTracker scans.
private static final long SCAN_INTERVAL_MILLIS = 10_000;
private UpdateListener mUpdateListener;
private Context mPrefContext;
private String mPreferenceGroupKey;
private PreferenceGroup mPreferenceGroup;
@VisibleForTesting
public WifiPickerTracker mWifiPickerTracker;
private WifiEntryPreference mPreference;
private WifiTracker mWifiTracker;
private AccessPointPreference mPreference;
private AccessPointPreference.UserBadgeCache mBadgeCache;
private int order;
private int mMetricsCategory;
// Worker thread used for WifiPickerTracker work.
private HandlerThread mWorkerThread;
/**
* Used to notify a parent controller that this controller has changed in availability, or has
@@ -99,34 +80,16 @@ public class WifiConnectionPreferenceController extends AbstractPreferenceContro
super(context);
mUpdateListener = updateListener;
mPreferenceGroupKey = preferenceGroupKey;
mWifiTracker = WifiTrackerFactory.create(context, this, lifecycle, true /* includeSaved */,
true /* includeScans */);
this.order = order;
mMetricsCategory = metricsCategory;
mWorkerThread = new HandlerThread(
TAG + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
Process.THREAD_PRIORITY_BACKGROUND);
mWorkerThread.start();
final Clock elapsedRealtimeClock = new SimpleClock(ZoneOffset.UTC) {
@Override
public long millis() {
return SystemClock.elapsedRealtime();
}
};
mWifiPickerTracker = new WifiPickerTracker(lifecycle, context,
context.getSystemService(WifiManager.class),
context.getSystemService(ConnectivityManager.class),
context.getSystemService(NetworkScoreManager.class),
new Handler(Looper.getMainLooper()),
mWorkerThread.getThreadHandler(),
elapsedRealtimeClock,
MAX_SCAN_AGE_MILLIS,
SCAN_INTERVAL_MILLIS,
this);
mBadgeCache = new AccessPointPreference.UserBadgeCache(context.getPackageManager());
}
@Override
public boolean isAvailable() {
return mWifiPickerTracker.getConnectedWifiEntry() != null;
return mWifiTracker.isConnected() && getCurrentAccessPoint() != null;
}
@Override
@@ -142,69 +105,74 @@ public class WifiConnectionPreferenceController extends AbstractPreferenceContro
update();
}
private void updatePreference(WifiEntry wifiEntry) {
private AccessPoint getCurrentAccessPoint() {
for (AccessPoint accessPoint : mWifiTracker.getAccessPoints()) {
if (accessPoint.isActive()) {
return accessPoint;
}
}
return null;
}
private void updatePreference(AccessPoint accessPoint) {
if (mPreference != null) {
mPreferenceGroup.removePreference(mPreference);
mPreference = null;
}
if (wifiEntry == null || mPrefContext == null) {
if (accessPoint == null) {
return;
}
if (mPrefContext != null) {
mPreference = new AccessPointPreference(accessPoint, mPrefContext, mBadgeCache,
R.drawable.ic_wifi_signal_0, false /* forSavedNetworks */);
mPreference.setKey(KEY);
mPreference.refresh();
mPreference.setOrder(order);
mPreference = new WifiEntryPreference(mPrefContext, wifiEntry);
mPreference.setKey(KEY);
mPreference.refresh();
mPreference.setOrder(order);
mPreference.setOnPreferenceClickListener(pref -> {
final Bundle args = new Bundle();
args.putString(WifiNetworkDetailsFragment2.KEY_CHOSEN_WIFIENTRY_KEY,
wifiEntry.getKey());
new SubSettingLauncher(mPrefContext)
.setTitleRes(R.string.pref_title_network_details)
.setDestination(WifiNetworkDetailsFragment2.class.getName())
.setArguments(args)
.setSourceMetricsCategory(mMetricsCategory)
.launch();
return true;
});
mPreferenceGroup.addPreference(mPreference);
mPreference.setOnPreferenceClickListener(pref -> {
Bundle args = new Bundle();
mPreference.getAccessPoint().saveWifiState(args);
new SubSettingLauncher(mPrefContext)
.setTitleRes(R.string.pref_title_network_details)
.setDestination(WifiNetworkDetailsFragment.class.getName())
.setArguments(args)
.setSourceMetricsCategory(mMetricsCategory)
.launch();
return true;
});
mPreferenceGroup.addPreference(mPreference);
}
}
private void update() {
final WifiEntry connectedWifiEntry = mWifiPickerTracker.getConnectedWifiEntry();
if (connectedWifiEntry == null) {
AccessPoint connectedAccessPoint = null;
if (mWifiTracker.isConnected()) {
connectedAccessPoint = getCurrentAccessPoint();
}
if (connectedAccessPoint == null) {
updatePreference(null);
} else {
if (mPreference == null || !mPreference.getWifiEntry().equals(connectedWifiEntry)) {
updatePreference(connectedWifiEntry);
} else if (mPreference != null) {
mPreference.refresh();
}
if (mPreference == null || !mPreference.getAccessPoint().equals(connectedAccessPoint)) {
updatePreference(connectedAccessPoint);
} else if (mPreference != null) {
mPreference.refresh();
}
}
mUpdateListener.onChildrenUpdated();
}
/** Called when the state of Wifi has changed. */
@Override
public void onWifiStateChanged() {
update();
}
/**
* Update the results when data changes.
*/
@Override
public void onWifiEntriesChanged() {
public void onWifiStateChanged(int state) {
update();
}
@Override
public void onNumSavedSubscriptionsChanged() {
// Do nothing.
public void onConnectedChanged() {
update();
}
@Override
public void onNumSavedNetworksChanged() {
// Do nothing.
public void onAccessPointsChanged() {
update();
}
}

View File

@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.net.NetworkCapabilities;
import android.net.wifi.ScanResult;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiConfiguration;
import android.os.UserHandle;
import android.os.UserManager;
@@ -38,8 +39,6 @@ public class WifiUtils {
private static final int SSID_ASCII_MIN_LENGTH = 1;
private static final int SSID_ASCII_MAX_LENGTH = 32;
private static final int PASSWORD_MIN_LENGTH = 8;
private static final int PASSWORD_MAX_LENGTH = 63;
public static boolean isSSIDTooLong(String ssid) {
@@ -56,13 +55,17 @@ public class WifiUtils {
return ssid.length() < SSID_ASCII_MIN_LENGTH;
}
public static boolean isHotspotPasswordValid(String password) {
if (TextUtils.isEmpty(password)) {
/**
* Check if the WPA2-PSK hotspot password is valid.
*/
public static boolean isHotspotWpa2PasswordValid(String password) {
final SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
try {
configBuilder.setPassphrase(password, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
} catch (IllegalArgumentException e) {
return false;
}
final int length = password.length();
return length >= PASSWORD_MIN_LENGTH && length <= PASSWORD_MAX_LENGTH;
return true;
}
/**

View File

@@ -23,8 +23,10 @@ import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.settings.SettingsEnums;
import android.content.AsyncQueryHandler;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@@ -41,9 +43,14 @@ import android.net.NetworkRequest;
import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.Uri;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.provider.Telephony.CarrierId;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.FeatureFlagUtils;
import android.util.Log;
@@ -98,6 +105,7 @@ import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.List;
import java.util.StringJoiner;
import java.util.stream.Collectors;
@@ -133,6 +141,8 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
@VisibleForTesting
static final String KEY_SSID_PREF = "ssid";
@VisibleForTesting
static final String KEY_EAP_SIM_SUBSCRIPTION_PREF = "eap_sim_subscription";
@VisibleForTesting
static final String KEY_MAC_ADDRESS_PREF = "mac_address";
@VisibleForTesting
static final String KEY_IP_ADDRESS_PREF = "ip_address";
@@ -156,6 +166,7 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
private NetworkInfo mNetworkInfo;
private NetworkCapabilities mNetworkCapabilities;
private int mRssiSignalLevel = -1;
@VisibleForTesting boolean mShowX; // Shows the Wi-Fi signal icon of Pie+x when it's true.
private String[] mSignalStr;
private WifiInfo mWifiInfo;
private final WifiManager mWifiManager;
@@ -170,6 +181,7 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
private Preference mFrequencyPref;
private Preference mSecurityPref;
private Preference mSsidPref;
private Preference mEapSimSubscriptionPref;
private Preference mMacAddressPref;
private Preference mIpAddressPref;
private Preference mGatewayPref;
@@ -187,6 +199,35 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
.clearCapabilities().addTransportType(TRANSPORT_WIFI).build();
private CarrierIdAsyncQueryHandler mCarrierIdAsyncQueryHandler;
private static final int TOKEN_QUERY_CARRIER_ID_AND_UPDATE_SIM_SUMMARY = 1;
private static final int COLUMN_CARRIER_NAME = 0;
private class CarrierIdAsyncQueryHandler extends AsyncQueryHandler {
private CarrierIdAsyncQueryHandler(Context context) {
super(context.getContentResolver());
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
if (token == TOKEN_QUERY_CARRIER_ID_AND_UPDATE_SIM_SUMMARY) {
if (mContext == null || cursor == null || !cursor.moveToFirst()) {
if (cursor != null) {
cursor.close();
}
mEapSimSubscriptionPref.setSummary(R.string.wifi_require_sim_card_to_connect);
return;
}
mEapSimSubscriptionPref.setSummary(mContext.getString(
R.string.wifi_require_specific_sim_card_to_connect,
cursor.getString(COLUMN_CARRIER_NAME)));
cursor.close();
return;
}
}
}
// Must be run on the UI thread since it directly manipulates UI state.
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
@Override
@@ -336,6 +377,7 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
mSecurityPref = screen.findPreference(KEY_SECURITY_PREF);
mSsidPref = screen.findPreference(KEY_SSID_PREF);
mEapSimSubscriptionPref = screen.findPreference(KEY_EAP_SIM_SUBSCRIPTION_PREF);
mMacAddressPref = screen.findPreference(KEY_MAC_ADDRESS_PREF);
mIpAddressPref = screen.findPreference(KEY_IP_ADDRESS_PREF);
mGatewayPref = screen.findPreference(KEY_GATEWAY_PREF);
@@ -507,12 +549,14 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
refreshIpLayerInfo();
// SSID Pref
refreshSsid();
// EAP SIM subscription
refreshEapSimSubscription();
// MAC Address Pref
refreshMacAddress();
}
private void refreshRssiViews() {
int signalLevel = mWifiEntry.getLevel();
final int signalLevel = mWifiEntry.getLevel();
// Disappears signal view if not in range. e.g. for saved networks.
if (signalLevel == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
@@ -521,11 +565,14 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
return;
}
if (mRssiSignalLevel == signalLevel) {
final boolean showX = mWifiEntry.shouldShowXLevelIcon();
if (mRssiSignalLevel == signalLevel && mShowX == showX) {
return;
}
mRssiSignalLevel = signalLevel;
Drawable wifiIcon = mIconInjector.getIcon(mRssiSignalLevel);
mShowX = showX;
Drawable wifiIcon = mIconInjector.getIcon(mShowX, mRssiSignalLevel);
if (mEntityHeaderController != null) {
mEntityHeaderController
@@ -630,6 +677,62 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
}
}
private void refreshEapSimSubscription() {
mEapSimSubscriptionPref.setVisible(false);
if (mWifiEntry.getSecurity() != WifiEntry.SECURITY_EAP) {
return;
}
final WifiConfiguration config = mWifiEntry.getWifiConfiguration();
if (config == null || config.enterpriseConfig == null) {
return;
}
if (!config.enterpriseConfig.isAuthenticationSimBased()) {
return;
}
mEapSimSubscriptionPref.setVisible(true);
// Checks if the SIM subscription is active.
final List<SubscriptionInfo> activeSubscriptionInfos = mContext
.getSystemService(SubscriptionManager.class).getActiveSubscriptionInfoList();
final int defaultDataSubscriptionId = SubscriptionManager.getDefaultDataSubscriptionId();
if (activeSubscriptionInfos != null) {
for (SubscriptionInfo subscriptionInfo : activeSubscriptionInfos) {
if (config.carrierId == subscriptionInfo.getCarrierId()) {
mEapSimSubscriptionPref.setSummary(subscriptionInfo.getDisplayName());
return;
}
// When it's UNKNOWN_CARRIER_ID, devices connects it with the SIM subscription of
// defaultDataSubscriptionId.
if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID
&& defaultDataSubscriptionId == subscriptionInfo.getSubscriptionId()) {
mEapSimSubscriptionPref.setSummary(subscriptionInfo.getDisplayName());
return;
}
}
}
if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
mEapSimSubscriptionPref.setSummary(R.string.wifi_no_related_sim_card);
return;
}
// The Wi-Fi network has specified carrier id, query carrier name from CarrierIdProvider.
if (mCarrierIdAsyncQueryHandler == null) {
mCarrierIdAsyncQueryHandler = new CarrierIdAsyncQueryHandler(mContext);
}
mCarrierIdAsyncQueryHandler.cancelOperation(TOKEN_QUERY_CARRIER_ID_AND_UPDATE_SIM_SUMMARY);
mCarrierIdAsyncQueryHandler.startQuery(TOKEN_QUERY_CARRIER_ID_AND_UPDATE_SIM_SUMMARY,
null /* cookie */,
CarrierId.All.CONTENT_URI,
new String[]{CarrierId.CARRIER_NAME},
CarrierId.CARRIER_ID + "=?",
new String[] {Integer.toString(config.carrierId)},
null /* orderBy */);
}
private void refreshMacAddress() {
final String macAddress = mWifiEntry.getMacAddress();
if (TextUtils.isEmpty(macAddress)) {
@@ -907,8 +1010,8 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
mContext = context;
}
public Drawable getIcon(int level) {
return mContext.getDrawable(Utils.getWifiIconResource(level)).mutate();
public Drawable getIcon(boolean showX, int level) {
return mContext.getDrawable(Utils.getWifiIconResource(showX, level)).mutate();
}
}

View File

@@ -25,6 +25,7 @@ import android.net.NetworkInfo.State;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
@@ -44,6 +45,9 @@ import com.android.settingslib.wifi.AccessPoint;
*/
public class ContextualWifiSlice extends WifiSlice {
@VisibleForTesting
static final String CONTEXTUAL_WIFI_EXPANDABLE = "contextual_wifi_expandable";
@VisibleForTesting
static final int COLLAPSED_ROW_COUNT = 0;
@@ -63,13 +67,17 @@ public class ContextualWifiSlice extends WifiSlice {
@Override
public Slice getSlice() {
final long currentUiSession = FeatureFactory.getFactory(mContext)
.getSlicesFeatureProvider().getUiSessionToken();
if (currentUiSession != sActiveUiSession) {
sActiveUiSession = currentUiSession;
sApRowCollapsed = hasWorkingNetwork();
} else if (!mWifiManager.isWifiEnabled()) {
sApRowCollapsed = false;
if (isExpandable()) {
final long currentUiSession = FeatureFactory.getFactory(mContext)
.getSlicesFeatureProvider().getUiSessionToken();
if (currentUiSession != sActiveUiSession) {
sActiveUiSession = currentUiSession;
sApRowCollapsed = hasWorkingNetwork();
} else if (!mWifiManager.isWifiEnabled()) {
sApRowCollapsed = false;
}
} else {
sApRowCollapsed = true;
}
return super.getSlice();
}
@@ -87,12 +95,18 @@ public class ContextualWifiSlice extends WifiSlice {
protected ListBuilder.RowBuilder getHeaderRow(boolean isWifiEnabled, AccessPoint accessPoint) {
final ListBuilder.RowBuilder builder = super.getHeaderRow(isWifiEnabled, accessPoint);
builder.setTitleItem(getHeaderIcon(isWifiEnabled, accessPoint), ListBuilder.ICON_IMAGE);
if (sApRowCollapsed) {
if (sApRowCollapsed && isWifiEnabled) {
builder.setSubtitle(getSubtitle(accessPoint));
}
return builder;
}
private boolean isExpandable() {
// Return whether this slice can be expandable.
return Settings.Global.getInt(mContext.getContentResolver(), CONTEXTUAL_WIFI_EXPANDABLE, 0)
!= 0;
}
private IconCompat getHeaderIcon(boolean isWifiEnabled, AccessPoint accessPoint) {
final Drawable drawable;
final int tint;

View File

@@ -118,7 +118,7 @@ public class WifiTetherPasswordPreferenceController extends WifiTetherBasePrefer
@Override
public boolean isTextValid(String value) {
return WifiUtils.isHotspotPasswordValid(value);
return WifiUtils.isHotspotWpa2PasswordValid(value);
}
private static String generateRandomPassword() {