Files
app_Settings/src/com/android/settings/applications/ManageApplications.java
Daniel Nishi 31027dae93 Add a music apps view to the Storage Settings.
When you tap the Music & Audio preference, it takes you a
hybridized applications view. In this view, there also is
a line item which defines the amount of storage used by
audio files on the device. Using the new storage query, we
can add this information very quickly to the view.

Bug: 33199077
Test: Settings robo tests
Change-Id: If51cba0b3de20805543a39049367eb13613081e7
2017-01-25 11:56:02 -08:00

1395 lines
56 KiB
Java

/*
* Copyright (C) 2006 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 android.app.Activity;
import android.app.usage.StorageStatsManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.icu.text.AlphabeticIndex;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.LocaleList;
import android.os.UserHandle;
import android.os.UserManager;
import android.preference.PreferenceFrameLayout;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.SectionIndexer;
import android.widget.Spinner;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.AppHeader;
import com.android.settings.R;
import com.android.settings.Settings.AllApplicationsActivity;
import com.android.settings.Settings.GamesStorageActivity;
import com.android.settings.Settings.HighPowerApplicationsActivity;
import com.android.settings.Settings.ManageExternalSourcesActivity;
import com.android.settings.Settings.NotificationAppListActivity;
import com.android.settings.Settings.OverlaySettingsActivity;
import com.android.settings.Settings.StorageUseActivity;
import com.android.settings.Settings.UsageAccessSettingsActivity;
import com.android.settings.Settings.WriteSettingsActivity;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
import com.android.settings.applications.AppStateInstallAppsBridge.InstallAppsState;
import com.android.settings.applications.AppStateUsageBridge.UsageState;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.dashboard.SummaryLoader;
import com.android.settings.deviceinfo.storage.StorageStatsSource;
import com.android.settings.fuelgauge.HighPowerDetail;
import com.android.settings.fuelgauge.PowerWhitelistBackend;
import com.android.settings.notification.AppNotificationSettings;
import com.android.settings.notification.ConfigureNotificationSettings;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.notification.NotificationBackend.AppRow;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.AppFilter;
import com.android.settingslib.applications.ApplicationsState.CompoundFilter;
import com.android.settingslib.applications.ApplicationsState.VolumeFilter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
/**
* Activity to pick an application that will be used to display installation information and
* options to uninstall/delete user data for system applications. This activity
* can be launched through Settings or via the ACTION_MANAGE_PACKAGE_STORAGE
* intent.
*/
public class ManageApplications extends InstrumentedPreferenceFragment
implements OnItemClickListener, OnItemSelectedListener {
static final String TAG = "ManageApplications";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// Intent extras.
public static final String EXTRA_CLASSNAME = "classname";
// Used for storage only.
public static final String EXTRA_VOLUME_UUID = "volumeUuid";
public static final String EXTRA_VOLUME_NAME = "volumeName";
public static final String EXTRA_STORAGE_TYPE = "storageType";
private static final String EXTRA_SORT_ORDER = "sortOrder";
private static final String EXTRA_SHOW_SYSTEM = "showSystem";
private static final String EXTRA_HAS_ENTRIES = "hasEntries";
private static final String EXTRA_HAS_BRIDGE = "hasBridge";
// attributes used as keys when passing values to InstalledAppDetails activity
public static final String APP_CHG = "chg";
// constant value that can be used to check return code from sub activity.
private static final int INSTALLED_APP_DETAILS = 1;
private static final int ADVANCED_SETTINGS = 2;
public static final int SIZE_TOTAL = 0;
public static final int SIZE_INTERNAL = 1;
public static final int SIZE_EXTERNAL = 2;
// Filter options used for displayed list of applications
// The order which they appear is the order they will show when spinner is present.
public static final int FILTER_APPS_POWER_WHITELIST = 0;
public static final int FILTER_APPS_POWER_WHITELIST_ALL = 1;
public static final int FILTER_APPS_ALL = 2;
public static final int FILTER_APPS_ENABLED = 3;
public static final int FILTER_APPS_DISABLED = 4;
public static final int FILTER_APPS_BLOCKED = 5;
public static final int FILTER_APPS_PERSONAL = 6;
public static final int FILTER_APPS_WORK = 7;
public static final int FILTER_APPS_USAGE_ACCESS = 8;
public static final int FILTER_APPS_WITH_OVERLAY = 9;
public static final int FILTER_APPS_WRITE_SETTINGS = 10;
public static final int FILTER_APPS_INSTALL_SOURCES = 12;
// Storage types. Used to determine what the extra item in the list of preferences is.
public static final int STORAGE_TYPE_DEFAULT = 0;
public static final int STORAGE_TYPE_MUSIC = 1;
// This is the string labels for the filter modes above, the order must be kept in sync.
public static final int[] FILTER_LABELS = new int[]{
R.string.high_power_filter_on, // High power whitelist, on
R.string.filter_all_apps, // Without disabled until used
R.string.filter_all_apps, // All apps
R.string.filter_enabled_apps, // Enabled
R.string.filter_apps_disabled, // Disabled
R.string.filter_notif_blocked_apps, // Blocked Notifications
R.string.filter_personal_apps, // Personal
R.string.filter_work_apps, // Work
R.string.filter_with_domain_urls_apps, // Domain URLs
R.string.filter_all_apps, // Usage access screen, never displayed
R.string.filter_overlay_apps, // Apps with overlay permission
R.string.filter_write_settings_apps, // Apps that can write system settings
R.string.filter_install_sources_apps, // Apps that are trusted sources of apks
};
// This is the actual mapping to filters from FILTER_ constants above, the order must
// be kept in sync.
public static final AppFilter[] FILTERS = new AppFilter[]{
new CompoundFilter(AppStatePowerBridge.FILTER_POWER_WHITELISTED,
ApplicationsState.FILTER_ALL_ENABLED), // High power whitelist, on
new CompoundFilter(ApplicationsState.FILTER_WITHOUT_DISABLED_UNTIL_USED,
ApplicationsState.FILTER_ALL_ENABLED), // Without disabled until used
ApplicationsState.FILTER_EVERYTHING, // All apps
ApplicationsState.FILTER_ALL_ENABLED, // Enabled
ApplicationsState.FILTER_DISABLED, // Disabled
AppStateNotificationBridge.FILTER_APP_NOTIFICATION_BLOCKED, // Blocked Notifications
ApplicationsState.FILTER_PERSONAL, // Personal
ApplicationsState.FILTER_WORK, // Work
ApplicationsState.FILTER_WITH_DOMAIN_URLS, // Apps with Domain URLs
AppStateUsageBridge.FILTER_APP_USAGE, // Apps with Domain URLs
AppStateOverlayBridge.FILTER_SYSTEM_ALERT_WINDOW, // Apps that can draw overlays
AppStateWriteSettingsBridge.FILTER_WRITE_SETTINGS, // Apps that can write system settings
AppStateInstallAppsBridge.FILTER_APP_SOURCES,
};
// sort order
private int mSortOrder = R.id.sort_order_alpha;
// whether showing system apps.
private boolean mShowSystem;
private ApplicationsState mApplicationsState;
public int mListType;
public int mFilter;
public ApplicationsAdapter mApplications;
private View mLoadingContainer;
private View mListContainer;
// ListView used to display list
private ListView mListView;
// Size resource used for packages whose size computation failed for some reason
CharSequence mInvalidSizeStr;
// layout inflater object used to inflate views
private LayoutInflater mInflater;
private String mCurrentPkgName;
private int mCurrentUid;
private boolean mFinishAfterDialog;
private Menu mOptionsMenu;
public static final int LIST_TYPE_MAIN = 0;
public static final int LIST_TYPE_NOTIFICATION = 1;
public static final int LIST_TYPE_STORAGE = 3;
public static final int LIST_TYPE_USAGE_ACCESS = 4;
public static final int LIST_TYPE_HIGH_POWER = 5;
public static final int LIST_TYPE_OVERLAY = 6;
public static final int LIST_TYPE_WRITE_SETTINGS = 7;
public static final int LIST_TYPE_MANAGE_SOURCES = 8;
public static final int LIST_TYPE_GAMES = 9;
private View mRootView;
private View mSpinnerHeader;
private Spinner mFilterSpinner;
private FilterSpinnerAdapter mFilterAdapter;
private NotificationBackend mNotifBackend;
private ResetAppsHelper mResetAppsHelper;
private String mVolumeUuid;
private String mVolumeName;
private int mStorageType;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication());
Intent intent = getActivity().getIntent();
Bundle args = getArguments();
String className = args != null ? args.getString(EXTRA_CLASSNAME) : null;
if (className == null) {
className = intent.getComponent().getClassName();
}
if (className.equals(AllApplicationsActivity.class.getName())) {
mShowSystem = true;
} else if (className.equals(NotificationAppListActivity.class.getName())
|| this instanceof NotificationApps) {
mListType = LIST_TYPE_NOTIFICATION;
mNotifBackend = new NotificationBackend();
} else if (className.equals(StorageUseActivity.class.getName())) {
if (args != null && args.containsKey(EXTRA_VOLUME_UUID)) {
mVolumeUuid = args.getString(EXTRA_VOLUME_UUID);
mVolumeName = args.getString(EXTRA_VOLUME_NAME);
mStorageType = args.getInt(EXTRA_STORAGE_TYPE, STORAGE_TYPE_DEFAULT);
mListType = LIST_TYPE_STORAGE;
} else {
// No volume selected, display a normal list, sorted by size.
mListType = LIST_TYPE_MAIN;
}
mSortOrder = R.id.sort_order_size;
} else if (className.equals(UsageAccessSettingsActivity.class.getName())) {
mListType = LIST_TYPE_USAGE_ACCESS;
} else if (className.equals(HighPowerApplicationsActivity.class.getName())) {
mListType = LIST_TYPE_HIGH_POWER;
// Default to showing system.
mShowSystem = true;
} else if (className.equals(OverlaySettingsActivity.class.getName())) {
mListType = LIST_TYPE_OVERLAY;
} else if (className.equals(WriteSettingsActivity.class.getName())) {
mListType = LIST_TYPE_WRITE_SETTINGS;
} else if (className.equals(ManageExternalSourcesActivity.class.getName())) {
mListType = LIST_TYPE_MANAGE_SOURCES;
} else if (className.equals(GamesStorageActivity.class.getName())) {
mListType = LIST_TYPE_GAMES;
mSortOrder = R.id.sort_order_size;
} else {
mListType = LIST_TYPE_MAIN;
}
mFilter = getDefaultFilter();
if (savedInstanceState != null) {
mSortOrder = savedInstanceState.getInt(EXTRA_SORT_ORDER, mSortOrder);
mShowSystem = savedInstanceState.getBoolean(EXTRA_SHOW_SYSTEM, mShowSystem);
}
mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value);
mResetAppsHelper = new ResetAppsHelper(getActivity());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// initialize the inflater
mInflater = inflater;
mRootView = inflater.inflate(R.layout.manage_applications_apps, null);
mLoadingContainer = mRootView.findViewById(R.id.loading_container);
mLoadingContainer.setVisibility(View.VISIBLE);
mListContainer = mRootView.findViewById(R.id.list_container);
if (mListContainer != null) {
// Create adapter and list view here
View emptyView = mListContainer.findViewById(com.android.internal.R.id.empty);
ListView lv = (ListView) mListContainer.findViewById(android.R.id.list);
if (emptyView != null) {
lv.setEmptyView(emptyView);
}
lv.setOnItemClickListener(this);
lv.setSaveEnabled(true);
lv.setItemsCanFocus(true);
lv.setTextFilterEnabled(true);
mListView = lv;
mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter);
if (savedInstanceState != null) {
mApplications.mHasReceivedLoadEntries =
savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false);
mApplications.mHasReceivedBridgeCallback =
savedInstanceState.getBoolean(EXTRA_HAS_BRIDGE, false);
}
if (mStorageType == STORAGE_TYPE_MUSIC) {
Context context = getContext();
mApplications.setExtraViewController(new MusicViewHolderController(
context,
new StorageStatsSource(context),
mVolumeUuid));
}
mListView.setAdapter(mApplications);
mListView.setRecyclerListener(mApplications);
mListView.setFastScrollEnabled(isFastScrollEnabled());
Utils.prepareCustomPreferencesList(container, mRootView, mListView, false);
}
// We have to do this now because PreferenceFrameLayout looks at it
// only when the view is added.
if (container instanceof PreferenceFrameLayout) {
((PreferenceFrameLayout.LayoutParams) mRootView.getLayoutParams()).removeBorders = true;
}
createHeader();
mResetAppsHelper.onRestoreInstanceState(savedInstanceState);
return mRootView;
}
private void createHeader() {
Activity activity = getActivity();
FrameLayout pinnedHeader = (FrameLayout) mRootView.findViewById(R.id.pinned_header);
mSpinnerHeader = activity.getLayoutInflater()
.inflate(R.layout.apps_filter_spinner, pinnedHeader, false);
mFilterSpinner = (Spinner) mSpinnerHeader.findViewById(R.id.filter_spinner);
mFilterAdapter = new FilterSpinnerAdapter(this);
mFilterSpinner.setAdapter(mFilterAdapter);
mFilterSpinner.setOnItemSelectedListener(this);
pinnedHeader.addView(mSpinnerHeader, 0);
mFilterAdapter.enableFilter(getDefaultFilter());
if (mListType == LIST_TYPE_MAIN) {
if (UserManager.get(getActivity()).getUserProfiles().size() > 1) {
mFilterAdapter.enableFilter(FILTER_APPS_PERSONAL);
mFilterAdapter.enableFilter(FILTER_APPS_WORK);
}
}
if (mListType == LIST_TYPE_NOTIFICATION) {
mFilterAdapter.enableFilter(FILTER_APPS_BLOCKED);
}
if (mListType == LIST_TYPE_HIGH_POWER) {
mFilterAdapter.enableFilter(FILTER_APPS_POWER_WHITELIST_ALL);
}
if (mListType == LIST_TYPE_STORAGE) {
AppFilter filter = new VolumeFilter(mVolumeUuid);
if (mStorageType == STORAGE_TYPE_MUSIC) {
filter = new CompoundFilter(ApplicationsState.FILTER_AUDIO, filter);
}
mApplications.setOverrideFilter(filter);
}
if (mListType == LIST_TYPE_GAMES) {
mApplications.setOverrideFilter(ApplicationsState.FILTER_GAMES);
}
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (mListType == LIST_TYPE_STORAGE) {
final Activity activity = getActivity();
final boolean isNewIAEnabled = FeatureFactory.getFactory(activity)
.getDashboardFeatureProvider(activity)
.isEnabled();
if (!isNewIAEnabled) {
FrameLayout pinnedHeader = (FrameLayout) mRootView.findViewById(R.id.pinned_header);
AppHeader.createAppHeader(getActivity(), null, mVolumeName, null, -1, pinnedHeader);
}
}
}
private int getDefaultFilter() {
switch (mListType) {
case LIST_TYPE_USAGE_ACCESS:
return FILTER_APPS_USAGE_ACCESS;
case LIST_TYPE_HIGH_POWER:
return FILTER_APPS_POWER_WHITELIST;
case LIST_TYPE_OVERLAY:
return FILTER_APPS_WITH_OVERLAY;
case LIST_TYPE_WRITE_SETTINGS:
return FILTER_APPS_WRITE_SETTINGS;
case LIST_TYPE_MANAGE_SOURCES:
return FILTER_APPS_INSTALL_SOURCES;
default:
return FILTER_APPS_ALL;
}
}
private boolean isFastScrollEnabled() {
switch (mListType) {
case LIST_TYPE_MAIN:
case LIST_TYPE_NOTIFICATION:
case LIST_TYPE_STORAGE:
case LIST_TYPE_GAMES:
return mSortOrder == R.id.sort_order_alpha;
default:
return false;
}
}
@Override
public int getMetricsCategory() {
switch (mListType) {
case LIST_TYPE_MAIN:
return MetricsEvent.MANAGE_APPLICATIONS;
case LIST_TYPE_NOTIFICATION:
return MetricsEvent.MANAGE_APPLICATIONS_NOTIFICATIONS;
case LIST_TYPE_STORAGE:
case LIST_TYPE_GAMES:
return MetricsEvent.APPLICATIONS_STORAGE_APPS;
case LIST_TYPE_USAGE_ACCESS:
return MetricsEvent.USAGE_ACCESS;
case LIST_TYPE_HIGH_POWER:
return MetricsEvent.APPLICATIONS_HIGH_POWER_APPS;
case LIST_TYPE_OVERLAY:
return MetricsEvent.SYSTEM_ALERT_WINDOW_APPS;
case LIST_TYPE_WRITE_SETTINGS:
return MetricsEvent.SYSTEM_ALERT_WINDOW_APPS;
case LIST_TYPE_MANAGE_SOURCES:
return MetricsEvent.MANAGE_EXTERNAL_SOURCES;
default:
return MetricsEvent.VIEW_UNKNOWN;
}
}
@Override
public void onResume() {
super.onResume();
updateView();
updateOptionsMenu();
if (mApplications != null) {
mApplications.resume(mSortOrder);
mApplications.updateLoading();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mResetAppsHelper.onSaveInstanceState(outState);
outState.putInt(EXTRA_SORT_ORDER, mSortOrder);
outState.putBoolean(EXTRA_SHOW_SYSTEM, mShowSystem);
outState.putBoolean(EXTRA_HAS_ENTRIES, mApplications.mHasReceivedLoadEntries);
outState.putBoolean(EXTRA_HAS_BRIDGE, mApplications.mHasReceivedBridgeCallback);
}
@Override
public void onPause() {
super.onPause();
if (mApplications != null) {
mApplications.pause();
}
}
@Override
public void onStop() {
super.onStop();
mResetAppsHelper.stop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (mApplications != null) {
mApplications.release();
}
mRootView = null;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) {
if (mListType == LIST_TYPE_NOTIFICATION) {
mApplications.mExtraInfoBridge.forceUpdate(mCurrentPkgName, mCurrentUid);
} else if (mListType == LIST_TYPE_HIGH_POWER || mListType == LIST_TYPE_OVERLAY
|| mListType == LIST_TYPE_WRITE_SETTINGS) {
if (mFinishAfterDialog) {
getActivity().onBackPressed();
} else {
mApplications.mExtraInfoBridge.forceUpdate(mCurrentPkgName, mCurrentUid);
}
} else {
mApplicationsState.requestSize(mCurrentPkgName, UserHandle.getUserId(mCurrentUid));
}
}
}
// utility method used to start sub activity
private void startApplicationDetailsActivity() {
switch (mListType) {
case LIST_TYPE_NOTIFICATION:
startAppInfoFragment(AppNotificationSettings.class,
R.string.app_notifications_title);
break;
case LIST_TYPE_USAGE_ACCESS:
startAppInfoFragment(UsageAccessDetails.class, R.string.usage_access);
break;
case LIST_TYPE_STORAGE:
startAppInfoFragment(AppStorageSettings.class, R.string.storage_settings);
break;
case LIST_TYPE_HIGH_POWER:
HighPowerDetail.show(this, mCurrentPkgName, INSTALLED_APP_DETAILS,
mFinishAfterDialog);
break;
case LIST_TYPE_OVERLAY:
startAppInfoFragment(DrawOverlayDetails.class, R.string.overlay_settings);
break;
case LIST_TYPE_WRITE_SETTINGS:
startAppInfoFragment(WriteSettingsDetails.class, R.string.write_system_settings);
break;
case LIST_TYPE_MANAGE_SOURCES:
startAppInfoFragment(ExternalSourcesDetails.class, R.string.install_other_apps);
case LIST_TYPE_GAMES:
startAppInfoFragment(AppStorageSettings.class, R.string.game_storage_settings);
break;
// TODO: Figure out if there is a way where we can spin up the profile's settings
// process ahead of time, to avoid a long load of data when user clicks on a managed app.
// Maybe when they load the list of apps that contains managed profile apps.
default:
startAppInfoFragment(InstalledAppDetails.class, R.string.application_info_label);
break;
}
}
private void startAppInfoFragment(Class<?> fragment, int titleRes) {
AppInfoBase.startAppInfoFragment(fragment, titleRes, mCurrentPkgName, mCurrentUid, this,
INSTALLED_APP_DETAILS);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
HelpUtils.prepareHelpMenuItem(getActivity(), menu, mListType == LIST_TYPE_MAIN
? R.string.help_uri_apps : R.string.help_uri_notifications, getClass().getName());
mOptionsMenu = menu;
inflater.inflate(R.menu.manage_apps, menu);
updateOptionsMenu();
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
updateOptionsMenu();
}
@Override
public void onDestroyOptionsMenu() {
mOptionsMenu = null;
}
void updateOptionsMenu() {
if (mOptionsMenu == null) {
return;
}
final Context context = getActivity();
if (FeatureFactory.getFactory(context).getDashboardFeatureProvider(context).isEnabled()) {
mOptionsMenu.findItem(R.id.advanced).setVisible(false);
} else {
mOptionsMenu.findItem(R.id.advanced).setVisible(
mListType == LIST_TYPE_MAIN || mListType == LIST_TYPE_NOTIFICATION);
}
mOptionsMenu.findItem(R.id.sort_order_alpha).setVisible(mListType == LIST_TYPE_STORAGE
&& mSortOrder != R.id.sort_order_alpha);
mOptionsMenu.findItem(R.id.sort_order_size).setVisible(mListType == LIST_TYPE_STORAGE
&& mSortOrder != R.id.sort_order_size);
mOptionsMenu.findItem(R.id.show_system).setVisible(!mShowSystem
&& mListType != LIST_TYPE_HIGH_POWER);
mOptionsMenu.findItem(R.id.hide_system).setVisible(mShowSystem
&& mListType != LIST_TYPE_HIGH_POWER);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int menuId = item.getItemId();
switch (item.getItemId()) {
case R.id.sort_order_alpha:
case R.id.sort_order_size:
mSortOrder = menuId;
mListView.setFastScrollEnabled(isFastScrollEnabled());
if (mApplications != null) {
mApplications.rebuild(mSortOrder);
}
break;
case R.id.show_system:
case R.id.hide_system:
mShowSystem = !mShowSystem;
mApplications.rebuild(false);
break;
case R.id.reset_app_preferences:
mResetAppsHelper.buildResetDialog();
return true;
case R.id.advanced:
if (mListType == LIST_TYPE_NOTIFICATION) {
((SettingsActivity) getActivity()).startPreferencePanel(
ConfigureNotificationSettings.class.getName(), null,
R.string.configure_notification_settings, null, this, ADVANCED_SETTINGS);
} else {
((SettingsActivity) getActivity()).startPreferencePanel(
AdvancedAppSettings.class.getName(), null, R.string.configure_apps,
null, this, ADVANCED_SETTINGS);
}
return true;
default:
// Handle the home button
return false;
}
updateOptionsMenu();
return true;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mApplications == null) {
return;
}
if (mApplications.getApplicationCount() > position) {
ApplicationsState.AppEntry entry = mApplications.getAppEntry(position);
mCurrentPkgName = entry.info.packageName;
mCurrentUid = entry.info.uid;
startApplicationDetailsActivity();
} else {
mApplications.mExtraViewController.onClick(this);
}
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mFilter = mFilterAdapter.getFilter(position);
mApplications.setFilter(mFilter);
if (DEBUG) Log.d(TAG, "Selecting filter " + mFilter);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
public void updateView() {
updateOptionsMenu();
final Activity host = getActivity();
if (host != null) {
host.invalidateOptionsMenu();
}
}
public void setHasDisabled(boolean hasDisabledApps) {
if (mListType != LIST_TYPE_MAIN) {
return;
}
mFilterAdapter.setFilterEnabled(FILTER_APPS_ENABLED, hasDisabledApps);
mFilterAdapter.setFilterEnabled(FILTER_APPS_DISABLED, hasDisabledApps);
}
static class FilterSpinnerAdapter extends ArrayAdapter<CharSequence> {
private final ManageApplications mManageApplications;
// Use ArrayAdapter for view logic, but have our own list for managing
// the options available.
private final ArrayList<Integer> mFilterOptions = new ArrayList<>();
public FilterSpinnerAdapter(ManageApplications manageApplications) {
super(manageApplications.mFilterSpinner.getContext(), R.layout.filter_spinner_item);
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mManageApplications = manageApplications;
}
public int getFilter(int position) {
return mFilterOptions.get(position);
}
public void setFilterEnabled(int filter, boolean enabled) {
if (enabled) {
enableFilter(filter);
} else {
disableFilter(filter);
}
}
public void enableFilter(int filter) {
if (mFilterOptions.contains(filter)) return;
if (DEBUG) Log.d(TAG, "Enabling filter " + filter);
mFilterOptions.add(filter);
Collections.sort(mFilterOptions);
mManageApplications.mSpinnerHeader.setVisibility(
mFilterOptions.size() > 1 ? View.VISIBLE : View.GONE);
notifyDataSetChanged();
if (mFilterOptions.size() == 1) {
if (DEBUG) Log.d(TAG, "Auto selecting filter " + filter);
mManageApplications.mFilterSpinner.setSelection(0);
mManageApplications.onItemSelected(null, null, 0, 0);
}
}
public void disableFilter(int filter) {
if (!mFilterOptions.remove((Integer) filter)) {
return;
}
if (DEBUG) Log.d(TAG, "Disabling filter " + filter);
Collections.sort(mFilterOptions);
mManageApplications.mSpinnerHeader.setVisibility(
mFilterOptions.size() > 1 ? View.VISIBLE : View.GONE);
notifyDataSetChanged();
if (mManageApplications.mFilter == filter) {
if (mFilterOptions.size() > 0) {
if (DEBUG) Log.d(TAG, "Auto selecting filter " + mFilterOptions.get(0));
mManageApplications.mFilterSpinner.setSelection(0);
mManageApplications.onItemSelected(null, null, 0, 0);
}
}
}
@Override
public int getCount() {
return mFilterOptions.size();
}
@Override
public CharSequence getItem(int position) {
return getFilterString(mFilterOptions.get(position));
}
private CharSequence getFilterString(int filter) {
return mManageApplications.getString(FILTER_LABELS[filter]);
}
}
/*
* Custom adapter implementation for the ListView
* This adapter maintains a map for each displayed application and its properties
* An index value on each AppInfo object indicates the correct position or index
* in the list. If the list gets updated dynamically when the user is viewing the list of
* applications, we need to return the correct index of position. This is done by mapping
* the getId methods via the package name into the internal maps and indices.
* The order of applications in the list is mirrored in mAppLocalList
*/
static class ApplicationsAdapter extends BaseAdapter implements Filterable,
ApplicationsState.Callbacks, AppStateBaseBridge.Callback,
AbsListView.RecyclerListener, SectionIndexer {
private static final SectionInfo[] EMPTY_SECTIONS = new SectionInfo[0];
private final ApplicationsState mState;
private final ApplicationsState.Session mSession;
private final ManageApplications mManageApplications;
private final Context mContext;
private final ArrayList<View> mActive = new ArrayList<View>();
private final AppStateBaseBridge mExtraInfoBridge;
private final Handler mBgHandler;
private final Handler mFgHandler;
private int mFilterMode;
private ArrayList<ApplicationsState.AppEntry> mBaseEntries;
private ArrayList<ApplicationsState.AppEntry> mEntries;
private boolean mResumed;
private int mLastSortMode = -1;
private int mWhichSize = SIZE_TOTAL;
CharSequence mCurFilterPrefix;
private PackageManager mPm;
private AppFilter mOverrideFilter;
private boolean mHasReceivedLoadEntries;
private boolean mHasReceivedBridgeCallback;
private FileViewHolderController mExtraViewController;
// These two variables are used to remember and restore the last scroll position when this
// fragment is paused. We need this special handling because app entries are added gradually
// when we rebuild the list after the user made some changes, like uninstalling an app.
private int mLastIndex = -1;
private int mLastTop;
private AlphabeticIndex.ImmutableIndex<Locale> mIndex;
private SectionInfo[] mSections = EMPTY_SECTIONS;
private int[] mPositionToSectionIndex;
private Filter mFilter = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
ArrayList<ApplicationsState.AppEntry> entries
= applyPrefixFilter(constraint, mBaseEntries);
FilterResults fr = new FilterResults();
fr.values = entries;
fr.count = entries.size();
return fr;
}
@Override
@SuppressWarnings("unchecked")
protected void publishResults(CharSequence constraint, FilterResults results) {
mCurFilterPrefix = constraint;
mEntries = (ArrayList<ApplicationsState.AppEntry>) results.values;
rebuildSections();
notifyDataSetChanged();
}
};
public ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications,
int filterMode) {
mState = state;
mFgHandler = new Handler();
mBgHandler = new Handler(mState.getBackgroundLooper());
mSession = state.newSession(this);
mManageApplications = manageApplications;
mContext = manageApplications.getActivity();
mPm = mContext.getPackageManager();
mFilterMode = filterMode;
if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
mExtraInfoBridge = new AppStateNotificationBridge(mContext, mState, this,
manageApplications.mNotifBackend);
} else if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
mExtraInfoBridge = new AppStateUsageBridge(mContext, mState, this);
} else if (mManageApplications.mListType == LIST_TYPE_HIGH_POWER) {
mExtraInfoBridge = new AppStatePowerBridge(mState, this);
} else if (mManageApplications.mListType == LIST_TYPE_OVERLAY) {
mExtraInfoBridge = new AppStateOverlayBridge(mContext, mState, this);
} else if (mManageApplications.mListType == LIST_TYPE_WRITE_SETTINGS) {
mExtraInfoBridge = new AppStateWriteSettingsBridge(mContext, mState, this);
} else if (mManageApplications.mListType == LIST_TYPE_MANAGE_SOURCES) {
mExtraInfoBridge = new AppStateInstallAppsBridge(mContext, mState, this);
} else {
mExtraInfoBridge = null;
}
}
public void setOverrideFilter(AppFilter overrideFilter) {
mOverrideFilter = overrideFilter;
rebuild(true);
}
public void setFilter(int filter) {
mFilterMode = filter;
rebuild(true);
}
public void setExtraViewController(FileViewHolderController extraViewController) {
mExtraViewController = extraViewController;
}
public void resume(int sort) {
if (DEBUG) Log.i(TAG, "Resume! mResumed=" + mResumed);
if (!mResumed) {
mResumed = true;
mSession.resume();
mLastSortMode = sort;
if (mExtraInfoBridge != null) {
mExtraInfoBridge.resume();
}
rebuild(false);
} else {
rebuild(sort);
}
}
public void pause() {
if (mResumed) {
mResumed = false;
mSession.pause();
if (mExtraInfoBridge != null) {
mExtraInfoBridge.pause();
}
}
// Record the current scroll position before pausing.
mLastIndex = mManageApplications.mListView.getFirstVisiblePosition();
View v = mManageApplications.mListView.getChildAt(0);
mLastTop = (v == null) ? 0 : (v.getTop() - mManageApplications.mListView.getPaddingTop());
}
public void release() {
mSession.release();
if (mExtraInfoBridge != null) {
mExtraInfoBridge.release();
}
}
public void rebuild(int sort) {
if (sort == mLastSortMode) {
return;
}
mLastSortMode = sort;
rebuild(true);
}
public void rebuild(boolean eraseold) {
if (!mHasReceivedLoadEntries
|| (mExtraInfoBridge != null && !mHasReceivedBridgeCallback)) {
// Don't rebuild the list until all the app entries are loaded.
return;
}
ApplicationsState.AppFilter filterObj;
Comparator<AppEntry> comparatorObj;
boolean emulated = Environment.isExternalStorageEmulated();
if (emulated) {
mWhichSize = SIZE_TOTAL;
} else {
mWhichSize = SIZE_INTERNAL;
}
filterObj = FILTERS[mFilterMode];
if (mOverrideFilter != null) {
filterObj = mOverrideFilter;
}
if (!mManageApplications.mShowSystem) {
filterObj = new CompoundFilter(filterObj,
ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER);
}
switch (mLastSortMode) {
case R.id.sort_order_size:
switch (mWhichSize) {
case SIZE_INTERNAL:
comparatorObj = ApplicationsState.INTERNAL_SIZE_COMPARATOR;
break;
case SIZE_EXTERNAL:
comparatorObj = ApplicationsState.EXTERNAL_SIZE_COMPARATOR;
break;
default:
comparatorObj = ApplicationsState.SIZE_COMPARATOR;
break;
}
break;
default:
comparatorObj = ApplicationsState.ALPHA_COMPARATOR;
break;
}
if (mExtraViewController != null) {
mExtraViewController.queryStats();
}
filterObj = new CompoundFilter(filterObj, ApplicationsState.FILTER_NOT_HIDE);
AppFilter finalFilterObj = filterObj;
mBgHandler.post(() -> {
final ArrayList<AppEntry> entries = mSession.rebuild(finalFilterObj,
comparatorObj, false);
if (entries != null) {
mFgHandler.post(() -> onRebuildComplete(entries));
}
});
}
static private boolean packageNameEquals(PackageItemInfo info1, PackageItemInfo info2) {
if (info1 == null || info2 == null) {
return false;
}
if (info1.packageName == null || info2.packageName == null) {
return false;
}
return info1.packageName.equals(info2.packageName);
}
private ArrayList<ApplicationsState.AppEntry> removeDuplicateIgnoringUser(
ArrayList<ApplicationsState.AppEntry> entries)
{
int size = entries.size();
// returnList will not have more entries than entries
ArrayList<ApplicationsState.AppEntry> returnEntries = new
ArrayList<ApplicationsState.AppEntry>(size);
// assume appinfo of same package but different users are grouped together
PackageItemInfo lastInfo = null;
for (int i = 0; i < size; i++) {
AppEntry appEntry = entries.get(i);
PackageItemInfo info = appEntry.info;
if (!packageNameEquals(lastInfo, appEntry.info)) {
returnEntries.add(appEntry);
}
lastInfo = info;
}
returnEntries.trimToSize();
return returnEntries;
}
@Override
public void onRebuildComplete(ArrayList<AppEntry> entries) {
if (mFilterMode == FILTER_APPS_POWER_WHITELIST ||
mFilterMode == FILTER_APPS_POWER_WHITELIST_ALL) {
entries = removeDuplicateIgnoringUser(entries);
}
mBaseEntries = entries;
if (mBaseEntries != null) {
mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries);
rebuildSections();
} else {
mEntries = null;
mSections = EMPTY_SECTIONS;
mPositionToSectionIndex = null;
}
notifyDataSetChanged();
// Restore the last scroll position if the number of entries added so far is bigger than
// it.
if (mLastIndex != -1 && getCount() > mLastIndex) {
mManageApplications.mListView.setSelectionFromTop(mLastIndex, mLastTop);
mLastIndex = -1;
}
if (mSession.getAllApps().size() != 0
&& mManageApplications.mListContainer.getVisibility() != View.VISIBLE) {
Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
mManageApplications.mListContainer, true, true);
}
if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
// No enabled or disabled filters for usage access.
return;
}
mManageApplications.setHasDisabled(mState.haveDisabledApps());
}
private void rebuildSections() {
if (mEntries!= null && mManageApplications.mListView.isFastScrollEnabled()) {
// Rebuild sections
if (mIndex == null) {
LocaleList locales = mContext.getResources().getConfiguration().getLocales();
if (locales.size() == 0) {
locales = new LocaleList(Locale.ENGLISH);
}
AlphabeticIndex<Locale> index = new AlphabeticIndex<>(locales.get(0));
int localeCount = locales.size();
for (int i = 1; i < localeCount; i++) {
index.addLabels(locales.get(i));
}
// Ensure we always have some base English locale buckets
index.addLabels(Locale.ENGLISH);
mIndex = index.buildImmutableIndex();
}
ArrayList<SectionInfo> sections = new ArrayList<>();
int lastSecId = -1;
int totalEntries = mEntries.size();
mPositionToSectionIndex = new int[totalEntries];
for (int pos = 0; pos < totalEntries; pos++) {
String label = mEntries.get(pos).label;
int secId = mIndex.getBucketIndex(TextUtils.isEmpty(label) ? "" : label);
if (secId != lastSecId) {
lastSecId = secId;
sections.add(new SectionInfo(mIndex.getBucket(secId).getLabel(), pos));
}
mPositionToSectionIndex[pos] = sections.size() - 1;
}
mSections = sections.toArray(EMPTY_SECTIONS);
} else {
mSections = EMPTY_SECTIONS;
mPositionToSectionIndex = null;
}
}
private void updateLoading() {
Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
mManageApplications.mListContainer,
mHasReceivedLoadEntries && mSession.getAllApps().size() != 0, false);
}
ArrayList<ApplicationsState.AppEntry> applyPrefixFilter(CharSequence prefix,
ArrayList<ApplicationsState.AppEntry> origEntries) {
if (prefix == null || prefix.length() == 0) {
return origEntries;
} else {
String prefixStr = ApplicationsState.normalize(prefix.toString());
final String spacePrefixStr = " " + prefixStr;
ArrayList<ApplicationsState.AppEntry> newEntries
= new ArrayList<ApplicationsState.AppEntry>();
for (int i = 0; i < origEntries.size(); i++) {
ApplicationsState.AppEntry entry = origEntries.get(i);
String nlabel = entry.getNormalizedLabel();
if (nlabel.startsWith(prefixStr) || nlabel.indexOf(spacePrefixStr) != -1) {
newEntries.add(entry);
}
}
return newEntries;
}
}
@Override
public void onExtraInfoUpdated() {
mHasReceivedBridgeCallback = true;
rebuild(false);
}
@Override
public void onRunningStateChanged(boolean running) {
mManageApplications.getActivity().setProgressBarIndeterminateVisibility(running);
}
@Override
public void onPackageListChanged() {
rebuild(false);
}
@Override
public void onPackageIconChanged() {
// We ensure icons are loaded when their item is displayed, so
// don't care about icons loaded in the background.
}
@Override
public void onLoadEntriesCompleted() {
mHasReceivedLoadEntries = true;
// We may have been skipping rebuilds until this came in, trigger one now.
rebuild(false);
}
@Override
public void onPackageSizeChanged(String packageName) {
for (int i = 0; i < mActive.size(); i++) {
AppViewHolder holder = (AppViewHolder) mActive.get(i).getTag();
ApplicationInfo info = holder.entry.info;
if (info == null) {
continue;
}
if (holder.entry.info.packageName.equals(packageName)) {
synchronized (holder.entry) {
updateSummary(holder);
}
if (holder.entry.info.packageName.equals(mManageApplications.mCurrentPkgName)
&& mLastSortMode == R.id.sort_order_size) {
// We got the size information for the last app the
// user viewed, and are sorting by size... they may
// have cleared data, so we immediately want to resort
// the list with the new size to reflect it to the user.
rebuild(false);
}
return;
}
}
}
@Override
public void onLauncherInfoChanged() {
if (!mManageApplications.mShowSystem) {
rebuild(false);
}
}
@Override
public void onAllSizesComputed() {
if (mLastSortMode == R.id.sort_order_size) {
rebuild(false);
}
}
public int getCount() {
if (mEntries == null) {
return 0;
}
int extraViewAddition =
(mExtraViewController != null && mExtraViewController.shouldShow()) ? 1 : 0;
return mEntries.size() + extraViewAddition;
}
public int getApplicationCount() {
return mEntries != null ? mEntries.size() : 0;
}
public Object getItem(int position) {
if (position == mEntries.size()) {
return mExtraViewController;
}
return mEntries.get(position);
}
public ApplicationsState.AppEntry getAppEntry(int position) {
return mEntries.get(position);
}
public long getItemId(int position) {
if (position == mEntries.size()) {
return -1;
}
return mEntries.get(position).id;
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public boolean isEnabled(int position) {
if (position == mEntries.size() && mExtraViewController != null &&
mExtraViewController.shouldShow()) {
return true;
}
if (mManageApplications.mListType != LIST_TYPE_HIGH_POWER) {
return true;
}
ApplicationsState.AppEntry entry = mEntries.get(position);
return !PowerWhitelistBackend.getInstance().isSysWhitelisted(entry.info.packageName);
}
public View getView(int position, View convertView, ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unnecessary calls
// to findViewById() on each row.
AppViewHolder holder = AppViewHolder.createOrRecycle(mManageApplications.mInflater,
convertView);
convertView = holder.rootView;
// Handle the extra view if it is the last entry.
if (mEntries != null && mExtraViewController != null && position == mEntries.size()) {
mExtraViewController.setupView(holder);
convertView.setEnabled(true);
} else {
// Bind the data efficiently with the holder
ApplicationsState.AppEntry entry = mEntries.get(position);
synchronized (entry) {
holder.entry = entry;
if (entry.label != null) {
holder.appName.setText(entry.label);
}
mState.ensureIcon(entry);
if (entry.icon != null) {
holder.appIcon.setImageDrawable(entry.icon);
}
updateSummary(holder);
if ((entry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
holder.disabled.setVisibility(View.VISIBLE);
holder.disabled.setText(R.string.not_installed);
} else if (!entry.info.enabled) {
holder.disabled.setVisibility(View.VISIBLE);
holder.disabled.setText(R.string.disabled);
} else {
holder.disabled.setVisibility(View.GONE);
}
}
convertView.setEnabled(isEnabled(position));
}
mActive.remove(convertView);
mActive.add(convertView);
return convertView;
}
private void updateSummary(AppViewHolder holder) {
switch (mManageApplications.mListType) {
case LIST_TYPE_NOTIFICATION:
if (holder.entry.extraInfo != null) {
holder.summary.setText(InstalledAppDetails.getNotificationSummary(
(AppRow) holder.entry.extraInfo, mContext));
} else {
holder.summary.setText(null);
}
break;
case LIST_TYPE_USAGE_ACCESS:
if (holder.entry.extraInfo != null) {
holder.summary.setText((new UsageState((PermissionState) holder.entry
.extraInfo)).isPermissible() ? R.string.switch_on_text :
R.string.switch_off_text);
} else {
holder.summary.setText(null);
}
break;
case LIST_TYPE_HIGH_POWER:
holder.summary.setText(HighPowerDetail.getSummary(mContext, holder.entry));
break;
case LIST_TYPE_OVERLAY:
holder.summary.setText(DrawOverlayDetails.getSummary(mContext, holder.entry));
break;
case LIST_TYPE_WRITE_SETTINGS:
holder.summary.setText(WriteSettingsDetails.getSummary(mContext,
holder.entry));
break;
case LIST_TYPE_MANAGE_SOURCES:
holder.summary
.setText(((InstallAppsState) holder.entry.extraInfo).getSummary());
break;
default:
holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
break;
}
}
@Override
public Filter getFilter() {
return mFilter;
}
@Override
public void onMovedToScrapHeap(View view) {
mActive.remove(view);
}
@Override
public Object[] getSections() {
return mSections;
}
@Override
public int getPositionForSection(int sectionIndex) {
return mSections[sectionIndex].position;
}
@Override
public int getSectionForPosition(int position) {
return mPositionToSectionIndex[position];
}
}
private static class SummaryProvider implements SummaryLoader.SummaryProvider {
private final Context mContext;
private final SummaryLoader mLoader;
private ApplicationsState.Session mSession;
private SummaryProvider(Context context, SummaryLoader loader) {
mContext = context;
mLoader = loader;
}
@Override
public void setListening(boolean listening) {
if (listening) {
new InstalledAppCounter(mContext, ApplicationFeatureProvider.IGNORE_INSTALL_REASON,
new PackageManagerWrapperImpl(mContext.getPackageManager())) {
@Override
protected void onCountComplete(int num) {
mLoader.setSummary(SummaryProvider.this,
mContext.getString(R.string.apps_summary, num));
}
@Override
protected List<UserInfo> getUsersToCount() {
return mUm.getProfiles(UserHandle.myUserId());
}
}.execute();
}
}
}
private static class SectionInfo {
final String label;
final int position;
public SectionInfo(String label, int position) {
this.label = label;
this.position = position;
}
@Override
public String toString() {
return label;
}
}
public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
= new SummaryLoader.SummaryProviderFactory() {
@Override
public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
SummaryLoader summaryLoader) {
return new SummaryProvider(activity, summaryLoader);
}
};
}