// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.history;
import static android.content.Intent.ACTION_VIEW;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.SystemClock;
import android.provider.Browser;
import android.view.ContextThemeWrapper;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import org.chromium.base.ContextUtils;
import org.chromium.base.IntentUtils;
import org.chromium.base.ResettersForTesting;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ActivityUtils;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.history.AppFilterCoordinator.AppInfo;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefChangeRegistrar;
import org.chromium.chrome.browser.preferences.PrefChangeRegistrar.PrefObserver;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.signin.services.SigninManager.SignInStateObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.browser.tabmodel.document.ChromeAsyncTabLauncher;
import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.components.browser_ui.util.ConversionUtils;
import org.chromium.components.browser_ui.widget.selectable_list.SelectableItemViewHolder;
import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.components.favicon.LargeIconBridge;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.UiUtils;
import org.chromium.ui.base.PageTransition;
import org.chromium.url.GURL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
/** Displays and manages the content view / list UI for browsing history. */
public class HistoryContentManager implements SignInStateObserver, PrefObserver {
/** Interface for a class that wants to receive updates from this Manager. */
public interface Observer {
/**
* Called after the content view was scrolled.
* @param loadedMore A boolean indicating if more items were loaded.
*/
void onScrolledCallback(boolean loadedMore);
/**
* Called after a user clicked on a HistoryItem.
* @param item The item that has been clicked.
*/
void onItemClicked(HistoryItem item);
/**
* Called after a user removed this HistoryItem.
* @param item The item that has been removed.
*/
void onItemRemoved(HistoryItem item);
/** Called after a user clicks the clear data button. */
void onClearBrowsingDataClicked();
/** Called to notify when the privacy disclaimer visibility has changed. */
void onPrivacyDisclaimerHasChanged();
/** Called after a user clicks the open full Chrome history button. */
void onOpenFullChromeHistoryClicked();
/** Called to notify when the user's sign in or pref state has changed. */
void onUserAccountStateChanged();
/**
* Called when history has been deleted by some party external to the currently visible
* history UI, e.g. via "clear browsing data."
*/
void onHistoryDeletedExternally();
}
private static final int FAVICON_MAX_CACHE_SIZE_BYTES =
10 * ConversionUtils.BYTES_PER_MEGABYTE; // 10MB
// PageTransition value to use for all URL requests triggered by the history page.
static final int PAGE_TRANSITION_TYPE = PageTransition.AUTO_BOOKMARK;
private static HistoryProvider sProviderForTests;
private static Boolean sIsScrollToLoadDisabledForTests;
private final Activity mActivity;
private final Observer mObserver;
private final boolean mIsSeparateActivity;
private final boolean mIsIncognito;
private final Profile mProfile;
private final boolean mIsScrollToLoadDisabled;
private final boolean mShouldShowClearDataIfAvailable;
private final HistoryUmaRecorder mUmaRecorder;
private final String mHostName;
private final Runnable mHideSoftKeyboard;
private final boolean mShowAppFilter;
private final List<AppInfo> mAppInfoList = new ArrayList<>();
private final Supplier<BottomSheetController> mBottomSheetController;
private final Supplier<Tab> mTabSupplier;
private final AppInfoCache mAppInfoCache;
private HistoryAdapter mHistoryAdapter;
private RecyclerView mRecyclerView;
private LargeIconBridge mLargeIconBridge;
private SelectionDelegate<HistoryItem> mSelectionDelegate;
private boolean mShouldShowPrivacyDisclaimers;
private boolean mLaunchedForApp;
private PrefChangeRegistrar mPrefChangeRegistrar;
private String mAppId;
private AppFilterCoordinator mAppFilterSheet;
private AppInfo mCurrentApp;
private long mAppQueryStartMs;
/**
* Creates a new HistoryContentManager.
*
* @param activity The Activity associated with the HistoryContentManager.
* @param observer The Observer to receive updates from this manager.
* @param isSeparateActivity Whether the history UI will be shown in a separate activity than
* the main Chrome activity.
* @param profile The Profile associated with this history.
* @param shouldShowPrivacyDisclaimers Whether the privacy disclaimers should be shown, if
* available.
* @param shouldShowClearDataIfAvailable Whether the the clear history data button should be
* shown, if available.
* @param hostName The hostName to retrieve history entries for, or null for all hosts.
* @param selectionDelegate A class responsible for handling list item selection, null for
* unselectable items.
* @param tabSupplier Supplies the current tab, null if the history UI will be shown in a
* separate activity. separate activity.
* @param umaRecorder Records UMA user action/histograms.
* @param historyProvider Provider of methods for querying and managing browsing history.
* @param appId The ID of the application from which the history activity is launched, passed as
* the client package name.
* @param launchedForApp Whether history UI is launched for app-specific history.
*/
public HistoryContentManager(
@NonNull Activity activity,
@NonNull Observer observer,
boolean isSeparateActivity,
Profile profile,
boolean shouldShowPrivacyDisclaimers,
boolean shouldShowClearDataIfAvailable,
@Nullable String hostName,
@Nullable SelectionDelegate<HistoryItem> selectionDelegate,
@Nullable Supplier<BottomSheetController> bottomSheetController,
@Nullable Supplier<Tab> tabSupplier,
@Nullable Runnable hideSoftKeyboard,
HistoryUmaRecorder umaRecorder,
HistoryProvider historyProvider,
String appId,
boolean launchedForApp,
boolean showAppFilter) {
mActivity = activity;
mObserver = observer;
mIsSeparateActivity = isSeparateActivity;
mIsIncognito = profile.isOffTheRecord();
mProfile = profile;
mBottomSheetController = bottomSheetController;
mHideSoftKeyboard = hideSoftKeyboard;
mShowAppFilter = showAppFilter;
mShouldShowPrivacyDisclaimers = shouldShowPrivacyDisclaimers;
mShouldShowClearDataIfAvailable = shouldShowClearDataIfAvailable;
mHostName = hostName;
mUmaRecorder = umaRecorder;
mIsScrollToLoadDisabled =
ChromeAccessibilityUtil.get().isAccessibilityEnabled()
|| UiUtils.isHardwareKeyboardAttached();
mAppId = appId;
mLaunchedForApp = launchedForApp;
mSelectionDelegate =
selectionDelegate != null
? selectionDelegate
: new SelectionDelegate<HistoryItem>() {
@Override
public boolean toggleSelectionForItem(HistoryItem bookmark) {
return false;
}
@Override
public boolean isItemSelected(HistoryItem item) {
return false;
}
@Override
public boolean isSelectionEnabled() {
return false;
}
};
mTabSupplier = tabSupplier;
// History service is not keyed for Incognito profiles and {@link HistoryServiceFactory}
// explicitly redirects to use regular profile for Incognito case.
mHistoryAdapter =
new HistoryAdapter(
this, sProviderForTests != null ? sProviderForTests : historyProvider);
// Create a recycler view.
mRecyclerView =
new RecyclerView(new ContextThemeWrapper(mActivity, R.style.VerticalRecyclerView));
mRecyclerView.setLayoutManager(new LinearLayoutManager(mActivity));
mRecyclerView.setAdapter(mHistoryAdapter);
mRecyclerView.setHasFixedSize(true);
// Create icon bridge to get icons for each entry.
mLargeIconBridge = new LargeIconBridge(profile);
ActivityManager activityManager =
((ActivityManager)
ContextUtils.getApplicationContext()
.getSystemService(Context.ACTIVITY_SERVICE));
int maxSize =
Math.min(
(activityManager.getMemoryClass() / 4) * ConversionUtils.BYTES_PER_MEGABYTE,
FAVICON_MAX_CACHE_SIZE_BYTES);
mLargeIconBridge.createCache(maxSize);
// Add the scroll listener for the recycler view.
mRecyclerView.addOnScrollListener(
new OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
LinearLayoutManager layoutManager =
(LinearLayoutManager) recyclerView.getLayoutManager();
if (!mHistoryAdapter.canLoadMoreItems() || isScrollToLoadDisabled()) {
return;
}
// Load more items if the scroll position is close to the bottom of the
// list.
boolean loadedMore = false;
if (layoutManager.findLastVisibleItemPosition()
> (mHistoryAdapter.getItemCount() - 25)) {
mHistoryAdapter.loadMoreItems();
loadedMore = true;
}
mObserver.onScrolledCallback(loadedMore);
}
});
// Create header and footer.
mHistoryAdapter.generateHeaderItems();
mHistoryAdapter.generateFooterItems();
// Listen to changes in sign in state.
IdentityServicesProvider.get().getSigninManager(profile).addSignInStateObserver(this);
// Create PrefChangeRegistrar to receive notifications on preference changes.
mPrefChangeRegistrar = new PrefChangeRegistrar();
mPrefChangeRegistrar.addObserver(Pref.ALLOW_DELETING_BROWSER_HISTORY, this);
mPrefChangeRegistrar.addObserver(Pref.INCOGNITO_MODE_AVAILABILITY, this);
mAppInfoCache = new AppInfoCache(mActivity.getPackageManager());
}
/** Tell the HistoryContentManager to start loading items. */
public void startLoadingItems() {
// Filtering the adapter to only the results from this particular host.
mHistoryAdapter.setHostName(mHostName);
mHistoryAdapter.setAppId(mAppId);
mHistoryAdapter.startLoadingItems();
}
/** Query all app IDs from the history database if required. */
void maybeQueryApps() {
if (!showAppFilter()) return;
mAppQueryStartMs = SystemClock.elapsedRealtime();
mHistoryAdapter.queryApps();
}
void onQueryAppsComplete(List<String> items) {
mUmaRecorder.recordQueryAppDuration(SystemClock.elapsedRealtime() - mAppQueryStartMs);
buildAppInfoList(items);
}
/**
* Build a list of {@link AppInfo} using the app query result.
*
* @param appIds List of app IDs found from the history database.
*/
private void buildAppInfoList(List<String> appIds) {
mAppInfoList.clear();
for (String appId : appIds) {
AppInfo appInfo = mAppInfoCache.get(appId);
// Filter out the app whose info cannot be found. TODO: Consider keeping it with
// a default app.
if (appInfo.isValid()) mAppInfoList.add(appInfo);
}
}
/**
* @return Whether there is apps to show in the filter UI. Could be false if the query is not
* completed or the result indeed is empty.
*/
boolean hasFilterList() {
return !mAppInfoList.isEmpty();
}
/**
* @return The RecyclerView for this HistoryContentManager.
*/
public RecyclerView getRecyclerView() {
return mRecyclerView;
}
/** @return The HistoryAdapter for this HistoryContentManager. */
public HistoryAdapter getAdapter() {
return mHistoryAdapter;
}
/** @return The Context for the associated history view. */
public Context getContext() {
return mActivity;
}
/** Called when the activity/native page is destroyed. */
public void onDestroyed() {
mHistoryAdapter.onDestroyed();
mLargeIconBridge.destroy();
mLargeIconBridge = null;
IdentityServicesProvider.get().getSigninManager(mProfile).removeSignInStateObserver(this);
mPrefChangeRegistrar.destroy();
}
/**
* Check if we want to enable the scrolling to load for recycled view. Noting this function
* will be called during testing with RecycledView == null. Will return False in such case.
* @return True if accessibility is enabled or a hardware keyboard is attached.
*/
boolean isScrollToLoadDisabled() {
if (sIsScrollToLoadDisabledForTests != null) {
return sIsScrollToLoadDisabledForTests.booleanValue();
}
return mIsScrollToLoadDisabled;
}
/** @return The ViewHolder for the HistoryItem. */
ViewHolder getHistoryItemViewHolder(View v) {
return new SelectableItemViewHolder<>(v, mSelectionDelegate);
}
/** Binds the ViewHolder with the given HistoryItem. */
public void bindViewHolderForHistoryItem(ViewHolder holder, HistoryItem item) {
item.setHistoryManager(this);
SelectableItemViewHolder<HistoryItem> selectableHolder =
(SelectableItemViewHolder<HistoryItem>) holder;
selectableHolder.displayItem(item);
}
/**
* @return Whether to show the remove button in a HistoryItemView.
*/
int getRemoveItemButtonVisibility() {
if (!UserPrefs.get(mProfile).getBoolean(Pref.ALLOW_DELETING_BROWSER_HISTORY)) {
return View.GONE;
}
return !mSelectionDelegate.isSelectionEnabled() ? View.VISIBLE : View.INVISIBLE;
}
/**
* Called to notify the content manager that history has been deleted by some party external to
* the currently visible history UI, e.g. via "clear browsing data."
*/
void onHistoryDeletedExternally() {
mSelectionDelegate.clearSelection();
mObserver.onHistoryDeletedExternally();
}
/**
* Sets the selectable item mode. Items only selectable if they have a SelectableItemViewHolder.
* @param active Whether the selection mode is on or not.
*/
public void setSelectionActive(boolean active) {
mHistoryAdapter.setSelectionActive(active);
}
/** @param shouldShow Whether the privacy disclaimers should be shown, if available. */
public void updatePrivacyDisclaimers(boolean shouldShow) {
mShouldShowPrivacyDisclaimers = shouldShow;
mHistoryAdapter.setPrivacyDisclaimer();
}
/**
* @return True if the available privacy disclaimers should be shown.
* Note that this may return true even if there are currently no privacy disclaimers.
*/
boolean getShouldShowPrivacyDisclaimersIfAvailable() {
return mShouldShowPrivacyDisclaimers;
}
/** Called to notify when the privacy disclaimer visibility has changed. */
void onPrivacyDisclaimerHasChanged() {
mObserver.onPrivacyDisclaimerHasChanged();
}
/** @return True if any privacy disclaimer should be visible, false otherwise. */
boolean hasPrivacyDisclaimers() {
return mHistoryAdapter.hasPrivacyDisclaimers();
}
/**
* @return True if history UI was launched for the app-specific mode.
*/
boolean launchedForApp() {
return HistoryManager.isAppSpecificHistoryEnabled() && mLaunchedForApp;
}
/**
* @return True if history page needs to show app filter UI.
*/
boolean showAppFilter() {
return HistoryManager.isAppSpecificHistoryEnabled() && mShowAppFilter;
}
/** returns whether the info header will be available for user upon request. */
boolean isInfoHeaderAvailable() {
// Info header becomes available when history was launched for app-specific mode
// or a set of conditions for privacy disclaimer are all met.
return launchedForApp() || hasPrivacyDisclaimers();
}
/** Called after a user clicks the privacy disclaimer link. */
void onPrivacyDisclaimerLinkClicked() {
openUrl(new GURL(UrlConstants.MY_ACTIVITY_URL_IN_HISTORY), null, true);
}
/**
* @return True if the clear history data button should be shown.
*/
boolean getShouldShowClearData() {
return mShouldShowClearDataIfAvailable
&& UserPrefs.get(mProfile).getBoolean(Pref.ALLOW_DELETING_BROWSER_HISTORY);
}
/** Opens the url of each of the visits in the provided list in a new tab. */
public void openItemsInNewTab(List<HistoryItem> items, boolean isIncognito) {
if (mIsSeparateActivity && items.size() > 1) {
ArrayList<String> additionalUrls = new ArrayList<>(items.size() - 1);
for (int i = 1; i < items.size(); i++) {
additionalUrls.add(items.get(i).getUrl().getSpec());
}
Intent intent = getOpenUrlIntent(items.get(0).getUrl(), isIncognito, true);
intent.putExtra(IntentHandler.EXTRA_ADDITIONAL_URLS, additionalUrls);
IntentHandler.startActivityForTrustedIntent(intent);
} else {
for (HistoryItem item : items) {
openUrl(item.getUrl(), isIncognito, true);
}
}
}
/**
* Open the provided url.
*
* @param url The url to open.
* @param isIncognito Whether to open the url in an incognito tab. If null, the tab will open in
* the current tab model.
* @param createNewTab Whether a new tab should be created. If false, the item will clobber the
* the current tab.
*/
public void openUrl(GURL url, Boolean isIncognito, boolean createNewTab) {
if (mIsSeparateActivity) {
// Only history entries are loaded into the existing tab.
if (launchedForApp() && !createNewTab) {
Intent intent = new Intent(ACTION_VIEW, Uri.parse(url.getSpec()));
intent.putExtra(IntentHandler.EXTRA_PAGE_TRANSITION_TYPE, PAGE_TRANSITION_TYPE);
mActivity.setResult(Activity.RESULT_OK, intent);
mActivity.finish();
} else {
IntentHandler.startActivityForTrustedIntent(
getOpenUrlIntent(url, isIncognito, createNewTab));
}
return;
}
assert mTabSupplier != null;
Tab tab = mTabSupplier.get();
assert tab != null;
if (createNewTab) {
new ChromeAsyncTabLauncher(isIncognito != null ? isIncognito : mIsIncognito)
.launchNewTab(
new LoadUrlParams(url, PAGE_TRANSITION_TYPE),
TabLaunchType.FROM_LINK,
tab);
} else {
tab.loadUrl(new LoadUrlParams(url, PAGE_TRANSITION_TYPE));
}
}
Intent getOpenUrlIntent(GURL url, Boolean isIncognito, boolean createNewTab) {
// Construct basic intent.
Intent viewIntent = createOpenUrlIntent(url, mActivity);
// Set other intent extras.
if (isIncognito != null) {
viewIntent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, isIncognito);
}
if (createNewTab) viewIntent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true);
return viewIntent;
}
/** @return Whether this class is displaying history for the incognito profile. */
public boolean isIncognito() {
return mIsIncognito;
}
/** @return The total number of history items in the content view. */
public int getItemCount() {
return mHistoryAdapter.getItemCount();
}
/**
* Adds the HistoryItem to the list of items being removed and removes it from the adapter. The
* removal will not be committed until #removeItems() is called.
* @param item The item to mark for removal.
*/
public void markItemForRemoval(HistoryItem item) {
mHistoryAdapter.markItemForRemoval(item);
}
/** Removes all items that have been marked for removal through #markItemForRemoval(). */
public void removeItems() {
mHistoryAdapter.removeItems();
}
/**
* Called after a user removes this HistoryItem.
* @param item The item that has been removed.
*/
public void onItemRemoved(HistoryItem item) {
mHistoryAdapter.markItemForRemoval(item);
mHistoryAdapter.removeItems();
announceItemRemoved(item);
mObserver.onItemRemoved(item);
}
void announceItemRemoved(HistoryItem item) {
mRecyclerView.announceForAccessibility(
mActivity.getString(R.string.delete_message, item.getTitle()));
}
void maybeResetAppFilterChip() {
if (showAppFilter()) mHistoryAdapter.resetAppFilterChip();
}
/**
* Called to perform a search.
*
* @param query The text to search for.
*/
public void search(String query) {
mHistoryAdapter.search(query);
}
/** Called when a search is ended. */
public void onEndSearch() {
mCurrentApp = null;
mHistoryAdapter.onEndSearch();
}
/**
* Called after a user clicks this HistoryItem.
* @param item The item that has been clicked.
*/
public void onItemClicked(HistoryItem item) {
mObserver.onItemClicked(item);
openUrl(item.getUrl(), null, false);
}
/** @return The {@link LargeIconBridge} used to fetch large favicons. */
public LargeIconBridge getLargeIconBridge() {
return mLargeIconBridge;
}
/** Called after a user clicks the clear data button. */
void onClearBrowsingDataClicked() {
mObserver.onClearBrowsingDataClicked();
}
/** Called after a user clicks the open full Chrome history button. */
void onOpenFullChromeHistoryClicked() {
mObserver.onOpenFullChromeHistoryClicked();
}
/** Called after a user clicks the filter by app button. */
void onAppFilterClicked() {
// Search mode starts with the soft keyboard open. Hide it first for the sheet
// to appear at the bottom as expected.
mHideSoftKeyboard.run();
if (mAppFilterSheet == null) {
mAppFilterSheet =
new AppFilterCoordinator(
mActivity,
mActivity.getWindow().getDecorView(),
mBottomSheetController.get(),
this::onAppUpdated,
mAppInfoList);
}
mAppFilterSheet.openSheet(mCurrentApp);
mUmaRecorder.recordAppFilterSheetOpened();
}
/** Callback from app filter sheet, with the newly chosen app to filter. */
@VisibleForTesting
void onAppUpdated(@Nullable AppInfo appInfo) {
if (Objects.equals(mCurrentApp, appInfo)) return;
mCurrentApp = appInfo;
getAdapter().updateHistory(mCurrentApp);
}
/** Removes the list header. */
public void removeHeader() {
mHistoryAdapter.removeHeader();
}
@Override
public void onSignedIn() {
mObserver.onUserAccountStateChanged();
mHistoryAdapter.onSignInStateChange();
}
@Override
public void onSignedOut() {
mObserver.onUserAccountStateChanged();
mHistoryAdapter.onSignInStateChange();
}
@Override
public void onPreferenceChange() {
mObserver.onUserAccountStateChanged();
mHistoryAdapter.onSignInStateChange();
}
/**
* Creates a view intent for opening the given url that is trusted and targets the correct main
* browsing activity.
*/
static Intent createOpenUrlIntent(GURL url, Activity activity) {
Intent viewIntent = new Intent(ACTION_VIEW, Uri.parse(url.getSpec()));
viewIntent.putExtra(
Browser.EXTRA_APPLICATION_ID, activity.getApplicationContext().getPackageName());
viewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
viewIntent.putExtra(IntentHandler.EXTRA_PAGE_TRANSITION_TYPE, PAGE_TRANSITION_TYPE);
// Determine component or class name.
ComponentName component;
if (activity instanceof HistoryActivity) { // phone
component =
IntentUtils.safeGetParcelableExtra(
activity.getIntent(), IntentHandler.EXTRA_PARENT_COMPONENT);
} else { // tablet
component = activity.getComponentName();
}
if (component != null) {
ActivityUtils.setNonAliasedComponentForMainBrowsingActivity(viewIntent, component);
} else {
viewIntent.setClass(activity, ChromeLauncherActivity.class);
}
IntentUtils.addTrustedIntentExtras(viewIntent);
return viewIntent;
}
static class AppInfoCache {
private static final AppInfo EMPTY_INFO = new AppInfo(null, null, null);
private HashMap<String, AppInfo> mAppInfoMap;
private PackageManager mPackageManager;
public AppInfoCache(PackageManager packageManager) {
mPackageManager = packageManager;
}
public AppInfo get(String appId) {
assert appId != null;
if (mAppInfoMap == null) mAppInfoMap = new HashMap<String, AppInfo>();
AppInfo appInfo = mAppInfoMap.get(appId);
if (appInfo == null) {
try {
PackageManager pm = mPackageManager;
var info = pm.getApplicationInfo(appId, PackageManager.GET_META_DATA);
var icon = pm.getApplicationIcon(info);
var label = pm.getApplicationLabel(info);
appInfo = new AppInfo(appId, icon, label);
} catch (NameNotFoundException e) {
// Can happen if the corresponding app was uninstalled, or unavailable for any
// reason. Map it with an empty info so it won't be queried again till next
// time history UI is launched.
appInfo = EMPTY_INFO;
}
mAppInfoMap.put(appId, appInfo);
}
return appInfo;
}
void setPackageManagerForTesting(PackageManager packageManager) {
mPackageManager = packageManager;
}
}
AppInfoCache getAppInfoCache() {
return mAppInfoCache;
}
/**
* @param provider The {@link HistoryProvider} that is used in place of a real one.
*/
public static void setProviderForTests(HistoryProvider provider) {
sProviderForTests = provider;
ResettersForTesting.register(() -> sProviderForTests = null);
}
/** @param isScrollToLoadDisabled Whether scrolling to load is disabled for tests. */
public static void setScrollToLoadDisabledForTesting(boolean isScrollToLoadDisabled) {
sIsScrollToLoadDisabledForTests = isScrollToLoadDisabled;
ResettersForTesting.register(() -> sIsScrollToLoadDisabledForTests = null);
}
void setPackageManagerForTesting(PackageManager packageManager) {
mAppInfoCache.setPackageManagerForTesting(packageManager); // IN-TEST
}
void setAppFilterSheetForTesting(AppFilterCoordinator appFilterSheet) {
mAppFilterSheet = appFilterSheet;
}
AppInfo getAppInfoForTesting() {
return mCurrentApp;
}
}