From e4800786cff0df47392d0954d700743a1f6cfc8f Mon Sep 17 00:00:00 2001 From: Raff Tsai Date: Fri, 8 Mar 2019 16:19:30 +0800 Subject: [PATCH 01/26] Do not allow draw on top for App notification settings Fixes: 119115683 Test: manual Change-Id: Ib7b878a23b4a99171c58b5de992fb87feca8a28a Merged-In: Ib7b878a23b4a99171c58b5de992fb87feca8a28a (cherry picked from commit fe86a2a51f0b8e01e5f595bd60edc594eef58aba) --- .../notification/AppNotificationSettings.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java index 72a4cc1a604..c2402a4d9c1 100644 --- a/src/com/android/settings/notification/AppNotificationSettings.java +++ b/src/com/android/settings/notification/AppNotificationSettings.java @@ -17,6 +17,8 @@ package com.android.settings.notification; import android.app.Activity; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; @@ -33,6 +35,8 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.Switch; +import android.view.Window; +import android.view.WindowManager; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; @@ -76,6 +80,9 @@ public class AppNotificationSettings extends NotificationSettingsBase { public void onResume() { super.onResume(); + getActivity().getWindow().addPrivateFlags(PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + android.util.EventLog.writeEvent(0x534e4554, "119115683", -1, ""); + if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) { Log.w(TAG, "Missing package or uid or packageinfo"); finish(); @@ -125,6 +132,15 @@ public class AppNotificationSettings extends NotificationSettingsBase { updateDependents(mAppRow.banned); } + @Override + public void onPause() { + super.onPause(); + final Window window = getActivity().getWindow(); + final WindowManager.LayoutParams attrs = window.getAttributes(); + attrs.privateFlags &= ~PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + window.setAttributes(attrs); + } + private void addHeaderPref() { ArrayMap rows = new ArrayMap<>(); rows.put(mAppRow.pkg, mAppRow); From c968eacbbf8a2b41f83b3212449f851a68882806 Mon Sep 17 00:00:00 2001 From: Raff Tsai Date: Fri, 8 Mar 2019 16:19:30 +0800 Subject: [PATCH 02/26] Do not allow draw on top for App notification settings Fixes: 119115683 Test: manual Change-Id: Ib7b878a23b4a99171c58b5de992fb87feca8a28a Merged-In: Ib7b878a23b4a99171c58b5de992fb87feca8a28a (cherry picked from commit fe86a2a51f0b8e01e5f595bd60edc594eef58aba) --- .../notification/AppNotificationSettings.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java index 6b6b183de21..4e578f46562 100644 --- a/src/com/android/settings/notification/AppNotificationSettings.java +++ b/src/com/android/settings/notification/AppNotificationSettings.java @@ -21,6 +21,8 @@ import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import android.app.Activity; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; @@ -36,6 +38,8 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.Switch; +import android.view.Window; +import android.view.WindowManager; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.AppHeader; @@ -77,6 +81,9 @@ public class AppNotificationSettings extends NotificationSettingsBase { public void onResume() { super.onResume(); + getActivity().getWindow().addPrivateFlags(PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + android.util.EventLog.writeEvent(0x534e4554, "119115683", -1, ""); + if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) { Log.w(TAG, "Missing package or uid or packageinfo"); finish(); @@ -126,6 +133,15 @@ public class AppNotificationSettings extends NotificationSettingsBase { updateDependents(mAppRow.banned); } + @Override + public void onPause() { + super.onPause(); + final Window window = getActivity().getWindow(); + final WindowManager.LayoutParams attrs = window.getAttributes(); + attrs.privateFlags &= ~PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + window.setAttributes(attrs); + } + private void addHeaderPref() { ArrayMap rows = new ArrayMap(); rows.put(mAppRow.pkg, mAppRow); From bef25b52fb62d5f5c767674afa3d0a8b0d424ee0 Mon Sep 17 00:00:00 2001 From: Yi-Ling Chuang Date: Tue, 28 May 2019 16:14:08 +0800 Subject: [PATCH 03/26] Load wallpaper colors in a background thread. In WallpaperManagerService, it takes some time to load wallpaper colors from image wallpapers since bitmap decoding will be involved. This will block the UI thread and lead to app launch latency. So here we are making it in another thread to avoid this. Bug: 133396959 Test: robotest, reboot phone and look at boot trace Change-Id: Ibd1952a4bf10431ba4be4dd69634d64354670daa --- src/com/android/settings/FallbackHome.java | 43 +++++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/com/android/settings/FallbackHome.java b/src/com/android/settings/FallbackHome.java index 59347addeab..e3944a65c64 100644 --- a/src/com/android/settings/FallbackHome.java +++ b/src/com/android/settings/FallbackHome.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ResolveInfo; +import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -65,7 +66,7 @@ public class FallbackHome extends Activity { @Override public void onColorsChanged(WallpaperColors colors, int which) { if (colors != null) { - View decorView = getWindow().getDecorView(); + final View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility( updateVisibilityFlagsFromColors(colors, decorView.getSystemUiVisibility())); mWallManager.removeOnColorsChangedListener(this); @@ -81,7 +82,7 @@ public class FallbackHome extends Activity { // we don't flash the wallpaper before SUW mProvisioned = Settings.Global.getInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0; - int flags; + final int flags; if (!mProvisioned) { setTheme(R.style.FallbackHome_SetupWizard); flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION @@ -91,18 +92,11 @@ public class FallbackHome extends Activity { | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; } - // Set the system ui flags to light status bar if the wallpaper supports dark text to match - // current system ui color tints. Use a listener to wait for colors if not ready yet. mWallManager = getSystemService(WallpaperManager.class); if (mWallManager == null) { Log.w(TAG, "Wallpaper manager isn't ready, can't listen to color changes!"); } else { - WallpaperColors colors = mWallManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM); - if (colors == null) { - mWallManager.addOnColorsChangedListener(mColorsChangedListener, null /* handler */); - } else { - flags = updateVisibilityFlagsFromColors(colors, flags); - } + loadWallpaperColors(flags); } getWindow().getDecorView().setSystemUiVisibility(flags); @@ -139,6 +133,33 @@ public class FallbackHome extends Activity { } }; + private void loadWallpaperColors(int flags) { + final AsyncTask loadWallpaperColorsTask = new AsyncTask() { + @Override + protected Integer doInBackground(Object... params) { + final WallpaperColors colors = + mWallManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM); + + // Use a listener to wait for colors if not ready yet. + if (colors == null) { + mWallManager.addOnColorsChangedListener(mColorsChangedListener, + null /* handler */); + return null; + } + return updateVisibilityFlagsFromColors(colors, flags); + } + + @Override + protected void onPostExecute(Integer flagsToUpdate) { + if (flagsToUpdate == null) { + return; + } + getWindow().getDecorView().setSystemUiVisibility(flagsToUpdate); + } + }; + loadWallpaperColorsTask.execute(); + } + private void maybeFinish() { if (getSystemService(UserManager.class).isUserUnlocked()) { final Intent homeIntent = new Intent(Intent.ACTION_MAIN) @@ -162,6 +183,8 @@ public class FallbackHome extends Activity { } } + // Set the system ui flags to light status bar if the wallpaper supports dark text to match + // current system ui color tints. private int updateVisibilityFlagsFromColors(WallpaperColors colors, int flags) { if ((colors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0) { return flags | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR From b49eb4348a5b117f022ef8a0972be7985a2d611a Mon Sep 17 00:00:00 2001 From: Pavel Grafov Date: Wed, 29 May 2019 14:36:46 +0100 Subject: [PATCH 04/26] Make work challenge pattern resizeable. Currently in split-screen mode if work challenge window is made too narrow, the dots of the pattern will go beyond window/screen boundaries. This change makes pattern behave in the same way as primary profile pattern does, see confirm_lock_pattern_normal.xml Fixes: 133135598 Test: manual, resizing work challenge in portrait and landscape. Change-Id: I40649911c2b7c587bf0b7e4a5c0449a5c2b7abb0 --- res/layout-land/confirm_lock_pattern.xml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/res/layout-land/confirm_lock_pattern.xml b/res/layout-land/confirm_lock_pattern.xml index 20a7bfcdd43..6ca9be4e29d 100644 --- a/res/layout-land/confirm_lock_pattern.xml +++ b/res/layout-land/confirm_lock_pattern.xml @@ -87,11 +87,9 @@ + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center"/> Date: Tue, 16 Apr 2019 14:51:11 -0700 Subject: [PATCH 05/26] Adds contextual cards for screen attention in Settings Homepage Bug: 128527964 Test: atest ContextualAdaptiveSleepSliceTest, maually verified. Change-Id: Ifaea7d8d4391e91cf6cbde38a2506728f55913d8 --- res/drawable/ic_settings_adaptive_sleep.xml | 31 ++++ res/values/strings.xml | 2 +- .../AdaptiveSleepPreferenceController.java | 29 ++-- .../display/AdaptiveSleepSettings.java | 12 ++ .../SettingsContextualCardProvider.java | 9 + .../slices/ContextualAdaptiveSleepSlice.java | 154 ++++++++++++++++++ .../settings/slices/CustomSliceRegistry.java | 17 +- .../ContextualAdaptiveSleepSliceTest.java | 115 +++++++++++++ 8 files changed, 355 insertions(+), 14 deletions(-) create mode 100644 res/drawable/ic_settings_adaptive_sleep.xml create mode 100644 src/com/android/settings/homepage/contextualcards/slices/ContextualAdaptiveSleepSlice.java create mode 100644 tests/robotests/src/com/android/settings/homepage/contextualcards/slices/ContextualAdaptiveSleepSliceTest.java diff --git a/res/drawable/ic_settings_adaptive_sleep.xml b/res/drawable/ic_settings_adaptive_sleep.xml new file mode 100644 index 00000000000..765ed949a0e --- /dev/null +++ b/res/drawable/ic_settings_adaptive_sleep.xml @@ -0,0 +1,31 @@ + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 4f962991f4b..8bcadc0f85e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2833,7 +2833,7 @@ Off - Prevents your screen from turning off if you’re looking at it. + Keep screen on when viewing it Screen attention uses the front camera to see if someone is looking at the screen. It works on device, and images are never stored or sent to Google. diff --git a/src/com/android/settings/display/AdaptiveSleepPreferenceController.java b/src/com/android/settings/display/AdaptiveSleepPreferenceController.java index 6b91792800e..e83410d3d7a 100644 --- a/src/com/android/settings/display/AdaptiveSleepPreferenceController.java +++ b/src/com/android/settings/display/AdaptiveSleepPreferenceController.java @@ -25,9 +25,9 @@ import com.android.settings.core.TogglePreferenceController; public class AdaptiveSleepPreferenceController extends TogglePreferenceController { - - private final String SYSTEM_KEY = ADAPTIVE_SLEEP; - private final int DEFAULT_VALUE = 0; + public static final String PREF_NAME = "adaptive_sleep"; + private static final String SYSTEM_KEY = ADAPTIVE_SLEEP; + private static final int DEFAULT_VALUE = 0; final boolean hasSufficientPermissions; @@ -35,9 +35,7 @@ public class AdaptiveSleepPreferenceController extends TogglePreferenceControlle super(context, key); final PackageManager packageManager = mContext.getPackageManager(); - final String attentionPackage = packageManager.getAttentionServicePackageName(); - hasSufficientPermissions = attentionPackage != null && packageManager.checkPermission( - Manifest.permission.CAMERA, attentionPackage) == PackageManager.PERMISSION_GRANTED; + hasSufficientPermissions = hasSufficientPermission(packageManager); } @Override @@ -46,7 +44,6 @@ public class AdaptiveSleepPreferenceController extends TogglePreferenceControlle SYSTEM_KEY, DEFAULT_VALUE) != DEFAULT_VALUE; } - @Override public boolean setChecked(boolean isChecked) { Settings.System.putInt(mContext.getContentResolver(), SYSTEM_KEY, @@ -57,10 +54,7 @@ public class AdaptiveSleepPreferenceController extends TogglePreferenceControlle @Override @AvailabilityStatus public int getAvailabilityStatus() { - return mContext.getResources().getBoolean( - com.android.internal.R.bool.config_adaptive_sleep_available) - ? AVAILABLE_UNSEARCHABLE - : UNSUPPORTED_ON_DEVICE; + return isControllerAvailable(mContext); } @Override @@ -69,4 +63,17 @@ public class AdaptiveSleepPreferenceController extends TogglePreferenceControlle ? R.string.adaptive_sleep_summary_on : R.string.adaptive_sleep_summary_off); } + + public static int isControllerAvailable(Context context) { + return context.getResources().getBoolean( + com.android.internal.R.bool.config_adaptive_sleep_available) + ? AVAILABLE_UNSEARCHABLE + : UNSUPPORTED_ON_DEVICE; + } + + private static boolean hasSufficientPermission(PackageManager packageManager) { + final String attentionPackage = packageManager.getAttentionServicePackageName(); + return attentionPackage != null && packageManager.checkPermission( + Manifest.permission.CAMERA, attentionPackage) == PackageManager.PERMISSION_GRANTED; + } } diff --git a/src/com/android/settings/display/AdaptiveSleepSettings.java b/src/com/android/settings/display/AdaptiveSleepSettings.java index 4c17a67b717..d0f2c9aa0b1 100644 --- a/src/com/android/settings/display/AdaptiveSleepSettings.java +++ b/src/com/android/settings/display/AdaptiveSleepSettings.java @@ -16,10 +16,15 @@ package com.android.settings.display; +import static com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice.PREF; +import static com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice.PREF_KEY_INTERACTED; + import android.app.settings.SettingsEnums; import android.content.Context; +import android.content.SharedPreferences; import android.os.Bundle; import android.provider.SearchIndexableResource; +import android.util.Log; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; @@ -40,8 +45,15 @@ public class AdaptiveSleepSettings extends DashboardFragment { super.onCreate(icicle); final FooterPreference footerPreference = mFooterPreferenceMixin.createFooterPreference(); + final Context context = getContext(); + footerPreference.setIcon(R.drawable.ic_privacy_shield_24dp); footerPreference.setTitle(R.string.adaptive_sleep_privacy); + + context.getSharedPreferences(PREF, Context.MODE_PRIVATE) + .edit() + .putBoolean(PREF_KEY_INTERACTED, true) + .apply(); } @Override diff --git a/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java b/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java index 86fee03e556..aaae076e620 100644 --- a/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java +++ b/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java @@ -64,12 +64,21 @@ public class SettingsContextualCardProvider extends ContextualCardProvider { .setCardName(contextualNotificationChannelSliceUri) .setCardCategory(ContextualCard.Category.POSSIBLE) .build(); + final String contextualAdaptiveSleepSliceUri = + CustomSliceRegistry.CONTEXTUAL_ADAPTIVE_SLEEP_URI.toString(); + final ContextualCard contextualAdaptiveSleepCard = + ContextualCard.newBuilder() + .setSliceUri(contextualAdaptiveSleepSliceUri) + .setCardName(contextualAdaptiveSleepSliceUri) + .setCardCategory(ContextualCard.Category.DEFAULT) + .build(); final ContextualCardList cards = ContextualCardList.newBuilder() .addCard(wifiCard) .addCard(connectedDeviceCard) .addCard(lowStorageCard) .addCard(batteryFixCard) .addCard(notificationChannelCard) + .addCard(contextualAdaptiveSleepCard) .build(); return cards; diff --git a/src/com/android/settings/homepage/contextualcards/slices/ContextualAdaptiveSleepSlice.java b/src/com/android/settings/homepage/contextualcards/slices/ContextualAdaptiveSleepSlice.java new file mode 100644 index 00000000000..2c091fa716c --- /dev/null +++ b/src/com/android/settings/homepage/contextualcards/slices/ContextualAdaptiveSleepSlice.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.settings.homepage.contextualcards.slices; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.display.AdaptiveSleepPreferenceController.PREF_NAME; +import static com.android.settings.display.AdaptiveSleepPreferenceController.isControllerAvailable; +import static com.android.settings.slices.CustomSliceRegistry.CONTEXTUAL_ADAPTIVE_SLEEP_URI; + +import android.app.PendingIntent; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; + +import androidx.core.graphics.drawable.IconCompat; +import androidx.slice.Slice; +import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.SliceAction; + +import com.android.settings.R; +import com.android.settings.SubSettings; +import com.android.settings.display.AdaptiveSleepSettings; +import com.android.settings.slices.CustomSliceable; +import com.android.settings.slices.SliceBuilderUtils; + +import com.google.common.annotations.VisibleForTesting; + +import java.util.concurrent.TimeUnit; + +public class ContextualAdaptiveSleepSlice implements CustomSliceable { + private static final String TAG = "ContextualAdaptiveSleepSlice"; + private static final long DEFAULT_SETUP_TIME = 0; + private Context mContext; + + @VisibleForTesting + static final long DEFERRED_TIME_DAYS = TimeUnit.DAYS.toMillis(14); + @VisibleForTesting + static final String PREF_KEY_SETUP_TIME = "adaptive_sleep_setup_time"; + + public static final String PREF_KEY_INTERACTED = "adaptive_sleep_interacted"; + public static final String PREF = "adaptive_sleep_slice"; + + public ContextualAdaptiveSleepSlice(Context context) { + mContext = context; + } + + @Override + public Slice getSlice() { + final long setupTime = mContext.getSharedPreferences(PREF, Context.MODE_PRIVATE).getLong( + PREF_KEY_SETUP_TIME, DEFAULT_SETUP_TIME); + if (setupTime == DEFAULT_SETUP_TIME) { + // Set the first setup time. + mContext.getSharedPreferences(PREF, Context.MODE_PRIVATE) + .edit() + .putLong(PREF_KEY_SETUP_TIME, System.currentTimeMillis()) + .apply(); + return null; + } + + // Display the contextual card only if all the following 3 conditions hold: + // 1. The Screen Attention is enabled in Settings. + // 2. The device is not recently set up. + // 3. Current user hasn't opened Screen Attention's settings page before. + if (isSettingsAvailable() && !isUserInteracted() && !isRecentlySetup()) { + final IconCompat icon = IconCompat.createWithResource(mContext, + R.drawable.ic_settings_adaptive_sleep); + final CharSequence title = mContext.getText(R.string.adaptive_sleep_title); + final CharSequence subtitle = mContext.getText(R.string.adaptive_sleep_description); + + final SliceAction pAction = SliceAction.createDeeplink(getPrimaryAction(), + icon, + ListBuilder.ICON_IMAGE, + title); + final ListBuilder listBuilder = new ListBuilder(mContext, + CONTEXTUAL_ADAPTIVE_SLEEP_URI, + ListBuilder.INFINITY) + .addRow(new ListBuilder.RowBuilder() + .setTitleItem(icon, ListBuilder.ICON_IMAGE) + .setTitle(title) + .setSubtitle(subtitle) + .setPrimaryAction(pAction)); + return listBuilder.build(); + } else { + return null; + } + } + + @Override + public Uri getUri() { + return CONTEXTUAL_ADAPTIVE_SLEEP_URI; + } + + @Override + public Intent getIntent() { + final CharSequence screenTitle = mContext.getText(R.string.adaptive_sleep_title); + final Uri contentUri = new Uri.Builder().appendPath(PREF_NAME).build(); + return SliceBuilderUtils.buildSearchResultPageIntent(mContext, + AdaptiveSleepSettings.class.getName(), PREF_NAME, screenTitle.toString(), + SettingsEnums.SLICE).setClassName(mContext.getPackageName(), + SubSettings.class.getName()).setData(contentUri); + } + + private PendingIntent getPrimaryAction() { + final Intent intent = getIntent(); + return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */); + } + + /** + * @return {@code true} if the current user has opened the Screen Attention settings page + * before, otherwise {@code false}. + */ + private boolean isUserInteracted() { + return mContext.getSharedPreferences(PREF, Context.MODE_PRIVATE).getBoolean( + PREF_KEY_INTERACTED, false); + } + + /** + * The device is recently set up means its first settings-open time is within 2 weeks ago. + * + * @return {@code true} if the device is recently set up, otherwise {@code false}. + */ + private boolean isRecentlySetup() { + final long endTime = System.currentTimeMillis() - DEFERRED_TIME_DAYS; + final long firstSetupTime = mContext.getSharedPreferences(PREF, + Context.MODE_PRIVATE).getLong(PREF_KEY_SETUP_TIME, DEFAULT_SETUP_TIME); + return firstSetupTime > endTime; + } + + /** + * Check whether the screen attention settings is enabled. Contextual card will only appear + * when the screen attention settings is available. + * + * @return {@code true} if screen attention settings is enabled, otherwise {@code false} + */ + @VisibleForTesting + boolean isSettingsAvailable() { + return isControllerAvailable(mContext) == AVAILABLE; + } +} \ No newline at end of file diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java index dc3324b3d92..ebfd7b34e7e 100644 --- a/src/com/android/settings/slices/CustomSliceRegistry.java +++ b/src/com/android/settings/slices/CustomSliceRegistry.java @@ -26,6 +26,7 @@ import android.util.ArrayMap; import androidx.annotation.VisibleForTesting; +import com.android.settings.display.AdaptiveSleepPreferenceController; import com.android.settings.flashlight.FlashlightSlice; import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController; import com.android.settings.homepage.contextualcards.deviceinfo.DataUsageSlice; @@ -34,6 +35,7 @@ import com.android.settings.homepage.contextualcards.deviceinfo.EmergencyInfoSli import com.android.settings.homepage.contextualcards.deviceinfo.StorageSlice; import com.android.settings.homepage.contextualcards.slices.BatteryFixSlice; import com.android.settings.homepage.contextualcards.slices.BluetoothDevicesSlice; +import com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice; import com.android.settings.homepage.contextualcards.slices.ContextualNotificationChannelSlice; import com.android.settings.homepage.contextualcards.slices.LowStorageSlice; import com.android.settings.homepage.contextualcards.slices.NotificationChannelSlice; @@ -64,6 +66,16 @@ public class CustomSliceRegistry { .appendPath(SettingsSlicesContract.KEY_AIRPLANE_MODE) .build(); + /** + * Uri for Contextual Adaptive Sleep Slice + */ + public static final Uri CONTEXTUAL_ADAPTIVE_SLEEP_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_INTENT) + .appendPath(AdaptiveSleepPreferenceController.PREF_NAME) + .build(); + /** * Uri for Battery Fix Slice. */ @@ -328,6 +340,7 @@ public class CustomSliceRegistry { sUriToSlice.put(BATTERY_FIX_SLICE_URI, BatteryFixSlice.class); sUriToSlice.put(BLUETOOTH_DEVICES_SLICE_URI, BluetoothDevicesSlice.class); + sUriToSlice.put(CONTEXTUAL_ADAPTIVE_SLEEP_URI, ContextualAdaptiveSleepSlice.class); sUriToSlice.put(CONTEXTUAL_NOTIFICATION_CHANNEL_SLICE_URI, ContextualNotificationChannelSlice.class); sUriToSlice.put(CONTEXTUAL_WIFI_SLICE_URI, ContextualWifiSlice.class); @@ -337,12 +350,12 @@ public class CustomSliceRegistry { sUriToSlice.put(FLASHLIGHT_SLICE_URI, FlashlightSlice.class); sUriToSlice.put(LOCATION_SLICE_URI, LocationSlice.class); sUriToSlice.put(LOW_STORAGE_SLICE_URI, LowStorageSlice.class); + sUriToSlice.put(MEDIA_OUTPUT_INDICATOR_SLICE_URI, MediaOutputIndicatorSlice.class); + sUriToSlice.put(MEDIA_OUTPUT_SLICE_URI, MediaOutputSlice.class); sUriToSlice.put(MOBILE_DATA_SLICE_URI, MobileDataSlice.class); sUriToSlice.put(NOTIFICATION_CHANNEL_SLICE_URI, NotificationChannelSlice.class); sUriToSlice.put(STORAGE_SLICE_URI, StorageSlice.class); sUriToSlice.put(WIFI_SLICE_URI, WifiSlice.class); - sUriToSlice.put(MEDIA_OUTPUT_SLICE_URI, MediaOutputSlice.class); - sUriToSlice.put(MEDIA_OUTPUT_INDICATOR_SLICE_URI, MediaOutputIndicatorSlice.class); } public static Class getSliceClassByUri(Uri uri) { diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/ContextualAdaptiveSleepSliceTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/ContextualAdaptiveSleepSliceTest.java new file mode 100644 index 00000000000..54fb2c3281a --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/ContextualAdaptiveSleepSliceTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.settings.homepage.contextualcards.slices; + +import static com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice.DEFERRED_TIME_DAYS; +import static com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice.PREF; +import static com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice.PREF_KEY_SETUP_TIME; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.net.Uri; + +import androidx.slice.Slice; +import androidx.slice.SliceProvider; +import androidx.slice.widget.SliceLiveData; + +import com.android.settings.slices.CustomSliceRegistry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class ContextualAdaptiveSleepSliceTest { + + private static final String pkgName = "adaptive_sleep"; + private Context mContext; + private ContextualAdaptiveSleepSlice mContextualAdaptiveSleepSlice; + @Mock + private PackageManager mPackageManager; + @Mock + private SharedPreferences mSharedPreferences; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + mContext = spy(RuntimeEnvironment.application); + mContextualAdaptiveSleepSlice = spy(new ContextualAdaptiveSleepSlice(mContext)); + + doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(mSharedPreferences).when(mContext).getSharedPreferences(eq(PREF), anyInt()); + doReturn(true).when(mContextualAdaptiveSleepSlice).isSettingsAvailable(); + doReturn(pkgName).when(mPackageManager).getAttentionServicePackageName(); + doReturn(-DEFERRED_TIME_DAYS).when(mSharedPreferences).getLong(eq(PREF_KEY_SETUP_TIME), + anyLong()); + } + + @Test + public void getUri_shouldReturnContextualAdaptiveSleepSliceUri() { + final Uri uri = mContextualAdaptiveSleepSlice.getUri(); + + assertThat(uri).isEqualTo(CustomSliceRegistry.CONTEXTUAL_ADAPTIVE_SLEEP_URI); + } + + @Test + public void getSlice_ShowIfFeatureIsAvailable() { + final Slice slice = mContextualAdaptiveSleepSlice.getSlice(); + + assertThat(slice).isNotNull(); + } + + @Test + public void getSlice_DoNotShowIfFeatureIsUnavailable() { + doReturn(false).when(mContextualAdaptiveSleepSlice).isSettingsAvailable(); + + final Slice slice = mContextualAdaptiveSleepSlice.getSlice(); + + assertThat(slice).isNull(); + } + + @Test + public void getSlice_ShowIfNotRecentlySetup() { + final Slice slice = mContextualAdaptiveSleepSlice.getSlice(); + + assertThat(slice).isNotNull(); + } + + @Test + public void getSlice_DoNotShowIfRecentlySetup() { + doReturn(System.currentTimeMillis()).when(mSharedPreferences).getLong( + eq(PREF_KEY_SETUP_TIME), anyLong()); + + final Slice slice = mContextualAdaptiveSleepSlice.getSlice(); + + assertThat(slice).isNull(); + } +} From 798b80e5ad79e7248b1b0f87d735babdf090285f Mon Sep 17 00:00:00 2001 From: Lei Yu Date: Wed, 29 May 2019 13:11:57 -0700 Subject: [PATCH 06/26] Update SliceView when slice is null When it is null, we should also update SliceView, so SliceView can update to be "invisible" Fixes: 133790296 Test: RunSettingsRoboTests Change-Id: I239405cce8bcadacbd374ccbb24d0fcbadc04880 --- .../settings/slices/SlicePreferenceController.java | 7 +++---- .../settings/slices/SlicePreferenceControllerTest.java | 10 ++++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/slices/SlicePreferenceController.java b/src/com/android/settings/slices/SlicePreferenceController.java index 89294c74cb0..2432c992b3b 100644 --- a/src/com/android/settings/slices/SlicePreferenceController.java +++ b/src/com/android/settings/slices/SlicePreferenceController.java @@ -40,7 +40,8 @@ public class SlicePreferenceController extends BasePreferenceController implemen LifecycleObserver, OnStart, OnStop, Observer { @VisibleForTesting LiveData mLiveData; - private SlicePreference mSlicePreference; + @VisibleForTesting + SlicePreference mSlicePreference; private Uri mUri; public SlicePreferenceController(Context context, String preferenceKey) { @@ -82,8 +83,6 @@ public class SlicePreferenceController extends BasePreferenceController implemen @Override public void onChanged(Slice slice) { - if (slice != null) { - mSlicePreference.onSliceUpdated(slice); - } + mSlicePreference.onSliceUpdated(slice); } } diff --git a/tests/robotests/src/com/android/settings/slices/SlicePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/slices/SlicePreferenceControllerTest.java index 364fb60947b..65eaddd50ab 100644 --- a/tests/robotests/src/com/android/settings/slices/SlicePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/slices/SlicePreferenceControllerTest.java @@ -41,6 +41,8 @@ public class SlicePreferenceControllerTest { @Mock private LiveData mLiveData; + @Mock + private SlicePreference mSlicePreference; private Context mContext; private SlicePreferenceController mController; private Uri mUri; @@ -53,6 +55,7 @@ public class SlicePreferenceControllerTest { mContext = spy(RuntimeEnvironment.application); mController = new SlicePreferenceController(mContext, KEY); mController.mLiveData = mLiveData; + mController.mSlicePreference = mSlicePreference; mUri = Uri.EMPTY; } @@ -78,4 +81,11 @@ public class SlicePreferenceControllerTest { mController.onStop(); verify(mLiveData).removeObserver(mController); } + + @Test + public void onChanged_nullSlice_updateSlice() { + mController.onChanged(null); + + verify(mController.mSlicePreference).onSliceUpdated(null); + } } \ No newline at end of file From c89ba29e927c3c0320f6b78aee9ed5303515a6fe Mon Sep 17 00:00:00 2001 From: Lei Yu Date: Tue, 28 May 2019 10:42:19 -0700 Subject: [PATCH 07/26] Fix issues in battery usage accounting 1. In High usage dialog, show top apps based on battery usage, not app time. 2. Refactor the check for hidden system modules into ShouldHideSipper, however don't smear it, this is also the current logic before this CL. Bug: 133445008 Test: RunSettingsRoboTests Change-Id: I851a1c9ef9b79a934ba0501cd96001f2e450bda4 --- .../BatteryAppListPreferenceController.java | 11 +--- .../settings/fuelgauge/BatteryUtils.java | 29 +++++++- .../detectors/HighUsageDetector.java | 46 +++++++------ ...atteryAppListPreferenceControllerTest.java | 13 ++-- .../settings/fuelgauge/BatteryUtilsTest.java | 9 +++ .../detectors/HighUsageDetectorTest.java | 66 +++++++++++-------- 6 files changed, 105 insertions(+), 69 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java index 5366ba19987..7741a979a92 100644 --- a/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java @@ -352,15 +352,10 @@ public class BatteryAppListPreferenceController extends AbstractPreferenceContro @VisibleForTesting boolean shouldHideSipper(BatterySipper sipper) { - // Don't show hidden system module - final String packageName = mBatteryUtils.getPackageName(sipper.getUid()); - if (!TextUtils.isEmpty(packageName) - && AppUtils.isHiddenSystemModule(mContext, packageName)) { - return true; - } - // Don't show over-counted and unaccounted in any condition + // Don't show over-counted, unaccounted and hidden system module in any condition return sipper.drainType == BatterySipper.DrainType.OVERCOUNTED - || sipper.drainType == BatterySipper.DrainType.UNACCOUNTED; + || sipper.drainType == BatterySipper.DrainType.UNACCOUNTED + || mBatteryUtils.isHiddenSystemModule(sipper); } @VisibleForTesting diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java index b293695ab82..e4e249c3725 100644 --- a/src/com/android/settings/fuelgauge/BatteryUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryUtils.java @@ -47,6 +47,7 @@ import com.android.settings.fuelgauge.batterytip.AnomalyInfo; import com.android.settings.fuelgauge.batterytip.BatteryDatabaseManager; import com.android.settings.fuelgauge.batterytip.StatsManagerConfig; import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.applications.AppUtils; import com.android.settingslib.fuelgauge.Estimate; import com.android.settingslib.fuelgauge.EstimateKt; import com.android.settingslib.fuelgauge.PowerWhitelistBackend; @@ -187,8 +188,10 @@ public class BatteryUtils { && sipper.drainType != BatterySipper.DrainType.UNACCOUNTED && sipper.drainType != BatterySipper.DrainType.BLUETOOTH && sipper.drainType != BatterySipper.DrainType.WIFI - && sipper.drainType != BatterySipper.DrainType.IDLE) { - // Don't add it if it is overcounted, unaccounted, wifi, bluetooth, or screen + && sipper.drainType != BatterySipper.DrainType.IDLE + && !isHiddenSystemModule(sipper)) { + // Don't add it if it is overcounted, unaccounted, wifi, bluetooth, screen + // or hidden system modules proportionalSmearPowerMah += sipper.totalPowerMah; } } @@ -251,7 +254,27 @@ public class BatteryUtils { || drainType == BatterySipper.DrainType.WIFI || (sipper.totalPowerMah * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP || mPowerUsageFeatureProvider.isTypeService(sipper) - || mPowerUsageFeatureProvider.isTypeSystem(sipper); + || mPowerUsageFeatureProvider.isTypeSystem(sipper) + || isHiddenSystemModule(sipper); + } + + /** + * Return {@code true} if one of packages in {@code sipper} is hidden system modules + */ + public boolean isHiddenSystemModule(BatterySipper sipper) { + if (sipper.uidObj == null) { + return false; + } + sipper.mPackages = mPackageManager.getPackagesForUid(sipper.getUid()); + if (sipper.mPackages != null) { + for (int i = 0, length = sipper.mPackages.length; i < length; i++) { + if (AppUtils.isHiddenSystemModule(mContext, sipper.mPackages[i])) { + return true; + } + } + } + + return false; } /** diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java index 02af00c39a9..067046ca852 100644 --- a/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java +++ b/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java @@ -20,7 +20,6 @@ import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; import android.content.Context; import android.os.BatteryStats; -import android.text.format.DateUtils; import androidx.annotation.VisibleForTesting; @@ -72,22 +71,33 @@ public class HighUsageDetector implements BatteryTipDetector { if (mPolicy.highUsageEnabled && mDischarging) { parseBatteryData(); if (mDataParser.isDeviceHeavilyUsed() || mPolicy.testHighUsageTip) { - final List batterySippers = mBatteryStatsHelper.getUsageList(); - for (int i = 0, size = batterySippers.size(); i < size; i++) { - final BatterySipper batterySipper = batterySippers.get(i); - if (!mBatteryUtils.shouldHideSipper(batterySipper)) { - final long foregroundTimeMs = mBatteryUtils.getProcessTimeMs( - BatteryUtils.StatusType.FOREGROUND, batterySipper.uidObj, - BatteryStats.STATS_SINCE_CHARGED); - if (foregroundTimeMs >= DateUtils.MINUTE_IN_MILLIS) { - mHighUsageAppList.add(new AppInfo.Builder() - .setUid(batterySipper.getUid()) - .setPackageName( - mBatteryUtils.getPackageName(batterySipper.getUid())) - .setScreenOnTimeMs(foregroundTimeMs) - .build()); - } + final BatteryStats batteryStats = mBatteryStatsHelper.getStats(); + final List batterySippers + = new ArrayList<>(mBatteryStatsHelper.getUsageList()); + final double totalPower = mBatteryStatsHelper.getTotalPower(); + final int dischargeAmount = batteryStats != null + ? batteryStats.getDischargeAmount(BatteryStats.STATS_SINCE_CHARGED) + : 0; + + Collections.sort(batterySippers, + (sipper1, sipper2) -> Double.compare(sipper2.totalSmearedPowerMah, + sipper1.totalSmearedPowerMah)); + for (BatterySipper batterySipper : batterySippers) { + final double percent = mBatteryUtils.calculateBatteryPercent( + batterySipper.totalSmearedPowerMah, totalPower, 0, dischargeAmount); + if ((percent + 0.5f < 1f) || mBatteryUtils.shouldHideSipper(batterySipper)) { + // Don't show it if we should hide or usage percentage is lower than 1% + continue; } + mHighUsageAppList.add(new AppInfo.Builder() + .setUid(batterySipper.getUid()) + .setPackageName( + mBatteryUtils.getPackageName(batterySipper.getUid())) + .build()); + if (mHighUsageAppList.size() >= mPolicy.highUsageAppCount) { + break; + } + } // When in test mode, add an app if necessary @@ -97,10 +107,6 @@ public class HighUsageDetector implements BatteryTipDetector { .setScreenOnTimeMs(TimeUnit.HOURS.toMillis(3)) .build()); } - - Collections.sort(mHighUsageAppList, Collections.reverseOrder()); - mHighUsageAppList = mHighUsageAppList.subList(0, - Math.min(mPolicy.highUsageAppCount, mHighUsageAppList.size())); } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryAppListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryAppListPreferenceControllerTest.java index 32829755a62..f8bcf016459 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryAppListPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryAppListPreferenceControllerTest.java @@ -20,12 +20,14 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.ModuleInfo; import android.content.pm.PackageManager; +import android.os.BatteryStats; import android.os.UserManager; import android.text.TextUtils; import android.text.format.DateUtils; @@ -94,6 +96,7 @@ public class BatteryAppListPreferenceControllerTest { when(mNormalBatterySipper.getPackages()).thenReturn(PACKAGE_NAMES); when(mNormalBatterySipper.getUid()).thenReturn(UID); mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; + mNormalBatterySipper.uidObj = mock(BatteryStats.Uid.class); mPreferenceController = new BatteryAppListPreferenceController(mContext, KEY_APP_LIST, null, mSettingsActivity, mFragment); @@ -203,15 +206,7 @@ public class BatteryAppListPreferenceControllerTest { @Test public void testShouldHideSipper_hiddenSystemModule_returnTrue() { - ReflectionHelpers.setStaticField(ApplicationsState.class, "sInstance", null); - final String packageName = "test.hidden.module"; - final ModuleInfo moduleInfo = new ModuleInfo(); - moduleInfo.setPackageName(packageName); - moduleInfo.setHidden(true); - final List modules = new ArrayList<>(); - modules.add(moduleInfo); - when(mBatteryUtils.getPackageName(anyInt() /* uid */)).thenReturn(packageName); - when(mPackageManager.getInstalledModules(anyInt() /* flags */)).thenReturn(modules); + when(mBatteryUtils.isHiddenSystemModule(mNormalBatterySipper)).thenReturn(true); assertThat(mPreferenceController.shouldHideSipper(mNormalBatterySipper)).isTrue(); } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java index a60460b8ce2..9deaddd2e6a 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java @@ -367,6 +367,15 @@ public class BatteryUtilsTest { assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } + @Test + public void testShouldHideSipper_hiddenSystemModule_ReturnTrue() { + mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; + when(mNormalBatterySipper.getUid()).thenReturn(UID); + when(mBatteryUtils.isHiddenSystemModule(mNormalBatterySipper)).thenReturn(true); + + assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); + } + @Test public void testCalculateBatteryPercent() { assertThat(mBatteryUtils.calculateBatteryPercent(BATTERY_SYSTEM_USAGE, TOTAL_BATTERY_USAGE, diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetectorTest.java index 7b9e5a5c73a..5c56f46c4bd 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetectorTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetectorTest.java @@ -18,6 +18,7 @@ package com.android.settings.fuelgauge.batterytip.detectors; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -40,6 +41,7 @@ import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; @@ -52,22 +54,25 @@ import java.util.List; @RunWith(RobolectricTestRunner.class) public class HighUsageDetectorTest { private static final int UID_HIGH = 123; - private static final int UID_ZERO = 345; - private static final long SCREEN_ON_TIME_MS = DateUtils.HOUR_IN_MILLIS; + private static final int UID_LOW = 345; + private static final double POWER_HIGH = 20000; + private static final double POWER_LOW = 10000; private Context mContext; - @Mock + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private BatteryStatsHelper mBatteryStatsHelper; @Mock - private BatteryUtils mBatteryUtils; - @Mock private BatterySipper mHighBatterySipper; @Mock - private BatterySipper mZeroBatterySipper; + private BatterySipper mLowBatterySipper; + @Mock + private BatterySipper mSystemBatterySipper; @Mock private HighUsageDataParser mDataParser; - private AppInfo mAppInfo; + private AppInfo mHighAppInfo; + private AppInfo mLowAppInfo; private BatteryTipPolicy mPolicy; + private BatteryUtils mBatteryUtils; private HighUsageDetector mHighUsageDetector; private List mUsageList; @@ -77,28 +82,37 @@ public class HighUsageDetectorTest { mContext = RuntimeEnvironment.application; mPolicy = spy(new BatteryTipPolicy(mContext)); + mBatteryUtils = spy(BatteryUtils.getInstance(mContext)); mHighUsageDetector = spy(new HighUsageDetector(mContext, mPolicy, mBatteryStatsHelper, true /* mDischarging */)); mHighUsageDetector.mBatteryUtils = mBatteryUtils; mHighUsageDetector.mDataParser = mDataParser; doNothing().when(mHighUsageDetector).parseBatteryData(); doReturn(UID_HIGH).when(mHighBatterySipper).getUid(); + doReturn(UID_LOW).when(mLowBatterySipper).getUid(); mHighBatterySipper.uidObj = mock(BatteryStats.Uid.class); - mZeroBatterySipper.uidObj = mock(BatteryStats.Uid.class); - doReturn(UID_ZERO).when(mZeroBatterySipper).getUid(); - mAppInfo = new AppInfo.Builder() + mHighBatterySipper.drainType = BatterySipper.DrainType.APP; + mHighBatterySipper.totalSmearedPowerMah = POWER_HIGH; + mLowBatterySipper.uidObj = mock(BatteryStats.Uid.class); + mLowBatterySipper.drainType = BatterySipper.DrainType.APP; + mLowBatterySipper.totalSmearedPowerMah = POWER_LOW; + when(mBatteryUtils.shouldHideSipper(mSystemBatterySipper)).thenReturn(true); + when(mBatteryUtils.shouldHideSipper(mHighBatterySipper)).thenReturn(false); + when(mBatteryUtils.shouldHideSipper(mLowBatterySipper)).thenReturn(false); + when(mBatteryStatsHelper.getStats().getDischargeAmount(anyInt())).thenReturn(100); + when(mBatteryStatsHelper.getTotalPower()).thenReturn(POWER_HIGH + POWER_LOW); + + + mHighAppInfo = new AppInfo.Builder() .setUid(UID_HIGH) - .setScreenOnTimeMs(SCREEN_ON_TIME_MS) + .build(); + mLowAppInfo = new AppInfo.Builder() + .setUid(UID_LOW) .build(); - doReturn(SCREEN_ON_TIME_MS).when(mBatteryUtils).getProcessTimeMs( - BatteryUtils.StatusType.FOREGROUND, mHighBatterySipper.uidObj, - BatteryStats.STATS_SINCE_CHARGED); - doReturn(0L).when(mBatteryUtils).getProcessTimeMs( - BatteryUtils.StatusType.FOREGROUND, mZeroBatterySipper.uidObj, - BatteryStats.STATS_SINCE_CHARGED); - mUsageList = new ArrayList<>(); + mUsageList.add(mSystemBatterySipper); + mUsageList.add(mLowBatterySipper); mUsageList.add(mHighBatterySipper); when(mBatteryStatsHelper.getUsageList()).thenReturn(mUsageList); } @@ -128,21 +142,15 @@ public class HighUsageDetectorTest { } @Test - public void testDetect_containsHighUsageApp_tipVisible() { + public void testDetect_containsHighUsageApp_tipVisibleAndSorted() { doReturn(true).when(mDataParser).isDeviceHeavilyUsed(); final HighUsageTip highUsageTip = (HighUsageTip) mHighUsageDetector.detect(); assertThat(highUsageTip.isVisible()).isTrue(); - assertThat(highUsageTip.getHighUsageAppList()).containsExactly(mAppInfo); - } - @Test - public void testDetect_containsHighUsageApp_removeZeroOne() { - doReturn(true).when(mDataParser).isDeviceHeavilyUsed(); - mUsageList.add(mZeroBatterySipper); - - final HighUsageTip highUsageTip = (HighUsageTip) mHighUsageDetector.detect(); - assertThat(highUsageTip.isVisible()).isTrue(); - assertThat(highUsageTip.getHighUsageAppList()).containsExactly(mAppInfo); + // Contain two appInfo and large one comes first + final List appInfos = highUsageTip.getHighUsageAppList(); + assertThat(appInfos).containsExactly(mLowAppInfo, mHighAppInfo); + assertThat(appInfos.get(0)).isEqualTo(mHighAppInfo); } } From 83f0d24f677a58eefb9a73ad6247da79b741e9d1 Mon Sep 17 00:00:00 2001 From: Adam He Date: Wed, 29 May 2019 14:21:33 -0700 Subject: [PATCH 08/26] Profile select only shows with multiple profiles present. Bug: 133328247 Change-Id: Id5d03a1aa74f1789c41af37562848603a90db84f Test: manual verification --- ...thServiceSettingsPreferenceController.java | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java b/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java index 809bfbdc21f..77aab34c9c3 100644 --- a/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java +++ b/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java @@ -96,18 +96,23 @@ public final class EnableContentCaptureWithServiceSettingsPreferenceController for (UserInfo info: userInfos) { userHandles.add(info.getUserHandle()); } - - AlertDialog.Builder builder = new AlertDialog.Builder(context); - UserAdapter adapter = UserAdapter.createUserAdapter(userManager, context, userHandles); - builder.setTitle(com.android.settingslib.R.string.choose_profile) - .setAdapter(adapter, (DialogInterface dialog, int which) -> { - final UserHandle user = userHandles.get(which); - // Show menu on top level items. - final Intent intent = pref.getIntent(); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); - context.startActivityAsUser(intent, user); - }) - .show(); + if (userHandles.size() == 1) { + final Intent intent = pref.getIntent().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + context.startActivityAsUser(intent, userHandles.get(0)); + } else { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + UserAdapter adapter = UserAdapter.createUserAdapter(userManager, context, + userHandles); + builder.setTitle(com.android.settingslib.R.string.choose_profile) + .setAdapter(adapter, (DialogInterface dialog, int which) -> { + final UserHandle user = userHandles.get(which); + // Show menu on top level items. + final Intent intent = pref.getIntent() + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + context.startActivityAsUser(intent, user); + }) + .show(); + } } } From bddf5149e56e3c9800ba41d9b3ad1addde650dae Mon Sep 17 00:00:00 2001 From: Sunny Shao Date: Mon, 27 May 2019 11:55:34 +0800 Subject: [PATCH 09/26] Fix the crash while tapping "app data usage" as second user Second user or guest is limited some mobile network access. We need to check current user is admin or not to decide the preferece page will be found in search or not. Bug: 133466016 Fixes: 133466016 Test: Manual test & make RunSettingsRoboTests -j56 ROBOTEST_FILTER=com.android.settings.network Change-Id: I48d3064a8aa28ac1f2ac699b42a999b9682b1b52 --- .../network/MobileNetworkListFragment.java | 6 ++ .../telephony/MobileNetworkSettings.java | 6 ++ .../MobileNetworkListFragmentTest.java | 79 +++++++++++++++++++ .../telephony/MobileNetworkSettingsTest.java | 33 ++++++++ 4 files changed, 124 insertions(+) create mode 100644 tests/robotests/src/com/android/settings/network/MobileNetworkListFragmentTest.java diff --git a/src/com/android/settings/network/MobileNetworkListFragment.java b/src/com/android/settings/network/MobileNetworkListFragment.java index c90827e6556..4690a288154 100644 --- a/src/com/android/settings/network/MobileNetworkListFragment.java +++ b/src/com/android/settings/network/MobileNetworkListFragment.java @@ -18,6 +18,7 @@ package com.android.settings.network; import android.app.settings.SettingsEnums; import android.content.Context; +import android.os.UserManager; import android.provider.SearchIndexableResource; import com.android.settings.R; @@ -66,5 +67,10 @@ public class MobileNetworkListFragment extends DashboardFragment { result.add(sir); return result; } + + @Override + protected boolean isPageSearchEnabled(Context context) { + return context.getSystemService(UserManager.class).isAdminUser(); + } }; } diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java index f8e5c3a518f..838aa12f95f 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java @@ -277,5 +277,11 @@ public class MobileNetworkSettings extends RestrictedDashboardFragment { result.add(sir); return result; } + + /** suppress full page if user is not admin */ + @Override + protected boolean isPageSearchEnabled(Context context) { + return context.getSystemService(UserManager.class).isAdminUser(); + } }; } diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkListFragmentTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkListFragmentTest.java new file mode 100644 index 00000000000..a65ff24f455 --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/MobileNetworkListFragmentTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.UserManager; + +import com.android.settings.search.BaseSearchIndexProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.util.ReflectionHelpers; + + +@RunWith(RobolectricTestRunner.class) +public class MobileNetworkListFragmentTest { + @Mock + private Context mContext; + @Mock + private UserManager mUserManager; + + private MobileNetworkListFragment mFragment; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mFragment = new MobileNetworkListFragment(); + } + + @Test + public void isPageSearchEnabled_adminUser_shouldReturnTrue() { + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); + when(mUserManager.isAdminUser()).thenReturn(true); + final BaseSearchIndexProvider provider = + (BaseSearchIndexProvider) mFragment.SEARCH_INDEX_DATA_PROVIDER; + + final Object obj = ReflectionHelpers.callInstanceMethod(provider, "isPageSearchEnabled", + ReflectionHelpers.ClassParameter.from(Context.class, mContext)); + final boolean isEnabled = (Boolean) obj; + + assertThat(isEnabled).isTrue(); + } + + @Test + public void isPageSearchEnabled_nonAdminUser_shouldReturnFalse() { + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); + when(mUserManager.isAdminUser()).thenReturn(false); + final BaseSearchIndexProvider provider = + (BaseSearchIndexProvider) mFragment.SEARCH_INDEX_DATA_PROVIDER; + + final Object obj = ReflectionHelpers.callInstanceMethod(provider, "isPageSearchEnabled", + ReflectionHelpers.ClassParameter.from(Context.class, mContext)); + final boolean isEnabled = (Boolean) obj; + + assertThat(isEnabled).isFalse(); + } +} diff --git a/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSettingsTest.java b/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSettingsTest.java index a999a9ee1cf..4afe0fc1e05 100644 --- a/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSettingsTest.java +++ b/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkSettingsTest.java @@ -30,6 +30,7 @@ import android.app.usage.NetworkStatsManager; import android.content.Context; import android.net.NetworkPolicyManager; import android.os.Bundle; +import android.os.UserManager; import android.provider.Settings; import android.telephony.TelephonyManager; @@ -38,6 +39,7 @@ import androidx.fragment.app.FragmentActivity; import com.android.settings.core.FeatureFlags; import com.android.settings.datausage.DataUsageSummaryPreferenceController; import com.android.settings.development.featureflags.FeatureFlagPersistent; +import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.testutils.shadow.ShadowEntityHeaderController; import com.android.settings.widget.EntityHeaderController; import com.android.settingslib.core.AbstractPreferenceController; @@ -50,6 +52,7 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; import java.util.List; @@ -135,4 +138,34 @@ public class MobileNetworkSettingsTest { mFragment.onActivityResult(REQUEST_CODE_DELETE_SUBSCRIPTION, 0, null); verify(mActivity).finish(); } + + @Test + public void isPageSearchEnabled_adminUser_shouldReturnTrue() { + final UserManager userManager = mock(UserManager.class); + when(mContext.getSystemService(UserManager.class)).thenReturn(userManager); + when(userManager.isAdminUser()).thenReturn(true); + final BaseSearchIndexProvider provider = + (BaseSearchIndexProvider) mFragment.SEARCH_INDEX_DATA_PROVIDER; + + final Object obj = ReflectionHelpers.callInstanceMethod(provider, "isPageSearchEnabled", + ReflectionHelpers.ClassParameter.from(Context.class, mContext)); + final boolean isEnabled = (Boolean) obj; + + assertThat(isEnabled).isTrue(); + } + + @Test + public void isPageSearchEnabled_nonAdminUser_shouldReturnFalse() { + final UserManager userManager = mock(UserManager.class); + when(mContext.getSystemService(UserManager.class)).thenReturn(userManager); + when(userManager.isAdminUser()).thenReturn(false); + final BaseSearchIndexProvider provider = + (BaseSearchIndexProvider) mFragment.SEARCH_INDEX_DATA_PROVIDER; + + final Object obj = ReflectionHelpers.callInstanceMethod(provider, "isPageSearchEnabled", + ReflectionHelpers.ClassParameter.from(Context.class, mContext)); + final boolean isEnabled = (Boolean) obj; + + assertThat(isEnabled).isFalse(); + } } From b2aaa26f3851a3e9e9f937be43a24663c04b6546 Mon Sep 17 00:00:00 2001 From: Lucas Dupin Date: Tue, 28 May 2019 17:58:07 -0700 Subject: [PATCH 10/26] Add setting to bypass lock screen Test: manual Test: make RunSettingsRoboTests ROBOTEST_FILTER=LockscreenBypassPreferenceControllerTest Bug: 130327302 Change-Id: I324c6e384480fa8576ba58d0bf73c1ef20484ee0 --- res/values/strings.xml | 9 +++ res/xml/privacy_dashboard_settings.xml | 8 ++ res/xml/security_lockscreen_settings.xml | 7 ++ .../LockscreenBypassPreferenceController.java | 59 ++++++++++++++ ...kscreenBypassPreferenceControllerTest.java | 77 +++++++++++++++++++ 5 files changed, 160 insertions(+) create mode 100644 src/com/android/settings/security/LockscreenBypassPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/security/LockscreenBypassPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index c84f51da8aa..128ff617341 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7976,6 +7976,15 @@ Lock screen + + Skip lock screen + + + After face unlock, go directly to last used screen + + + Lock screen, Lockscreen, Skip, Bypass + When work profile is locked diff --git a/res/xml/privacy_dashboard_settings.xml b/res/xml/privacy_dashboard_settings.xml index aa789b90424..7057ec570b9 100644 --- a/res/xml/privacy_dashboard_settings.xml +++ b/res/xml/privacy_dashboard_settings.xml @@ -62,6 +62,14 @@ android:summary="@string/summary_placeholder" settings:searchable="false"/> + + + + + Date: Fri, 24 May 2019 10:52:55 -0700 Subject: [PATCH 11/26] Add SIM color picker Bug: 133193155 Test: make ROBOTEST_FILTER="(RenameMobileNetwork*)" RunSettingsRoboTests -j48 Change-Id: Ifae8b77bc5b4ba1039ede70889468a018f57d78f --- ...ialog_mobile_network_color_picker_item.xml | 41 ++++++ res/layout/dialog_mobile_network_rename.xml | 103 ++++++++++----- res/values/dimens.xml | 1 + res/values/strings.xml | 12 +- .../RenameMobileNetworkDialogFragment.java | 120 +++++++++++++++++- ...RenameMobileNetworkDialogFragmentTest.java | 17 ++- 6 files changed, 253 insertions(+), 41 deletions(-) create mode 100644 res/layout/dialog_mobile_network_color_picker_item.xml diff --git a/res/layout/dialog_mobile_network_color_picker_item.xml b/res/layout/dialog_mobile_network_color_picker_item.xml new file mode 100644 index 00000000000..b6f40929ef4 --- /dev/null +++ b/res/layout/dialog_mobile_network_color_picker_item.xml @@ -0,0 +1,41 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/layout/dialog_mobile_network_rename.xml b/res/layout/dialog_mobile_network_rename.xml index d67f0dc6824..921ab8602e5 100644 --- a/res/layout/dialog_mobile_network_rename.xml +++ b/res/layout/dialog_mobile_network_rename.xml @@ -21,44 +21,89 @@ + android:layout_height="wrap_content"> - - - + android:paddingEnd="@dimen/sim_content_padding" + android:paddingStart="@dimen/sim_content_padding"> - - + - + + + + + + - + + + android:paddingEnd="@dimen/sim_content_padding" + android:paddingStart="@dimen/sim_content_padding"> + + + + + + + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 09ab682cde9..fbf1a24d7a9 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -166,6 +166,7 @@ 16dip 8dip + 12dip 16dip 24dip diff --git a/res/values/strings.xml b/res/values/strings.xml index fa5d1238be3..2eb3be877c3 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10956,10 +10956,14 @@ subscription in various places in the Settings app. The default name is typically just the carrier name, but especially in multi-SIM configurations users may want to use a different name. [CHAR LIMIT=40] --> - SIM name - - Rename + SIM name & color + + Name + + Color (used by compatible apps) + + Save Use SIM diff --git a/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragment.java b/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragment.java index 82554c2eca7..a28fc91e43a 100644 --- a/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragment.java +++ b/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragment.java @@ -19,6 +19,10 @@ package com.android.settings.network.telephony; import android.app.Dialog; import android.app.settings.SettingsEnums; import android.content.Context; +import android.content.res.Resources; +import android.graphics.Paint; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.OvalShape; import android.os.Bundle; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; @@ -30,7 +34,12 @@ import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; import android.widget.EditText; +import android.widget.ImageView; +import android.widget.Spinner; import android.widget.TextView; import com.android.settings.R; @@ -42,9 +51,12 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; -/** A dialog allowing the display name of a mobile network subscription to be changed */ +/** + * A dialog allowing the display name of a mobile network subscription to be changed + */ public class RenameMobileNetworkDialogFragment extends InstrumentedDialogFragment { - public static final String TAG ="RenameMobileNetwork"; + + public static final String TAG = "RenameMobileNetwork"; private static final String KEY_SUBSCRIPTION_ID = "subscription_id"; @@ -52,6 +64,8 @@ public class RenameMobileNetworkDialogFragment extends InstrumentedDialogFragmen private SubscriptionManager mSubscriptionManager; private int mSubId; private EditText mNameView; + private Spinner mColorSpinner; + private Color[] mColors; public static RenameMobileNetworkDialogFragment newInstance(int subscriptionId) { final Bundle args = new Bundle(1); @@ -76,6 +90,11 @@ public class RenameMobileNetworkDialogFragment extends InstrumentedDialogFragmen return mNameView; } + @VisibleForTesting + protected Spinner getColorSpinnerView() { + return mColorSpinner; + } + @Override public void onAttach(Context context) { super.onAttach(context); @@ -87,6 +106,8 @@ public class RenameMobileNetworkDialogFragment extends InstrumentedDialogFragmen @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + mColors = getColors(); + final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); final LayoutInflater layoutInflater = builder.getContext().getSystemService( LayoutInflater.class); @@ -95,9 +116,11 @@ public class RenameMobileNetworkDialogFragment extends InstrumentedDialogFragmen builder.setTitle(R.string.mobile_network_sim_name) .setView(view) .setPositiveButton(R.string.mobile_network_sim_name_rename, (dialog, which) -> { - String newName = mNameView.getText().toString(); - mSubscriptionManager.setDisplayName(newName, mSubId, + mSubscriptionManager.setDisplayName(mNameView.getText().toString(), mSubId, SubscriptionManager.NAME_SOURCE_USER_INPUT); + mSubscriptionManager.setIconTint( + mColors[mColorSpinner.getSelectedItemPosition()].getColor(), + mSubId); }) .setNegativeButton(android.R.string.cancel, null); return builder.create(); @@ -105,7 +128,7 @@ public class RenameMobileNetworkDialogFragment extends InstrumentedDialogFragmen @VisibleForTesting protected void populateView(View view) { - mNameView = view.findViewById(R.id.edittext); + mNameView = view.findViewById(R.id.name_edittext); final SubscriptionInfo info = mSubscriptionManager.getActiveSubscriptionInfo(mSubId); if (info == null) { Log.w(TAG, "got null SubscriptionInfo for mSubId:" + mSubId); @@ -117,6 +140,17 @@ public class RenameMobileNetworkDialogFragment extends InstrumentedDialogFragmen mNameView.setSelection(displayName.length()); } + mColorSpinner = view.findViewById(R.id.color_spinner); + final ColorAdapter adapter = new ColorAdapter(getContext(), + R.layout.dialog_mobile_network_color_picker_item, mColors); + mColorSpinner.setAdapter(adapter); + for (int i = 0; i < mColors.length; i++) { + if (mColors[i].getColor() == info.getIconTint()) { + mColorSpinner.setSelection(i); + break; + } + } + final TextView operatorName = view.findViewById(R.id.operator_name_value); final ServiceState serviceState = mTelephonyManager.getServiceStateForSubscriber(mSubId); operatorName.setText(serviceState.getOperatorAlphaLong()); @@ -134,4 +168,80 @@ public class RenameMobileNetworkDialogFragment extends InstrumentedDialogFragmen public int getMetricsCategory() { return SettingsEnums.MOBILE_NETWORK_RENAME_DIALOG; } + + private class ColorAdapter extends ArrayAdapter { + + private Context mContext; + private int mItemResId; + + public ColorAdapter(Context context, int resource, Color[] colors) { + super(context, resource, colors); + mContext = context; + mItemResId = resource; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final LayoutInflater inflater = (LayoutInflater) + mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + if (convertView == null) { + convertView = inflater.inflate(mItemResId, null); + } + ((ImageView) convertView.findViewById(R.id.color_icon)) + .setImageDrawable(getItem(position).getDrawable()); + ((TextView) convertView.findViewById(R.id.color_label)) + .setText(getItem(position).getLabel()); + + return convertView; + } + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + return getView(position, convertView, parent); + } + } + + private Color[] getColors() { + final Resources res = getContext().getResources(); + final int[] colorInts = res.getIntArray(com.android.internal.R.array.sim_colors); + final String[] colorStrings = res.getStringArray(R.array.color_picker); + final int iconSize = res.getDimensionPixelSize(R.dimen.color_swatch_size); + final int strokeWidth = res.getDimensionPixelSize(R.dimen.color_swatch_stroke_width); + final Color[] colors = new Color[colorInts.length]; + for (int i = 0; i < colors.length; i++) { + colors[i] = new Color(colorStrings[i], colorInts[i], iconSize, strokeWidth); + } + return colors; + } + + private static class Color { + + private String mLabel; + private int mColor; + private ShapeDrawable mDrawable; + + private Color(String label, int color, int iconSize, int strokeWidth) { + mLabel = label; + mColor = color; + mDrawable = new ShapeDrawable(new OvalShape()); + mDrawable.setIntrinsicHeight(iconSize); + mDrawable.setIntrinsicWidth(iconSize); + mDrawable.getPaint().setStrokeWidth(strokeWidth); + mDrawable.getPaint().setStyle(Paint.Style.FILL_AND_STROKE); + mDrawable.getPaint().setColor(color); + } + + private String getLabel() { + return mLabel; + } + + private int getColor() { + return mColor; + } + + private ShapeDrawable getDrawable() { + return mDrawable; + } + } } diff --git a/tests/robotests/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragmentTest.java b/tests/robotests/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragmentTest.java index 5b008be8511..53b4f002698 100644 --- a/tests/robotests/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragmentTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.DialogInterface; +import android.graphics.Color; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -38,6 +39,7 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.EditText; +import android.widget.Spinner; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.FragmentActivity; @@ -58,6 +60,7 @@ import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) @Config(shadows = ShadowAlertDialogCompat.class) public class RenameMobileNetworkDialogFragmentTest { + @Mock private TelephonyManager mTelephonyMgr; @Mock @@ -95,7 +98,7 @@ public class RenameMobileNetworkDialogFragmentTest { } @Test - public void dialog_cancelButtonClicked_setDisplayNameNotCalled() { + public void dialog_cancelButtonClicked_setDisplayNameAndIconTintNotCalled() { when(mSubscriptionMgr.getActiveSubscriptionInfo(mSubscriptionId)).thenReturn( mSubscriptionInfo); final AlertDialog dialog = startDialog(); @@ -106,10 +109,11 @@ public class RenameMobileNetworkDialogFragmentTest { negativeButton.performClick(); verify(mSubscriptionMgr, never()).setDisplayName(anyString(), anyInt(), anyInt()); + verify(mSubscriptionMgr, never()).setIconTint(anyInt(), anyInt()); } @Test - public void dialog_renameButtonClicked_setDisplayNameCalled() { + public void dialog_saveButtonClicked_setDisplayNameAndIconTint() { when(mSubscriptionMgr.getActiveSubscriptionInfo(mSubscriptionId)).thenReturn( mSubscriptionInfo); @@ -117,6 +121,9 @@ public class RenameMobileNetworkDialogFragmentTest { final EditText nameView = mFragment.getNameView(); nameView.setText("test2"); + final Spinner colorSpinnerView = mFragment.getColorSpinnerView(); + colorSpinnerView.setSelection(0); + final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); positiveButton.performClick(); @@ -124,6 +131,8 @@ public class RenameMobileNetworkDialogFragmentTest { verify(mSubscriptionMgr).setDisplayName(captor.capture(), eq(mSubscriptionId), eq(SubscriptionManager.NAME_SOURCE_USER_INPUT)); assertThat(captor.getValue()).isEqualTo("test2"); + verify(mSubscriptionMgr) + .setIconTint(eq(Color.parseColor("#ff00796b" /* teal */)), eq(mSubscriptionId)); } @Test @@ -140,7 +149,9 @@ public class RenameMobileNetworkDialogFragmentTest { assertThat(view.findViewById(R.id.number_label).getVisibility()).isEqualTo(View.GONE); } - /** Helper method to start the dialog */ + /** + * Helper method to start the dialog + */ private AlertDialog startDialog() { mFragment.show(mActivity.getSupportFragmentManager(), null); return ShadowAlertDialogCompat.getLatestAlertDialog(); From be98ea25cad6aff53a517b7ac4bdf735ba69a7af Mon Sep 17 00:00:00 2001 From: Mehdi Alizadeh Date: Wed, 29 May 2019 20:21:03 -0700 Subject: [PATCH 12/26] Increase char limit to 60 for string translation Bug: 133862422 Test: None Change-Id: I3fa6705a6657c5aa65e496cba2ce470fb558a45f --- res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 49ac4cce010..cba7ad8c826 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10200,7 +10200,7 @@ Not supported by your default home app, %s - + Switch default home app From 1a2b34b625c5e40056a80a3020e7e5f269289d7f Mon Sep 17 00:00:00 2001 From: Raff Tsai Date: Thu, 30 May 2019 06:39:39 +0800 Subject: [PATCH 13/26] Fix IllegalArgumentException in AudioHelper Audio streams for phone calls in BT device (STREAM_VOICE_CALL) is not supported in AudioManager.getStreamMinVolume(), fallback to use STREAM_VOICE_CALL Fixes: 133812547 Test: make RunSettingsRoboTests Change-Id: I8e330f60bae2a7cf9cd0cf7288076b8320ccc504 --- .../settings/notification/AudioHelper.java | 13 +++- .../notification/AudioHelperTest.java | 64 +++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 tests/robotests/src/com/android/settings/notification/AudioHelperTest.java diff --git a/src/com/android/settings/notification/AudioHelper.java b/src/com/android/settings/notification/AudioHelper.java index d1781139064..01945fd5f6a 100644 --- a/src/com/android/settings/notification/AudioHelper.java +++ b/src/com/android/settings/notification/AudioHelper.java @@ -22,6 +22,7 @@ import android.media.AudioManager; import android.media.AudioSystem; import android.os.UserHandle; import android.os.UserManager; +import android.util.Log; import com.android.settings.Utils; @@ -30,6 +31,7 @@ import com.android.settings.Utils; */ public class AudioHelper { + private static final String TAG = "AudioHelper"; private Context mContext; private AudioManager mAudioManager; @@ -76,6 +78,15 @@ public class AudioHelper { } public int getMinVolume(int stream) { - return mAudioManager.getStreamMinVolume(stream); + int minVolume; + try { + minVolume = mAudioManager.getStreamMinVolume(stream); + } catch (IllegalArgumentException e) { + Log.w(TAG, "Invalid stream type " + stream); + // Fallback to STREAM_VOICE_CALL because CallVolumePreferenceController.java default + // return STREAM_VOICE_CALL in getAudioStream + minVolume = mAudioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL); + } + return minVolume; } } diff --git a/tests/robotests/src/com/android/settings/notification/AudioHelperTest.java b/tests/robotests/src/com/android/settings/notification/AudioHelperTest.java new file mode 100644 index 00000000000..79d0198ce49 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/AudioHelperTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.settings.notification; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class AudioHelperTest { + + private static final int START = -10; + private static final int END = 10; + private static final int DEFAULT = -100; + + private Context mContext; + private AudioHelper mAudioHelper; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mAudioHelper = new AudioHelper(mContext); + } + + @Test + public void getMaxVolume_anyStreamType_getValue() { + int volume = DEFAULT; + + for (int i = START; i < END; i++) { + volume = mAudioHelper.getMaxVolume(i); + assertThat(volume).isNotEqualTo(DEFAULT); + } + } + + @Test + public void getMinVolume_anyStreamType_getValue() { + int volume = DEFAULT; + + for (int i = START; i < END; i++) { + volume = mAudioHelper.getMinVolume(i); + assertThat(volume).isNotEqualTo(DEFAULT); + } + } +} From c0c4b3a1115725eeb4b989baab7062962581091c Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Thu, 30 May 2019 10:31:40 -0400 Subject: [PATCH 14/26] Fix a11y readout of notification importance buttons Test: manual with TB Bug: 134047401 Change-Id: Idab3426a37ed3f25c2cde47e0747ac9a31850f62 --- res/layout/notif_importance_preference.xml | 4 +-- .../notification/ImportancePreference.java | 2 ++ .../NotificationButtonRelativeLayout.java | 33 +++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/com/android/settings/notification/NotificationButtonRelativeLayout.java diff --git a/res/layout/notif_importance_preference.xml b/res/layout/notif_importance_preference.xml index 2fc2740a829..395e04908e8 100644 --- a/res/layout/notif_importance_preference.xml +++ b/res/layout/notif_importance_preference.xml @@ -24,7 +24,7 @@ android:padding="@dimen/notification_importance_toggle_marginTop" android:orientation="vertical"> - - + Date: Thu, 30 May 2019 08:30:49 -0700 Subject: [PATCH 15/26] Remove Permissions Hub. Bug: 132292477 Test: View all related screens. Change-Id: Ic0e890e04f210f3be3eb80c95e398a40a8062001 --- src/com/android/settings/Utils.java | 5 - ...entLocationAccessPreferenceController.java | 4 +- ...ermissionBarChartPreferenceController.java | 5 +- ...ocationAccessPreferenceControllerTest.java | 71 --------- ...sibilityUsagePreferenceControllerTest.java | 4 - ...ssionBarChartPreferenceControllerTest.java | 140 ------------------ 6 files changed, 2 insertions(+), 227 deletions(-) diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 75db3e18188..c4b1400f5c6 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -130,11 +130,6 @@ public final class Utils extends com.android.settingslib.Utils { public static final String PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED = "device_identifier_access_restrictions_disabled"; - /** - * Whether to show the Permissions Hub. - */ - public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled"; - /** * Finds a matching activity for a preference's intent. If a matching * activity is not found, it will remove the preference. diff --git a/src/com/android/settings/location/RecentLocationAccessPreferenceController.java b/src/com/android/settings/location/RecentLocationAccessPreferenceController.java index c835a514b1a..2f0dafdf6c3 100644 --- a/src/com/android/settings/location/RecentLocationAccessPreferenceController.java +++ b/src/com/android/settings/location/RecentLocationAccessPreferenceController.java @@ -64,9 +64,7 @@ public class RecentLocationAccessPreferenceController extends AbstractPreference @Override public boolean isAvailable() { - return Boolean.parseBoolean( - DeviceConfig.getProperty(DeviceConfig.NAMESPACE_PRIVACY, - Utils.PROPERTY_PERMISSIONS_HUB_ENABLED)); + return false; } @Override diff --git a/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java b/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java index 399216c3466..28533df03a4 100644 --- a/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java +++ b/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java @@ -89,10 +89,7 @@ public class PermissionBarChartPreferenceController extends BasePreferenceContro @Override public int getAvailabilityStatus() { - return Boolean.parseBoolean( - DeviceConfig.getProperty(DeviceConfig.NAMESPACE_PRIVACY, - com.android.settings.Utils.PROPERTY_PERMISSIONS_HUB_ENABLED)) ? - AVAILABLE_UNSEARCHABLE : UNSUPPORTED_ON_DEVICE; + return UNSUPPORTED_ON_DEVICE; } @Override diff --git a/tests/robotests/src/com/android/settings/location/RecentLocationAccessPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/RecentLocationAccessPreferenceControllerTest.java index 71a80de0689..aeda699fe29 100644 --- a/tests/robotests/src/com/android/settings/location/RecentLocationAccessPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/RecentLocationAccessPreferenceControllerTest.java @@ -92,75 +92,4 @@ public class RecentLocationAccessPreferenceControllerTest { // We have not yet set the property to show the Permissions Hub. assertThat(mController.isAvailable()).isEqualTo(false); } - - @Test - public void isAvailable_permissionHubEnabled_shouldReturnTrue() { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, - Utils.PROPERTY_PERMISSIONS_HUB_ENABLED, "true", true); - - assertThat(mController.isAvailable()).isEqualTo(true); - } - - /** Verifies the title text, details text are correct, and the click listener is set. */ - @Test - @Ignore - public void updateState_whenAppListIsEmpty_shouldDisplayTitleTextAndDetailsText() { - doReturn(new ArrayList<>()).when(mRecentLocationApps).getAppListSorted(); - mController.displayPreference(mScreen); - mController.updateState(mLayoutPreference); - - final TextView title = mAppEntitiesHeaderView.findViewById(R.id.header_title); - assertThat(title.getText()).isEqualTo( - mContext.getText(R.string.location_category_recent_location_access)); - final TextView details = mAppEntitiesHeaderView.findViewById(R.id.header_details); - assertThat(details.getText()).isEqualTo( - mContext.getText(R.string.location_recent_location_access_view_details)); - assertThat(details.hasOnClickListeners()).isTrue(); - } - - @Test - public void updateState_whenAppListMoreThanThree_shouldDisplayTopThreeApps() { - final List accesses = createMockAccesses(6); - doReturn(accesses).when(mRecentLocationApps).getAppListSorted(); - mController.displayPreference(mScreen); - mController.updateState(mLayoutPreference); - - // The widget can display the top 3 apps from the list when there're more than 3. - final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view); - final ImageView appIconView1 = app1View.findViewById(R.id.app_icon); - final TextView appTitle1 = app1View.findViewById(R.id.app_title); - - assertThat(app1View.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(appIconView1.getDrawable()).isNotNull(); - assertThat(appTitle1.getText()).isEqualTo("appTitle0"); - - final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view); - final ImageView appIconView2 = app2View.findViewById(R.id.app_icon); - final TextView appTitle2 = app2View.findViewById(R.id.app_title); - - assertThat(app2View.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(appIconView2.getDrawable()).isNotNull(); - assertThat(appTitle2.getText()).isEqualTo("appTitle1"); - - final View app3View = mAppEntitiesHeaderView.findViewById(R.id.app3_view); - final ImageView appIconView3 = app3View.findViewById(R.id.app_icon); - final TextView appTitle3 = app3View.findViewById(R.id.app_title); - - assertThat(app3View.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(appIconView3.getDrawable()).isNotNull(); - assertThat(appTitle3.getText()).isEqualTo("appTitle2"); - } - - private List createMockAccesses(int count) { - final List accesses = new ArrayList<>(); - for (int i = 0; i < count; i++) { - final Drawable icon = mock(Drawable.class); - // Add mock accesses - final RecentLocationAccesses.Access access = new RecentLocationAccesses.Access( - "packageName", android.os.Process.myUserHandle(), icon, - "appTitle" + i, "appSummary" + i, 1000 - i); - accesses.add(access); - } - return accesses; - } } diff --git a/tests/robotests/src/com/android/settings/privacy/AccessibilityUsagePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/privacy/AccessibilityUsagePreferenceControllerTest.java index 33109b0638d..d75bf4bf955 100644 --- a/tests/robotests/src/com/android/settings/privacy/AccessibilityUsagePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/privacy/AccessibilityUsagePreferenceControllerTest.java @@ -65,8 +65,6 @@ public class AccessibilityUsagePreferenceControllerTest { @Test public void getAvailabilityStatus_noEnabledServices_shouldReturnUnsupported() { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, - Utils.PROPERTY_PERMISSIONS_HUB_ENABLED, "true", true); mAccessibilityManager.setEnabledAccessibilityServiceList(new ArrayList<>()); AccessibilityUsagePreferenceController controller = new AccessibilityUsagePreferenceController(mContext, "test_key"); @@ -76,8 +74,6 @@ public class AccessibilityUsagePreferenceControllerTest { @Test public void getAvailabilityStatus_enabledServices_shouldReturnAvailable() { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, - Utils.PROPERTY_PERMISSIONS_HUB_ENABLED, "false", true); mAccessibilityManager.setEnabledAccessibilityServiceList( new ArrayList<>(Arrays.asList(new AccessibilityServiceInfo()))); AccessibilityUsagePreferenceController controller = diff --git a/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java index dc824ed04f7..1335db5c5ed 100644 --- a/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java @@ -117,144 +117,4 @@ public class PermissionBarChartPreferenceControllerTest { // We have not yet set the property to show the Permissions Hub. assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); } - - @Test - public void getAvailabilityStatus_permissionHubEnabled_shouldReturnAvailableUnsearchable() { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, - Utils.PROPERTY_PERMISSIONS_HUB_ENABLED, - "true", true); - - assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE); - } - - @Test - public void displayPreference_shouldInitializeBarChart() { - mController.displayPreference(mScreen); - - verify(mPreference).initializeBarChart(any(BarChartInfo.class)); - } - - @Test - public void displayPreference_usageInfosSet_shouldSetBarViewInfos() { - final RuntimePermissionUsageInfo info1 = - new RuntimePermissionUsageInfo("permission 1", 10); - mController.mOldUsageInfos.add(info1); - - mController.displayPreference(mScreen); - - verify(mPreference).setBarViewInfos(any(BarViewInfo[].class)); - verify(mPreference).initializeBarChart(any(BarChartInfo.class)); - } - - @Test - public void onPermissionUsageResult_differentPermissionResultSet_shouldSetBarViewInfos() { - final List infos1 = new ArrayList<>(); - final RuntimePermissionUsageInfo info1 = - new RuntimePermissionUsageInfo("permission 1", 10); - infos1.add(info1); - mController.displayPreference(mScreen); - mController.onPermissionUsageResult(infos1); - - verify(mPreference).setBarViewInfos(any(BarViewInfo[].class)); - - final List infos2 = new ArrayList<>(); - final RuntimePermissionUsageInfo info2 = - new RuntimePermissionUsageInfo("permission 2", 20); - infos2.add(info2); - mController.onPermissionUsageResult(infos2); - - verify(mPreference, times(2)).setBarViewInfos(any(BarViewInfo[].class)); - } - - @Test - public void onPermissionUsageResult_samePermissionResultSet_shouldNotSetBarViewInfos() { - final List mInfos = new ArrayList<>(); - final RuntimePermissionUsageInfo info1 = - new RuntimePermissionUsageInfo("permission 1", 10); - mInfos.add(info1); - mController.displayPreference(mScreen); - mController.onPermissionUsageResult(mInfos); - - mController.onPermissionUsageResult(mInfos); - - verify(mPreference, times(1)).setBarViewInfos(any(BarViewInfo[].class)); - } - - @Test - public void onStart_usageInfosNotSetAndPermissionHubEnabled_shouldShowProgressBar() { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, - Utils.PROPERTY_PERMISSIONS_HUB_ENABLED, - "true", true); - mController.displayPreference(mScreen); - - mController.onStart(); - - verify(mFragment).setLoadingEnabled(true /* enabled */); - verify(mPreference).updateLoadingState(true /* isLoading */); - } - - @Test - public void onStart_usageInfosSetAndPermissionHubEnabled_shouldNotUpdatePrefLoadingState() { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, - Utils.PROPERTY_PERMISSIONS_HUB_ENABLED, - "true", true); - final RuntimePermissionUsageInfo info1 = - new RuntimePermissionUsageInfo("permission 1", 10); - mController.mOldUsageInfos.add(info1); - mController.displayPreference(mScreen); - - mController.onStart(); - - verify(mFragment).setLoadingEnabled(true /* enabled */); - verify(mPreference).updateLoadingState(false /* isLoading */); - } - - @Test - public void onStart_permissionHubDisabled_shouldNotShowProgressBar() { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, - Utils.PROPERTY_PERMISSIONS_HUB_ENABLED, - "false", true); - - mController.onStart(); - - verify(mFragment, never()).setLoadingEnabled(true /* enabled */); - verify(mPreference, never()).updateLoadingState(true /* isLoading */); - } - - @Test - public void onPermissionUsageResult_shouldHideProgressBar() { - final List infos1 = new ArrayList<>(); - final RuntimePermissionUsageInfo info1 = - new RuntimePermissionUsageInfo("permission 1", 10); - infos1.add(info1); - mController.displayPreference(mScreen); - - mController.onPermissionUsageResult(infos1); - - verify(mFragment).setLoadingEnabled(false /* enabled */); - verify(mPreference).updateLoadingState(false /* isLoading */); - } - - @Test - public void onPermissionUsageResult_shouldBeSorted() { - final List infos = new ArrayList<>(); - infos.add(new RuntimePermissionUsageInfo(PHONE, 10)); - infos.add(new RuntimePermissionUsageInfo(LOCATION, 10)); - infos.add(new RuntimePermissionUsageInfo(CAMERA, 10)); - infos.add(new RuntimePermissionUsageInfo(SMS, 1)); - infos.add(new RuntimePermissionUsageInfo(MICROPHONE, 10)); - infos.add(new RuntimePermissionUsageInfo(CONTACTS, 42)); - infos.add(new RuntimePermissionUsageInfo(CALENDAR, 10)); - mController.displayPreference(mScreen); - - mController.onPermissionUsageResult(infos); - - assertThat(infos.get(0).getName()).isEqualTo(CONTACTS); - assertThat(infos.get(1).getName()).isEqualTo(LOCATION); - assertThat(infos.get(2).getName()).isEqualTo(MICROPHONE); - assertThat(infos.get(3).getName()).isEqualTo(CAMERA); - assertThat(infos.get(4).getName()).isEqualTo(CALENDAR); - assertThat(infos.get(5).getName()).isEqualTo(PHONE); - assertThat(infos.get(6).getName()).isEqualTo(SMS); - } } From 5ef0a1d11d2f1cb99a6880d4eeb4f714a1b0bfeb Mon Sep 17 00:00:00 2001 From: joshmccloskey Date: Tue, 28 May 2019 20:19:46 -0700 Subject: [PATCH 16/26] String update. Test: Manual. Bug: 133783098 Change-Id: If8ea2b6c9372a3da02b93b815f76e3143cc54dea --- res/values/strings.xml | 22 +++++++++---------- ...tingsRemoveButtonPreferenceController.java | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 2cdc2d6425c..9862387beb9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -885,7 +885,7 @@ Face added - Tap to set up face unlock + Set up face unlock Face unlock @@ -955,25 +955,25 @@ App sign-in \u0026 payments - Require for face unlock + Requirements for face unlock - Open eyes looking at screen + Require eyes to be open - To unlock the phone, always require looking at the screen with your eyes open + To unlock the phone, your eyes must be open - Confirm button + Always require confirmation - When authenticating for apps, always require confirmation + When using face unlock in apps, always require confirmation step Delete face data - Set up new face unlock + Set up face unlock Use face unlock to unlock your device, sign in to apps, and confirm payments.\n\nKeep in mind:\nLooking at the phone can unlock it when you don\u2019t intend to.\n\nYour phone can be unlocked by someone else if it\u2019s held up to your face while your eyes are open.\n\nYour phone can be unlocked by someone who looks a lot like you, say, an identical sibling. Delete face data? - Data recorded by face unlock will be permanently and securely deleted. After removal, you will need your PIN, pattern, or password to unlock your phone, sign in to apps, and confirm payments. + The images and biometric data used by face unlock will be permanently and securely deleted. After removal, you will need your PIN, pattern, or password to unlock your phone, sign in to apps, and confirm payments. @@ -1075,11 +1075,11 @@ Protect your phone with a screen lock option so no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up fingerprint. Tap Cancel, then set a PIN, pattern, or password. - Protect your tablet with a screen lock option so no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. Tap Cancel, then set a PIN, pattern, or password. + By protecting your tablet with a screen lock option, no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. To go back, tap Cancel. - Protect your device with a screen lock option so no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. Tap Cancel, then set a PIN, pattern, or password. + By protecting your device with a screen lock option, no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. To go back, tap Cancel. - Protect your phone with a screen lock option so no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. Tap Cancel, then set a PIN, pattern, or password. + By protecting your phone with a screen lock option, no one will be able to use it if it is lost or stolen. You also need a screen lock option to set up face unlock. To go back, tap Cancel. Skip PIN setup? diff --git a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java index d532a7636f4..1c1b81df6c8 100644 --- a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java +++ b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java @@ -64,7 +64,7 @@ public class FaceSettingsRemoveButtonPreferenceController extends BasePreference builder.setTitle(R.string.security_settings_face_settings_remove_dialog_title) .setMessage(R.string.security_settings_face_settings_remove_dialog_details) - .setPositiveButton(R.string.okay, mOnClickListener) + .setPositiveButton(R.string.delete, mOnClickListener) .setNegativeButton(R.string.cancel, mOnClickListener); AlertDialog dialog = builder.create(); dialog.setCanceledOnTouchOutside(false); From eeefeebeffc9a3b2957200cf57c104089978cd34 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Thu, 30 May 2019 14:47:07 -0400 Subject: [PATCH 17/26] Change notification alert icon Test: manual Bug: 134054170 Change-Id: Ibd66a55a30ac9fbeb7a32638fe236067c6175bc4 --- res/drawable/ic_notifications_alert.xml | 24 +++++++++++++++++++ res/layout/notif_importance_preference.xml | 2 +- .../NotificationSettingsBase.java | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 res/drawable/ic_notifications_alert.xml diff --git a/res/drawable/ic_notifications_alert.xml b/res/drawable/ic_notifications_alert.xml new file mode 100644 index 00000000000..5a25459e353 --- /dev/null +++ b/res/drawable/ic_notifications_alert.xml @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/res/layout/notif_importance_preference.xml b/res/layout/notif_importance_preference.xml index 395e04908e8..29c337a699e 100644 --- a/res/layout/notif_importance_preference.xml +++ b/res/layout/notif_importance_preference.xml @@ -33,7 +33,7 @@ android:focusable="true"> Date: Thu, 30 May 2019 16:52:08 -0400 Subject: [PATCH 18/26] Reorder notification settings Test: manual Fixes: 132271798 Change-Id: I57e726a332513620bea25b97345b1a8564c6d5ef --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index fc3888b8df7..f7465fec324 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2507,7 +2507,7 @@ - + Date: Thu, 30 May 2019 14:16:52 -0700 Subject: [PATCH 19/26] Change switch text color to black in dark mode It seems that the attribute values that are used to text color in the switch bar are selectors which leads to problems because they include an alpha value when the switch is "off". This would cause the contrast between the text and the background to be too low for GAR. This CL simply makes it so that in dark mode the text for the switch bar is just true black since adjusting the attributes would have wide scale implications for all UIs using device default themes. Test: visual inspection Bug: 132755745 Change-Id: I309db82e9bf020fb654e0b93f6ec84380ae092f7 --- res/values-night/themes.xml | 4 ++++ res/values/themes.xml | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/res/values-night/themes.xml b/res/values-night/themes.xml index f47513e7b6f..ec0978b3391 100644 --- a/res/values-night/themes.xml +++ b/res/values-night/themes.xml @@ -31,6 +31,10 @@ + - +