/* * 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.settings.applications.ApplicationsState.AppEntry; import android.app.TabActivity; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.net.Uri; import android.os.Bundle; import android.provider.Settings; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.animation.AnimationUtils; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.BaseAdapter; 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; import java.util.List; /** * 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 TabActivity implements OnItemClickListener, DialogInterface.OnCancelListener, 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; // 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; // These are for keeping track of activity and tab switch state. private int mCurView; private boolean mCreatedRunning; private boolean mResumedRunning; private boolean mActivityResumed; private Object mNonConfigInstance; 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; 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(); } }; 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(); 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) { mListContainer.setVisibility(View.VISIBLE); mLoadingContainer.setVisibility(View.GONE); mWaitingForData = false; mBaseEntries = apps; mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries); notifyDataSetChanged(); } @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(); } // Finish the activity if the user presses the back button to cancel the activity public void onCancel(DialogInterface dialog) { finish(); } 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; 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, mNonConfigInstance); mCreatedRunning = true; } boolean haveData = true; if (mActivityResumed && !mResumedRunning) { haveData = mRunningProcessesView.doResume(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( this, android.R.anim.fade_out)); mRunningProcessesView.startAnimation(AnimationUtils.loadAnimation( this, android.R.anim.fade_in)); mRunningProcessesView.setVisibility(View.VISIBLE); mLoadingContainer.setVisibility(View.GONE); } } public void showCurrentTab() { String tabId = getTabHost().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)) { selectView(VIEW_RUNNING); return; } else { // Invalid option. Do nothing return; } mFilterApps = newOption; selectView(VIEW_LIST); } public void onTabChanged(String tabId) { showCurrentTab(); } }