diff --git a/res/xml/configure_notification_settings_v2.xml b/res/xml/configure_notification_settings_v2.xml index 6b1e69190e5..a0d2dabdc84 100644 --- a/res/xml/configure_notification_settings_v2.xml +++ b/res/xml/configure_notification_settings_v2.xml @@ -42,6 +42,15 @@ android:key="recent_notifications_category" android:title="@string/recent_notifications"> + + + = 0 && mLevels[trapezoidIndex] == 0)) { + return; + } // Selects all if users click the same trapezoid item two times. if (trapezoidIndex == mSelectedIndex) { setSelectedIndex(SELECTED_INDEX_ALL); diff --git a/src/com/android/settings/fuelgauge/ConvertUtils.java b/src/com/android/settings/fuelgauge/ConvertUtils.java index 2e8726e38ab..00dcb6be62b 100644 --- a/src/com/android/settings/fuelgauge/ConvertUtils.java +++ b/src/com/android/settings/fuelgauge/ConvertUtils.java @@ -70,8 +70,12 @@ public final class ConvertUtils { public static final int CONSUMER_TYPE_SYSTEM_BATTERY = 3; private static String sZoneId; - private static SimpleDateFormat sSimpleDateFormat; - private static SimpleDateFormat sSimpleDateFormatForHour; + private static String sZoneIdForHour; + + @VisibleForTesting + static SimpleDateFormat sSimpleDateFormat; + @VisibleForTesting + static SimpleDateFormat sSimpleDateFormatForHour; private ConvertUtils() {} @@ -140,25 +144,19 @@ public final class ConvertUtils { sZoneId = currentZoneId; sSimpleDateFormat = new SimpleDateFormat("MMM dd,yyyy HH:mm:ss", Locale.ENGLISH); - sSimpleDateFormatForHour = null; } return sSimpleDateFormat.format(new Date(timestamp)); } /** Converts UTC timestamp to local time hour data. */ - public static int utcToLocalTimeHour(long timestamp) { + public static String utcToLocalTimeHour(long timestamp) { final String currentZoneId = TimeZone.getDefault().getID(); - if (!currentZoneId.equals(sZoneId) || sSimpleDateFormatForHour == null) { - sZoneId = currentZoneId; - sSimpleDateFormat = null; - sSimpleDateFormatForHour = new SimpleDateFormat("HH", Locale.ENGLISH); - } - try { - return Integer.parseInt( - sSimpleDateFormatForHour.format(new Date(timestamp))); - } catch (NumberFormatException e) { - return Integer.MIN_VALUE; + if (!currentZoneId.equals(sZoneIdForHour) || sSimpleDateFormatForHour == null) { + sZoneIdForHour = currentZoneId; + sSimpleDateFormatForHour = new SimpleDateFormat("h aa", Locale.ENGLISH); } + return sSimpleDateFormatForHour.format(new Date(timestamp)) + .toLowerCase(Locale.getDefault()); } /** Gets indexed battery usage data for each corresponding time slot. */ diff --git a/src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java b/src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java index 5396be5ba5e..c71a26aaede 100644 --- a/src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java +++ b/src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java @@ -49,6 +49,7 @@ import com.android.settings.widget.PrimarySwitchPreference; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.utils.StringUtil; +import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.widget.TwoTargetPreference; import java.util.ArrayList; @@ -70,9 +71,9 @@ public class RecentNotifyingAppsPreferenceController extends AbstractPreferenceC @VisibleForTesting static final String KEY_SEE_ALL = "all_notifications"; + static final String KEY_PLACEHOLDER = "app"; private static final int SHOW_RECENT_APP_COUNT = 3; private static final int DAYS = 3; - private static final Set SKIP_SYSTEM_PACKAGES = new ArraySet<>(); private final Fragment mHost; private final PackageManager mPm; @@ -148,13 +149,17 @@ public class RecentNotifyingAppsPreferenceController extends AbstractPreferenceC @VisibleForTesting void refreshUi(Context prefContext) { - reloadData(); - final List recentApps = getDisplayableRecentAppList(); - if (recentApps != null && !recentApps.isEmpty()) { - displayRecentApps(prefContext, recentApps); - } else { - displayOnlyAllAppsLink(); - } + ThreadUtils.postOnBackgroundThread(() -> { + reloadData(); + final List recentApps = getDisplayableRecentAppList(); + ThreadUtils.postOnMainThread(() -> { + if (recentApps != null && !recentApps.isEmpty()) { + displayRecentApps(prefContext, recentApps); + } else { + displayOnlyAllAppsLink(); + } + }); + }); } @VisibleForTesting @@ -198,8 +203,7 @@ public class RecentNotifyingAppsPreferenceController extends AbstractPreferenceC } } - @VisibleForTesting - static String getKey(int userId, String pkg) { + private static String getKey(int userId, String pkg) { return userId + "|" + pkg; } @@ -221,19 +225,9 @@ public class RecentNotifyingAppsPreferenceController extends AbstractPreferenceC mSeeAllPref.setSummary(null); mSeeAllPref.setIcon(R.drawable.ic_chevron_right_24dp); - // Rebind prefs/avoid adding new prefs if possible. Adding/removing prefs causes jank. - // Build a cached preference pool - final Map appPreferences = new ArrayMap<>(); - int prefCount = mCategory.getPreferenceCount(); - for (int i = 0; i < prefCount; i++) { - final Preference pref = mCategory.getPreference(i); - final String key = pref.getKey(); - if (!TextUtils.equals(key, KEY_SEE_ALL)) { - appPreferences.put(key, (PrimarySwitchPreference) pref); - } - } + int keyIndex = 1; final int recentAppsCount = recentApps.size(); - for (int i = 0; i < recentAppsCount; i++) { + for (int i = 0; i < recentAppsCount; i++, keyIndex++) { final NotifyingApp app = recentApps.get(i); // Bind recent apps to existing prefs if possible, or create a new pref. final String pkgName = app.getPackage(); @@ -243,20 +237,12 @@ public class RecentNotifyingAppsPreferenceController extends AbstractPreferenceC continue; } - boolean rebindPref = true; - PrimarySwitchPreference pref = appPreferences.remove(getKey(app.getUserId(), - pkgName)); - if (pref == null) { - pref = new PrimarySwitchPreference(prefContext); - rebindPref = false; - } - pref.setKey(getKey(app.getUserId(), pkgName)); + PrimarySwitchPreference pref = mCategory.findPreference(KEY_PLACEHOLDER + keyIndex); pref.setTitle(appEntry.label); pref.setIcon(mIconDrawableFactory.getBadgedIcon(appEntry.info)); pref.setIconSize(TwoTargetPreference.ICON_SIZE_SMALL); pref.setSummary(StringUtil.formatRelativeTime(mContext, System.currentTimeMillis() - app.getLastNotified(), true)); - pref.setOrder(i); Bundle args = new Bundle(); args.putString(AppInfoBase.ARG_PACKAGE_NAME, pkgName); args.putInt(AppInfoBase.ARG_PACKAGE_UID, appEntry.info.uid); @@ -280,13 +266,10 @@ public class RecentNotifyingAppsPreferenceController extends AbstractPreferenceC pref.setChecked( !mNotificationBackend.getNotificationsBanned(pkgName, appEntry.info.uid)); - if (!rebindPref) { - mCategory.addPreference(pref); - } } - // Remove unused prefs from pref cache pool - for (Preference unusedPrefs : appPreferences.values()) { - mCategory.removePreference(unusedPrefs); + // If there are less than SHOW_RECENT_APP_COUNT recent apps, remove placeholders + for (int i = keyIndex; i <= SHOW_RECENT_APP_COUNT; i++) { + mCategory.removePreferenceRecursively(KEY_PLACEHOLDER + i); } } diff --git a/tests/robotests/src/com/android/settings/display/BrightnessLevelPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/BrightnessLevelPreferenceControllerTest.java index 15d4c973baf..8fd847a8073 100644 --- a/tests/robotests/src/com/android/settings/display/BrightnessLevelPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/BrightnessLevelPreferenceControllerTest.java @@ -22,15 +22,16 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.Context; +import android.hardware.display.BrightnessInfo; import android.os.PowerManager; import android.provider.Settings.System; +import android.view.Display; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; @@ -52,6 +53,8 @@ public class BrightnessLevelPreferenceControllerTest { @Mock private PowerManager mPowerManager; @Mock + private Display mDisplay; + @Mock private PreferenceScreen mScreen; @Mock private Preference mPreference; @@ -65,7 +68,7 @@ public class BrightnessLevelPreferenceControllerTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; + mContext = spy(RuntimeEnvironment.application); mContentResolver = mContext.getContentResolver(); when(mPowerManager.getBrightnessConstraint( PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM)).thenReturn(0.0f); @@ -78,6 +81,7 @@ public class BrightnessLevelPreferenceControllerTest { ShadowApplication.getInstance().setSystemService(POWER_SERVICE, mPowerManager); when(mScreen.findPreference(anyString())).thenReturn(mPreference); + when(mContext.getDisplay()).thenReturn(mDisplay); mController = spy(new BrightnessLevelPreferenceController(mContext, null)); doReturn(false).when(mController).isInVrMode(); } @@ -101,8 +105,6 @@ public class BrightnessLevelPreferenceControllerTest { controller.onStart(); - assertThat(shadowContentResolver.getContentObservers( - System.getUriFor(System.SCREEN_BRIGHTNESS_FLOAT))).isNotEmpty(); assertThat(shadowContentResolver.getContentObservers( System.getUriFor(System.SCREEN_BRIGHTNESS_FOR_VR))).isNotEmpty(); assertThat(shadowContentResolver.getContentObservers( @@ -119,8 +121,6 @@ public class BrightnessLevelPreferenceControllerTest { controller.onStart(); controller.onStop(); - assertThat(shadowContentResolver.getContentObservers( - System.getUriFor(System.SCREEN_BRIGHTNESS_FLOAT))).isEmpty(); assertThat(shadowContentResolver.getContentObservers( System.getUriFor(System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT))).isEmpty(); assertThat(shadowContentResolver.getContentObservers( @@ -143,7 +143,8 @@ public class BrightnessLevelPreferenceControllerTest { System.putInt(mContentResolver, System.SCREEN_BRIGHTNESS_MODE, System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); - System.putFloat(mContentResolver, System.SCREEN_BRIGHTNESS_FLOAT, 0.1f); + when(mDisplay.getBrightnessInfo()).thenReturn( + new BrightnessInfo(0.1f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF)); mController.updateState(mPreference); @@ -156,7 +157,8 @@ public class BrightnessLevelPreferenceControllerTest { System.putInt(mContentResolver, System.SCREEN_BRIGHTNESS_MODE, System.SCREEN_BRIGHTNESS_MODE_MANUAL); - System.putFloat(mContentResolver, System.SCREEN_BRIGHTNESS_FLOAT, 0.5f); + when(mDisplay.getBrightnessInfo()).thenReturn( + new BrightnessInfo(0.5f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF)); mController.updateState(mPreference); @@ -175,32 +177,5 @@ public class BrightnessLevelPreferenceControllerTest { System.putFloat(mContentResolver, System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT, -20f); mController.updateState(mPreference); verify(mPreference).setSummary("0%"); - - // Auto mode - doReturn(false).when(mController).isInVrMode(); - System.putInt(mContentResolver, System.SCREEN_BRIGHTNESS_MODE, - System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); - - reset(mPreference); - System.putFloat(mContentResolver, System.SCREEN_BRIGHTNESS_FLOAT, 1.15f); - mController.updateState(mPreference); - verify(mPreference).setSummary("100%"); - - System.putFloat(mContentResolver, System.SCREEN_BRIGHTNESS_FLOAT, -10f); - mController.updateState(mPreference); - verify(mPreference).setSummary("0%"); - - // Manual mode - System.putInt(mContentResolver, System.SCREEN_BRIGHTNESS_MODE, - System.SCREEN_BRIGHTNESS_MODE_MANUAL); - - reset(mPreference); - System.putFloat(mContentResolver, System.SCREEN_BRIGHTNESS_FLOAT, 1.15f); - mController.updateState(mPreference); - verify(mPreference).setSummary("100%"); - - System.putFloat(mContentResolver, System.SCREEN_BRIGHTNESS_FLOAT, -10f); - mController.updateState(mPreference); - verify(mPreference).setSummary("0%"); } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java index e95b158391b..12913a29dc8 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java @@ -39,10 +39,12 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.TimeZone; @@ -254,6 +256,32 @@ public final class ConvertUtilsTest { assertBatteryDiffEntry(entryList.get(0), 68, 40L, 50L); } + @Test + public void testUtcToLocalTime_returnExpectedResult() { + final long timestamp = 1619196786769L; + ConvertUtils.sSimpleDateFormat = null; + // Invokes the method first to create the SimpleDateFormat. + ConvertUtils.utcToLocalTime(/*timestamp=*/ 0); + ConvertUtils.sSimpleDateFormat + .setTimeZone(TimeZone.getTimeZone("GMT")); + + assertThat(ConvertUtils.utcToLocalTime(timestamp)) + .isEqualTo("Apr 23,2021 16:53:06"); + } + + @Test + public void testUtcToLocalTmeHour_returnExpectedResult() { + final long timestamp = 1619196786769L; + ConvertUtils.sSimpleDateFormatForHour = null; + // Invokes the method first to create the SimpleDateFormat. + ConvertUtils.utcToLocalTimeHour(/*timestamp=*/ 0); + ConvertUtils.sSimpleDateFormatForHour + .setTimeZone(TimeZone.getTimeZone("GMT")); + + assertThat(ConvertUtils.utcToLocalTimeHour(timestamp)) + .isEqualTo("4 pm"); + } + private static BatteryHistEntry createBatteryHistEntry( String packageName, String appLabel, double consumePower, long uid, long foregroundUsageTimeInMs, long backgroundUsageTimeInMs) { diff --git a/tests/robotests/src/com/android/settings/notification/RecentNotifyingAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/RecentNotifyingAppsPreferenceControllerTest.java index 6226b9a318b..983bf535bdd 100644 --- a/tests/robotests/src/com/android/settings/notification/RecentNotifyingAppsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/RecentNotifyingAppsPreferenceControllerTest.java @@ -48,6 +48,7 @@ import android.service.notification.NotifyingApp; import android.text.TextUtils; import com.android.settings.R; +import com.android.settings.widget.PrimarySwitchPreference; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.instantapps.InstantAppDataProvider; @@ -80,6 +81,9 @@ public class RecentNotifyingAppsPreferenceControllerTest { private PreferenceScreen mScreen; @Mock private PreferenceCategory mCategory; + private PrimarySwitchPreference mApp1; + private PrimarySwitchPreference mApp2; + private PrimarySwitchPreference mApp3; @Mock private Preference mSeeAllPref; @Mock @@ -115,6 +119,15 @@ public class RecentNotifyingAppsPreferenceControllerTest { mController = new RecentNotifyingAppsPreferenceController( mContext, mBackend, mIUsageStatsManager, mUserManager, mAppState, mHost); when(mScreen.findPreference(anyString())).thenReturn(mCategory); + mApp1 = new PrimarySwitchPreference(mContext); + mApp1.setKey("app1"); + mApp2 = new PrimarySwitchPreference(mContext); + mApp2.setKey("app2"); + mApp3 = new PrimarySwitchPreference(mContext); + mApp3.setKey("app3"); + when(mCategory.findPreference("app1")).thenReturn(mApp1); + when(mCategory.findPreference("app2")).thenReturn(mApp2); + when(mCategory.findPreference("app3")).thenReturn(mApp3); when(mScreen.findPreference(RecentNotifyingAppsPreferenceController.KEY_SEE_ALL)) .thenReturn(mSeeAllPref); @@ -169,12 +182,18 @@ public class RecentNotifyingAppsPreferenceControllerTest { app2.mPackage = "pkg.class2"; app2.mTimeStamp = System.currentTimeMillis() - 1000; events.add(app2); + ApplicationsState.AppEntry app1Entry = mock(ApplicationsState.AppEntry.class); + ApplicationsState.AppEntry app2Entry = mock(ApplicationsState.AppEntry.class); + app1Entry.info = mApplicationInfo; + app1Entry.label = "app 1"; + app2Entry.info = mApplicationInfo; + app2Entry.label = "app 2"; // app1, app2 are valid apps. app3 is invalid. when(mAppState.getEntry(app.getPackageName(), UserHandle.myUserId())) - .thenReturn(mAppEntry); + .thenReturn(app1Entry); when(mAppState.getEntry(app1.getPackageName(), UserHandle.myUserId())) - .thenReturn(mAppEntry); + .thenReturn(app2Entry); when(mAppState.getEntry(app2.getPackageName(), UserHandle.myUserId())) .thenReturn(null); when(mPackageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn( @@ -192,7 +211,10 @@ public class RecentNotifyingAppsPreferenceControllerTest { verify(mCategory).setTitle(R.string.recent_notifications); // Only add app1 & app2. app3 skipped because it's invalid app. - verify(mCategory, times(2)).addPreference(any(Preference.class)); + assertThat(mApp1.getTitle()).isEqualTo(app1Entry.label); + assertThat(mApp2.getTitle()).isEqualTo(app2Entry.label); + + verify(mCategory).removePreferenceRecursively(mApp3.getKey()); verify(mSeeAllPref).setSummary(null); verify(mSeeAllPref).setIcon(R.drawable.ic_chevron_right_24dp); @@ -209,22 +231,24 @@ public class RecentNotifyingAppsPreferenceControllerTest { Event app1 = new Event(); app1.mEventType = Event.NOTIFICATION_INTERRUPTION; app1.mPackage = "com.foo.barinstant"; - app1.mTimeStamp = System.currentTimeMillis() + 200; + app1.mTimeStamp = System.currentTimeMillis() - 200; events.add(app1); UsageEvents usageEvents = getUsageEvents( new String[] {"com.foo.bar", "com.foo.barinstant"}, events); when(mIUsageStatsManager.queryEventsForUser(anyLong(), anyLong(), anyInt(), anyString())) .thenReturn(usageEvents); + ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class); ApplicationsState.AppEntry app1Entry = mock(ApplicationsState.AppEntry.class); - ApplicationsState.AppEntry app2Entry = mock(ApplicationsState.AppEntry.class); + appEntry.info = mApplicationInfo; + appEntry.label = "app 1"; app1Entry.info = mApplicationInfo; - app2Entry.info = mApplicationInfo; + app1Entry.label = "app 2"; when(mAppState.getEntry( - app.getPackageName(), UserHandle.myUserId())).thenReturn(app1Entry); + app.getPackageName(), UserHandle.myUserId())).thenReturn(appEntry); when(mAppState.getEntry( - app1.getPackageName(), UserHandle.myUserId())).thenReturn(app2Entry); + app1.getPackageName(), UserHandle.myUserId())).thenReturn(app1Entry); // Only the regular app app1 should have its intent resolve. when(mPackageManager.resolveActivity(argThat(intentMatcher(app.getPackageName())), @@ -233,7 +257,7 @@ public class RecentNotifyingAppsPreferenceControllerTest { // Make sure app2 is considered an instant app. ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", (InstantAppDataProvider) (ApplicationInfo info) -> { - if (info == app2Entry.info) { + if (info == app1Entry.info) { return true; } else { return false; @@ -242,15 +266,10 @@ public class RecentNotifyingAppsPreferenceControllerTest { mController.displayPreference(mScreen); - ArgumentCaptor prefCaptor = ArgumentCaptor.forClass(Preference.class); - verify(mCategory, times(2)).addPreference(prefCaptor.capture()); - List prefs = prefCaptor.getAllValues(); - assertThat(prefs.get(1).getKey()).isEqualTo( - RecentNotifyingAppsPreferenceController.getKey(UserHandle.myUserId(), - app.getPackageName())); - assertThat(prefs.get(0).getKey()).isEqualTo( - RecentNotifyingAppsPreferenceController.getKey(UserHandle.myUserId(), - app1.getPackageName())); + assertThat(mApp1.getTitle()).isEqualTo(appEntry.label); + assertThat(mApp2.getTitle()).isEqualTo(app1Entry.label); + + verify(mCategory).removePreferenceRecursively(mApp3.getKey()); } @Test @@ -274,7 +293,7 @@ public class RecentNotifyingAppsPreferenceControllerTest { mController.displayPreference(mScreen); - verify(mCategory).addPreference(argThat(summaryMatches("Just now"))); + assertThat(mApp1.getSummary()).isEqualTo("Just now"); } @Test