diff --git a/src/com/android/settings/applications/AllAppsInfoPreferenceController.java b/src/com/android/settings/applications/AllAppsInfoPreferenceController.java index d39c6e90b8c..325b25a3618 100644 --- a/src/com/android/settings/applications/AllAppsInfoPreferenceController.java +++ b/src/com/android/settings/applications/AllAppsInfoPreferenceController.java @@ -19,39 +19,54 @@ package com.android.settings.applications; import android.app.usage.UsageStats; import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import java.util.List; -public class AllAppsInfoPreferenceController extends BasePreferenceController { +public class AllAppsInfoPreferenceController extends BasePreferenceController + implements RecentAppStatsMixin.RecentAppStatsListener { - private List mRecentApps; + @VisibleForTesting + Preference mPreference; public AllAppsInfoPreferenceController(Context context, String key) { super(context, key); } - public void setRecentApps(List recentApps) { - mRecentApps = recentApps; - } - @Override public int getAvailabilityStatus() { - return mRecentApps == null || mRecentApps.isEmpty() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + return AVAILABLE; } @Override - public void updateState(Preference preference) { - super.updateState(preference); + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + // In most cases, device has recently opened apps. So, we hide it by default. + mPreference.setVisible(false); + } + + @Override + public void onReloadDataCompleted(@NonNull List recentApps) { + // If device has recently opened apps, we don't show all apps preference. + if (!recentApps.isEmpty()) { + mPreference.setVisible(false); + return; + } + + mPreference.setVisible(true); // Show total number of installed apps as See all's summary. new InstalledAppCounter(mContext, InstalledAppCounter.IGNORE_INSTALL_REASON, mContext.getPackageManager()) { @Override protected void onCountComplete(int num) { - preference.setSummary(mContext.getString(R.string.apps_summary, num)); + mPreference.setSummary(mContext.getString(R.string.apps_summary, num)); } }.execute(); } diff --git a/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java b/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java index c32a33dc5a0..5e57908035f 100644 --- a/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java +++ b/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java @@ -17,8 +17,13 @@ package com.android.settings.applications; import android.app.settings.SettingsEnums; +import android.app.usage.UsageStats; import android.content.Context; +import android.os.Bundle; import android.provider.SearchIndexableResource; +import android.view.View; + +import androidx.annotation.NonNull; import com.android.settings.R; import com.android.settings.Utils; @@ -27,17 +32,21 @@ import com.android.settings.notification.EmergencyBroadcastPreferenceController; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.search.SearchIndexable; +import com.android.settingslib.widget.AppEntitiesHeaderController; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @SearchIndexable -public class AppAndNotificationDashboardFragment extends DashboardFragment { +public class AppAndNotificationDashboardFragment extends DashboardFragment + implements RecentAppStatsMixin.RecentAppStatsListener { private static final String TAG = "AppAndNotifDashboard"; - private boolean mIsFirstLaunch; + private View mProgressHeader; + private View mProgressAnimation; + private RecentAppStatsMixin mRecentAppStatsMixin; private RecentAppsPreferenceController mRecentAppsPreferenceController; private AllAppsInfoPreferenceController mAllAppsInfoPreferenceController; @@ -66,29 +75,40 @@ public class AppAndNotificationDashboardFragment extends DashboardFragment { super.onAttach(context); use(SpecialAppAccessPreferenceController.class).setSession(getSettingsLifecycle()); + + mRecentAppStatsMixin = new RecentAppStatsMixin(context, + AppEntitiesHeaderController.MAXIMUM_APPS); + getSettingsLifecycle().addObserver(mRecentAppStatsMixin); + mRecentAppStatsMixin.addListener(this); + mRecentAppsPreferenceController = use(RecentAppsPreferenceController.class); mRecentAppsPreferenceController.setFragment(this /* fragment */); + mRecentAppStatsMixin.addListener(mRecentAppsPreferenceController); mAllAppsInfoPreferenceController = use(AllAppsInfoPreferenceController.class); - mAllAppsInfoPreferenceController.setRecentApps( - mRecentAppsPreferenceController.getRecentApps()); - - mIsFirstLaunch = true; + mRecentAppStatsMixin.addListener(mAllAppsInfoPreferenceController); } @Override - public void onResume() { - if (!mIsFirstLaunch) { - mRecentAppsPreferenceController.reloadData(); - mAllAppsInfoPreferenceController.setRecentApps( - mRecentAppsPreferenceController.getRecentApps()); - } + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mProgressHeader = setPinnedHeaderView(R.layout.progress_header); + mProgressAnimation = mProgressHeader.findViewById(R.id.progress_bar_animation); + setLoadingEnabled(false); + } - super.onResume(); - mIsFirstLaunch = false; + @Override + public void onStart() { + super.onStart(); + setLoadingEnabled(true); + } - if (mRecentAppsPreferenceController.isAvailable()) { - Utils.setActionBarShadowAnimation(getActivity(), getSettingsLifecycle(), getListView()); + @Override + public void onReloadDataCompleted(@NonNull List recentApps) { + setLoadingEnabled(false); + if (!recentApps.isEmpty()) { + Utils.setActionBarShadowAnimation(getActivity(), getSettingsLifecycle(), + getListView()); } } @@ -97,6 +117,13 @@ public class AppAndNotificationDashboardFragment extends DashboardFragment { return buildPreferenceControllers(context); } + private void setLoadingEnabled(boolean enabled) { + if (mProgressHeader != null && mProgressAnimation != null) { + mProgressHeader.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE); + mProgressAnimation.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE); + } + } + private static List buildPreferenceControllers(Context context) { final List controllers = new ArrayList<>(); controllers.add(new EmergencyBroadcastPreferenceController(context, diff --git a/src/com/android/settings/applications/RecentAppStatsMixin.java b/src/com/android/settings/applications/RecentAppStatsMixin.java new file mode 100644 index 00000000000..4bf38642624 --- /dev/null +++ b/src/com/android/settings/applications/RecentAppStatsMixin.java @@ -0,0 +1,194 @@ +/* + * 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.applications; + +import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; + +import android.app.Application; +import android.app.usage.UsageStats; +import android.app.usage.UsageStatsManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.PowerManager; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import com.android.settingslib.applications.AppUtils; +import com.android.settingslib.applications.ApplicationsState; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.utils.ThreadUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +public class RecentAppStatsMixin implements Comparator, LifecycleObserver, OnStart { + + private static final String TAG = "RecentAppStatsMixin"; + private static final Set SKIP_SYSTEM_PACKAGES = new ArraySet<>(); + + @VisibleForTesting + final List mRecentApps; + private final int mUserId; + private final int mMaximumApps; + private final Context mContext; + private final PackageManager mPm; + private final PowerManager mPowerManager;; + private final UsageStatsManager mUsageStatsManager; + private final ApplicationsState mApplicationsState; + private final List mAppStatsListeners; + private Calendar mCalendar; + + static { + SKIP_SYSTEM_PACKAGES.addAll(Arrays.asList( + "android", + "com.android.phone", + SETTINGS_PACKAGE_NAME, + "com.android.systemui", + "com.android.providers.calendar", + "com.android.providers.media" + )); + } + + public RecentAppStatsMixin(Context context, int maximumApps) { + mContext = context; + mMaximumApps = maximumApps; + mUserId = UserHandle.myUserId(); + mPm = mContext.getPackageManager(); + mPowerManager = mContext.getSystemService(PowerManager.class); + mUsageStatsManager = mContext.getSystemService(UsageStatsManager.class); + mApplicationsState = ApplicationsState.getInstance( + (Application) mContext.getApplicationContext()); + mRecentApps = new ArrayList<>(); + mAppStatsListeners = new ArrayList<>(); + } + + @Override + public void onStart() { + ThreadUtils.postOnBackgroundThread(() -> { + loadDisplayableRecentApps(mMaximumApps); + for (RecentAppStatsListener listener : mAppStatsListeners) { + ThreadUtils.postOnMainThread(() -> listener.onReloadDataCompleted(mRecentApps)); + } + }); + } + + @Override + public final int compare(UsageStats a, UsageStats b) { + // return by descending order + return Long.compare(b.getLastTimeUsed(), a.getLastTimeUsed()); + } + + public void addListener(@NonNull RecentAppStatsListener listener) { + mAppStatsListeners.add(listener); + } + + @VisibleForTesting + void loadDisplayableRecentApps(int number) { + mRecentApps.clear(); + mCalendar = Calendar.getInstance(); + mCalendar.add(Calendar.DAY_OF_YEAR, -1); + final List mStats = mPowerManager.isPowerSaveMode() + ? new ArrayList<>() + : mUsageStatsManager.queryUsageStats( + UsageStatsManager.INTERVAL_BEST, mCalendar.getTimeInMillis(), + System.currentTimeMillis()); + + final Map map = new ArrayMap<>(); + final int statCount = mStats.size(); + for (int i = 0; i < statCount; i++) { + final UsageStats pkgStats = mStats.get(i); + if (!shouldIncludePkgInRecents(pkgStats)) { + continue; + } + final String pkgName = pkgStats.getPackageName(); + final UsageStats existingStats = map.get(pkgName); + if (existingStats == null) { + map.put(pkgName, pkgStats); + } else { + existingStats.add(pkgStats); + } + } + final List packageStats = new ArrayList<>(); + packageStats.addAll(map.values()); + Collections.sort(packageStats, this /* comparator */); + int count = 0; + for (UsageStats stat : packageStats) { + final ApplicationsState.AppEntry appEntry = mApplicationsState.getEntry( + stat.getPackageName(), mUserId); + if (appEntry == null) { + continue; + } + mRecentApps.add(stat); + count++; + if (count >= number) { + break; + } + } + } + + /** + * Whether or not the app should be included in recent list. + */ + private boolean shouldIncludePkgInRecents(UsageStats stat) { + final String pkgName = stat.getPackageName(); + if (stat.getLastTimeUsed() < mCalendar.getTimeInMillis()) { + Log.d(TAG, "Invalid timestamp (usage time is more than 24 hours ago), skipping " + + pkgName); + return false; + } + + if (SKIP_SYSTEM_PACKAGES.contains(pkgName)) { + Log.d(TAG, "System package, skipping " + pkgName); + return false; + } + if (AppUtils.isHiddenSystemModule(mContext, pkgName)) { + return false; + } + final Intent launchIntent = new Intent().addCategory(Intent.CATEGORY_LAUNCHER) + .setPackage(pkgName); + + if (mPm.resolveActivity(launchIntent, 0) == null) { + // Not visible on launcher -> likely not a user visible app, skip if non-instant. + final ApplicationsState.AppEntry appEntry = + mApplicationsState.getEntry(pkgName, mUserId); + if (appEntry == null || appEntry.info == null || !AppUtils.isInstant(appEntry.info)) { + Log.d(TAG, "Not a user visible or instant app, skipping " + pkgName); + return false; + } + } + return true; + } + + public interface RecentAppStatsListener { + + void onReloadDataCompleted(List recentApps); + } +} diff --git a/src/com/android/settings/applications/RecentAppsPreferenceController.java b/src/com/android/settings/applications/RecentAppsPreferenceController.java index be86dd5b63f..4f5ec01fc7e 100644 --- a/src/com/android/settings/applications/RecentAppsPreferenceController.java +++ b/src/com/android/settings/applications/RecentAppsPreferenceController.java @@ -16,24 +16,17 @@ package com.android.settings.applications; -import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; - import android.app.Application; import android.app.settings.SettingsEnums; import android.app.usage.UsageStats; -import android.app.usage.UsageStatsManager; import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; import android.icu.text.RelativeDateTimeFormatter; -import android.os.PowerManager; import android.os.UserHandle; -import android.util.ArrayMap; -import android.util.ArraySet; import android.util.IconDrawableFactory; import android.util.Log; import android.view.View; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.Fragment; import androidx.preference.Preference; @@ -44,35 +37,24 @@ import com.android.settings.applications.appinfo.AppInfoDashboardFragment; import com.android.settings.applications.manageapplications.ManageApplications; import com.android.settings.core.BasePreferenceController; import com.android.settings.core.SubSettingLauncher; -import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.utils.StringUtil; import com.android.settingslib.widget.AppEntitiesHeaderController; import com.android.settingslib.widget.AppEntityInfo; import com.android.settingslib.widget.LayoutPreference; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collections; -import java.util.Comparator; import java.util.List; -import java.util.Map; -import java.util.Set; /** * This controller displays up to three recently used apps. * If there is no recently used app, we only show up an "App Info" preference. */ public class RecentAppsPreferenceController extends BasePreferenceController - implements Comparator { + implements RecentAppStatsMixin.RecentAppStatsListener { @VisibleForTesting static final String KEY_DIVIDER = "recent_apps_divider"; - private static final String TAG = "RecentAppsCtrl"; - private static final Set SKIP_SYSTEM_PACKAGES = new ArraySet<>(); - @VisibleForTesting AppEntitiesHeaderController mAppEntitiesController; @VisibleForTesting @@ -80,41 +62,19 @@ public class RecentAppsPreferenceController extends BasePreferenceController @VisibleForTesting Preference mDivider; - private final PackageManager mPm; - private final UsageStatsManager mUsageStatsManager; private final ApplicationsState mApplicationsState; private final int mUserId; private final IconDrawableFactory mIconDrawableFactory; - private final PowerManager mPowerManager; private Fragment mHost; - private Calendar mCal; - private List mStats; private List mRecentApps; - private boolean mHasRecentApps; - - static { - SKIP_SYSTEM_PACKAGES.addAll(Arrays.asList( - "android", - "com.android.phone", - SETTINGS_PACKAGE_NAME, - "com.android.systemui", - "com.android.providers.calendar", - "com.android.providers.media" - )); - } public RecentAppsPreferenceController(Context context, String key) { super(context, key); mApplicationsState = ApplicationsState.getInstance( (Application) mContext.getApplicationContext()); mUserId = UserHandle.myUserId(); - mPm = mContext.getPackageManager(); mIconDrawableFactory = IconDrawableFactory.newInstance(mContext); - mPowerManager = mContext.getSystemService(PowerManager.class); - mUsageStatsManager = mContext.getSystemService(UsageStatsManager.class); - mRecentApps = new ArrayList<>(); - reloadData(); } public void setFragment(Fragment fragment) { @@ -123,7 +83,7 @@ public class RecentAppsPreferenceController extends BasePreferenceController @Override public int getAvailabilityStatus() { - return mRecentApps.isEmpty() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE; + return AVAILABLE_UNSEARCHABLE; } @Override @@ -131,7 +91,7 @@ public class RecentAppsPreferenceController extends BasePreferenceController super.displayPreference(screen); mDivider = screen.findPreference(KEY_DIVIDER); - mRecentAppsPreference = (LayoutPreference) screen.findPreference(getPreferenceKey()); + mRecentAppsPreference = screen.findPreference(getPreferenceKey()); final View view = mRecentAppsPreference.findViewById(R.id.app_entities_header); mAppEntitiesController = AppEntitiesHeaderController.newInstance(mContext, view) .setHeaderTitleRes(R.string.recent_app_category_title) @@ -143,14 +103,11 @@ public class RecentAppsPreferenceController extends BasePreferenceController .setSourceMetricsCategory(SettingsEnums.SETTINGS_APP_NOTIF_CATEGORY) .launch(); }); - - refreshUi(); } @Override - public void updateState(Preference preference) { - super.updateState(preference); - + public void onReloadDataCompleted(@NonNull List recentApps) { + mRecentApps = recentApps; refreshUi(); // Show total number of installed apps as See all's summary. new InstalledAppCounter(mContext, InstalledAppCounter.IGNORE_INSTALL_REASON, @@ -164,38 +121,17 @@ public class RecentAppsPreferenceController extends BasePreferenceController }.execute(); } - @Override - public final int compare(UsageStats a, UsageStats b) { - // return by descending order - return Long.compare(b.getLastTimeUsed(), a.getLastTimeUsed()); - } - - List getRecentApps() { - return mRecentApps; - } - - @VisibleForTesting - void refreshUi() { - if (mRecentApps != null && !mRecentApps.isEmpty()) { + private void refreshUi() { + if (!mRecentApps.isEmpty()) { displayRecentApps(); + mRecentAppsPreference.setVisible(true); + mDivider.setVisible(true); } else { mDivider.setVisible(false); + mRecentAppsPreference.setVisible(false); } } - @VisibleForTesting - void reloadData() { - mCal = Calendar.getInstance(); - mCal.add(Calendar.DAY_OF_YEAR, -1); - mStats = mPowerManager.isPowerSaveMode() - ? new ArrayList<>() - : mUsageStatsManager.queryUsageStats( - UsageStatsManager.INTERVAL_BEST, mCal.getTimeInMillis(), - System.currentTimeMillis()); - - updateDisplayableRecentAppList(); - } - private void displayRecentApps() { int showAppsCount = 0; @@ -209,8 +145,6 @@ public class RecentAppsPreferenceController extends BasePreferenceController break; } } - mAppEntitiesController.apply(); - mDivider.setVisible(true); } private AppEntityInfo createAppEntity(UsageStats stat) { @@ -234,73 +168,4 @@ public class RecentAppsPreferenceController extends BasePreferenceController SettingsEnums.SETTINGS_APP_NOTIF_CATEGORY)) .build(); } - - private void updateDisplayableRecentAppList() { - mRecentApps.clear(); - final Map map = new ArrayMap<>(); - final int statCount = mStats.size(); - for (int i = 0; i < statCount; i++) { - final UsageStats pkgStats = mStats.get(i); - if (!shouldIncludePkgInRecents(pkgStats)) { - continue; - } - final String pkgName = pkgStats.getPackageName(); - final UsageStats existingStats = map.get(pkgName); - if (existingStats == null) { - map.put(pkgName, pkgStats); - } else { - existingStats.add(pkgStats); - } - } - final List packageStats = new ArrayList<>(); - packageStats.addAll(map.values()); - Collections.sort(packageStats, this /* comparator */); - int count = 0; - for (UsageStats stat : packageStats) { - final ApplicationsState.AppEntry appEntry = mApplicationsState.getEntry( - stat.getPackageName(), mUserId); - if (appEntry == null) { - continue; - } - mRecentApps.add(stat); - count++; - if (count >= AppEntitiesHeaderController.MAXIMUM_APPS) { - break; - } - } - } - - - /** - * Whether or not the app should be included in recent list. - */ - private boolean shouldIncludePkgInRecents(UsageStats stat) { - final String pkgName = stat.getPackageName(); - if (stat.getLastTimeUsed() < mCal.getTimeInMillis()) { - Log.d(TAG, "Invalid timestamp (usage time is more than 24 hours ago), skipping " - + pkgName); - return false; - } - - if (SKIP_SYSTEM_PACKAGES.contains(pkgName)) { - Log.d(TAG, "System package, skipping " + pkgName); - return false; - } - if (AppUtils.isHiddenSystemModule(mContext, pkgName)) { - return false; - } - final Intent launchIntent = new Intent().addCategory(Intent.CATEGORY_LAUNCHER) - .setPackage(pkgName); - - if (mPm.resolveActivity(launchIntent, 0) == null) { - // Not visible on launcher -> likely not a user visible app, skip if non-instant. - final ApplicationsState.AppEntry appEntry = - mApplicationsState.getEntry(pkgName, mUserId); - if (appEntry == null || appEntry.info == null || !AppUtils.isInstant(appEntry.info)) { - Log.d(TAG, "Not a user visible or instant app, skipping " + pkgName); - return false; - } - } - return true; - } } diff --git a/tests/robotests/src/com/android/settings/applications/AllAppsInfoPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/AllAppsInfoPreferenceControllerTest.java index ec3cf657421..2944db2dff9 100644 --- a/tests/robotests/src/com/android/settings/applications/AllAppsInfoPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/AllAppsInfoPreferenceControllerTest.java @@ -17,16 +17,25 @@ package com.android.settings.applications; import static com.android.settings.core.BasePreferenceController.AVAILABLE; -import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import android.app.usage.UsageStats; import android.content.Context; +import android.os.UserManager; + +import androidx.preference.Preference; 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; @@ -36,29 +45,45 @@ import java.util.List; @RunWith(RobolectricTestRunner.class) public class AllAppsInfoPreferenceControllerTest { + @Mock + private UserManager mUserManager; private AllAppsInfoPreferenceController mController; @Before public void setUp() { - final Context context = RuntimeEnvironment.application; + MockitoAnnotations.initMocks(this); + final Context context = spy(RuntimeEnvironment.application); + final Preference preference = new Preference(context); + doReturn(mUserManager).when(context).getSystemService(Context.USER_SERVICE); + when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{}); mController = new AllAppsInfoPreferenceController(context, "test_key"); + mController.mPreference = preference; } @Test - public void getAvailabilityStatus_hasRecentApps_shouldReturnConditionallyUnavailable() { + public void getAvailabilityStatus_shouldReturnAVAILABLE() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void onReloadDataCompleted_recentAppsSet_hidePreference() { final List stats = new ArrayList<>(); final UsageStats stat1 = new UsageStats(); stat1.mLastTimeUsed = System.currentTimeMillis(); stat1.mPackageName = "pkg.class"; stats.add(stat1); - mController.setRecentApps(stats); - assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); + mController.onReloadDataCompleted(stats); + + assertThat(mController.mPreference.isVisible()).isFalse(); } @Test - public void getAvailabilityStatus_noRecentApps_shouldReturnAvailable() { - // No data - assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + public void onReloadDataCompleted_noRecentAppSet_showPreference() { + final List stats = new ArrayList<>(); + + mController.onReloadDataCompleted(stats); + + assertThat(mController.mPreference.isVisible()).isTrue(); } } diff --git a/tests/robotests/src/com/android/settings/applications/RecentAppStatsMixinTest.java b/tests/robotests/src/com/android/settings/applications/RecentAppStatsMixinTest.java new file mode 100644 index 00000000000..0fb2a7ec328 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/RecentAppStatsMixinTest.java @@ -0,0 +1,309 @@ +/* + * 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.applications; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +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.app.usage.UsageStats; +import android.app.usage.UsageStatsManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.ModuleInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.PowerManager; +import android.os.UserHandle; +import android.os.UserManager; + +import com.android.settingslib.applications.AppUtils; +import com.android.settingslib.applications.ApplicationsState; +import com.android.settingslib.applications.instantapps.InstantAppDataProvider; + +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; +import org.robolectric.util.ReflectionHelpers; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +public class RecentAppStatsMixinTest { + + @Mock + private UsageStatsManager mUsageStatsManager; + @Mock + private UserManager mUserManager; + @Mock + private ApplicationsState mAppState; + @Mock + private PackageManager mPackageManager; + @Mock + private ApplicationsState.AppEntry mAppEntry; + @Mock + private ApplicationInfo mApplicationInfo; + @Mock + private PowerManager mPowerManager; + + private RecentAppStatsMixin mRecentAppStatsMixin; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + final Context context = spy(RuntimeEnvironment.application); + when(context.getApplicationContext()).thenReturn(context); + ReflectionHelpers.setStaticField(ApplicationsState.class, "sInstance", mAppState); + doReturn(mUsageStatsManager).when(context).getSystemService(Context.USAGE_STATS_SERVICE); + doReturn(mUserManager).when(context).getSystemService(Context.USER_SERVICE); + doReturn(mPackageManager).when(context).getPackageManager(); + doReturn(mPowerManager).when(context).getSystemService(PowerManager.class); + when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{}); + + mRecentAppStatsMixin = new RecentAppStatsMixin(context, 3 /* maximumApps */); + } + + @Test + public void loadDisplayableRecentApps_oneValidRecentAppSet_shouldHaveOneRecentApp() { + final List stats = new ArrayList<>(); + final UsageStats stat1 = new UsageStats(); + stat1.mLastTimeUsed = System.currentTimeMillis(); + stat1.mPackageName = "pkg.class"; + stats.add(stat1); + // stat1 is valid app. + when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) + .thenReturn(new ResolveInfo()); + when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) + .thenReturn(stats); + mAppEntry.info = mApplicationInfo; + + mRecentAppStatsMixin.loadDisplayableRecentApps(3); + + assertThat(mRecentAppStatsMixin.mRecentApps.size()).isEqualTo(1); + } + + @Test + public void loadDisplayableRecentApps_threeValidRecentAppsSet_shouldHaveThreeRecentApps() { + final List stats = new ArrayList<>(); + final UsageStats stat1 = new UsageStats(); + final UsageStats stat2 = new UsageStats(); + final UsageStats stat3 = new UsageStats(); + stat1.mLastTimeUsed = System.currentTimeMillis(); + stat1.mPackageName = "pkg.class"; + stats.add(stat1); + + stat2.mLastTimeUsed = System.currentTimeMillis(); + stat2.mPackageName = "pkg.class2"; + stats.add(stat2); + + stat3.mLastTimeUsed = System.currentTimeMillis(); + stat3.mPackageName = "pkg.class3"; + stats.add(stat3); + + when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mAppState.getEntry(stat3.mPackageName, UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) + .thenReturn(new ResolveInfo()); + when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) + .thenReturn(stats); + mAppEntry.info = mApplicationInfo; + + mRecentAppStatsMixin.loadDisplayableRecentApps(3); + + assertThat(mRecentAppStatsMixin.mRecentApps.size()).isEqualTo(3); + } + + @Test + public void loadDisplayableRecentApps_oneValidAndTwoInvalidSet_shouldHaveOneRecentApp() { + final List stats = new ArrayList<>(); + final UsageStats stat1 = new UsageStats(); + final UsageStats stat2 = new UsageStats(); + final UsageStats stat3 = new UsageStats(); + stat1.mLastTimeUsed = System.currentTimeMillis(); + stat1.mPackageName = "pkg.class"; + stats.add(stat1); + + stat2.mLastTimeUsed = System.currentTimeMillis(); + stat2.mPackageName = "com.android.settings"; + stats.add(stat2); + + stat3.mLastTimeUsed = System.currentTimeMillis(); + stat3.mPackageName = "pkg.class3"; + stats.add(stat3); + + // stat1, stat2 are valid apps. stat3 is invalid. + when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mAppState.getEntry(stat3.mPackageName, UserHandle.myUserId())) + .thenReturn(null); + when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) + .thenReturn(new ResolveInfo()); + when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) + .thenReturn(stats); + mAppEntry.info = mApplicationInfo; + + mRecentAppStatsMixin.loadDisplayableRecentApps(3); + + // Only stat1. stat2 is skipped because of the package name, stat3 skipped because + // it's invalid app. + assertThat(mRecentAppStatsMixin.mRecentApps.size()).isEqualTo(1); + } + + @Test + public void loadDisplayableRecentApps_oneInstantAppSet_shouldHaveOneRecentApp() { + final List stats = new ArrayList<>(); + // Instant app. + final UsageStats stat = new UsageStats(); + stat.mLastTimeUsed = System.currentTimeMillis() + 200; + stat.mPackageName = "com.foo.barinstant"; + stats.add(stat); + + ApplicationsState.AppEntry statEntry = mock(ApplicationsState.AppEntry.class); + statEntry.info = mApplicationInfo; + + when(mAppState.getEntry(stat.mPackageName, UserHandle.myUserId())).thenReturn(statEntry); + when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) + .thenReturn(stats); + + // Make sure stat is considered an instant app. + ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", + (InstantAppDataProvider) (ApplicationInfo info) -> info == statEntry.info); + + mRecentAppStatsMixin.loadDisplayableRecentApps(3); + + assertThat(mRecentAppStatsMixin.mRecentApps.size()).isEqualTo(1); + } + + @Test + public void loadDisplayableRecentApps_withNullAppEntryOrInfo_shouldNotCrash() { + final List stats = new ArrayList<>(); + final UsageStats stat1 = new UsageStats(); + final UsageStats stat2 = new UsageStats(); + stat1.mLastTimeUsed = System.currentTimeMillis(); + stat1.mPackageName = "pkg.class"; + stats.add(stat1); + + stat2.mLastTimeUsed = System.currentTimeMillis(); + stat2.mPackageName = "pkg.class2"; + stats.add(stat2); + + // app1 has AppEntry with null info, app2 has null AppEntry. + mAppEntry.info = null; + when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())) + .thenReturn(null); + + when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) + .thenReturn(stats); + + // We should not crash here. + mRecentAppStatsMixin.loadDisplayableRecentApps(3); + } + + @Test + public void loadDisplayableRecentApps_hiddenSystemModuleSet_shouldNotHaveHiddenSystemModule() { + final List stats = new ArrayList<>(); + // Regular app. + final UsageStats stat1 = new UsageStats(); + stat1.mLastTimeUsed = System.currentTimeMillis(); + stat1.mPackageName = "com.foo.bar"; + stats.add(stat1); + + // Hidden system module. + final UsageStats stat2 = new UsageStats(); + stat2.mLastTimeUsed = System.currentTimeMillis() + 200; + stat2.mPackageName = "com.foo.hidden"; + stats.add(stat2); + + ApplicationsState.AppEntry stat1Entry = mock(ApplicationsState.AppEntry.class); + ApplicationsState.AppEntry stat2Entry = mock(ApplicationsState.AppEntry.class); + stat1Entry.info = mApplicationInfo; + stat2Entry.info = mApplicationInfo; + + when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())).thenReturn(stat1Entry); + when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())).thenReturn(stat2Entry); + + final ModuleInfo moduleInfo1 = new ModuleInfo(); + moduleInfo1.setPackageName(stat1.mPackageName); + moduleInfo1.setHidden(false); + + final ModuleInfo moduleInfo2 = new ModuleInfo(); + moduleInfo2.setPackageName(stat2.mPackageName); + moduleInfo2.setHidden(true); + + ReflectionHelpers.setStaticField(ApplicationsState.class, "sInstance", null); + final List modules = new ArrayList<>(); + modules.add(moduleInfo2); + when(mPackageManager.getInstalledModules(anyInt() /* flags */)) + .thenReturn(modules); + + when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) + .thenReturn(new ResolveInfo()); + when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) + .thenReturn(stats); + + mRecentAppStatsMixin.loadDisplayableRecentApps(3); + + assertThat(mRecentAppStatsMixin.mRecentApps.size()).isEqualTo(1); + } + + @Test + public void loadDisplayableRecentApps_powerSaverModeOn_shouldHaveEmptyList() { + when(mPowerManager.isPowerSaveMode()).thenReturn(true); + + final List stats = new ArrayList<>(); + final UsageStats stat1 = new UsageStats(); + + stat1.mLastTimeUsed = System.currentTimeMillis(); + stat1.mPackageName = "pkg.class"; + stats.add(stat1); + + // stat1, stat2 are valid apps. stat3 is invalid. + when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) + .thenReturn(new ResolveInfo()); + when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) + .thenReturn(stats); + mAppEntry.info = mApplicationInfo; + + mRecentAppStatsMixin.loadDisplayableRecentApps(3); + + assertThat(mRecentAppStatsMixin.mRecentApps).isEmpty(); + } +} diff --git a/tests/robotests/src/com/android/settings/applications/RecentAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/RecentAppsPreferenceControllerTest.java index 1a28f37548f..2928d6f4398 100644 --- a/tests/robotests/src/com/android/settings/applications/RecentAppsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/RecentAppsPreferenceControllerTest.java @@ -16,33 +16,23 @@ package com.android.settings.applications; -import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE; -import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.usage.UsageStats; -import android.app.usage.UsageStatsManager; import android.content.Context; -import android.content.Intent; import android.content.pm.ApplicationInfo; -import android.content.pm.ModuleInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.PowerManager; import android.os.UserHandle; import android.os.UserManager; import android.view.LayoutInflater; @@ -53,9 +43,7 @@ import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.R; -import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; -import com.android.settingslib.applications.instantapps.InstantAppDataProvider; import com.android.settingslib.widget.AppEntitiesHeaderController; import com.android.settingslib.widget.AppEntityInfo; import com.android.settingslib.widget.LayoutPreference; @@ -63,7 +51,6 @@ import com.android.settingslib.widget.LayoutPreference; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; @@ -79,8 +66,6 @@ public class RecentAppsPreferenceControllerTest { @Mock private PreferenceScreen mScreen; @Mock - private UsageStatsManager mUsageStatsManager; - @Mock private UserManager mUserManager; @Mock private ApplicationsState mAppState; @@ -91,11 +76,8 @@ public class RecentAppsPreferenceControllerTest { @Mock private ApplicationInfo mApplicationInfo; @Mock - private PowerManager mPowerManager; - @Mock private Fragment mFragment; - private LayoutPreference mRecentAppsPreference; private RecentAppsPreferenceController mController; @Before @@ -104,92 +86,33 @@ public class RecentAppsPreferenceControllerTest { final Context context = spy(RuntimeEnvironment.application); when(context.getApplicationContext()).thenReturn(context); ReflectionHelpers.setStaticField(ApplicationsState.class, "sInstance", mAppState); - doReturn(mUsageStatsManager).when(context).getSystemService(Context.USAGE_STATS_SERVICE); doReturn(mUserManager).when(context).getSystemService(Context.USER_SERVICE); doReturn(mPackageManager).when(context).getPackageManager(); - doReturn(mPowerManager).when(context).getSystemService(PowerManager.class); when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{}); final View appEntitiesHeaderView = LayoutInflater.from(context).inflate( R.layout.app_entities_header, null /* root */); final Preference dividerPreference = new Preference(context); - mRecentAppsPreference = spy(new LayoutPreference(context, appEntitiesHeaderView)); + final LayoutPreference recentAppsPreference = + spy(new LayoutPreference(context, appEntitiesHeaderView)); mController = spy(new RecentAppsPreferenceController(context, "test_key")); mController.setFragment(mFragment); + mController.mAppEntitiesController = mock(AppEntitiesHeaderController.class); - mController.mRecentAppsPreference = mRecentAppsPreference; + mController.mRecentAppsPreference = recentAppsPreference; mController.mDivider = dividerPreference; when(mScreen.findPreference(RecentAppsPreferenceController.KEY_DIVIDER)) .thenReturn(dividerPreference); - when(mScreen.findPreference("test_key")).thenReturn(mRecentAppsPreference); - when(mRecentAppsPreference.findViewById(R.id.app_entities_header)).thenReturn( + when(mScreen.findPreference("test_key")).thenReturn(recentAppsPreference); + when(recentAppsPreference.findViewById(R.id.app_entities_header)).thenReturn( appEntitiesHeaderView); } @Test - public void getAvailabilityStatus_hasRecentApps_shouldReturnAvailable() { - final List stats = new ArrayList<>(); - final UsageStats stat1 = new UsageStats(); - stat1.mLastTimeUsed = System.currentTimeMillis(); - stat1.mPackageName = "pkg.class"; - stats.add(stat1); - // stat1 is valid app. - when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) - .thenReturn(mAppEntry); - when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) - .thenReturn(new ResolveInfo()); - when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) - .thenReturn(stats); - mAppEntry.info = mApplicationInfo; - mController.reloadData(); - - assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); - } - - @Test - public void getAvailabilityStatus_noRecentApps_shouldReturnConditionallyUnavailable() { - // No data - assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); - } - - @Test - public void getAvailabilityStatus_powerSaverModeOn_shouldReturnConditionallyUnavailable() { - when(mPowerManager.isPowerSaveMode()).thenReturn(true); - - final List stats = new ArrayList<>(); - final UsageStats stat1 = new UsageStats(); - - stat1.mLastTimeUsed = System.currentTimeMillis(); - stat1.mPackageName = "pkg.class"; - stats.add(stat1); - - // stat1, stat2 are valid apps. stat3 is invalid. - when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) - .thenReturn(mAppEntry); - when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) - .thenReturn(new ResolveInfo()); - when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) - .thenReturn(stats); - mAppEntry.info = mApplicationInfo; - mController.reloadData(); - - assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); - } - - @Test - public void displayPreference_shouldNotReloadData() { - mController.displayPreference(mScreen); - - verify(mController, never()).reloadData(); - } - - @Test - public void displayPreference_shouldRefreshUi() { - mController.displayPreference(mScreen); - - verify(mController).refreshUi(); + public void getAvailabilityStatus_shouldReturnAVAILABLE_UNSEARCHABLE() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE); } @Test @@ -200,7 +123,7 @@ public class RecentAppsPreferenceControllerTest { } @Test - public void updateState_threeValidRecentOpenAppsSet_setAppEntityThreeTime() { + public void onReloadDataCompleted_threeValidRecentOpenAppsSet_setAppEntityThreeTime() { final List stats = new ArrayList<>(); final UsageStats stat1 = new UsageStats(); final UsageStats stat2 = new UsageStats(); @@ -216,22 +139,15 @@ public class RecentAppsPreferenceControllerTest { stat3.mLastTimeUsed = System.currentTimeMillis(); stat3.mPackageName = "pkg.class3"; stats.add(stat3); - - // stat1, stat2 are valid apps. stat3 is invalid. when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) .thenReturn(mAppEntry); when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())) .thenReturn(mAppEntry); when(mAppState.getEntry(stat3.mPackageName, UserHandle.myUserId())) .thenReturn(mAppEntry); - when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) - .thenReturn(new ResolveInfo()); - when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) - .thenReturn(stats); mAppEntry.info = mApplicationInfo; - mController.reloadData(); - mController.updateState(mRecentAppsPreference); + mController.onReloadDataCompleted(stats); verify(mController.mAppEntitiesController, times(3)) .setAppEntity(anyInt(), any(AppEntityInfo.class)); @@ -240,165 +156,12 @@ public class RecentAppsPreferenceControllerTest { } @Test - public void updateState_oneValidRecentOpenAppSet_setAppEntityOneTime() { + public void onReloadDataCompleted_noRecentOpenAppsSet_shouldHideRecentAppPreference() { final List stats = new ArrayList<>(); - final UsageStats stat1 = new UsageStats(); - final UsageStats stat2 = new UsageStats(); - final UsageStats stat3 = new UsageStats(); - stat1.mLastTimeUsed = System.currentTimeMillis(); - stat1.mPackageName = "pkg.class"; - stats.add(stat1); - stat2.mLastTimeUsed = System.currentTimeMillis(); - stat2.mPackageName = "com.android.settings"; - stats.add(stat2); + mController.onReloadDataCompleted(stats); - stat3.mLastTimeUsed = System.currentTimeMillis(); - stat3.mPackageName = "pkg.class3"; - stats.add(stat3); - - // stat1, stat2 are valid apps. stat3 is invalid. - when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) - .thenReturn(mAppEntry); - when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())) - .thenReturn(mAppEntry); - when(mAppState.getEntry(stat3.mPackageName, UserHandle.myUserId())) - .thenReturn(null); - when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) - .thenReturn(new ResolveInfo()); - when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) - .thenReturn(stats); - mAppEntry.info = mApplicationInfo; - mController.reloadData(); - - mController.updateState(mRecentAppsPreference); - - // Only add stat1. stat2 is skipped because of the package name, stat3 skipped because - // it's invalid app. - verify(mController.mAppEntitiesController, times(1)) - .setAppEntity(anyInt(), any(AppEntityInfo.class)); - assertThat(mController.mRecentAppsPreference.isVisible()).isTrue(); - assertThat(mController.mDivider.isVisible()).isTrue(); - } - - @Test - public void updateState_instantAppSet_shouldSetAppEntityForInstantApp() { - // Regular app. - final List stats = new ArrayList<>(); - final UsageStats stat1 = new UsageStats(); - stat1.mLastTimeUsed = System.currentTimeMillis(); - stat1.mPackageName = "com.foo.bar"; - stats.add(stat1); - - // Instant app. - final UsageStats stat2 = new UsageStats(); - stat2.mLastTimeUsed = System.currentTimeMillis() + 200; - stat2.mPackageName = "com.foo.barinstant"; - stats.add(stat2); - - ApplicationsState.AppEntry stat1Entry = mock(ApplicationsState.AppEntry.class); - ApplicationsState.AppEntry stat2Entry = mock(ApplicationsState.AppEntry.class); - stat1Entry.info = mApplicationInfo; - stat2Entry.info = mApplicationInfo; - - when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())).thenReturn(stat1Entry); - when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())).thenReturn(stat2Entry); - - // Only the regular app stat1 should have its intent resolve. - when(mPackageManager.resolveActivity(argThat(intentMatcher(stat1.mPackageName)), anyInt())) - .thenReturn(new ResolveInfo()); - when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) - .thenReturn(stats); - - // Make sure stat2 is considered an instant app. - ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider", - (InstantAppDataProvider) (ApplicationInfo info) -> info == stat2Entry.info); - mController.reloadData(); - - mController.updateState(mRecentAppsPreference); - - verify(mController.mAppEntitiesController, times(2)) - .setAppEntity(anyInt(), any(AppEntityInfo.class)); - } - - @Test - public void updateState_withNullAppEntryOrInfo_shouldNotCrash() { - final List stats = new ArrayList<>(); - final UsageStats stat1 = new UsageStats(); - final UsageStats stat2 = new UsageStats(); - stat1.mLastTimeUsed = System.currentTimeMillis(); - stat1.mPackageName = "pkg.class"; - stats.add(stat1); - - stat2.mLastTimeUsed = System.currentTimeMillis(); - stat2.mPackageName = "pkg.class2"; - stats.add(stat2); - - // app1 has AppEntry with null info, app2 has null AppEntry. - mAppEntry.info = null; - when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) - .thenReturn(mAppEntry); - when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())) - .thenReturn(null); - - when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) - .thenReturn(stats); - - // We should not crash here. - mController.updateState(mRecentAppsPreference); - } - - @Test - public void updateState_hiddenSystemModuleSet_shouldNotShowHiddenSystemModule() { - final List stats = new ArrayList<>(); - // Regular app. - final UsageStats stat1 = new UsageStats(); - stat1.mLastTimeUsed = System.currentTimeMillis(); - stat1.mPackageName = "com.foo.bar"; - stats.add(stat1); - - // Hidden system module. - final UsageStats stat2 = new UsageStats(); - stat2.mLastTimeUsed = System.currentTimeMillis() + 200; - stat2.mPackageName = "com.foo.hidden"; - stats.add(stat2); - - ApplicationsState.AppEntry stat1Entry = mock(ApplicationsState.AppEntry.class); - ApplicationsState.AppEntry stat2Entry = mock(ApplicationsState.AppEntry.class); - stat1Entry.info = mApplicationInfo; - stat2Entry.info = mApplicationInfo; - - when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())).thenReturn(stat1Entry); - when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())).thenReturn(stat2Entry); - - final ModuleInfo moduleInfo1 = new ModuleInfo(); - moduleInfo1.setPackageName(stat1.mPackageName); - moduleInfo1.setHidden(false); - - final ModuleInfo moduleInfo2 = new ModuleInfo(); - moduleInfo2.setPackageName(stat2.mPackageName); - moduleInfo2.setHidden(true); - - ReflectionHelpers.setStaticField(ApplicationsState.class, "sInstance", null); - final List modules = new ArrayList<>(); - modules.add(moduleInfo2); - when(mPackageManager.getInstalledModules(anyInt() /* flags */)) - .thenReturn(modules); - - when(mPackageManager.resolveActivity(any(Intent.class), anyInt())) - .thenReturn(new ResolveInfo()); - when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) - .thenReturn(stats); - mController.reloadData(); - - mController.updateState(mRecentAppsPreference); - - // Only add stat1. stat2 is skipped because it is hidden module. - verify(mController.mAppEntitiesController).setAppEntity(anyInt(), any(AppEntityInfo.class)); - } - - // Used for matching an intent with a specific package name. - private static ArgumentMatcher intentMatcher(String packageName) { - return intent -> packageName.equals(intent.getPackage()); + assertThat(mController.mRecentAppsPreference.isVisible()).isFalse(); + assertThat(mController.mDivider.isVisible()).isFalse(); } }