From 64fdef1dc7c354d55d991ca91105818dcb5e423a Mon Sep 17 00:00:00 2001 From: Joshua Mccloskey Date: Thu, 15 Apr 2021 14:02:51 -0700 Subject: [PATCH 01/12] Updated settings for new get/set feature change Test: Verified e2e get/set work within settings Bug: 184657294 Change-Id: I0240b092751eb1ca205d1cf245c005c0fe9d4338 --- .../FaceSettingsAttentionPreferenceController.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java index 200c0b949ec..0eb823bc4d9 100644 --- a/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java +++ b/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java @@ -57,15 +57,15 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe private final GetFeatureCallback mGetFeatureCallback = new GetFeatureCallback() { @Override - public void onCompleted(boolean success, int feature, boolean value) { - if (feature == FaceManager.FEATURE_REQUIRE_ATTENTION && success) { - if (!mFaceManager.hasEnrolledTemplates(getUserId())) { - mPreference.setEnabled(false); - } else { - mPreference.setEnabled(true); - mPreference.setChecked(value); + public void onCompleted(boolean success, int[] features, boolean[] featureState) { + boolean requireAttentionEnabled = false; + for (int i = 0; i < features.length; i++) { + if (features[i] == FaceManager.FEATURE_REQUIRE_ATTENTION) { + requireAttentionEnabled = featureState[i]; } } + mPreference.setEnabled(success); + mPreference.setChecked(requireAttentionEnabled); } }; From d451d16712b44c084a3c5e866ae3efb17d2c9569 Mon Sep 17 00:00:00 2001 From: Weng Su Date: Fri, 23 Apr 2021 20:34:07 +0800 Subject: [PATCH 02/12] Show security screen for ACTION_PROCESS_WIFI_EASY_CONNECT_URI intent Bug: 183181987 Test: manual test make RunSettingsRoboTests ROBOTEST_FILTER=WifiDppConfiguratorActivityTest Change-Id: I26e97142cc1c30909bd14eb85d466442274446a9 --- .../wifi/dpp/WifiDppConfiguratorActivity.java | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivity.java b/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivity.java index 6659c7ace91..ecaf9ee8fc4 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivity.java +++ b/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivity.java @@ -144,31 +144,8 @@ public class WifiDppConfiguratorActivity extends WifiDppBaseActivity implements } break; case Settings.ACTION_PROCESS_WIFI_EASY_CONNECT_URI: - final Uri uri = intent.getData(); - final String uriString = (uri == null) ? null : uri.toString(); - mWifiDppQrCode = WifiQrCode.getValidWifiDppQrCodeOrNull(uriString); - mWifiDppRemoteBandSupport = intent.getIntArrayExtra( - Settings.EXTRA_EASY_CONNECT_BAND_LIST); // returns null if none - final boolean isDppSupported = WifiDppUtils.isWifiDppEnabled(this); - if (!isDppSupported) { - Log.e(TAG, - "ACTION_PROCESS_WIFI_EASY_CONNECT_URI for a device that doesn't " - + "support Wifi DPP - use WifiManager#isEasyConnectSupported"); - } - if (mWifiDppQrCode == null) { - Log.e(TAG, "ACTION_PROCESS_WIFI_EASY_CONNECT_URI with null URI!"); - } - if (mWifiDppQrCode == null || !isDppSupported) { - cancelActivity = true; - } else { - final WifiNetworkConfig connectedConfig = getConnectedWifiNetworkConfigOrNull(); - if (connectedConfig == null || !connectedConfig.isSupportWifiDpp(this)) { - showChooseSavedWifiNetworkFragment(/* addToBackStack */ false); - } else { - mWifiNetworkConfig = connectedConfig; - showAddDeviceFragment(/* addToBackStack */ false); - } - } + WifiDppUtils.showLockScreen(this, + () -> handleActionProcessWifiEasyConnectUriIntent(intent)); break; default: cancelActivity = true; @@ -180,6 +157,34 @@ public class WifiDppConfiguratorActivity extends WifiDppBaseActivity implements } } + private void handleActionProcessWifiEasyConnectUriIntent(Intent intent) { + final Uri uri = intent.getData(); + final String uriString = (uri == null) ? null : uri.toString(); + mWifiDppQrCode = WifiQrCode.getValidWifiDppQrCodeOrNull(uriString); + mWifiDppRemoteBandSupport = intent.getIntArrayExtra( + Settings.EXTRA_EASY_CONNECT_BAND_LIST); // returns null if none + final boolean isDppSupported = WifiDppUtils.isWifiDppEnabled(this); + if (!isDppSupported) { + Log.e(TAG, + "ACTION_PROCESS_WIFI_EASY_CONNECT_URI for a device that doesn't " + + "support Wifi DPP - use WifiManager#isEasyConnectSupported"); + } + if (mWifiDppQrCode == null) { + Log.e(TAG, "ACTION_PROCESS_WIFI_EASY_CONNECT_URI with null URI!"); + } + if (mWifiDppQrCode == null || !isDppSupported) { + finish(); + } else { + final WifiNetworkConfig connectedConfig = getConnectedWifiNetworkConfigOrNull(); + if (connectedConfig == null || !connectedConfig.isSupportWifiDpp(this)) { + showChooseSavedWifiNetworkFragment(/* addToBackStack */ false); + } else { + mWifiNetworkConfig = connectedConfig; + showAddDeviceFragment(/* addToBackStack */ false); + } + } + } + private void showQrCodeScannerFragment() { WifiDppQrCodeScannerFragment fragment = (WifiDppQrCodeScannerFragment) mFragmentManager.findFragmentByTag( From 2b78aa098893d74cd3c86ea96fa28d90c43acb29 Mon Sep 17 00:00:00 2001 From: Tsung-Mao Fang Date: Fri, 23 Apr 2021 22:26:19 +0800 Subject: [PATCH 03/12] Fix unable to change language In some cases, recycler view receives the cancel touch event, it causes recycler view doesn't update the new locale list. Currently, we also treat cancel event as a signal to update the locale. According to the document, we can treat cancel event as a up event, but not perform any action that you normally would. It's ok in our case since we check if updated list is same as last one in adapater. Test: Drag and drop locale item, and make sure the language system is updated. Fix: 184051013 Fix: 184903189 Fix: 185000363 Change-Id: I06610080f34c5ed3562c74e1cdd955571cae55c0 --- src/com/android/settings/localepicker/LocaleRecyclerView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/localepicker/LocaleRecyclerView.java b/src/com/android/settings/localepicker/LocaleRecyclerView.java index e82874dab70..d32a735d48c 100644 --- a/src/com/android/settings/localepicker/LocaleRecyclerView.java +++ b/src/com/android/settings/localepicker/LocaleRecyclerView.java @@ -37,7 +37,7 @@ class LocaleRecyclerView extends RecyclerView { @Override public boolean onTouchEvent(MotionEvent e) { - if (e.getAction() == MotionEvent.ACTION_UP) { + if (e.getAction() == MotionEvent.ACTION_UP || e.getAction() == MotionEvent.ACTION_CANCEL) { LocaleDragAndDropAdapter adapter = (LocaleDragAndDropAdapter) this.getAdapter(); if (adapter != null) { adapter.doTheUpdate(); From 7dc2624b51f54689f226a628b16b83bbf372a6eb Mon Sep 17 00:00:00 2001 From: Eugene Susla Date: Tue, 10 Nov 2020 11:54:39 -0800 Subject: [PATCH 04/12] Companion Device App showing in Settings Added a feature that users are able to see/remove the apps' associations in Settings/Connected devices. It will display the icon of the associations app, app's name and a button that users are able to remove the associations. Also it will pop up a dialog alerting user before remove the associations. Screenshot: https://screenshot.googleplex.com/APSRhW2retYmAAK Bug: 165951651 Test: Manually Test Change-Id: Iccaeaf516e8a78d4ef33415c1c2d7379139ec88c --- .../companion_apps_remove_button_widget.xml | 24 ++ res/layout/preference_companion_app.xml | 26 ++ res/values/dimens.xml | 3 + res/values/strings.xml | 8 + res/xml/bluetooth_device_details_fragment.xml | 3 + ...uetoothDetailsCompanionAppsController.java | 224 ++++++++++++++++++ .../BluetoothDeviceDetailsFragment.java | 2 + .../CompanionAppWidgetPreference.java | 58 +++++ ...othDetailsCompanionAppsControllerTest.java | 178 ++++++++++++++ .../CompanionAppWidgetPreferenceTest.java | 103 ++++++++ 10 files changed, 629 insertions(+) create mode 100644 res/layout/companion_apps_remove_button_widget.xml create mode 100644 res/layout/preference_companion_app.xml create mode 100644 src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsController.java create mode 100644 src/com/android/settings/bluetooth/CompanionAppWidgetPreference.java create mode 100644 tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/bluetooth/CompanionAppWidgetPreferenceTest.java diff --git a/res/layout/companion_apps_remove_button_widget.xml b/res/layout/companion_apps_remove_button_widget.xml new file mode 100644 index 00000000000..a3c229537ac --- /dev/null +++ b/res/layout/companion_apps_remove_button_widget.xml @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/res/layout/preference_companion_app.xml b/res/layout/preference_companion_app.xml new file mode 100644 index 00000000000..22712753b2f --- /dev/null +++ b/res/layout/preference_companion_app.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 6047092f8d3..74db2323822 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -205,6 +205,9 @@ 21dp 16dp + + 20dp + 1px 16dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 221f049dd4e..9bfb9bd9904 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1817,6 +1817,10 @@ Device\'s Bluetooth address: %1$s Forget device? + + Remove association + + Disconnect App? Your phone will no longer be paired with %1$s @@ -1824,12 +1828,16 @@ Your tablet will no longer be paired with %1$s Your device will no longer be paired with %1$s + + %1$s app will no longer connect to your %2$s %1$s will no longer be paired with any device linked to this account Forget device + + Disconnect app Connect to\u2026 diff --git a/res/xml/bluetooth_device_details_fragment.xml b/res/xml/bluetooth_device_details_fragment.xml index 94052b67213..5084d4d481a 100644 --- a/res/xml/bluetooth_device_details_fragment.xml +++ b/res/xml/bluetooth_device_details_fragment.xml @@ -44,6 +44,9 @@ settings:allowDividerBelow="true" settings:allowDividerAbove="true"/> + + diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsController.java new file mode 100644 index 00000000000..f2a94ba7440 --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsController.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2021 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.bluetooth; + +import static com.android.internal.util.CollectionUtils.filter; + +import android.companion.Association; +import android.companion.CompanionDeviceManager; +import android.companion.ICompanionDeviceManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AlertDialog; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import com.google.common.base.Objects; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + + +/** + * This class adds Companion Device app rows to launch the app or remove the associations + */ +public class BluetoothDetailsCompanionAppsController extends BluetoothDetailsController { + public static final String KEY_DEVICE_COMPANION_APPS = "device_companion_apps"; + private static final String LOG_TAG = "BTCompanionController"; + + private CachedBluetoothDevice mCachedDevice; + + @VisibleForTesting + PreferenceCategory mProfilesContainer; + + @VisibleForTesting + CompanionDeviceManager mCompanionDeviceManager; + + @VisibleForTesting + PackageManager mPackageManager; + + public BluetoothDetailsCompanionAppsController(Context context, + PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle) { + super(context, fragment, device, lifecycle); + mCachedDevice = device; + mCompanionDeviceManager = context.getSystemService(CompanionDeviceManager.class); + mPackageManager = context.getPackageManager(); + lifecycle.addObserver(this); + } + + @Override + protected void init(PreferenceScreen screen) { + mProfilesContainer = screen.findPreference(getPreferenceKey()); + mProfilesContainer.setLayoutResource(R.layout.preference_companion_app); + } + + private List getAssociations(String address) { + return filter( + mCompanionDeviceManager.getAllAssociations(), + a -> Objects.equal(address, a.getDeviceMacAddress())); + } + + private static void removePreference(PreferenceCategory container, String packageName) { + Preference preference = container.findPreference(packageName); + if (preference != null) { + container.removePreference(preference); + } + } + + private void removeAssociationDialog(String packageName, String address, + PreferenceCategory container, CharSequence appName, Context context) { + DialogInterface.OnClickListener dialogClickListener = (dialog, which) -> { + if (which == DialogInterface.BUTTON_POSITIVE) { + removeAssociation(packageName, address, container); + } + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + + builder.setPositiveButton( + R.string.bluetooth_companion_app_remove_association_confirm_button, + dialogClickListener) + .setNegativeButton(android.R.string.cancel, dialogClickListener) + .setTitle(R.string.bluetooth_companion_app_remove_association_dialog_title) + .setMessage(mContext.getString( + R.string.bluetooth_companion_app_body, appName, mCachedDevice.getName())) + .show(); + } + + private static void removeAssociation(String packageName, String address, + PreferenceCategory container) { + try { + java.util.Objects.requireNonNull(ICompanionDeviceManager.Stub.asInterface( + ServiceManager.getService( + Context.COMPANION_DEVICE_SERVICE))).disassociate( + address, packageName); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + + removePreference(container, packageName); + } + + private CharSequence getAppName(String packageName) { + CharSequence appName = null; + try { + appName = mPackageManager.getApplicationLabel( + mPackageManager.getApplicationInfo(packageName, 0)); + } catch (PackageManager.NameNotFoundException e) { + Log.e(LOG_TAG, "Package Not Found", e); + } + + return appName; + } + + private List getPreferencesNeedToShow(String address, PreferenceCategory container) { + List preferencesToRemove = new ArrayList<>(); + Set packages = getAssociations(address) + .stream().map(Association::getPackageName) + .collect(Collectors.toSet()); + + for (int i = 0; i < container.getPreferenceCount(); i++) { + String preferenceKey = container.getPreference(i).getKey(); + if (packages.isEmpty() || !packages.contains(preferenceKey)) { + preferencesToRemove.add(preferenceKey); + } + } + + for (String preferenceName : preferencesToRemove) { + removePreference(container, preferenceName); + } + + return packages.stream() + .filter(p -> container.findPreference(p) == null) + .collect(Collectors.toList()); + } + + /** + * Refreshes the state of the preferences for all the associations, possibly adding or + * removing preferences as needed. + */ + @Override + protected void refresh() { + updatePreferences(mContext, mCachedDevice.getAddress(), mProfilesContainer); + } + + /** + * Add preferences for each association for the bluetooth device + */ + public void updatePreferences(Context context, + String address, PreferenceCategory container) { + Set addedPackages = new HashSet<>(); + + for (String packageName : getPreferencesNeedToShow(address, container)) { + CharSequence appName = getAppName(packageName); + + if (TextUtils.isEmpty(appName) || !addedPackages.add(packageName)) { + continue; + } + + Drawable removeIcon = context.getResources().getDrawable(R.drawable.ic_clear); + CompanionAppWidgetPreference preference = new CompanionAppWidgetPreference( + removeIcon, + v -> removeAssociationDialog(packageName, address, container, appName, context), + context + ); + + Drawable appIcon; + + try { + appIcon = mPackageManager.getApplicationIcon(packageName); + } catch (PackageManager.NameNotFoundException e) { + Log.e(LOG_TAG, "Icon Not Found", e); + continue; + } + Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName); + preference.setIcon(appIcon); + preference.setTitle(appName.toString()); + preference.setOnPreferenceClickListener(v -> { + context.startActivity(intent); + return true; + }); + + preference.setKey(packageName); + preference.setVisible(true); + container.addPreference(preference); + } + } + + @Override + public String getPreferenceKey() { + return KEY_DEVICE_COMPANION_APPS; + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java index a8812475ace..4980ba313fb 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java @@ -185,6 +185,8 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment lifecycle, mManager)); controllers.add(new BluetoothDetailsButtonsController(context, this, mCachedDevice, lifecycle)); + controllers.add(new BluetoothDetailsCompanionAppsController(context, this, + mCachedDevice, lifecycle)); controllers.add(new BluetoothDetailsProfilesController(context, this, mManager, mCachedDevice, lifecycle)); controllers.add(new BluetoothDetailsMacAddressController(context, this, mCachedDevice, diff --git a/src/com/android/settings/bluetooth/CompanionAppWidgetPreference.java b/src/com/android/settings/bluetooth/CompanionAppWidgetPreference.java new file mode 100644 index 00000000000..cd0643373d6 --- /dev/null +++ b/src/com/android/settings/bluetooth/CompanionAppWidgetPreference.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 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.bluetooth; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.ImageButton; + +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.android.settings.R; + +/** + * A custom preference for companion device apps. Added a button for association removal + */ +public class CompanionAppWidgetPreference extends Preference { + private Drawable mWidgetIcon; + private View.OnClickListener mWidgetListener; + private int mImageButtonPadding; + + public CompanionAppWidgetPreference(Drawable widgetIcon, View.OnClickListener widgetListener, + Context context) { + super(context); + mWidgetIcon = widgetIcon; + mWidgetListener = widgetListener; + mImageButtonPadding = context.getResources().getDimensionPixelSize( + R.dimen.bluetooth_companion_app_widget); + setWidgetLayoutResource(R.layout.companion_apps_remove_button_widget); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + ImageButton imageButton = (ImageButton) holder.findViewById(R.id.remove_button); + imageButton.setPadding( + mImageButtonPadding, mImageButtonPadding, mImageButtonPadding, mImageButtonPadding); + imageButton.setColorFilter(getContext().getColor(android.R.color.darker_gray)); + imageButton.setImageDrawable(mWidgetIcon); + imageButton.setOnClickListener(mWidgetListener); + } + +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsControllerTest.java new file mode 100644 index 00000000000..3f49938412b --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsControllerTest.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2021 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.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.companion.Association; +import android.companion.CompanionDeviceManager; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.robolectric.RobolectricTestRunner; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@RunWith(RobolectricTestRunner.class) +public class BluetoothDetailsCompanionAppsControllerTest extends + BluetoothDetailsControllerTestBase { + private static final String PACKAGE_NAME_ONE = "com.google.android.deskclock"; + private static final String PACKAGE_NAME_TWO = "com.google.android.calculator"; + private static final String PACKAGE_NAME_THREE = "com.google.android.GoogleCamera"; + private static final CharSequence APP_NAME_ONE = "deskclock"; + private static final CharSequence APP_NAME_TWO = "calculator"; + private static final CharSequence APP_NAME_THREE = "GoogleCamera"; + + @Mock + private CompanionDeviceManager mCompanionDeviceManager; + @Mock + private PackageManager mPackageManager; + + private BluetoothDetailsCompanionAppsController mController; + private PreferenceCategory mProfiles; + private List mPackages; + private List mAppNames; + private List mAssociations; + + + @Override + public void setUp() { + super.setUp(); + mPackages = Arrays.asList(PACKAGE_NAME_ONE, PACKAGE_NAME_TWO, PACKAGE_NAME_THREE); + mAppNames = Arrays.asList(APP_NAME_ONE, APP_NAME_TWO, APP_NAME_THREE); + mProfiles = spy(new PreferenceCategory(mContext)); + mAssociations = new ArrayList<>(); + when(mCompanionDeviceManager.getAllAssociations()).thenReturn(mAssociations); + when(mProfiles.getPreferenceManager()).thenReturn(mPreferenceManager); + setupDevice(mDeviceConfig); + mController = + new BluetoothDetailsCompanionAppsController(mContext, mFragment, mCachedDevice, + mLifecycle); + mController.mCompanionDeviceManager = mCompanionDeviceManager; + mController.mPackageManager = mPackageManager; + mController.mProfilesContainer = mProfiles; + mProfiles.setKey(mController.getPreferenceKey()); + mScreen.addPreference(mProfiles); + } + + private void setupFakeLabelAndInfo(String packageName, CharSequence appName) { + ApplicationInfo appInfo = mock(ApplicationInfo.class); + try { + when(mPackageManager.getApplicationInfo(packageName, 0)).thenReturn(appInfo); + when(mPackageManager.getApplicationLabel(appInfo)).thenReturn(appName); + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } + } + + private void addFakeAssociation(String packageName, CharSequence appName) { + setupFakeLabelAndInfo(packageName, appName); + Association association = new Association( + 0, mCachedDevice.getAddress(), packageName, "", true, System.currentTimeMillis()); + mAssociations.add(association); + showScreen(mController); + } + + private Preference getPreference(int index) { + PreferenceCategory preferenceCategory = mProfiles.findPreference( + mController.getPreferenceKey()); + return Objects.requireNonNull(preferenceCategory).getPreference(index); + } + + private void removeAssociation(String packageName) { + mAssociations = mAssociations.stream() + .filter(a -> !a.getPackageName().equals(packageName)) + .collect(Collectors.toList()); + + when(mCompanionDeviceManager.getAllAssociations()).thenReturn(mAssociations); + + showScreen(mController); + } + + @Test + public void addOneAssociation_preferenceShouldBeAdded() { + addFakeAssociation(PACKAGE_NAME_ONE, APP_NAME_ONE); + + Preference preferenceOne = getPreference(0); + + assertThat(preferenceOne.getClass()).isEqualTo(CompanionAppWidgetPreference.class); + assertThat(preferenceOne.getKey()).isEqualTo(PACKAGE_NAME_ONE); + assertThat(preferenceOne.getTitle()).isEqualTo(APP_NAME_ONE.toString()); + assertThat(mProfiles.getPreferenceCount()).isEqualTo(1); + + removeAssociation(PACKAGE_NAME_ONE); + + assertThat(mProfiles.getPreferenceCount()).isEqualTo(0); + } + + @Test + public void removeOneAssociation_preferenceShouldBeRemoved() { + addFakeAssociation(PACKAGE_NAME_ONE, APP_NAME_ONE); + + removeAssociation(PACKAGE_NAME_ONE); + + assertThat(mProfiles.getPreferenceCount()).isEqualTo(0); + } + + @Test + public void addMultipleAssociations_preferencesShouldBeAdded() { + for (int i = 0; i < mPackages.size(); i++) { + addFakeAssociation(mPackages.get(i), mAppNames.get(i)); + + Preference preference = getPreference(i); + + assertThat(preference.getClass()).isEqualTo(CompanionAppWidgetPreference.class); + assertThat(preference.getKey()).isEqualTo(mPackages.get(i)); + assertThat(preference.getTitle()).isEqualTo(mAppNames.get(i).toString()); + assertThat(mProfiles.getPreferenceCount()).isEqualTo(i + 1); + } + } + + @Test + public void removeMultipleAssociations_preferencesShouldBeRemoved() { + for (int i = 0; i < mPackages.size(); i++) { + addFakeAssociation(mPackages.get(i), mAppNames.get(i).toString()); + } + + for (int i = 0; i < mPackages.size(); i++) { + removeAssociation(mPackages.get(i)); + + assertThat(mProfiles.getPreferenceCount()).isEqualTo(mPackages.size() - i - 1); + + if (i == mPackages.size() - 1) { + break; + } + + assertThat(getPreference(0).getKey()).isEqualTo(mPackages.get(i + 1)); + assertThat(getPreference(0).getTitle()).isEqualTo(mAppNames.get(i + 1).toString()); + } + } +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/CompanionAppWidgetPreferenceTest.java b/tests/robotests/src/com/android/settings/bluetooth/CompanionAppWidgetPreferenceTest.java new file mode 100644 index 00000000000..8ee3ef78d81 --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/CompanionAppWidgetPreferenceTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2021 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.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.ContextThemeWrapper; +import android.view.View; + +import com.android.settings.R; +import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowAlertDialogCompat.class}) +public class CompanionAppWidgetPreferenceTest { + private static final String TITLE_ONE = "Test Title 1"; + private static final String TITLE_TWO = "Test Title 1"; + private static final String KEY_ONE = "Test Key 1"; + private static final String KEY_TWO = "Test Key 1"; + + private Context mContext; + private Drawable mWidgetIconOne; + private Drawable mWidgetIconTwo; + private Drawable mAppIconOne; + private Drawable mAppIconTwo; + + @Mock + private View.OnClickListener mWidgetListenerOne; + @Mock + private View.OnClickListener mWidgetListenerTwo; + + private List mPreferenceContainer; + + @Before + public void setUp() { + mPreferenceContainer = new ArrayList<>(); + Context context = spy(RuntimeEnvironment.application.getApplicationContext()); + mContext = new ContextThemeWrapper(context, R.style.Theme_Settings); + mWidgetIconOne = mock(Drawable.class); + mAppIconOne = mock(Drawable.class); + mWidgetListenerOne = mock(View.OnClickListener.class); + mWidgetIconTwo = mock(Drawable.class); + mAppIconTwo = mock(Drawable.class); + mWidgetListenerTwo = mock(View.OnClickListener.class); + } + + private void setUpPreferenceContainer(Drawable widgetIcon, Drawable appIcon, + View.OnClickListener listener, String title, String key) { + CompanionAppWidgetPreference preference = new CompanionAppWidgetPreference( + widgetIcon, listener, mContext); + preference.setIcon(appIcon); + preference.setTitle(title); + preference.setKey(key); + mPreferenceContainer.add(preference); + } + + @Test + public void setUpPreferenceContainer_preferenceShouldBeAdded() { + setUpPreferenceContainer( + mWidgetIconOne, mAppIconOne, mWidgetListenerOne, TITLE_ONE, KEY_ONE); + + assertThat(mPreferenceContainer.get(0).getIcon()).isEqualTo(mAppIconOne); + assertThat(mPreferenceContainer.get(0).getKey()).isEqualTo(KEY_ONE); + assertThat(mPreferenceContainer.get(0).getTitle()).isEqualTo(TITLE_ONE); + + setUpPreferenceContainer( + mWidgetIconTwo, mAppIconTwo, mWidgetListenerTwo, TITLE_TWO, KEY_TWO); + + assertThat(mPreferenceContainer.get(1).getIcon()).isEqualTo(mAppIconTwo); + assertThat(mPreferenceContainer.get(1).getKey()).isEqualTo(KEY_TWO); + assertThat(mPreferenceContainer.get(1).getTitle()).isEqualTo(TITLE_TWO); + } +} From 75c5fc59060b44de9d97db5fce94af51f96b8a1a Mon Sep 17 00:00:00 2001 From: ykhung Date: Sun, 25 Apr 2021 17:07:58 +0800 Subject: [PATCH 05/12] Wrap and carry clicked slot information data for restriction page Refresh category title by different time slot condition Bug: 177406865 Test: make SettingsRoboTests Test: make SettingsGoogleRoboTests Change-Id: I006f6825fdb471f863c0355c65bfc4ca9d980191 --- .../fuelgauge/AdvancedPowerUsageDetail.java | 11 +++++-- .../BatteryChartPreferenceController.java | 32 ++++++++++++------- .../fuelgauge/ExpandDividerPreference.java | 3 ++ 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java index 3ef7c455302..2ebff347396 100644 --- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java @@ -113,6 +113,7 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements private String mUsagePercent; private String mPackageName; private String mAppLabel; + private String mSlotInformation; private int mUid; private int mIconId; private int mConsumedPower; @@ -124,18 +125,22 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements /** Launches battery details page for an individual battery consumer. */ public static void startBatteryDetailPage( Activity caller, InstrumentedPreferenceFragment fragment, - BatteryDiffEntry diffEntry, String usagePercent) { + BatteryDiffEntry diffEntry, String usagePercent, + boolean isValidToShowSummary, String slotInformation) { final BatteryHistEntry histEntry = diffEntry.mBatteryHistEntry; final LaunchBatteryDetailPageArgs launchArgs = new LaunchBatteryDetailPageArgs(); // configure the launch argument. launchArgs.mUsagePercent = usagePercent; launchArgs.mPackageName = diffEntry.getPackageName(); launchArgs.mAppLabel = diffEntry.getAppLabel(); + launchArgs.mSlotInformation = slotInformation; launchArgs.mUid = (int) histEntry.mUid; launchArgs.mIconId = diffEntry.getAppIconId(); launchArgs.mConsumedPower = (int) diffEntry.mConsumePower; - launchArgs.mForegroundTimeMs = diffEntry.mForegroundUsageTimeInMs; - launchArgs.mBackgroundTimeMs = diffEntry.mBackgroundUsageTimeInMs; + launchArgs.mForegroundTimeMs = + isValidToShowSummary ? diffEntry.mForegroundUsageTimeInMs : 0; + launchArgs.mBackgroundTimeMs = + isValidToShowSummary ? diffEntry.mBackgroundUsageTimeInMs : 0; launchArgs.mIsUserEntry = histEntry.isUserEntry(); startBatteryDetailPage(caller, fragment, launchArgs); } diff --git a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java index ff7447725fc..99e6b0db666 100644 --- a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java @@ -151,21 +151,22 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll final PowerGaugePreference powerPref = (PowerGaugePreference) preference; final BatteryDiffEntry diffEntry = powerPref.getBatteryDiffEntry(); final BatteryHistEntry histEntry = diffEntry.mBatteryHistEntry; + final String packageName = histEntry.mPackageName; // Checks whether the package is installed or not. boolean isValidPackage = true; if (histEntry.isAppEntry()) { if (mBatteryUtils == null) { mBatteryUtils = BatteryUtils.getInstance(mPrefContext); } - isValidPackage = mBatteryUtils.getPackageUid(histEntry.mPackageName) + isValidPackage = mBatteryUtils.getPackageUid(packageName) != BatteryUtils.UID_NULL; } Log.d(TAG, String.format("handleClick() label=%s key=%s isValid:%b %s", - diffEntry.getAppLabel(), histEntry.getKey(), isValidPackage, - histEntry.mPackageName)); + diffEntry.getAppLabel(), histEntry.getKey(), isValidPackage, packageName)); if (isValidPackage) { AdvancedPowerUsageDetail.startBatteryDetailPage( - mActivity, mFragment, diffEntry, powerPref.getPercent()); + mActivity, mFragment, diffEntry, powerPref.getPercent(), + isValidToShowSummary(packageName), getSlotInformation()); return true; } return false; @@ -267,8 +268,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll || (mTrapezoidIndex == trapezoidIndex && !isForce)) { return false; } - Log.d(TAG, String.format("refreshUi: index=%d batteryIndexedMap.size=%d", - mTrapezoidIndex, mBatteryIndexedMap.size())); + Log.d(TAG, String.format("refreshUi: index=%d size=%d isForce:%b", + trapezoidIndex, mBatteryIndexedMap.size(), isForce)); mTrapezoidIndex = trapezoidIndex; mHandler.post(() -> { @@ -471,11 +472,9 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll final long backgroundUsageTimeInMs = entry.mBackgroundUsageTimeInMs; final long totalUsageTimeInMs = foregroundUsageTimeInMs + backgroundUsageTimeInMs; // Checks whether the package is allowed to show summary or not. - for (CharSequence notAllowPackageName : mNotAllowShowSummaryPackages) { - if (TextUtils.equals(entry.getPackageName(), notAllowPackageName)) { - preference.setSummary(null); - return; - } + if (!isValidToShowSummary(entry.getPackageName())) { + preference.setSummary(null); + return; } String usageTimeSummary = null; // Not shows summary for some system components without usage time. @@ -515,6 +514,17 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll return mPrefContext.getString(resourceId, timeSequence); } + private boolean isValidToShowSummary(String packageName) { + if (mNotAllowShowSummaryPackages != null) { + for (CharSequence notAllowPackageName : mNotAllowShowSummaryPackages) { + if (TextUtils.equals(packageName, notAllowPackageName)) { + return false; + } + } + } + return true; + } + private static String utcToLocalTime(long[] timestamps) { final StringBuilder builder = new StringBuilder(); for (int index = 0; index < timestamps.length; index++) { diff --git a/src/com/android/settings/fuelgauge/ExpandDividerPreference.java b/src/com/android/settings/fuelgauge/ExpandDividerPreference.java index 40536eb37c7..b89f2d42acb 100644 --- a/src/com/android/settings/fuelgauge/ExpandDividerPreference.java +++ b/src/com/android/settings/fuelgauge/ExpandDividerPreference.java @@ -38,6 +38,7 @@ public class ExpandDividerPreference extends Preference { private OnExpandListener mOnExpandListener; private boolean mIsExpanded = false; + private String mTitleContent = null; /** A callback listener for expand state is changed by users. */ public interface OnExpandListener { @@ -72,6 +73,7 @@ public class ExpandDividerPreference extends Preference { } void setTitle(final String titleContent) { + mTitleContent = titleContent; if (mTextView != null) { mTextView.postDelayed( () -> mTextView.setText(titleContent), 50); @@ -95,5 +97,6 @@ public class ExpandDividerPreference extends Preference { if (mImageView != null) { mImageView.setImageResource(iconId); } + setTitle(mTitleContent); } } From 5c05e0f16031048f92cb752da058e81090251cec Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Mon, 26 Apr 2021 09:22:23 +0800 Subject: [PATCH 06/12] Fix the GAR issue of TalkBack said "double-tap to activate" on the slider title of Accessibility button page. Root cause: Set "android:selectable" as true. Solution: Remove "android:selectable". Bug: 183469419 Test: manual test Change-Id: I337cfe0795205dec5e4654b90d6c7e2c7fd7ca77 --- res/xml/accessibility_button_settings.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/res/xml/accessibility_button_settings.xml b/res/xml/accessibility_button_settings.xml index 5e81616bffc..bb16f0397cd 100644 --- a/res/xml/accessibility_button_settings.xml +++ b/res/xml/accessibility_button_settings.xml @@ -56,7 +56,6 @@ From 244268b98d37d81f0bfe2ad470542e334201c7d8 Mon Sep 17 00:00:00 2001 From: changbetty Date: Sun, 25 Apr 2021 14:33:36 +0800 Subject: [PATCH 07/12] Add TextWatcher for the ip config field [Root Cause] We always check the valid for the ip config field. If the text is empty we will set the default for user even if user deleted the values. [Solution] Add TextWatcher for the ip config field (include gateway/network prefix length/dns1). If the text change and the text is empty, we will set the hint for user, not the default. Bug: 179331339 Test: manual test make RunSettingsRoboTests ROBOTEST_FILTER=WifiConfigController2 Change-Id: I1976200c70ae768285a44aff5df54ced00a78171 --- .../settings/wifi/WifiConfigController2.java | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/wifi/WifiConfigController2.java b/src/com/android/settings/wifi/WifiConfigController2.java index 9f3b1a711d9..1e0670788ad 100644 --- a/src/com/android/settings/wifi/WifiConfigController2.java +++ b/src/com/android/settings/wifi/WifiConfigController2.java @@ -1339,12 +1339,13 @@ public class WifiConfigController2 implements TextWatcher, mIpAddressView = (TextView) mView.findViewById(R.id.ipaddress); mIpAddressView.addTextChangedListener(this); mGatewayView = (TextView) mView.findViewById(R.id.gateway); - mGatewayView.addTextChangedListener(this); + mGatewayView.addTextChangedListener(getIpConfigFieldsTextWatcher(mGatewayView)); mNetworkPrefixLengthView = (TextView) mView.findViewById( R.id.network_prefix_length); - mNetworkPrefixLengthView.addTextChangedListener(this); + mNetworkPrefixLengthView.addTextChangedListener( + getIpConfigFieldsTextWatcher(mNetworkPrefixLengthView)); mDns1View = (TextView) mView.findViewById(R.id.dns1); - mDns1View.addTextChangedListener(this); + mDns1View.addTextChangedListener(getIpConfigFieldsTextWatcher(mDns1View)); mDns2View = (TextView) mView.findViewById(R.id.dns2); mDns2View.addTextChangedListener(this); } @@ -1562,6 +1563,47 @@ public class WifiConfigController2 implements TextWatcher, // work done in afterTextChanged } + /* TODO: Add more test cases for this TextWatcher b/186368002 + * This TextWatcher is for IP config fields (Gateway/Network Prefix Length/DNS1) to prevent + * to rewrite the value in these columns that the user wanted to change after they saved. + * When afterTextChanged we will check the text is empty or not then set the Hint for user. + */ + private TextWatcher getIpConfigFieldsTextWatcher(final TextView view) { + return new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // work done in afterTextChanged + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // work done in afterTextChanged + } + + @Override + public void afterTextChanged(Editable s) { + if (s.length() == 0) { + if (view.getId() == R.id.gateway) { + mGatewayView.setHint(R.string.wifi_gateway_hint); + } else if (view.getId() == R.id.network_prefix_length) { + mNetworkPrefixLengthView.setHint(R.string.wifi_network_prefix_length_hint); + } else if (view.getId() == R.id.dns1) { + mDns1View.setHint(R.string.wifi_dns1_hint); + } + Button submit = mConfigUi.getSubmitButton(); + if (submit == null) return; + + submit.setEnabled(false); + } else { + ThreadUtils.postOnMainThread(() -> { + showWarningMessagesIfAppropriate(); + enableSubmitIfAppropriate(); + }); + } + } + }; + } + @Override public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) { if (textView == mPasswordView) { From 9b284a9af13af072b80818963eca156c823417bb Mon Sep 17 00:00:00 2001 From: jasonwshsu Date: Tue, 20 Apr 2021 15:19:15 +0800 Subject: [PATCH 08/12] Wrap a white adaptive background if it is not an AdaptiveIcon Bug: 183501919 Test: manual test Change-Id: I6a899aa0fcb724d5318274190f359080fc2d0615 --- src/com/android/settings/Utils.java | 34 ++++++++++++++++--- .../accessibility/AccessibilitySettings.java | 3 +- .../PasswordsPreferenceController.java | 2 +- .../DefaultAppPreferenceController.java | 2 +- .../CurrentDreamPreferenceController.java | 2 +- .../widget/RadioButtonPickerFragment.java | 2 +- 6 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 84183154236..031fb8a9d92 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -48,6 +48,7 @@ import android.content.res.TypedArray; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.VectorDrawable; @@ -94,6 +95,7 @@ import android.widget.EditText; import android.widget.ListView; import android.widget.TabWidget; +import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.core.graphics.drawable.IconCompat; @@ -111,6 +113,7 @@ import com.android.settings.dashboard.profileselector.ProfileFragmentBridge; import com.android.settings.dashboard.profileselector.ProfileSelectFragment; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settingslib.widget.ActionBarShadowController; +import com.android.settingslib.widget.AdaptiveIcon; import java.util.Iterator; import java.util.List; @@ -967,15 +970,36 @@ public final class Utils extends com.android.settingslib.Utils { } /** - * Sets the preference icon with a drawable that is scaled down to to avoid crashing Settings if - * it's too big. + * Gets the adaptive icon with a drawable that wrapped with an adaptive background using {@code + * backgroundColor} if it is not a {@link AdaptiveIconDrawable} + * + * If the given {@code icon} is too big, it will be auto scaled down to to avoid crashing + * Settings. */ - public static void setSafeIcon(Preference pref, Drawable icon) { + public static Drawable getAdaptiveIcon(Context context, Drawable icon, + @ColorInt int backgroundColor) { + Drawable adaptiveIcon = getSafeIcon(icon); + + if (!(adaptiveIcon instanceof AdaptiveIconDrawable)) { + adaptiveIcon = new AdaptiveIcon(context, adaptiveIcon); + ((AdaptiveIcon) adaptiveIcon).setBackgroundColor(backgroundColor); + } + + return adaptiveIcon; + } + + /** + * Gets the icon with a drawable that is scaled down to to avoid crashing Settings if it's too + * big and not a {@link VectorDrawable}. + */ + public static Drawable getSafeIcon(Drawable icon) { Drawable safeIcon = icon; + if ((icon != null) && !(icon instanceof VectorDrawable)) { safeIcon = getSafeDrawable(icon, 500, 500); } - pref.setIcon(safeIcon); + + return safeIcon; } /** @@ -985,7 +1009,7 @@ public final class Utils extends com.android.settingslib.Utils { * @param maxWidth maximum width, in pixels. * @param maxHeight maximum height, in pixels. */ - public static Drawable getSafeDrawable(Drawable original, int maxWidth, int maxHeight) { + private static Drawable getSafeDrawable(Drawable original, int maxWidth, int maxHeight) { final int actualWidth = original.getMinimumWidth(); final int actualHeight = original.getMinimumHeight(); diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index af8bf47036f..d4db3953f79 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -28,6 +28,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.graphics.Color; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; @@ -663,7 +664,7 @@ public class AccessibilitySettings extends DashboardFragment { preference.setKey(key); preference.setTitle(title); preference.setSummary(summary); - Utils.setSafeIcon(preference, icon); + preference.setIcon(Utils.getAdaptiveIcon(mContext, icon, Color.WHITE)); preference.setFragment(fragment); preference.setIconSize(ICON_SIZE_MEDIUM); preference.setPersistent(false); // Disable SharedPreferences. diff --git a/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java b/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java index 47882ded46e..a9d1572b7e5 100644 --- a/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java +++ b/src/com/android/settings/applications/autofill/PasswordsPreferenceController.java @@ -124,7 +124,7 @@ public class PasswordsPreferenceController extends BasePreferenceController serviceInfo, serviceInfo.applicationInfo, user); - Utils.setSafeIcon(pref, icon); + pref.setIcon(Utils.getSafeIcon(icon)); pref.setIntent( new Intent(Intent.ACTION_MAIN) .setClassName(serviceInfo.packageName, service.getPasswordsActivity())); diff --git a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java index 8e8e072a017..fd6f4c243e7 100644 --- a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java +++ b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java @@ -66,7 +66,7 @@ public abstract class DefaultAppPreferenceController extends AbstractPreferenceC } if (!TextUtils.isEmpty(defaultAppLabel)) { preference.setSummary(defaultAppLabel); - Utils.setSafeIcon(preference, getDefaultAppIcon()); + preference.setIcon(Utils.getSafeIcon(getDefaultAppIcon())); } else { Log.d(TAG, "No default app"); preference.setSummary(R.string.app_list_preference_none); diff --git a/src/com/android/settings/dream/CurrentDreamPreferenceController.java b/src/com/android/settings/dream/CurrentDreamPreferenceController.java index a73898fdc0a..c267921fd2a 100644 --- a/src/com/android/settings/dream/CurrentDreamPreferenceController.java +++ b/src/com/android/settings/dream/CurrentDreamPreferenceController.java @@ -88,6 +88,6 @@ public class CurrentDreamPreferenceController extends BasePreferenceController { } final GearPreference gearPref = (GearPreference) preference; gearPref.setIconSize(RestrictedPreference.ICON_SIZE_SMALL); - Utils.setSafeIcon(gearPref, mBackend.getActiveIcon()); + gearPref.setIcon(Utils.getSafeIcon(mBackend.getActiveIcon())); } } diff --git a/src/com/android/settings/widget/RadioButtonPickerFragment.java b/src/com/android/settings/widget/RadioButtonPickerFragment.java index 0f4fbc3433b..7d765df60fc 100644 --- a/src/com/android/settings/widget/RadioButtonPickerFragment.java +++ b/src/com/android/settings/widget/RadioButtonPickerFragment.java @@ -205,7 +205,7 @@ public abstract class RadioButtonPickerFragment extends InstrumentedPreferenceFr public RadioButtonPreference bindPreference(RadioButtonPreference pref, String key, CandidateInfo info, String defaultKey) { pref.setTitle(info.loadLabel()); - Utils.setSafeIcon(pref, info.loadIcon()); + pref.setIcon(Utils.getSafeIcon(info.loadIcon())); pref.setKey(key); if (TextUtils.equals(defaultKey, key)) { pref.setChecked(true); From d67f878c676b95996224105150b32fd21df3e471 Mon Sep 17 00:00:00 2001 From: "Wesley.CW Wang" Date: Thu, 22 Apr 2021 18:12:20 +0800 Subject: [PATCH 09/12] Make app usage page support legacy version - Add a flag to divide legacy and new design - Add new layout for tristate design Bug: 178197718 Test: make SettingsRoboTests Change-Id: I61f694cad8ac9cea8195191feacbe80bac2df62e --- res/xml/power_usage_detail_legacy.xml | 66 +++++++++ .../fuelgauge/AdvancedPowerUsageDetail.java | 131 ++++++++++++------ .../AdvancedPowerUsageDetailTest.java | 52 ++++++- 3 files changed, 201 insertions(+), 48 deletions(-) create mode 100644 res/xml/power_usage_detail_legacy.xml diff --git a/res/xml/power_usage_detail_legacy.xml b/res/xml/power_usage_detail_legacy.xml new file mode 100644 index 00000000000..5ab0a94695f --- /dev/null +++ b/res/xml/power_usage_detail_legacy.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java index 3ef7c455302..d1c2316287c 100644 --- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java @@ -42,6 +42,8 @@ import com.android.settings.applications.appinfo.ButtonActionDialogFragment; import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.widget.EntityHeaderController; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; @@ -61,7 +63,7 @@ import java.util.List; */ public class AdvancedPowerUsageDetail extends DashboardFragment implements ButtonActionDialogFragment.AppButtonsDialogListener, - RadioButtonPreference.OnClickListener { + BatteryTipPreferenceController.BatteryTipListener, RadioButtonPreference.OnClickListener { public static final String TAG = "AdvancedPowerDetail"; public static final String EXTRA_UID = "extra_uid"; @@ -73,6 +75,8 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements public static final String EXTRA_POWER_USAGE_PERCENT = "extra_power_usage_percent"; public static final String EXTRA_POWER_USAGE_AMOUNT = "extra_power_usage_amount"; + private static final String KEY_PREF_FOREGROUND = "app_usage_foreground"; + private static final String KEY_PREF_BACKGROUND = "app_usage_background"; private static final String KEY_PREF_HEADER = "header_view"; private static final String KEY_PREF_UNRESTRICTED = "unrestricted_pref"; private static final String KEY_PREF_OPTIMIZED = "optimized_pref"; @@ -92,7 +96,10 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements BatteryUtils mBatteryUtils; @VisibleForTesting BatteryOptimizeUtils mBatteryOptimizeUtils; - + @VisibleForTesting + Preference mForegroundPreference; + @VisibleForTesting + Preference mBackgroundPreference; @VisibleForTesting Preference mFooterPreference; @VisibleForTesting @@ -101,12 +108,11 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements RadioButtonPreference mOptimizePreference; @VisibleForTesting RadioButtonPreference mUnrestrictedPreference; - private AppButtonsPreferenceController mAppButtonsPreferenceController; - private UnrestrictedPreferenceController mUnrestrictedPreferenceController; - private OptimizedPreferenceController mOptimizedPreferenceController; - private RestrictedPreferenceController mRestrictedPreferenceController; + @VisibleForTesting + boolean enableTriState = true; - private String mPackageName; + private AppButtonsPreferenceController mAppButtonsPreferenceController; + private BackgroundActivityPreferenceController mBackgroundActivityPreferenceController; // A wrapper class to carry LaunchBatteryDetailPage required arguments. private static final class LaunchBatteryDetailPageArgs { @@ -227,22 +233,17 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements public void onCreate(Bundle icicle) { super.onCreate(icicle); - mPackageName = getArguments().getString(EXTRA_PACKAGE_NAME); - mFooterPreference = findPreference(KEY_FOOTER_PREFERENCE); - mHeaderPreference = (LayoutPreference) findPreference(KEY_PREF_HEADER); + final String packageName = getArguments().getString(EXTRA_PACKAGE_NAME); + if (enableTriState) { + onCreateForTriState(packageName); + } else { + mForegroundPreference = findPreference(KEY_PREF_FOREGROUND); + mBackgroundPreference = findPreference(KEY_PREF_BACKGROUND); + } + mHeaderPreference = findPreference(KEY_PREF_HEADER); - mUnrestrictedPreference = findPreference(KEY_PREF_UNRESTRICTED); - mOptimizePreference = findPreference(KEY_PREF_OPTIMIZED); - mRestrictedPreference = findPreference(KEY_PREF_RESTRICTED); - mUnrestrictedPreference.setOnClickListener(this); - mOptimizePreference.setOnClickListener(this); - mRestrictedPreference.setOnClickListener(this); - - mBatteryOptimizeUtils = new BatteryOptimizeUtils( - getContext(), getArguments().getInt(EXTRA_UID), mPackageName); - - if (mPackageName != null) { - mAppEntry = mState.getEntry(mPackageName, UserHandle.myUserId()); + if (packageName != null) { + mAppEntry = mState.getEntry(packageName, UserHandle.myUserId()); } } @@ -251,7 +252,11 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements super.onResume(); initHeader(); - initPreference(); + if (enableTriState) { + initPreferenceForTriState(getContext()); + } else { + initPreference(getContext()); + } } @VisibleForTesting @@ -281,17 +286,39 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements controller.setIsInstantApp(AppUtils.isInstant(mAppEntry.info)); } - final long foregroundTimeMs = bundle.getLong(EXTRA_FOREGROUND_TIME); - final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME); - //TODO(b/178197718) Update layout to support multiple lines - controller.setSummary(getAppActiveTime(foregroundTimeMs, backgroundTimeMs)); + if (enableTriState) { + final long foregroundTimeMs = bundle.getLong(EXTRA_FOREGROUND_TIME); + final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME); + //TODO(b/178197718) Update layout to support multiple lines + controller.setSummary(getAppActiveTime(foregroundTimeMs, backgroundTimeMs)); + } controller.done(context, true /* rebindActions */); } @VisibleForTesting - void initPreference() { - final Context context = getContext(); + void initPreference(Context context) { + final Bundle bundle = getArguments(); + final long foregroundTimeMs = bundle.getLong(EXTRA_FOREGROUND_TIME); + final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME); + mForegroundPreference.setSummary( + TextUtils.expandTemplate(getText(R.string.battery_used_for), + StringUtil.formatElapsedTime( + context, + foregroundTimeMs, + /* withSeconds */ false, + /* collapseTimeUnit */ false))); + mBackgroundPreference.setSummary( + TextUtils.expandTemplate(getText(R.string.battery_active_for), + StringUtil.formatElapsedTime( + context, + backgroundTimeMs, + /* withSeconds */ false, + /* collapseTimeUnit */ false))); + } + + @VisibleForTesting + void initPreferenceForTriState(Context context) { final String stateString; final String footerString; //TODO(b/178197718) Update strings @@ -308,7 +335,6 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements } else { //Present default string to normal app. footerString = context.getString(R.string.manager_battery_usage_footer); - } mFooterPreference.setTitle(Html.fromHtml(footerString, Html.FROM_HTML_MODE_COMPACT)); } @@ -325,7 +351,7 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements @Override protected int getPreferenceScreenResId() { - return R.xml.power_usage_detail; + return enableTriState ? R.xml.power_usage_detail : R.xml.power_usage_detail_legacy; } @Override @@ -336,18 +362,20 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements final String packageName = bundle.getString(EXTRA_PACKAGE_NAME); mAppButtonsPreferenceController = new AppButtonsPreferenceController( - (SettingsActivity) getActivity(), this, getSettingsLifecycle(), packageName, mState, - REQUEST_UNINSTALL, REQUEST_REMOVE_DEVICE_ADMIN); + (SettingsActivity) getActivity(), this, getSettingsLifecycle(), packageName, + mState, REQUEST_UNINSTALL, REQUEST_REMOVE_DEVICE_ADMIN); controllers.add(mAppButtonsPreferenceController); - mUnrestrictedPreferenceController = - new UnrestrictedPreferenceController(context, uid, packageName); - mOptimizedPreferenceController = - new OptimizedPreferenceController(context, uid, packageName); - mRestrictedPreferenceController = - new RestrictedPreferenceController(context, uid, packageName); - controllers.add(mUnrestrictedPreferenceController); - controllers.add(mOptimizedPreferenceController); - controllers.add(mRestrictedPreferenceController); + if (enableTriState) { + controllers.add(new UnrestrictedPreferenceController(context, uid, packageName)); + controllers.add(new OptimizedPreferenceController(context, uid, packageName)); + controllers.add(new RestrictedPreferenceController(context, uid, packageName)); + } else { + mBackgroundActivityPreferenceController = new BackgroundActivityPreferenceController( + context, this, uid, packageName); + controllers.add(mBackgroundActivityPreferenceController); + controllers.add(new BatteryOptimizationPreferenceController( + (SettingsActivity) getActivity(), this, packageName)); + } return controllers; } @@ -367,6 +395,12 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements } } + @Override + public void onBatteryTipHandled(BatteryTip batteryTip) { + mBackgroundActivityPreferenceController.updateSummary( + findPreference(mBackgroundActivityPreferenceController.getPreferenceKey())); + } + @Override public void onRadioButtonClicked(RadioButtonPreference selected) { updatePreferenceState(mUnrestrictedPreference, selected.getKey()); @@ -378,6 +412,19 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements preference.setChecked(selectedKey.equals(preference.getKey())); } + private void onCreateForTriState(String packageName) { + mUnrestrictedPreference = findPreference(KEY_PREF_UNRESTRICTED); + mOptimizePreference = findPreference(KEY_PREF_OPTIMIZED); + mRestrictedPreference = findPreference(KEY_PREF_RESTRICTED); + mFooterPreference = findPreference(KEY_FOOTER_PREFERENCE); + mUnrestrictedPreference.setOnClickListener(this); + mOptimizePreference.setOnClickListener(this); + mRestrictedPreference.setOnClickListener(this); + + mBatteryOptimizeUtils = new BatteryOptimizeUtils( + getContext(), getArguments().getInt(EXTRA_UID), packageName); + } + //TODO(b/178197718) Update method to support time period private CharSequence getAppActiveTime(long foregroundTimeMs, long backgroundTimeMs) { final long totalTimeMs = foregroundTimeMs + backgroundTimeMs; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java index dd10a9e1556..5185593e8b7 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java @@ -47,6 +47,7 @@ import androidx.loader.app.LoaderManager; import androidx.preference.Preference; import androidx.recyclerview.widget.RecyclerView; +import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.ShadowActivityManager; @@ -82,6 +83,7 @@ public class AdvancedPowerUsageDetailTest { private static final String USAGE_PERCENT = "16%"; private static final int ICON_ID = 123; private static final int UID = 1; + private static final int POWER_MAH = 150; private static final long BACKGROUND_TIME_MS = 100; private static final long FOREGROUND_ACTIVITY_TIME_MS = 123; private static final long FOREGROUND_SERVICE_TIME_MS = 444; @@ -117,6 +119,8 @@ public class AdvancedPowerUsageDetailTest { @Mock private BatteryOptimizeUtils mBatteryOptimizeUtils; private Context mContext; + private Preference mForegroundPreference; + private Preference mBackgroundPreference; private Preference mFooterPreference; private RadioButtonPreference mRestrictedPreference; private RadioButtonPreference mOptimizePreference; @@ -166,6 +170,7 @@ public class AdvancedPowerUsageDetailTest { mFragment.mHeaderPreference = mHeaderPreference; mFragment.mState = mState; + mFragment.enableTriState = true; mFragment.mBatteryUtils = new BatteryUtils(RuntimeEnvironment.application); mFragment.mBatteryOptimizeUtils = mBatteryOptimizeUtils; mAppEntry.info = mock(ApplicationInfo.class); @@ -190,10 +195,14 @@ public class AdvancedPowerUsageDetailTest { nullable(UserHandle.class)); doAnswer(callable).when(mActivity).startActivity(captor.capture()); + mForegroundPreference = new Preference(mContext); + mBackgroundPreference = new Preference(mContext); mFooterPreference = new Preference(mContext); mRestrictedPreference = new RadioButtonPreference(mContext); mOptimizePreference = new RadioButtonPreference(mContext); mUnrestrictedPreference = new RadioButtonPreference(mContext); + mFragment.mForegroundPreference = mForegroundPreference; + mFragment.mBackgroundPreference = mBackgroundPreference; mFragment.mFooterPreference = mFooterPreference; mFragment.mRestrictedPreference = mRestrictedPreference; mFragment.mOptimizePreference = mOptimizePreference; @@ -205,6 +214,17 @@ public class AdvancedPowerUsageDetailTest { ShadowEntityHeaderController.reset(); } + @Test + public void testGetPreferenceScreenResId_returnNewLayout() { + assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(R.xml.power_usage_detail); + } + + @Test + public void testGetPreferenceScreenResId_disableTriState_returnLegacyLayout() { + mFragment.enableTriState = false; + assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(R.xml.power_usage_detail_legacy); + } + @Test public void testInitHeader_NoAppEntry_BuildByBundle() { mFragment.mAppEntry = null; @@ -455,32 +475,52 @@ public class AdvancedPowerUsageDetailTest { } @Test - public void testInitPreference_isValidPackageName_hasCorrectString() { + public void testInitPreference_hasCorrectSummary() { + Bundle bundle = new Bundle(4); + bundle.putLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME, BACKGROUND_TIME_MS); + bundle.putLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME, FOREGROUND_TIME_MS); + bundle.putString(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT, USAGE_PERCENT); + bundle.putInt(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_AMOUNT, POWER_MAH); + when(mFragment.getArguments()).thenReturn(bundle); + + doReturn(mContext.getText(R.string.battery_used_for)).when(mFragment).getText( + R.string.battery_used_for); + doReturn(mContext.getText(R.string.battery_active_for)).when(mFragment).getText( + R.string.battery_active_for); + + mFragment.initPreference(mContext); + + assertThat(mForegroundPreference.getSummary().toString()).isEqualTo("Used for 0 min"); + assertThat(mBackgroundPreference.getSummary().toString()).isEqualTo("Active for 0 min"); + } + + @Test + public void testInitPreferenceForTriState_isValidPackageName_hasCorrectString() { when(mBatteryOptimizeUtils.isValidPackageName()).thenReturn(false); - mFragment.initPreference(); + mFragment.initPreferenceForTriState(mContext); assertThat(mFooterPreference.getTitle().toString()) .isEqualTo("This app requires Optimized battery usage."); } @Test - public void testInitPreference_isSystemOrDefaultApp_hasCorrectString() { + public void testInitPreferenceForTriState_isSystemOrDefaultApp_hasCorrectString() { when(mBatteryOptimizeUtils.isValidPackageName()).thenReturn(true); when(mBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(true); - mFragment.initPreference(); + mFragment.initPreferenceForTriState(mContext); assertThat(mFooterPreference.getTitle() .toString()).isEqualTo("This app requires Unrestricted battery usage."); } @Test - public void testInitPreference_hasCorrectString() { + public void testInitPreferenceForTriState_hasCorrectString() { when(mBatteryOptimizeUtils.isValidPackageName()).thenReturn(true); when(mBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(false); - mFragment.initPreference(); + mFragment.initPreferenceForTriState(mContext); assertThat(mFooterPreference.getTitle().toString()) .isEqualTo("Changing how an app uses your battery can affect its performance."); From 1001614b141989b8aca099d4ff40801df54632e2 Mon Sep 17 00:00:00 2001 From: ykhung Date: Mon, 26 Apr 2021 11:53:38 +0800 Subject: [PATCH 10/12] Draw percentage information into BatteryChartView manually screen: https://screenshot.googleplex.com/Bw4Whpzff5pCT5J Bug: 183921876 Test: make SettingsRoboTests Test: make SettingsGoogleRoboTests Change-Id: I61c0f73e27dc2980d0812e00c2fdfda9a628b696 --- res/layout/battery_chart_graph.xml | 11 +- res/values/dimens.xml | 1 + .../settings/fuelgauge/BatteryChartView.java | 100 +++++++++++++++--- .../fuelgauge/BatteryHistoryPreference.java | 2 + 4 files changed, 95 insertions(+), 19 deletions(-) diff --git a/res/layout/battery_chart_graph.xml b/res/layout/battery_chart_graph.xml index 6187d07b66c..2984f43d5bc 100644 --- a/res/layout/battery_chart_graph.xml +++ b/res/layout/battery_chart_graph.xml @@ -26,7 +26,7 @@ android:id="@+id/chart_summary" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="45dp" + android:layout_marginBottom="30dp" android:textAppearance="?android:attr/textAppearanceSmall" settings:textColor="?android:attr/textColorSecondary" android:text="@string/battery_usage_chart_graph_hint" /> @@ -34,9 +34,16 @@ + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 0aaf930e112..b471d0fb559 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -441,6 +441,7 @@ 24dp + 3dp 1dp 4dp 3dp diff --git a/src/com/android/settings/fuelgauge/BatteryChartView.java b/src/com/android/settings/fuelgauge/BatteryChartView.java index 366946e12e2..d56cbd45bec 100644 --- a/src/com/android/settings/fuelgauge/BatteryChartView.java +++ b/src/com/android/settings/fuelgauge/BatteryChartView.java @@ -22,11 +22,13 @@ import android.graphics.Color; import android.graphics.CornerPathEffect; import android.graphics.Paint; import android.graphics.Path; +import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; +import android.widget.TextView; import androidx.appcompat.widget.AppCompatImageView; @@ -38,6 +40,8 @@ import java.util.Locale; /** A widget component to draw chart graph. */ public class BatteryChartView extends AppCompatImageView implements View.OnClickListener { private static final String TAG = "BatteryChartView"; + // For drawing the percentage information. + private static final String[] PERCENTAGES = new String[] {"100%", "50%", "0%"}; private static final int DEFAULT_TRAPEZOID_COUNT = 12; /** Selects all trapezoid shapes. */ public static final int SELECTED_INDEX_ALL = -1; @@ -58,8 +62,14 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick private int mTrapezoidColor; private int mTrapezoidSolidColor; private final int mDividerColor = Color.parseColor("#CDCCC5"); + // For drawing the percentage information. + private int mTextPadding; + private final Rect mIndent = new Rect(); + private final Rect[] mPercentageBound = + new Rect[] {new Rect(), new Rect(), new Rect()}; private int[] mLevels; + private Paint mTextPaint; private Paint mDividerPaint; private Paint mTrapezoidPaint; private TrapezoidSlot[] mTrapezoidSlot; @@ -128,6 +138,37 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick mOnSelectListener = listener; } + /** Sets the companion {@link TextView} for percentage information. */ + public void setCompanionTextView(TextView textView) { + requestLayout(); + if (textView != null) { + // Pre-draws the view first to load style atttributions into paint. + textView.draw(new Canvas()); + mTextPaint = textView.getPaint(); + } else { + mTextPaint = null; + } + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + // Measures text bounds and updates indent configuration. + if (mTextPaint != null) { + for (int index = 0; index < PERCENTAGES.length; index++) { + mTextPaint.getTextBounds( + PERCENTAGES[index], 0, PERCENTAGES[index].length(), + mPercentageBound[index]); + } + // Updates the indent configurations. + mIndent.top = mPercentageBound[0].height(); + mIndent.right = mPercentageBound[0].width() + mTextPadding * 2; + Log.d(TAG, "setIndent:" + mPercentageBound[0]); + } else { + mIndent.set(0, 0, 0, 0); + } + } + @Override public void draw(Canvas canvas) { super.draw(canvas); @@ -194,32 +235,56 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick mTrapezoidPaint.setPathEffect( new CornerPathEffect( resources.getDimensionPixelSize(R.dimen.chartview_trapezoid_radius))); + // Initializes for drawing text information. + mTextPadding = resources.getDimensionPixelSize(R.dimen.chartview_text_padding); } private void drawHorizontalDividers(Canvas canvas) { + final int width = getWidth() - mIndent.right; + final int height = getHeight() - mIndent.top - mIndent.bottom; // Draws the top divider line for 100% curve. - float offsetY = mDividerWidth * 0.5f; - canvas.drawLine(0, offsetY, getWidth(), offsetY, mDividerPaint); + float offsetY = mIndent.top + mDividerWidth * .5f; + canvas.drawLine(0, offsetY, width, offsetY, mDividerPaint); + if (mTextPaint != null) { + canvas.drawText( + PERCENTAGES[0], + getWidth() - mPercentageBound[0].width(), + offsetY + mPercentageBound[0].height() *.5f , mTextPaint); + } // Draws the center divider line for 50% curve. final float availableSpace = - getHeight() - mDividerWidth * 2 - mTrapezoidVOffset - mDividerHeight; - offsetY = mDividerWidth + availableSpace * 0.5f; - canvas.drawLine(0, offsetY, getWidth(), offsetY, mDividerPaint); - // Draws the center divider line for 0% curve. - offsetY = getHeight() - mDividerHeight - mDividerWidth * 0.5f; - canvas.drawLine(0, offsetY, getWidth(), offsetY, mDividerPaint); + height - mDividerWidth * 2 - mTrapezoidVOffset - mDividerHeight; + offsetY = mIndent.top + mDividerWidth + availableSpace * .5f; + canvas.drawLine(0, offsetY, width, offsetY, mDividerPaint); + if (mTextPaint != null) { + canvas.drawText( + PERCENTAGES[1], + getWidth() - mPercentageBound[1].width(), + offsetY + mPercentageBound[1].height() *.5f , mTextPaint); + } + // Draws the bottom divider line for 0% curve. + offsetY = mIndent.top + (height - mDividerHeight - mDividerWidth * .5f); + canvas.drawLine(0, offsetY, width, offsetY, mDividerPaint); + if (mTextPaint != null) { + canvas.drawText( + PERCENTAGES[2], + getWidth() - mPercentageBound[2].width(), + offsetY + mPercentageBound[2].height() *.5f , mTextPaint); + } } private void drawVerticalDividers(Canvas canvas) { + final int width = getWidth() - mIndent.right; final int dividerCount = mTrapezoidCount + 1; final float dividerSpace = dividerCount * mDividerWidth; - final float unitWidth = (getWidth() - dividerSpace) / (float) mTrapezoidCount; - final float startY = getHeight() - mDividerHeight; - final float trapezoidSlotOffset = mTrapezoidHOffset + mDividerWidth * 0.5f; + final float unitWidth = (width - dividerSpace) / (float) mTrapezoidCount; + final float bottomY = getHeight() - mIndent.bottom; + final float startY = bottomY - mDividerHeight; + final float trapezoidSlotOffset = mTrapezoidHOffset + mDividerWidth * .5f; // Draws each vertical dividers. - float startX = mDividerWidth * 0.5f; + float startX = mDividerWidth * .5f; for (int index = 0; index < dividerCount; index++) { - canvas.drawLine(startX, startY, startX, getHeight(), mDividerPaint); + canvas.drawLine(startX, startY, startX, bottomY, mDividerPaint); final float nextX = startX + mDividerWidth + unitWidth; // Updates the trapezoid slots for drawing. if (index < mTrapezoidSlot.length) { @@ -236,8 +301,9 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick return; } final float trapezoidBottom = - getHeight() - mDividerHeight - mDividerWidth - mTrapezoidVOffset; - final float availableSpace = trapezoidBottom - mDividerWidth; + getHeight() - mIndent.bottom - mDividerHeight - mDividerWidth + - mTrapezoidVOffset; + final float availableSpace = trapezoidBottom - mDividerWidth * .5f - mIndent.top; final float unitHeight = availableSpace / 100f; // Draws all trapezoid shapes into the canvas. final Path trapezoidPath = new Path(); @@ -249,8 +315,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick // Configures the trapezoid paint color. mTrapezoidPaint.setColor( mSelectedIndex == index || mSelectedIndex == SELECTED_INDEX_ALL - ? mTrapezoidSolidColor - : mTrapezoidColor); + ? mTrapezoidSolidColor + : mTrapezoidColor); final float leftTop = round(trapezoidBottom - mLevels[index] * unitHeight); final float rightTop = round(trapezoidBottom - mLevels[index + 1] * unitHeight); trapezoidPath.reset(); diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java b/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java index 94e35cb3966..b2818bf601d 100644 --- a/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java +++ b/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java @@ -100,6 +100,8 @@ public class BatteryHistoryPreference extends Preference { } if (mIsChartGraphEnabled) { mBatteryChartView = (BatteryChartView) view.findViewById(R.id.battery_chart); + mBatteryChartView.setCompanionTextView( + (TextView) view.findViewById(R.id.companion_text)); if (mChartPreferenceController != null) { mChartPreferenceController.setBatteryChartView(mBatteryChartView); } From 91428695260bd41c9877bbe0e3b83b1989161bcf Mon Sep 17 00:00:00 2001 From: Tsung-Mao Fang Date: Mon, 26 Apr 2021 20:31:01 +0800 Subject: [PATCH 11/12] Tweak title in privacy page There's an item under Settings > Privacy called "Privacy". The strings should not be repeating the page title. Test: see screen Fix: 186131466 Change-Id: If60cbabd163e8168b02a63a96c82d93ad6ee3c8d --- res/xml/privacy_dashboard_settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/xml/privacy_dashboard_settings.xml b/res/xml/privacy_dashboard_settings.xml index fcd0de493f8..a1a608d20ac 100644 --- a/res/xml/privacy_dashboard_settings.xml +++ b/res/xml/privacy_dashboard_settings.xml @@ -78,7 +78,7 @@ From 4f3ee93285418649be19424bdd8fab2b0c8303df Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Thu, 15 Apr 2021 15:51:47 +0800 Subject: [PATCH 12/12] Remove divider in Date & Time Settings - Add preferenceCategory for Time zone Fixes: 183578528 Test: robotest & visual Change-Id: If275c569d810a8bc4d2e2620c51b54379dca31b4 --- res/xml/date_time_prefs.xml | 41 +++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/res/xml/date_time_prefs.xml b/res/xml/date_time_prefs.xml index 47056dfb5a8..27311322e28 100644 --- a/res/xml/date_time_prefs.xml +++ b/res/xml/date_time_prefs.xml @@ -38,26 +38,31 @@ android:summary="@string/summary_placeholder" settings:userRestriction="no_config_date_time"/> - + - + - - + + + + +