/* * 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 com.android.settings.R; import com.android.internal.content.PackageHelper; import com.android.settings.applications.ApplicationsState.AppEntry; import com.android.settings.Settings.RunningServicesActivity; import com.android.settings.Settings.StorageUseActivity; import android.app.Fragment; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.os.Bundle; import android.os.Environment; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StatFs; import android.preference.PreferenceActivity; import android.provider.Settings; 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.view.inputmethod.InputMethodManager; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.CheckBox; import android.widget.Filter; import android.widget.Filterable; import android.widget.ImageView; import android.widget.ListView; import android.widget.TabHost; import android.widget.TextView; import android.widget.AdapterView.OnItemClickListener; import java.util.ArrayList; import java.util.Comparator; 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_FORWARD_LOCK) == 0 && (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; } } /** * 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 OnItemClickListener, TabHost.TabContentFactory, TabHost.OnTabChangeListener { static final String TAG = "ManageApplications"; static final boolean DEBUG = false; // 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; // 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 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; // sort order private int mSortOrder = SORT_ORDER_ALPHA; // Filter value private int mFilterApps = FILTER_APPS_THIRD_PARTY; private ApplicationsState mApplicationsState; private ApplicationsAdapter mApplicationsAdapter; // Size resource used for packages whose size computation failed for some reason private CharSequence mInvalidSizeStr; private CharSequence mComputingSizeStr; // layout inflater object used to inflate views private LayoutInflater mInflater; private String mCurrentPkgName; private View mLoadingContainer; private View mListContainer; // ListView used to display list private ListView mListView; // Custom view used to display running processes private RunningProcessesView mRunningProcessesView; LinearColorBar mColorBar; TextView mStorageChartLabel; TextView mUsedStorageText; TextView mFreeStorageText; private Menu mOptionsMenu; // These are for keeping track of activity and tab switch state. private int mCurView; private boolean mCreatedRunning; private boolean mResumedRunning; private boolean mActivityResumed; private StatFs mDataFileStats; private StatFs mSDCardFileStats; private boolean mLastShowedInternalStorage = true; private long mLastUsedStorage, mLastAppStorage, mLastFreeStorage; static final String TAB_DOWNLOADED = "Downloaded"; static final String TAB_RUNNING = "Running"; static final String TAB_ALL = "All"; static final String TAB_SDCARD = "OnSdCard"; private View mRootView; // -------------- Copied from TabActivity -------------- private TabHost mTabHost; private String mDefaultTab = null; // -------------- Copied from TabActivity -------------- final Runnable mRunningProcessesAvail = new Runnable() { public void run() { handleRunningProcessesAvail(); } }; // View Holder used when displaying views static class AppViewHolder { ApplicationsState.AppEntry entry; TextView appName; ImageView appIcon; TextView appSize; TextView disabled; CheckBox checkBox; void updateSizeText(ManageApplications ma) { if (DEBUG) Log.i(TAG, "updateSizeText of " + entry.label + " " + entry + ": " + entry.sizeStr); if (entry.sizeStr != null) { appSize.setText(entry.sizeStr); } else if (entry.size == ApplicationsState.SIZE_INVALID) { appSize.setText(ma.mInvalidSizeStr); } } } /* * 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 */ class ApplicationsAdapter extends BaseAdapter implements Filterable, ApplicationsState.Callbacks, AbsListView.RecyclerListener { private final ApplicationsState mState; private final ArrayList mActive = new ArrayList(); private ArrayList mBaseEntries; private ArrayList mEntries; private boolean mResumed; private int mLastFilterMode=-1, mLastSortMode=-1; private boolean mWaitingForData; 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(); updateStorageUsage(); } }; public ApplicationsAdapter(ApplicationsState state) { mState = state; } public void resume(int filter, int sort) { if (DEBUG) Log.i(TAG, "Resume! mResumed=" + mResumed); if (!mResumed) { mResumed = true; mState.resume(this); mLastFilterMode = filter; mLastSortMode = sort; rebuild(true); } else { rebuild(filter, sort); } } public void pause() { if (mResumed) { mResumed = false; mState.pause(); } } public void rebuild(int filter, int sort) { if (filter == mLastFilterMode && sort == mLastSortMode) { return; } mLastFilterMode = filter; mLastSortMode = sort; rebuild(true); } public void rebuild(boolean eraseold) { if (DEBUG) Log.i(TAG, "Rebuilding app list..."); ApplicationsState.AppFilter filterObj; Comparator comparatorObj; switch (mLastFilterMode) { case FILTER_APPS_THIRD_PARTY: filterObj = ApplicationsState.THIRD_PARTY_FILTER; break; case FILTER_APPS_SDCARD: filterObj = ApplicationsState.ON_SD_CARD_FILTER; break; default: filterObj = null; break; } switch (mLastSortMode) { case SORT_ORDER_SIZE: comparatorObj = ApplicationsState.SIZE_COMPARATOR; break; default: comparatorObj = ApplicationsState.ALPHA_COMPARATOR; break; } ArrayList entries = mState.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(); updateStorageUsage(); if (entries == null) { mWaitingForData = true; mListContainer.setVisibility(View.INVISIBLE); mLoadingContainer.setVisibility(View.VISIBLE); } else { mListContainer.setVisibility(View.VISIBLE); 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 (mLoadingContainer.getVisibility() == View.VISIBLE) { mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( getActivity(), android.R.anim.fade_out)); mListContainer.startAnimation(AnimationUtils.loadAnimation( getActivity(), android.R.anim.fade_in)); } mListContainer.setVisibility(View.VISIBLE); mLoadingContainer.setVisibility(View.GONE); mWaitingForData = false; mBaseEntries = apps; mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries); notifyDataSetChanged(); 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 parent, View view, int position, long id) { ApplicationsState.AppEntry entry = mApplicationsAdapter.getAppEntry(position); mCurrentPkgName = entry.info.packageName; startApplicationDetailsActivity(); } public View createTabContent(String tag) { return mRootView; } static final int VIEW_NOTHING = 0; static final int VIEW_LIST = 1; static final int VIEW_RUNNING = 2; void updateStorageUsage() { if (mCurView == VIEW_RUNNING) { return; } long freeStorage = 0; long appStorage = 0; long totalStorage = 0; CharSequence newLabel = null; if (mFilterApps == FILTER_APPS_SDCARD) { if (mLastShowedInternalStorage) { mLastShowedInternalStorage = false; } newLabel = getActivity().getText(R.string.sd_card_storage); mSDCardFileStats.restat(Environment.getExternalStorageDirectory().toString()); try { totalStorage = (long)mSDCardFileStats.getBlockCount() * mSDCardFileStats.getBlockSize(); freeStorage = (long) mSDCardFileStats.getAvailableBlocks() * mSDCardFileStats.getBlockSize(); } catch (IllegalArgumentException e) { // use the old value of mFreeMem } } else { if (!mLastShowedInternalStorage) { mLastShowedInternalStorage = true; } newLabel = getActivity().getText(R.string.internal_storage); mDataFileStats.restat("/data"); try { totalStorage = (long)mDataFileStats.getBlockCount() * mDataFileStats.getBlockSize(); freeStorage = (long) mDataFileStats.getAvailableBlocks() * mDataFileStats.getBlockSize(); } catch (IllegalArgumentException e) { } final int N = mApplicationsAdapter.getCount(); for (int i=0; i 0) { mColorBar.setRatios((totalStorage-freeStorage-appStorage)/(float)totalStorage, appStorage/(float)totalStorage, freeStorage/(float)totalStorage); long usedStorage = totalStorage - freeStorage; if (mLastUsedStorage != usedStorage) { mLastUsedStorage = usedStorage; String sizeStr = Formatter.formatShortFileSize(getActivity(), usedStorage); mUsedStorageText.setText(getActivity().getResources().getString( R.string.service_foreground_processes, sizeStr)); } if (mLastFreeStorage != freeStorage) { mLastFreeStorage = freeStorage; String sizeStr = Formatter.formatShortFileSize(getActivity(), freeStorage); mFreeStorageText.setText(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(""); } } } private void selectView(int which) { if (which == VIEW_LIST) { if (mResumedRunning) { mRunningProcessesView.doPause(); mResumedRunning = false; } if (mCurView != which) { mRunningProcessesView.setVisibility(View.GONE); mListContainer.setVisibility(View.VISIBLE); mLoadingContainer.setVisibility(View.GONE); } if (mActivityResumed) { mApplicationsAdapter.resume(mFilterApps, mSortOrder); } } else if (which == VIEW_RUNNING) { if (!mCreatedRunning) { mRunningProcessesView.doCreate(null); mCreatedRunning = true; } boolean haveData = true; if (mActivityResumed && !mResumedRunning) { haveData = mRunningProcessesView.doResume(this, mRunningProcessesAvail); mResumedRunning = true; } mApplicationsAdapter.pause(); if (mCurView != which) { if (haveData) { mRunningProcessesView.setVisibility(View.VISIBLE); } else { mLoadingContainer.setVisibility(View.VISIBLE); } mListContainer.setVisibility(View.GONE); } } mCurView = which; } void handleRunningProcessesAvail() { if (mCurView == VIEW_RUNNING) { mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( getActivity(), android.R.anim.fade_out)); mRunningProcessesView.startAnimation(AnimationUtils.loadAnimation( getActivity(), android.R.anim.fade_in)); mRunningProcessesView.setVisibility(View.VISIBLE); mLoadingContainer.setVisibility(View.GONE); } } public void showCurrentTab() { String tabId = mDefaultTab = mTabHost.getCurrentTabTag(); int newOption; if (TAB_DOWNLOADED.equalsIgnoreCase(tabId)) { newOption = FILTER_APPS_THIRD_PARTY; } else if (TAB_ALL.equalsIgnoreCase(tabId)) { newOption = FILTER_APPS_ALL; } else if (TAB_SDCARD.equalsIgnoreCase(tabId)) { newOption = FILTER_APPS_SDCARD; } else if (TAB_RUNNING.equalsIgnoreCase(tabId)) { ((InputMethodManager)getActivity().getSystemService(Context.INPUT_METHOD_SERVICE)) .hideSoftInputFromWindow( getActivity().getWindow().getDecorView().getWindowToken(), 0); selectView(VIEW_RUNNING); return; } else { // Invalid option. Do nothing return; } mFilterApps = newOption; selectView(VIEW_LIST); updateStorageUsage(); updateOptionsMenu(); } public void onTabChanged(String tabId) { showCurrentTab(); } }