/* * 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 static android.net.NetworkPolicyManager.POLICY_NONE; import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.AppOpsManager; import android.app.Fragment; import android.app.INotificationManager; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.NetworkPolicyManager; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.preference.PreferenceFrameLayout; import android.support.v4.view.PagerAdapter; import android.support.v4.view.PagerTabStrip; import android.support.v4.view.ViewPager; import android.text.BidiFormatter; 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.view.animation.AnimationUtils; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.BaseAdapter; import android.widget.Filter; import android.widget.Filterable; import android.widget.ListView; import android.widget.TextView; import com.android.internal.app.IMediaContainerService; import com.android.internal.content.PackageHelper; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.Settings.RunningServicesActivity; import com.android.settings.Settings.StorageUseActivity; import com.android.settings.applications.ApplicationsState.AppEntry; import com.android.settings.deviceinfo.StorageMeasurement; import com.android.settings.Utils; import java.util.ArrayList; import java.util.Comparator; import java.util.List; final class CanBeOnSdCardChecker { final IPackageManager mPm; int mInstallLocation; CanBeOnSdCardChecker() { mPm = IPackageManager.Stub.asInterface( ServiceManager.getService("package")); } void init() { try { mInstallLocation = mPm.getInstallLocation(); } catch (RemoteException e) { Log.e("CanBeOnSdCardChecker", "Is Package Manager running?"); return; } } boolean check(ApplicationInfo info) { boolean canBe = false; if ((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { canBe = true; } else { if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { if (info.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL || info.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { canBe = true; } else if (info.installLocation == PackageInfo.INSTALL_LOCATION_UNSPECIFIED) { if (mInstallLocation == PackageHelper.APP_INSTALL_EXTERNAL) { // For apps with no preference and the default value set // to install on sdcard. canBe = true; } } } } return canBe; } } interface AppClickListener { void onItemClick(ManageApplications.TabInfo tab, AdapterView parent, View view, int position, long id); } /** * 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 Fragment implements AppClickListener, DialogInterface.OnClickListener, DialogInterface.OnDismissListener { static final String TAG = "ManageApplications"; static final boolean DEBUG = false; private static final String EXTRA_SORT_ORDER = "sortOrder"; private static final String EXTRA_SHOW_BACKGROUND = "showBackground"; private static final String EXTRA_DEFAULT_LIST_TYPE = "defaultListType"; private static final String EXTRA_RESET_DIALOG = "resetDialog"; // 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; public static final int SIZE_TOTAL = 0; public static final int SIZE_INTERNAL = 1; public static final int SIZE_EXTERNAL = 2; // sort order that can be changed through the menu can be sorted alphabetically // or size(descending) private static final int MENU_OPTIONS_BASE = 0; // Filter options used for displayed list of applications public static final int FILTER_APPS_ALL = MENU_OPTIONS_BASE + 0; public static final int FILTER_APPS_THIRD_PARTY = MENU_OPTIONS_BASE + 1; public static final int FILTER_APPS_SDCARD = MENU_OPTIONS_BASE + 2; public static final int FILTER_APPS_DISABLED = MENU_OPTIONS_BASE + 3; public static final int SORT_ORDER_ALPHA = MENU_OPTIONS_BASE + 4; public static final int SORT_ORDER_SIZE = MENU_OPTIONS_BASE + 5; public static final int SHOW_RUNNING_SERVICES = MENU_OPTIONS_BASE + 6; public static final int SHOW_BACKGROUND_PROCESSES = MENU_OPTIONS_BASE + 7; public static final int RESET_APP_PREFERENCES = MENU_OPTIONS_BASE + 8; // sort order private int mSortOrder = SORT_ORDER_ALPHA; private ApplicationsState mApplicationsState; public static class TabInfo implements OnItemClickListener { public final ManageApplications mOwner; public final ApplicationsState mApplicationsState; public final CharSequence mLabel; public final int mListType; public final int mFilter; public final AppClickListener mClickListener; public final CharSequence mInvalidSizeStr; public final CharSequence mComputingSizeStr; private final Bundle mSavedInstanceState; public ApplicationsAdapter mApplications; public LayoutInflater mInflater; public View mRootView; private IMediaContainerService mContainerService; private View mLoadingContainer; private View mListContainer; // ListView used to display list private ListView mListView; // Custom view used to display running processes private RunningProcessesView mRunningProcessesView; private LinearColorBar mColorBar; private TextView mStorageChartLabel; private TextView mUsedStorageText; private TextView mFreeStorageText; private long mFreeStorage = 0, mAppStorage = 0, mTotalStorage = 0; private long mLastUsedStorage, mLastAppStorage, mLastFreeStorage; final Runnable mRunningProcessesAvail = new Runnable() { public void run() { handleRunningProcessesAvail(); } }; public TabInfo(ManageApplications owner, ApplicationsState apps, CharSequence label, int listType, AppClickListener clickListener, Bundle savedInstanceState) { mOwner = owner; mApplicationsState = apps; mLabel = label; mListType = listType; switch (listType) { case LIST_TYPE_DOWNLOADED: mFilter = FILTER_APPS_THIRD_PARTY; break; case LIST_TYPE_SDCARD: mFilter = FILTER_APPS_SDCARD; break; case LIST_TYPE_DISABLED: mFilter = FILTER_APPS_DISABLED; break; default: mFilter = FILTER_APPS_ALL; break; } mClickListener = clickListener; mInvalidSizeStr = owner.getActivity().getText(R.string.invalid_size_value); mComputingSizeStr = owner.getActivity().getText(R.string.computing_size); mSavedInstanceState = savedInstanceState; } public void setContainerService(IMediaContainerService containerService) { mContainerService = containerService; updateStorageUsage(); } public View build(LayoutInflater inflater, ViewGroup contentParent, View contentChild) { if (mRootView != null) { return mRootView; } mInflater = inflater; mRootView = inflater.inflate(mListType == LIST_TYPE_RUNNING ? R.layout.manage_applications_running : 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); mListView.setAdapter(mApplications); mListView.setRecyclerListener(mApplications); mColorBar = (LinearColorBar)mListContainer.findViewById(R.id.storage_color_bar); mStorageChartLabel = (TextView)mListContainer.findViewById(R.id.storageChartLabel); mUsedStorageText = (TextView)mListContainer.findViewById(R.id.usedStorageText); mFreeStorageText = (TextView)mListContainer.findViewById(R.id.freeStorageText); Utils.prepareCustomPreferencesList(contentParent, contentChild, mListView, false); if (mFilter == FILTER_APPS_SDCARD) { mStorageChartLabel.setText(mOwner.getActivity().getText( R.string.sd_card_storage)); } else { mStorageChartLabel.setText(mOwner.getActivity().getText( R.string.internal_storage)); } applyCurrentStorage(); } mRunningProcessesView = (RunningProcessesView)mRootView.findViewById( R.id.running_processes); if (mRunningProcessesView != null) { mRunningProcessesView.doCreate(mSavedInstanceState); } return mRootView; } public void detachView() { if (mRootView != null) { ViewGroup group = (ViewGroup)mRootView.getParent(); if (group != null) { group.removeView(mRootView); } } } public void resume(int sortOrder) { if (mApplications != null) { mApplications.resume(sortOrder); } if (mRunningProcessesView != null) { boolean haveData = mRunningProcessesView.doResume(mOwner, mRunningProcessesAvail); if (haveData) { mRunningProcessesView.setVisibility(View.VISIBLE); mLoadingContainer.setVisibility(View.INVISIBLE); } else { mLoadingContainer.setVisibility(View.VISIBLE); } } } public void pause() { if (mApplications != null) { mApplications.pause(); } if (mRunningProcessesView != null) { mRunningProcessesView.doPause(); } } public void release() { if (mApplications != null) { mApplications.release(); } } void updateStorageUsage() { // Make sure a callback didn't come at an inopportune time. if (mOwner.getActivity() == null) return; // Doesn't make sense for stuff that is not an app list. if (mApplications == null) return; mFreeStorage = 0; mAppStorage = 0; mTotalStorage = 0; if (mFilter == FILTER_APPS_SDCARD) { if (mContainerService != null) { try { final long[] stats = mContainerService.getFileSystemStats( Environment.getExternalStorageDirectory().getPath()); mTotalStorage = stats[0]; mFreeStorage = stats[1]; } catch (RemoteException e) { Log.w(TAG, "Problem in container service", e); } } if (mApplications != null) { final int N = mApplications.getCount(); for (int i=0; i 0) { BidiFormatter bidiFormatter = BidiFormatter.getInstance(); mColorBar.setRatios((mTotalStorage-mFreeStorage-mAppStorage)/(float)mTotalStorage, mAppStorage/(float)mTotalStorage, mFreeStorage/(float)mTotalStorage); long usedStorage = mTotalStorage - mFreeStorage; if (mLastUsedStorage != usedStorage) { mLastUsedStorage = usedStorage; String sizeStr = bidiFormatter.unicodeWrap( Formatter.formatShortFileSize(mOwner.getActivity(), usedStorage)); mUsedStorageText.setText(mOwner.getActivity().getResources().getString( R.string.service_foreground_processes, sizeStr)); } if (mLastFreeStorage != mFreeStorage) { mLastFreeStorage = mFreeStorage; String sizeStr = bidiFormatter.unicodeWrap( Formatter.formatShortFileSize(mOwner.getActivity(), mFreeStorage)); mFreeStorageText.setText(mOwner.getActivity().getResources().getString( R.string.service_background_processes, sizeStr)); } } else { mColorBar.setRatios(0, 0, 0); if (mLastUsedStorage != -1) { mLastUsedStorage = -1; mUsedStorageText.setText(""); } if (mLastFreeStorage != -1) { mLastFreeStorage = -1; mFreeStorageText.setText(""); } } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { mClickListener.onItemClick(this, parent, view, position, id); } void handleRunningProcessesAvail() { mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( mOwner.getActivity(), android.R.anim.fade_out)); mRunningProcessesView.startAnimation(AnimationUtils.loadAnimation( mOwner.getActivity(), android.R.anim.fade_in)); mRunningProcessesView.setVisibility(View.VISIBLE); mLoadingContainer.setVisibility(View.GONE); } } private final ArrayList mTabs = new ArrayList(); private int mNumTabs; TabInfo mCurTab = null; // Size resource used for packages whose size computation failed for some reason CharSequence mInvalidSizeStr; private CharSequence mComputingSizeStr; // layout inflater object used to inflate views private LayoutInflater mInflater; private String mCurrentPkgName; private Menu mOptionsMenu; // These are for keeping track of activity and spinner switch state. private boolean mActivityResumed; static final int LIST_TYPE_DOWNLOADED = 0; static final int LIST_TYPE_RUNNING = 1; static final int LIST_TYPE_SDCARD = 2; static final int LIST_TYPE_ALL = 3; static final int LIST_TYPE_DISABLED = 4; private boolean mShowBackground = false; private int mDefaultListType = -1; private ViewGroup mContentContainer; private View mRootView; private ViewPager mViewPager; AlertDialog mResetDialog; class MyPagerAdapter extends PagerAdapter implements ViewPager.OnPageChangeListener { int mCurPos = 0; @Override public int getCount() { return mNumTabs; } @Override public Object instantiateItem(ViewGroup container, int position) { TabInfo tab = mTabs.get(position); View root = tab.build(mInflater, mContentContainer, mRootView); container.addView(root); root.setTag(R.id.name, tab); return root; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View)object); } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } @Override public int getItemPosition(Object object) { return super.getItemPosition(object); //return ((TabInfo)((View)object).getTag(R.id.name)).mListType; } @Override public CharSequence getPageTitle(int position) { return mTabs.get(position).mLabel; } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { mCurPos = position; } @Override public void onPageScrollStateChanged(int state) { if (state == ViewPager.SCROLL_STATE_IDLE) { updateCurrentTab(mCurPos); } } } /* * 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, AbsListView.RecyclerListener { private final ApplicationsState mState; private final ApplicationsState.Session mSession; private final TabInfo mTab; private final Context mContext; private final ArrayList mActive = new ArrayList(); private final int mFilterMode; private ArrayList mBaseEntries; private ArrayList mEntries; private boolean mResumed; private int mLastSortMode=-1; private boolean mWaitingForData; private int mWhichSize = SIZE_TOTAL; CharSequence mCurFilterPrefix; private Filter mFilter = new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { ArrayList entries = applyPrefixFilter(constraint, mBaseEntries); FilterResults fr = new FilterResults(); fr.values = entries; fr.count = entries.size(); return fr; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { mCurFilterPrefix = constraint; mEntries = (ArrayList)results.values; notifyDataSetChanged(); mTab.updateStorageUsage(); } }; public ApplicationsAdapter(ApplicationsState state, TabInfo tab, int filterMode) { mState = state; mSession = state.newSession(this); mTab = tab; mContext = tab.mOwner.getActivity(); mFilterMode = filterMode; } public void resume(int sort) { if (DEBUG) Log.i(TAG, "Resume! mResumed=" + mResumed); if (!mResumed) { mResumed = true; mSession.resume(); mLastSortMode = sort; rebuild(true); } else { rebuild(sort); } } public void pause() { if (mResumed) { mResumed = false; mSession.pause(); } } public void release() { mSession.release(); } public void rebuild(int sort) { if (sort == mLastSortMode) { return; } mLastSortMode = sort; rebuild(true); } public void rebuild(boolean eraseold) { if (DEBUG) Log.i(TAG, "Rebuilding app list..."); ApplicationsState.AppFilter filterObj; Comparator comparatorObj; boolean emulated = Environment.isExternalStorageEmulated(); if (emulated) { mWhichSize = SIZE_TOTAL; } else { mWhichSize = SIZE_INTERNAL; } switch (mFilterMode) { case FILTER_APPS_THIRD_PARTY: filterObj = ApplicationsState.THIRD_PARTY_FILTER; break; case FILTER_APPS_SDCARD: filterObj = ApplicationsState.ON_SD_CARD_FILTER; if (!emulated) { mWhichSize = SIZE_EXTERNAL; } break; case FILTER_APPS_DISABLED: filterObj = ApplicationsState.DISABLED_FILTER; break; default: filterObj = ApplicationsState.ALL_ENABLED_FILTER; break; } switch (mLastSortMode) { case 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; } ArrayList entries = mSession.rebuild(filterObj, comparatorObj); if (entries == null && !eraseold) { // Don't have new list yet, but can continue using the old one. return; } mBaseEntries = entries; if (mBaseEntries != null) { mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries); } else { mEntries = null; } notifyDataSetChanged(); mTab.updateStorageUsage(); if (entries == null) { mWaitingForData = true; mTab.mListContainer.setVisibility(View.INVISIBLE); mTab.mLoadingContainer.setVisibility(View.VISIBLE); } else { mTab.mListContainer.setVisibility(View.VISIBLE); mTab.mLoadingContainer.setVisibility(View.GONE); } } ArrayList applyPrefixFilter(CharSequence prefix, ArrayList origEntries) { if (prefix == null || prefix.length() == 0) { return origEntries; } else { String prefixStr = ApplicationsState.normalize(prefix.toString()); final String spacePrefixStr = " " + prefixStr; ArrayList newEntries = new ArrayList(); for (int i=0; i apps) { if (mTab.mLoadingContainer.getVisibility() == View.VISIBLE) { mTab.mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( mContext, android.R.anim.fade_out)); mTab.mListContainer.startAnimation(AnimationUtils.loadAnimation( mContext, android.R.anim.fade_in)); } mTab.mListContainer.setVisibility(View.VISIBLE); mTab.mLoadingContainer.setVisibility(View.GONE); mWaitingForData = false; mBaseEntries = apps; mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries); notifyDataSetChanged(); mTab.updateStorageUsage(); } @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 onPackageSizeChanged(String packageName) { for (int i=0; i() { @Override protected Void doInBackground(Void... params) { List apps = pm.getInstalledApplications( PackageManager.GET_DISABLED_COMPONENTS); for (int i=0; i parent, View view, int position, long id) { if (tab.mApplications != null && tab.mApplications.getCount() > position) { ApplicationsState.AppEntry entry = tab.mApplications.getAppEntry(position); mCurrentPkgName = entry.info.packageName; startApplicationDetailsActivity(); } } public void updateCurrentTab(int position) { TabInfo tab = mTabs.get(position); mCurTab = tab; // Put things in the correct paused/resumed state. if (mActivityResumed) { mCurTab.build(mInflater, mContentContainer, mRootView); mCurTab.resume(mSortOrder); } else { mCurTab.pause(); } for (int i=0; i