/* * 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.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.ResolveInfo; 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.util.ArraySet; 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.MetricsProto.MetricsEvent; import com.android.settings.AppHeader; import com.android.settings.InstrumentedFragment; import com.android.settings.R; import com.android.settings.Settings.AllApplicationsActivity; import com.android.settings.Settings.HighPowerApplicationsActivity; 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.AppStateUsageBridge.UsageState; import com.android.settings.dashboard.SummaryLoader; 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.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 InstrumentedFragment 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"; 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_SILENT = 6; public static final int FILTER_APPS_SENSITIVE = 7; public static final int FILTER_APPS_HIDE_NOTIFICATIONS = 8; public static final int FILTER_APPS_PRIORITY = 9; public static final int FILTER_APPS_PERSONAL = 10; public static final int FILTER_APPS_WORK = 11; public static final int FILTER_APPS_USAGE_ACCESS = 13; public static final int FILTER_APPS_WITH_OVERLAY = 14; public static final int FILTER_APPS_WRITE_SETTINGS = 15; // 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_notif_silent, // Silenced Notifications R.string.filter_notif_sensitive_apps, // Sensitive Notifications R.string.filter_notif_hide_notifications_apps, // Sensitive Notifications R.string.filter_notif_priority_apps, // Priority 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 }; // 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 AppStateNotificationBridge.FILTER_APP_NOTIFICATION_SILENCED, // Silenced Notifications AppStateNotificationBridge.FILTER_APP_NOTIFICATION_HIDE_SENSITIVE, // Sensitive Notifications AppStateNotificationBridge.FILTER_APP_NOTIFICATION_HIDE_ALL, // Hide all Notifications AppStateNotificationBridge.FILTER_APP_NOTIFICATION_PRIORITY, // Priority 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 }; // 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; private View mRootView; private View mSpinnerHeader; private Spinner mFilterSpinner; private FilterSpinnerAdapter mFilterAdapter; private NotificationBackend mNotifBackend; private ResetAppsHelper mResetAppsHelper; private String mVolumeUuid; private String mVolumeName; @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())) { 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); 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 { 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); } 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 = (ViewGroup) 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); mFilterAdapter.enableFilter(FILTER_APPS_SILENT); mFilterAdapter.enableFilter(FILTER_APPS_SENSITIVE); mFilterAdapter.enableFilter(FILTER_APPS_HIDE_NOTIFICATIONS); mFilterAdapter.enableFilter(FILTER_APPS_PRIORITY); } if (mListType == LIST_TYPE_HIGH_POWER) { mFilterAdapter.enableFilter(FILTER_APPS_POWER_WHITELIST_ALL); } if (mListType == LIST_TYPE_STORAGE) { mApplications.setOverrideFilter(new VolumeFilter(mVolumeUuid)); } } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); if (mListType == LIST_TYPE_STORAGE) { 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; default: return FILTER_APPS_ALL; } } private boolean isFastScrollEnabled() { switch (mListType) { case LIST_TYPE_MAIN: case LIST_TYPE_NOTIFICATION: case LIST_TYPE_STORAGE: return mSortOrder == R.id.sort_order_alpha; default: return false; } } @Override protected 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: 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; 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; // 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; } 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 && mApplications.getCount() > position) { ApplicationsState.AppEntry entry = mApplications.getAppEntry(position); mCurrentPkgName = entry.info.packageName; mCurrentUid = entry.info.uid; startApplicationDetailsActivity(); } } @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.getActivity(), 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 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 { mExtraInfoBridge = null; } } public void setOverrideFilter(AppFilter overrideFilter) { mOverrideFilter = overrideFilter; rebuild(true); } public void setFilter(int filter) { mFilterMode = filter; rebuild(true); } 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(); } } } 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; } if (DEBUG) Log.i(TAG, "Rebuilding app list..."); 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; } 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(); 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(); 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() { return mEntries != null ? mEntries.size() : 0; } public Object getItem(int position) { return mEntries.get(position); } public ApplicationsState.AppEntry getAppEntry(int position) { return mEntries.get(position); } public long getItemId(int position) { return mEntries.get(position).id; } @Override public boolean areAllItemsEnabled() { return false; } @Override public boolean isEnabled(int position) { 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; // 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); } } mActive.remove(convertView); mActive.add(convertView); convertView.setEnabled(isEnabled(position)); 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; 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 AppCounter(mContext) { @Override protected void onCountComplete(int num) { mLoader.setSummary(SummaryProvider.this, mContext.getString(R.string.apps_summary, num)); } @Override protected boolean includeInCount(ApplicationInfo info) { if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { return true; } else if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { return true; } Intent launchIntent = new Intent(Intent.ACTION_MAIN, null) .addCategory(Intent.CATEGORY_LAUNCHER) .setPackage(info.packageName); int userId = UserHandle.getUserId(info.uid); List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser( launchIntent, PackageManager.GET_DISABLED_COMPONENTS | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); return intents != null && intents.size() != 0; } }.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); } }; }