Java tutorial
/******************************************************************************** * (C) Copyright 2000-2010. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ********************************************************************************/ package org.uguess.android.sysinfo; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnMultiChoiceClickListener; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageStatsObserver; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageStats; import android.content.pm.ResolveInfo; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; import android.support.v4.app.ListFragment; import android.text.ClipboardManager; import android.text.Html; import android.text.format.DateUtils; import android.text.format.Formatter; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; 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.Window; import android.view.animation.AnimationUtils; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; import java.text.Collator; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.concurrent.CountDownLatch; /** * ApplicationManager */ public final class ApplicationManager extends ListFragment implements Constants { private static final String PSTORE_APPMANAGER = ApplicationManager.class.getSimpleName(); private static final int MSG_COPING = MSG_PRIVATE + 1; private static final int MSG_COPING_ERROR = MSG_PRIVATE + 2; private static final int MSG_COPING_FINISHED = MSG_PRIVATE + 3; private static final int MSG_REFRESH_PKG_SIZE = MSG_PRIVATE + 4; private static final int MSG_REFRESH_PKG_LABEL = MSG_PRIVATE + 5; private static final int MSG_REFRESH_PKG_ICON = MSG_PRIVATE + 6; private static final int MSG_REFRESH_BACKUP_STATE = MSG_PRIVATE + 7; private static final int MSG_UPDATE = MSG_PRIVATE + 8; private static final int APP_TYPE_ALL = 0; private static final int APP_TYPE_SYS = 1; private static final int APP_TYPE_USER = 2; private static final int ORDER_TYPE_NAME = 0; private static final int ORDER_TYPE_CODE_SIZE = 1; private static final int ORDER_TYPE_DATA_SIZE = 2; private static final int ORDER_TYPE_CACHE_SIZE = 3; private static final int ORDER_TYPE_TOTAL_SIZE = 4; private static final int ORDER_TYPE_INSTALL_DATE = 5; private static final int ORDER_TYPE_BACKUP_STATE = 6; private static final int REQUEST_SETTINGS = 1; private static final int REQUEST_RESTORE = 2; private static final String PREF_KEY_FILTER_APP_TYPE = "filter_app_type"; //$NON-NLS-1$ private static final String PREF_KEY_APP_EXPORT_DIR = "app_export_dir"; //$NON-NLS-1$ private static final String PREF_KEY_SHOW_BACKUP_STATE = "show_backup_state"; //$NON-NLS-1$ private static final int ACTION_MENU = 0; private static final int ACTION_MANAGE = 1; private static final int ACTION_LAUNCH = 2; private static final int ACTION_SEARCH = 3; private static final int ACTION_DETAILS = 4; static final String KEY_RESTORE_PATH = "restore_path"; //$NON-NLS-1$ static final String KEY_ARCHIVE_PATH = "archive_path"; //$NON-NLS-1$ private static final String DEFAULT_EXPORT_FOLDER = "/sdcard/backups/"; //$NON-NLS-1$ private static final String SYS_APP = "system/"; //$NON-NLS-1$ private static final String USER_APP = "user/"; //$NON-NLS-1$ private static final String ARCHIVED = "archived/"; //$NON-NLS-1$ static Method mdGetPackageSizeInfo; static { try { mdGetPackageSizeInfo = PackageManager.class.getMethod("getPackageSizeInfo", //$NON-NLS-1$ String.class, IPackageStatsObserver.class); } catch (Exception e) { Log.e(ApplicationManager.class.getName(), e.getLocalizedMessage(), e); } } ProgressDialog progress; View rootView; volatile boolean aborted; String versionPrefix; AppCache appCache; PkgSizeUpdaterThread sizeUpdater; ResourceUpdaterThread resUpdater; BackupStateUpdaterThread backupUpdater; Handler handler = new Handler() { @Override public void handleMessage(Message msg) { Activity ctx = getActivity(); ArrayAdapter<AppInfoHolder> adapter; switch (msg.what) { case MSG_INIT_OK: adapter = (ArrayAdapter<AppInfoHolder>) getListView().getAdapter(); adapter.setNotifyOnChange(false); adapter.clear(); synchronized (appCache) { ArrayList<AppInfoHolder> localList = appCache.appList; for (int i = 0, size = localList.size(); i < size; i++) { adapter.add(localList.get(i)); } } // should always no selection at this stage hideButtons(); adapter.notifyDataSetChanged(); sendEmptyMessage(MSG_DISMISS_PROGRESS); if (getListView().getCount() == 0) { Util.shortToast(ctx, R.string.no_app_show); } break; case MSG_COPING: if (progress != null) { progress.setMessage(getString(R.string.exporting, msg.obj)); progress.setProgress(progress.getProgress() + 1); } break; case MSG_COPING_ERROR: if (msg.arg1 == 0 && progress != null) { progress.dismiss(); progress = null; } Util.shortToast(ctx, getString(R.string.copy_error, msg.obj)); break; case MSG_COPING_FINISHED: final List<AppInfoHolder> apps = (List<AppInfoHolder>) msg.obj; if (progress != null) { progress.setMessage(msg.arg2 > 0 ? getString(R.string.exported_skip, msg.arg1, msg.arg2) : getString(R.string.exported, msg.arg1)); progress.setProgress(progress.getMax()); progress.dismiss(); progress = null; } Util.shortToast(ctx, msg.arg2 > 0 ? getString(R.string.exported_to_skip, msg.arg1, Util.getStringOption(ctx, PSTORE_APPMANAGER, PREF_KEY_APP_EXPORT_DIR, DEFAULT_EXPORT_FOLDER), msg.arg2) : getString(R.string.exported_to, msg.arg1, Util.getStringOption(ctx, PSTORE_APPMANAGER, PREF_KEY_APP_EXPORT_DIR, DEFAULT_EXPORT_FOLDER))); Notification nc = new Notification(R.drawable.logo2, getResources().getString(R.string.export_complete), System.currentTimeMillis()); PendingIntent pit = PendingIntent.getActivity(ctx, 0, new Intent(), 0); nc.flags |= Notification.FLAG_AUTO_CANCEL; nc.setLatestEventInfo(ctx, getResources().getString(R.string.export_complete), msg.arg2 > 0 ? getString(R.string.exported_skip, msg.arg1, msg.arg2) : getString(R.string.exported, msg.arg1), pit); ((NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE)) .notify(NOTIFY_EXPORT_FINISHED, nc); toggleAllSelection(false); if (Util.getBooleanOption(ctx, PSTORE_APPMANAGER, PREF_KEY_SHOW_BACKUP_STATE)) { // reload backup state if (backupUpdater != null) { backupUpdater.aborted = true; } (backupUpdater = new BackupStateUpdaterThread(ctx, apps, appCache, handler)).start(); ; } break; case MSG_DISMISS_PROGRESS: if (progress != null) { progress.dismiss(); progress = null; } break; case MSG_REFRESH_PKG_SIZE: case MSG_REFRESH_PKG_LABEL: case MSG_REFRESH_BACKUP_STATE: adapter = (ArrayAdapter<AppInfoHolder>) getListView().getAdapter(); if (msg.arg1 == 1) { adapter.setNotifyOnChange(false); adapter.clear(); synchronized (appCache) { ArrayList<AppInfoHolder> localList = appCache.appList; for (int i = 0, size = localList.size(); i < size; i++) { adapter.add(localList.get(i)); } } } adapter.notifyDataSetChanged(); break; case MSG_REFRESH_PKG_ICON: ((ArrayAdapter<AppInfoHolder>) getListView().getAdapter()).notifyDataSetChanged(); break; case MSG_TOAST: Util.shortToast(ctx, (String) msg.obj); break; case MSG_UPDATE: if (sizeUpdater != null) { sizeUpdater.aborted = true; } if (resUpdater != null) { resUpdater.aborted = true; } if (backupUpdater != null) { backupUpdater.aborted = true; } appCache.update((ArrayList<AppInfoHolder>) msg.obj); appCache.reOrder( Util.getIntOption(ctx, PSTORE_APPMANAGER, PREF_KEY_SORT_ORDER_TYPE, ORDER_TYPE_NAME), Util.getIntOption(ctx, PSTORE_APPMANAGER, PREF_KEY_SORT_DIRECTION, ORDER_ASC)); handler.sendEmptyMessage(MSG_INIT_OK); if (Util.getBooleanOption(ctx, PSTORE_APPMANAGER, PREF_KEY_SHOW_SIZE)) { (sizeUpdater = new PkgSizeUpdaterThread(ctx, appCache, handler)).start(); } (resUpdater = new ResourceUpdaterThread(ctx, appCache, handler)).start(); if (Util.getBooleanOption(ctx, PSTORE_APPMANAGER, PREF_KEY_SHOW_BACKUP_STATE)) { (backupUpdater = new BackupStateUpdaterThread(ctx, null, appCache, handler)).start(); } break; case MSG_CONTENT_READY: sendEmptyMessage(MSG_DISMISS_PROGRESS); Util.handleMsgSendContentReady((String) msg.obj, "Android Applications - ", //$NON-NLS-1$ ctx, msg.arg2 == 1); break; case MSG_CHECK_FORCE_COMPRESSION: sendEmptyMessage(MSG_DISMISS_PROGRESS); Util.checkForceCompression(this, ctx, (String) msg.obj, msg.arg1, "android_applications"); //$NON-NLS-1$ break; } } }; OnCheckedChangeListener checkListener = new OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { ((AppInfoHolder) getListView().getItemAtPosition((Integer) buttonView.getTag())).checked = isChecked; View v = rootView.findViewById(R.id.app_footer); if (isChecked) { if (v.getVisibility() != View.VISIBLE) { v.setVisibility(View.VISIBLE); v.startAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.footer_appear)); } } else if (getSelectedCount(getListView()) == 0) { hideButtons(); } } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); versionPrefix = getString(R.string.version); appCache = new AppCache(); setHasOptionsMenu(true); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { rootView = inflater.inflate(R.layout.app_lst_view, container, false); ((Button) rootView.findViewById(R.id.btn_export)).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { doExport(); } }); ((Button) rootView.findViewById(R.id.btn_sel_all)).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { toggleAllSelection(true); } }); ((Button) rootView.findViewById(R.id.btn_desel_all)).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { toggleAllSelection(false); } }); ListView lstApps = (ListView) rootView.findViewById(android.R.id.list); lstApps.setFastScrollEnabled(true); registerForContextMenu(lstApps); ArrayAdapter<AppInfoHolder> adapter = new ArrayAdapter<AppInfoHolder>(getActivity(), R.layout.app_item) { public android.view.View getView(int position, android.view.View convertView, android.view.ViewGroup parent) { View view; TextView txt_name, txt_size, txt_ver, txt_time; ImageView img_type; CheckBox ckb_app; Activity ctx = getActivity(); if (convertView == null) { view = ctx.getLayoutInflater().inflate(R.layout.app_item, parent, false); } else { view = convertView; } if (position >= getCount()) { return view; } AppInfoHolder itm = getItem(position); txt_name = (TextView) view.findViewById(R.id.app_name); if (itm.label != null) { txt_name.setText(itm.label); } else { txt_name.setText(itm.appInfo.packageName); } if (Util.getBooleanOption(ctx, PSTORE_APPMANAGER, PREF_KEY_SHOW_BACKUP_STATE)) { switch (itm.backupState) { case 1: txt_name.setTextColor(Color.YELLOW); break; case 2: txt_name.setTextColor(0xff00bb00); break; case 3: txt_name.setTextColor(0xffF183BD); break; default: txt_name.setTextColor(Color.WHITE); break; } } else { txt_name.setTextColor(Color.WHITE); } txt_ver = (TextView) view.findViewById(R.id.app_version); txt_ver.setText(itm.version); txt_size = (TextView) view.findViewById(R.id.app_size); if (Util.getBooleanOption(ctx, PSTORE_APPMANAGER, PREF_KEY_SHOW_SIZE)) { txt_size.setVisibility(View.VISIBLE); if (itm.size != null) { txt_size.setText(itm.size); } else { txt_size.setText(R.string.computing); } } else { txt_size.setVisibility(View.GONE); } txt_time = (TextView) view.findViewById(R.id.app_time); if (Util.getBooleanOption(ctx, PSTORE_APPMANAGER, PREF_KEY_SHOW_DATE)) { txt_time.setVisibility(View.VISIBLE); if (itm.appInfo.sourceDir != null) { File f = new File(itm.appInfo.sourceDir); txt_time.setText(DateUtils.formatDateTime(ctx, f.lastModified(), DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME)); } else { txt_time.setText(R.string.unknown); } } else { txt_time.setVisibility(View.GONE); } img_type = (ImageView) view.findViewById(R.id.img_app_icon); if (Util.getBooleanOption(ctx, PSTORE_APPMANAGER, PREF_KEY_SHOW_ICON)) { img_type.setVisibility(View.VISIBLE); if (itm.icon != null) { img_type.setImageDrawable(itm.icon); } else { try { img_type.setImageDrawable(ctx.getPackageManager().getDefaultActivityIcon()); } catch (Exception fe) { img_type.setImageDrawable(null); Log.e(ApplicationManager.class.getName(), fe.getLocalizedMessage()); } } } else { img_type.setVisibility(View.GONE); } ckb_app = (CheckBox) view.findViewById(R.id.ckb_app); ckb_app.setTag(position); ckb_app.setChecked(itm.checked); ckb_app.setOnCheckedChangeListener(checkListener); View imgLock = view.findViewById(R.id.img_lock); imgLock.setVisibility(itm.isPrivate ? View.VISIBLE : View.GONE); return view; } }; setListAdapter(adapter); return rootView; } @Override public void onListItemClick(ListView l, View v, int position, long id) { AppInfoHolder holder = (AppInfoHolder) l.getItemAtPosition(position); int action = Util.getIntOption(getActivity(), PSTORE_APPMANAGER, PREF_KEY_DEFAULT_TAP_ACTION, ACTION_MENU); handleAction(holder, action); } @Override public void onDestroyView() { if (progress != null) { progress.dismiss(); progress = null; } rootView = null; ((ArrayAdapter<AppInfoHolder>) getListView().getAdapter()).clear(); super.onDestroyView(); } @Override public void onDestroy() { appCache.clear(); super.onDestroy(); } @Override public void onStart() { super.onStart(); loadApps(); } @Override public void onStop() { if (sizeUpdater != null) { sizeUpdater.aborted = true; sizeUpdater = null; } if (resUpdater != null) { resUpdater.aborted = true; resUpdater = null; } if (backupUpdater != null) { backupUpdater.aborted = true; backupUpdater = null; } ((NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE)) .cancel(NOTIFY_EXPORT_FINISHED); super.onStop(); } @Override public void onResume() { aborted = false; super.onResume(); } @Override public void onPause() { aborted = true; handler.removeMessages(MSG_CHECK_FORCE_COMPRESSION); handler.removeMessages(MSG_CONTENT_READY); super.onPause(); } private void loadApps() { if (progress != null) { progress.dismiss(); } progress = new ProgressDialog(getActivity()); progress.setMessage(getResources().getText(R.string.loading)); progress.setIndeterminate(true); progress.show(); new Thread(new Runnable() { public void run() { final PackageManager pm = getActivity().getPackageManager(); List<ApplicationInfo> allApps = pm.getInstalledApplications(0); final List<ApplicationInfo> filteredApps = filterApps(allApps); ArrayList<AppInfoHolder> dataList = new ArrayList<AppInfoHolder>(); for (int i = 0, size = filteredApps.size(); i < size; i++) { ApplicationInfo info = filteredApps.get(i); AppInfoHolder holder = new AppInfoHolder(); holder.appInfo = info; try { PackageInfo pi = pm.getPackageInfo(info.packageName, 0); holder.version = versionPrefix + " " //$NON-NLS-1$ + (pi.versionName == null ? String.valueOf(pi.versionCode) : pi.versionName); holder.versionCode = pi.versionCode; if (info.sourceDir != null && info.sourceDir.contains("/data/app-private")) //$NON-NLS-1$ { holder.isPrivate = true; } } catch (NameNotFoundException e) { Log.e(ApplicationManager.class.getName(), e.getLocalizedMessage(), e); } dataList.add(holder); } handler.sendMessage(handler.obtainMessage(MSG_UPDATE, dataList)); } }, "MainUpdater").start(); //$NON-NLS-1$ } List<ApplicationInfo> filterApps(List<ApplicationInfo> apps) { if (apps == null || apps.size() == 0) { return apps; } int type = Util.getIntOption(getActivity(), PSTORE_APPMANAGER, PREF_KEY_FILTER_APP_TYPE, APP_TYPE_ALL); if (type == APP_TYPE_SYS) { List<ApplicationInfo> sysApps = new ArrayList<ApplicationInfo>(); for (int i = 0, size = apps.size(); i < size; i++) { ApplicationInfo ai = apps.get(i); if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { sysApps.add(ai); } } return sysApps; } else if (type == APP_TYPE_USER) { List<ApplicationInfo> userApps = new ArrayList<ApplicationInfo>(); for (int i = 0, size = apps.size(); i < size; i++) { ApplicationInfo ai = apps.get(i); if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { userApps.add(ai); } } return userApps; } return apps; } private static boolean ensureSDCard() { String state = Environment.getExternalStorageState(); return Environment.MEDIA_MOUNTED.equals(state); } private static List<AppInfoHolder> getSelected(ListView lstApps) { int count = lstApps.getCount(); ArrayList<AppInfoHolder> apps = new ArrayList<AppInfoHolder>(); for (int i = 0; i < count; i++) { AppInfoHolder holder = (AppInfoHolder) lstApps.getItemAtPosition(i); if (holder.checked) { apps.add(holder); } } return apps; } int getSelectedCount(ListView lstApps) { int count = lstApps.getCount(); int s = 0; for (int i = 0; i < count; i++) { AppInfoHolder holder = (AppInfoHolder) lstApps.getItemAtPosition(i); if (holder.checked) { s++; } } return s; } void export(final List<AppInfoHolder> apps) { if (apps == null || apps.isEmpty()) { Util.shortToast(getActivity(), R.string.no_app_selected); return; } if (progress != null) { progress.dismiss(); } progress = new ProgressDialog(getActivity()); progress.setMessage(getResources().getString(R.string.start_exporting)); progress.setIndeterminate(false); progress.setCancelable(false); progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progress.setMax(apps.size()); progress.show(); new Thread(new Runnable() { public void run() { String exportFolder = Util.getStringOption(getActivity(), PSTORE_APPMANAGER, PREF_KEY_APP_EXPORT_DIR, DEFAULT_EXPORT_FOLDER); File output = new File(exportFolder); if (!output.exists()) { if (!output.mkdirs()) { handler.sendMessage(Message.obtain(handler, MSG_COPING_ERROR, 0, 0, getString(R.string.error_create_folder, output.getAbsolutePath()))); return; } } File sysoutput = new File(output, SYS_APP); if (!sysoutput.exists()) { if (!sysoutput.mkdirs()) { handler.sendMessage(Message.obtain(handler, MSG_COPING_ERROR, 0, 0, getString(R.string.error_create_folder, sysoutput.getAbsolutePath()))); return; } } File useroutput = new File(output, USER_APP); if (!useroutput.exists()) { if (!useroutput.mkdirs()) { handler.sendMessage(Message.obtain(handler, MSG_COPING_ERROR, 0, 0, getString(R.string.error_create_folder, useroutput.getAbsolutePath()))); return; } } int skipped = 0; int succeed = 0; for (int i = 0, size = apps.size(); i < size; i++) { ApplicationInfo app = apps.get(i).appInfo; String src = app.sourceDir; if (src != null) { File srcFile = new File(src); if (src.contains("/data/app-private") //$NON-NLS-1$ || !srcFile.canRead()) { skipped++; continue; } String appName = getFileName(src); if (appName != null && app.packageName != null) { // always use package name as the file name for // versatile match appName = app.packageName + ".apk"; //$NON-NLS-1$ File targetOutput = useroutput; if ((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { targetOutput = sysoutput; } File destFile = new File(targetOutput, appName); handler.sendMessage(Message.obtain(handler, MSG_COPING, appName)); try { copyFile(srcFile, destFile); succeed++; } catch (Exception e) { Log.e(ApplicationManager.class.getName(), e.getLocalizedMessage(), e); handler.sendMessage( Message.obtain(handler, MSG_COPING_ERROR, 1, 0, e.getLocalizedMessage())); continue; } } } } handler.sendMessage(Message.obtain(handler, MSG_COPING_FINISHED, succeed, skipped, apps)); } }).start(); } static String getFileName(String fullName) { if (fullName == null) { return null; } int idx = fullName.lastIndexOf('/'); if (idx == -1) { return fullName; } return fullName.substring(idx + 1); } static String getFolderName(String fullName) { if (fullName == null) { return null; } int idx = fullName.lastIndexOf('/'); if (idx == -1) { return fullName; } fullName = fullName.substring(0, idx); // find the second last segment idx = fullName.lastIndexOf('/'); if (idx == -1) { return fullName; } return fullName.substring(idx + 1); } static void copyFile(File src, File dest) throws IOException { InputStream fis = new BufferedInputStream(new FileInputStream(src), 8192 * 4); OutputStream fos = new BufferedOutputStream(new FileOutputStream(dest), 8192 * 4); byte[] buf = new byte[4096]; int i; while ((i = fis.read(buf)) != -1) { fos.write(buf, 0, i); } fis.close(); fos.close(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { Activity ctx = getActivity(); if (requestCode == REQUEST_SETTINGS && data != null) { Util.updateStringOption(data, ctx, PSTORE_APPMANAGER, PREF_KEY_APP_EXPORT_DIR); Util.updateIntOption(data, ctx, PSTORE_APPMANAGER, PREF_KEY_FILTER_APP_TYPE, APP_TYPE_ALL); Util.updateIntOption(data, ctx, PSTORE_APPMANAGER, PREF_KEY_SORT_ORDER_TYPE, ORDER_TYPE_NAME); Util.updateIntOption(data, ctx, PSTORE_APPMANAGER, PREF_KEY_SORT_DIRECTION, ORDER_ASC); Util.updateIntOption(data, ctx, PSTORE_APPMANAGER, PREF_KEY_DEFAULT_TAP_ACTION, ACTION_MENU); Util.updateBooleanOption(data, ctx, PSTORE_APPMANAGER, PREF_KEY_SHOW_SIZE); Util.updateBooleanOption(data, ctx, PSTORE_APPMANAGER, PREF_KEY_SHOW_DATE); Util.updateBooleanOption(data, ctx, PSTORE_APPMANAGER, PREF_KEY_SHOW_ICON); Util.updateBooleanOption(data, ctx, PSTORE_APPMANAGER, PREF_KEY_SHOW_BACKUP_STATE); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { MenuItem mi = menu.add(Menu.NONE, MI_DELETE, Menu.NONE, R.string.uninstall); mi.setIcon(android.R.drawable.ic_menu_delete); Util.setShowAsAction(mi, MenuItem.SHOW_AS_ACTION_IF_ROOM); mi = menu.add(Menu.NONE, MI_SHARE, Menu.NONE, R.string.share); mi.setIcon(android.R.drawable.ic_menu_share); Util.setShowAsAction(mi, MenuItem.SHOW_AS_ACTION_IF_ROOM); if (Util.getSettingsIntent(getActivity().getPackageManager(), "com.android.settings.UsageStats") != null) //$NON-NLS-1$ { mi = menu.add(Menu.NONE, MI_USAGE_STATS, Menu.NONE, R.string.usage_stats); mi.setIcon(android.R.drawable.ic_menu_recent_history); Util.setShowAsAction(mi, MenuItem.SHOW_AS_ACTION_NEVER); } mi = menu.add(Menu.NONE, MI_REVERT, Menu.NONE, R.string.restore); mi.setIcon(android.R.drawable.ic_menu_revert); Util.setShowAsAction(mi, MenuItem.SHOW_AS_ACTION_ALWAYS); mi = menu.add(Menu.NONE, MI_PREFERENCE, Menu.NONE, R.string.preference); mi.setIcon(android.R.drawable.ic_menu_preferences); Util.setShowAsAction(mi, MenuItem.SHOW_AS_ACTION_NEVER); } @Override public boolean onOptionsItemSelected(MenuItem item) { Activity ctx = getActivity(); if (item.getItemId() == MI_PREFERENCE) { Intent it = new Intent(ctx, AppSettings.class); it.putExtra(PREF_KEY_FILTER_APP_TYPE, Util.getIntOption(ctx, PSTORE_APPMANAGER, PREF_KEY_FILTER_APP_TYPE, APP_TYPE_ALL)); it.putExtra(PREF_KEY_APP_EXPORT_DIR, Util.getStringOption(ctx, PSTORE_APPMANAGER, PREF_KEY_APP_EXPORT_DIR, DEFAULT_EXPORT_FOLDER)); it.putExtra(PREF_KEY_SORT_ORDER_TYPE, Util.getIntOption(ctx, PSTORE_APPMANAGER, PREF_KEY_SORT_ORDER_TYPE, ORDER_TYPE_NAME)); it.putExtra(PREF_KEY_SORT_DIRECTION, Util.getIntOption(ctx, PSTORE_APPMANAGER, PREF_KEY_SORT_DIRECTION, ORDER_ASC)); it.putExtra(PREF_KEY_SHOW_SIZE, Util.getBooleanOption(ctx, PSTORE_APPMANAGER, PREF_KEY_SHOW_SIZE)); it.putExtra(PREF_KEY_SHOW_DATE, Util.getBooleanOption(ctx, PSTORE_APPMANAGER, PREF_KEY_SHOW_DATE)); it.putExtra(PREF_KEY_SHOW_ICON, Util.getBooleanOption(ctx, PSTORE_APPMANAGER, PREF_KEY_SHOW_ICON)); it.putExtra(PREF_KEY_SHOW_BACKUP_STATE, Util.getBooleanOption(ctx, PSTORE_APPMANAGER, PREF_KEY_SHOW_BACKUP_STATE)); it.putExtra(PREF_KEY_DEFAULT_TAP_ACTION, Util.getIntOption(ctx, PSTORE_APPMANAGER, PREF_KEY_DEFAULT_TAP_ACTION, ACTION_MENU)); startActivityForResult(it, REQUEST_SETTINGS); return true; } else if (item.getItemId() == MI_REVERT) { Intent it = new Intent(ctx, RestoreAppActivity.class); it.putExtra(KEY_RESTORE_PATH, new File( Util.getStringOption(ctx, PSTORE_APPMANAGER, PREF_KEY_APP_EXPORT_DIR, DEFAULT_EXPORT_FOLDER), USER_APP).getAbsolutePath()); it.putExtra(KEY_ARCHIVE_PATH, new File( Util.getStringOption(ctx, PSTORE_APPMANAGER, PREF_KEY_APP_EXPORT_DIR, DEFAULT_EXPORT_FOLDER), ARCHIVED).getAbsolutePath()); startActivityForResult(it, REQUEST_RESTORE); return true; } else if (item.getItemId() == MI_USAGE_STATS) { Intent it = Util.getSettingsIntent(ctx.getPackageManager(), "com.android.settings.UsageStats"); //$NON-NLS-1$ if (it != null) { startActivity(it); } return true; } else if (item.getItemId() == MI_SHARE) { doShare(); return true; } else if (item.getItemId() == MI_DELETE) { doUninstall(); return true; } return false; } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { menu.setHeaderTitle(R.string.actions); menu.add(Menu.NONE, MI_MANAGE, Menu.NONE, R.string.manage); menu.add(Menu.NONE, MI_LAUNCH, Menu.NONE, R.string.run); menu.add(Menu.NONE, MI_SEARCH, Menu.NONE, R.string.search_market); menu.add(Menu.NONE, MI_DETAILS, Menu.NONE, R.string.details); } @Override public boolean onContextItemSelected(MenuItem item) { int pos = ((AdapterContextMenuInfo) item.getMenuInfo()).position; if (pos >= 0 && pos < getListView().getCount()) { AppInfoHolder ai = (AppInfoHolder) getListView().getItemAtPosition(pos); if (item.getItemId() == MI_MANAGE) { handleAction(ai, ACTION_MANAGE); return true; } else if (item.getItemId() == MI_LAUNCH) { handleAction(ai, ACTION_LAUNCH); return true; } else if (item.getItemId() == MI_SEARCH) { handleAction(ai, ACTION_SEARCH); return true; } else if (item.getItemId() == MI_DETAILS) { handleAction(ai, ACTION_DETAILS); return true; } } return false; } void handleAction(final AppInfoHolder ai, int action) { Activity ctx = getActivity(); String pkgName = ai.appInfo.packageName; switch (action) { case ACTION_MENU: OnClickListener listener = new OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); // bypass the 'showMenu' action offset int action = which + 1; handleAction(ai, action); } }; new AlertDialog.Builder(ctx).setTitle(R.string.actions) .setItems(new CharSequence[] { getString(R.string.manage), getString(R.string.run), getString(R.string.search_market), getString(R.string.details) }, listener) .create().show(); break; case ACTION_MANAGE: Intent it = new Intent(Intent.ACTION_VIEW); it.setClassName("com.android.settings", //$NON-NLS-1$ "com.android.settings.InstalledAppDetails"); //$NON-NLS-1$ it.putExtra("com.android.settings.ApplicationPkgName", pkgName); //$NON-NLS-1$ // this is for Froyo it.putExtra("pkg", pkgName); //$NON-NLS-1$ List<ResolveInfo> acts = ctx.getPackageManager().queryIntentActivities(it, 0); if (acts.size() > 0) { startActivity(it); } else { // for ginger bread it = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS", //$NON-NLS-1$ Uri.fromParts("package", pkgName, null)); //$NON-NLS-1$ acts = ctx.getPackageManager().queryIntentActivities(it, 0); if (acts.size() > 0) { startActivity(it); } else { Log.d(ApplicationManager.class.getName(), "Failed to resolve activity for InstalledAppDetails"); //$NON-NLS-1$ } } break; case ACTION_LAUNCH: if (!pkgName.equals(ctx.getPackageName())) { it = new Intent("android.intent.action.MAIN"); //$NON-NLS-1$ it.addCategory(Intent.CATEGORY_LAUNCHER); acts = ctx.getPackageManager().queryIntentActivities(it, 0); if (acts != null) { boolean started = false; for (int i = 0, size = acts.size(); i < size; i++) { ResolveInfo ri = acts.get(i); if (pkgName.equals(ri.activityInfo.packageName)) { it.setClassName(ri.activityInfo.packageName, ri.activityInfo.name); it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); try { startActivity(it); started = true; } catch (Exception e) { Log.e(ApplicationManager.class.getName(), "Cannot start activity: " + pkgName, //$NON-NLS-1$ e); } break; } } if (!started) { Util.shortToast(ctx, R.string.run_failed); } } } break; case ACTION_SEARCH: it = new Intent(Intent.ACTION_VIEW); it.setData(Uri.parse("market://search?q=pname:" + pkgName)); //$NON-NLS-1$ it = Intent.createChooser(it, null); startActivity(it); break; case ACTION_DETAILS: ApplicationInfo appInfo = ai.appInfo; String installDate; String fileSize; if (appInfo.sourceDir != null) { File f = new File(appInfo.sourceDir); installDate = DateUtils.formatDateTime(ctx, f.lastModified(), DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME); fileSize = Formatter.formatFileSize(ctx, f.length()); } else { installDate = fileSize = getString(R.string.unknown); } StringBuffer sb = new StringBuffer().append("<small>") //$NON-NLS-1$ .append(getString(R.string.pkg_name)).append(": ") //$NON-NLS-1$ .append(appInfo.packageName).append("<br>") //$NON-NLS-1$ .append(getString(R.string.version_code)).append(": ") //$NON-NLS-1$ .append(ai.versionCode).append("<br>") //$NON-NLS-1$ .append(getString(R.string.target_sdk)).append(": ") //$NON-NLS-1$ .append(Util.getTargetSdkVersion(ctx, appInfo)).append("<br>") //$NON-NLS-1$ .append(getString(R.string.uid)).append(": ") //$NON-NLS-1$ .append(appInfo.uid).append("<br>") //$NON-NLS-1$ .append(getString(R.string.file_size)).append(": ") //$NON-NLS-1$ .append(fileSize).append("<br>") //$NON-NLS-1$ .append(getString(R.string.public_source)).append(": ") //$NON-NLS-1$ .append(appInfo.publicSourceDir).append("<br>") //$NON-NLS-1$ .append(getString(R.string.source)).append(": ") //$NON-NLS-1$ .append(appInfo.sourceDir).append("<br>") //$NON-NLS-1$ .append(getString(R.string.data)).append(": ") //$NON-NLS-1$ .append(appInfo.dataDir).append("<br>") //$NON-NLS-1$ .append(getString(R.string.installed_date)).append(": ") //$NON-NLS-1$ .append(installDate).append("<br>") //$NON-NLS-1$ .append(getString(R.string.process)).append(": ") //$NON-NLS-1$ .append(appInfo.processName).append("<br>") //$NON-NLS-1$ .append(getString(R.string.app_class)).append(": ") //$NON-NLS-1$ .append(appInfo.className == null ? "" //$NON-NLS-1$ : appInfo.className) .append("<br>") //$NON-NLS-1$ .append(getString(R.string.task_affinity)).append(": ") //$NON-NLS-1$ .append(appInfo.taskAffinity).append("<br>") //$NON-NLS-1$ .append(getString(R.string.permission)).append(": ") //$NON-NLS-1$ .append(appInfo.permission == null ? "" //$NON-NLS-1$ : appInfo.permission) .append("<br>") //$NON-NLS-1$ .append(getString(R.string.flags)).append(": ") //$NON-NLS-1$ .append(appInfo.flags).append("<br>") //$NON-NLS-1$ .append(getString(R.string.enabled)).append(": ") //$NON-NLS-1$ .append(appInfo.enabled).append("<br>") //$NON-NLS-1$ .append(getString(R.string.manage_space_ac)).append(": ") //$NON-NLS-1$ .append(appInfo.manageSpaceActivityName == null ? "" //$NON-NLS-1$ : appInfo.manageSpaceActivityName) .append("</small>"); //$NON-NLS-1$ new AlertDialog.Builder(ctx).setTitle(ai.label == null ? appInfo.packageName : ai.label) .setNeutralButton(R.string.close, null).setMessage(Html.fromHtml(sb.toString())).create() .show(); break; } } private void doUninstall() { final List<AppInfoHolder> sels = getSelected(getListView()); if (sels == null || sels.size() == 0) { Util.shortToast(getActivity(), R.string.no_app_selected); } else { OnClickListener listener = new OnClickListener() { public void onClick(DialogInterface dialog, int which) { boolean canUninstall = false; for (int i = 0, size = sels.size(); i < size; i++) { ApplicationInfo app = sels.get(i).appInfo; Intent it = new Intent(Intent.ACTION_DELETE, Uri.parse("package:" //$NON-NLS-1$ + app.packageName)); if (!canUninstall) { List<ResolveInfo> acts = getActivity().getPackageManager().queryIntentActivities(it, 0); canUninstall = acts.size() > 0; } if (canUninstall) { startActivity(it); } } if (!canUninstall) { Util.shortToast(getActivity(), R.string.uninstall_fail); Log.d(ApplicationManager.class.getName(), "No activity found to handle the uninstall request."); //$NON-NLS-1$ } } }; new AlertDialog.Builder(getActivity()).setTitle(R.string.warning).setMessage(R.string.uninstall_msg) .setPositiveButton(android.R.string.ok, listener) .setNegativeButton(android.R.string.cancel, null).create().show(); } } private void doShare() { final List<AppInfoHolder> sels = getSelected(getListView()); if (sels == null || sels.size() == 0) { Util.shortToast(getActivity(), R.string.no_app_selected); } else { final boolean[] items = new boolean[] { true, true, true, true }; OnMultiChoiceClickListener selListener = new OnMultiChoiceClickListener() { public void onClick(DialogInterface dialog, int which, boolean isChecked) { items[which] = isChecked; } }; OnClickListener sendListener = new OnClickListener() { public void onClick(DialogInterface dialog, int which) { OnClickListener typeListener = new OnClickListener() { public void onClick(DialogInterface dialog, int which) { sendContent(items, sels, which == 0); } }; new AlertDialog.Builder(getActivity()).setTitle(R.string.actions) .setItems(new String[] { getString(R.string.copy), getString(R.string.send), }, typeListener) .create().show(); } }; new AlertDialog.Builder(getActivity()).setTitle(R.string.include) .setMultiChoiceItems( new CharSequence[] { getString(R.string.version), getString(R.string.target_sdk), getString(R.string.pkg_name), getString(R.string.market_link), }, items, selListener) .setPositiveButton(android.R.string.ok, sendListener) .setNegativeButton(android.R.string.cancel, null).create().show(); } } void sendContent(final boolean[] items, final List<AppInfoHolder> apps, final boolean isCopy) { if (progress != null) { progress.dismiss(); } progress = new ProgressDialog(getActivity()); progress.setMessage(getResources().getText(R.string.loading)); progress.setIndeterminate(true); progress.show(); // move this out of the thread code to avoid exception under honeycomb final ClipboardManager cm = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); new Thread(new Runnable() { public void run() { String content = collectTextContent(items, apps); if (isCopy) { if (cm != null && content != null) { cm.setText(content); } handler.sendEmptyMessage(MSG_DISMISS_PROGRESS); handler.sendMessage(handler.obtainMessage(MSG_TOAST, getString(R.string.copied_hint))); return; } if (aborted) { return; } if (content != null) { handler.sendMessage(handler.obtainMessage(MSG_CHECK_FORCE_COMPRESSION, SiragonManager.PLAINTEXT, 0, content)); } else { handler.sendMessage( handler.obtainMessage(MSG_CONTENT_READY, SiragonManager.PLAINTEXT, 0, content)); } } }).start(); } String collectTextContent(boolean[] items, List<AppInfoHolder> apps) { StringBuilder sb = new StringBuilder(); for (int i = 0, size = apps.size(); i < size; i++) { AppInfoHolder ai = apps.get(i); if (i > 0) { sb.append('\n'); } sb.append(ai.label == null ? ai.appInfo.packageName : ai.label); if (items[0]) { sb.append(", " + ai.version); //$NON-NLS-1$ } if (items[1]) { sb.append(", SDK " + Util.getTargetSdkVersion(getActivity(), ai.appInfo)); //$NON-NLS-1$ } if (items[2]) { sb.append(", " + ai.appInfo.packageName); //$NON-NLS-1$ } if (items[3]) { sb.append(", http://market.android.com/search?q=pname:" //$NON-NLS-1$ + ai.appInfo.packageName); } } if (sb.length() > 0) { return sb.toString(); } return null; } void doExport() { final List<AppInfoHolder> sels = getSelected(getListView()); if (sels == null || sels.size() == 0) { Util.shortToast(getActivity(), R.string.no_app_selected); } else if (!ensureSDCard()) { Util.shortToast(getActivity(), R.string.error_sdcard); } else { OnClickListener listener = new OnClickListener() { public void onClick(DialogInterface dialog, int which) { if (which == Dialog.BUTTON_POSITIVE) { export(sels); } } }; new AlertDialog.Builder(getActivity()).setTitle(R.string.warning) .setMessage(getString(R.string.warning_msg, Util.getStringOption(getActivity(), PSTORE_APPMANAGER, PREF_KEY_APP_EXPORT_DIR, DEFAULT_EXPORT_FOLDER))) .setPositiveButton(R.string.cont, listener).setNegativeButton(android.R.string.cancel, listener) .create().show(); } } void toggleAllSelection(boolean selected) { ListView lstApps = getListView(); int totalCount = lstApps.getCount(); for (int i = 0; i < totalCount; i++) { AppInfoHolder holder = (AppInfoHolder) lstApps.getItemAtPosition(i); holder.checked = selected; } if (!selected) { hideButtons(); } ((ArrayAdapter) lstApps.getAdapter()).notifyDataSetChanged(); } void hideButtons() { if (rootView == null) { return; } View v = rootView.findViewById(R.id.app_footer); if (v.getVisibility() != View.GONE) { v.setVisibility(View.GONE); v.startAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.footer_disappear)); } } /** * PackageSizeObserver */ private static final class PkgSizeObserver extends IPackageStatsObserver.Stub { private CountDownLatch count; private Activity ac; private AppCache appCache; PkgSizeObserver(CountDownLatch count, Activity ac, AppCache appCache) { this.count = count; this.ac = ac; this.appCache = appCache; } void invokeGetPkgSize(String pkgName, PackageManager pm) { if (mdGetPackageSizeInfo != null) { try { mdGetPackageSizeInfo.invoke(pm, pkgName, this); } catch (Exception e) { Log.e(ApplicationManager.class.getName(), e.getLocalizedMessage(), e); } } } public void onGetStatsCompleted(PackageStats pStats, boolean succeeded) throws RemoteException { AppInfoHolder holder = appCache.appLookup.get(pStats.packageName); if (holder != null) { synchronized (appCache) { holder.size = Formatter.formatFileSize(ac, pStats.codeSize) + " + " //$NON-NLS-1$ + Formatter.formatFileSize(ac, pStats.dataSize) + " (" //$NON-NLS-1$ + Formatter.formatFileSize(ac, pStats.cacheSize) + ')'; holder.codeSize = pStats.codeSize; holder.dataSize = pStats.dataSize; holder.cacheSize = pStats.cacheSize; } } count.countDown(); } } /** * PkgSizeUpdaterThread */ private static final class PkgSizeUpdaterThread extends Thread { private Activity ac; private AppCache appCache; private Handler handler; volatile boolean aborted; PkgSizeUpdaterThread(Activity ac, AppCache appCache, Handler handler) { super("SizeUpdater"); //$NON-NLS-1$ this.ac = ac; this.appCache = appCache; this.handler = handler; } @Override public void run() { PackageManager pm = ac.getPackageManager(); ArrayList<AppInfoHolder> localList = appCache.generateLocalList(); int totalSize = localList.size(); int secSize = 32; int num = totalSize / secSize; if (num * secSize < totalSize) { num++; } for (int k = 0; k < num; k++) { int secCount = (k + 1) * secSize > totalSize ? (totalSize - k * secSize) : secSize; CountDownLatch count = new CountDownLatch(secCount); PkgSizeObserver observer = new PkgSizeObserver(count, ac, appCache); for (int i = 0; i < secCount; i++) { if (aborted) { return; } observer.invokeGetPkgSize(localList.get(k * secSize + i).appInfo.packageName, pm); } try { count.await(); if (k == num - 1) { int type = Util.getIntOption(ac, PSTORE_APPMANAGER, PREF_KEY_SORT_ORDER_TYPE, ORDER_TYPE_NAME); if (type == ORDER_TYPE_CODE_SIZE || type == ORDER_TYPE_DATA_SIZE || type == ORDER_TYPE_CACHE_SIZE || type == ORDER_TYPE_TOTAL_SIZE) { appCache.reOrder(type, Util.getIntOption(ac, PSTORE_APPMANAGER, PREF_KEY_SORT_DIRECTION, ORDER_ASC)); handler.sendMessage(handler.obtainMessage(MSG_REFRESH_PKG_SIZE, 1, 0)); return; } } handler.sendMessage(handler.obtainMessage(MSG_REFRESH_PKG_SIZE, 0, 0)); } catch (InterruptedException e) { Log.e(ApplicationManager.class.getName(), e.getLocalizedMessage(), e); } } } } /** * ResourceUpdaterThread */ private static final class ResourceUpdaterThread extends Thread { private Activity ac; private AppCache appCache; private Handler handler; volatile boolean aborted; ResourceUpdaterThread(Activity ac, AppCache appCache, Handler handler) { super("ResourceUpdater"); //$NON-NLS-1$ this.ac = ac; this.appCache = appCache; this.handler = handler; } public void run() { ApplicationInfo ai; AppInfoHolder holder; PackageManager pm = ac.getPackageManager(); ArrayList<AppInfoHolder> localList = appCache.generateLocalList(); for (int i = 0, size = localList.size(); i < size; i++) { if (aborted) { return; } ai = localList.get(i).appInfo; CharSequence label = ai.loadLabel(pm); holder = appCache.appLookup.get(ai.packageName); if (holder != null) { synchronized (appCache) { holder.label = label; } } } // reorder by new names if (Util.getIntOption(ac, PSTORE_APPMANAGER, PREF_KEY_SORT_ORDER_TYPE, ORDER_TYPE_NAME) == ORDER_TYPE_NAME) { appCache.reOrder(ORDER_TYPE_NAME, Util.getIntOption(ac, PSTORE_APPMANAGER, PREF_KEY_SORT_DIRECTION, ORDER_ASC)); handler.sendMessage(handler.obtainMessage(MSG_REFRESH_PKG_LABEL, 1, 0)); } else { handler.sendMessage(handler.obtainMessage(MSG_REFRESH_PKG_LABEL, 0, 0)); } if (Util.getBooleanOption(ac, PSTORE_APPMANAGER, PREF_KEY_SHOW_ICON)) { for (int i = 0, size = localList.size(); i < size; i++) { if (aborted) { return; } ai = localList.get(i).appInfo; try { Drawable icon = ai.loadIcon(pm); holder = appCache.appLookup.get(ai.packageName); if (holder != null) { holder.icon = icon; } } catch (OutOfMemoryError oom) { Log.e(ApplicationManager.class.getName(), "OOM when loading icon: " //$NON-NLS-1$ + ai.packageName, oom); } } handler.sendEmptyMessage(MSG_REFRESH_PKG_ICON); } } } /** * BackupStateUpdaterThread */ private static final class BackupStateUpdaterThread extends Thread { private Activity ac; private List<AppInfoHolder> apps; private AppCache appCache; private Handler handler; volatile boolean aborted; BackupStateUpdaterThread(Activity ac, List<AppInfoHolder> apps, AppCache appCache, Handler handler) { super("BackupStateUpdater"); //$NON-NLS-1$ this.ac = ac; this.apps = apps; this.appCache = appCache; this.handler = handler; } public void run() { if (apps == null) { apps = appCache.generateLocalList(); } if (apps == null || apps.size() == 0) { return; } String exportFolder = Util.getStringOption(ac, PSTORE_APPMANAGER, PREF_KEY_APP_EXPORT_DIR, DEFAULT_EXPORT_FOLDER); File sysoutput = null; File useroutput = null; File output = new File(exportFolder); if (output.exists()) { sysoutput = new File(output, SYS_APP); if (!sysoutput.exists()) { sysoutput = null; } useroutput = new File(output, USER_APP); if (!useroutput.exists()) { useroutput = null; } } ApplicationInfo ai; AppInfoHolder holder; PackageInfo pi; PackageManager pm = ac.getPackageManager(); for (int i = 0, size = apps.size(); i < size; i++) { if (aborted) { return; } ai = apps.get(i).appInfo; holder = appCache.appLookup.get(ai.packageName); if (holder != null) { File targetOutput = useroutput; if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { targetOutput = sysoutput; } if (targetOutput != null) { String src = ai.sourceDir; if (src != null) { String appName = getFileName(src); if (appName != null && ai.packageName != null) { // always use package name as the file name for // versatile match appName = ai.packageName + ".apk"; //$NON-NLS-1$ File destFile = new File(targetOutput, appName); if (destFile.exists()) { pi = pm.getPackageArchiveInfo(destFile.getAbsolutePath(), 0); if (pi != null) { synchronized (appCache) { if (pi.versionCode < holder.versionCode) { holder.backupState = 1; } else if (pi.versionCode == holder.versionCode) { holder.backupState = 2; } else { holder.backupState = 3; } } continue; } } } } } synchronized (appCache) { holder.backupState = 0; } } } // reorder by backup state if (Util.getIntOption(ac, PSTORE_APPMANAGER, PREF_KEY_SORT_ORDER_TYPE, ORDER_TYPE_NAME) == ORDER_TYPE_BACKUP_STATE) { appCache.reOrder(ORDER_TYPE_BACKUP_STATE, Util.getIntOption(ac, PSTORE_APPMANAGER, PREF_KEY_SORT_DIRECTION, ORDER_ASC)); handler.sendMessage(handler.obtainMessage(MSG_REFRESH_BACKUP_STATE, 1, 0)); } else { handler.sendMessage(handler.obtainMessage(MSG_REFRESH_BACKUP_STATE, 0, 0)); } } } /** * AppInfoHolder */ private static final class AppInfoHolder { ApplicationInfo appInfo; CharSequence label; CharSequence version; Drawable icon; String size; long codeSize, dataSize, cacheSize; int backupState; int versionCode; boolean isPrivate; boolean checked; AppInfoHolder() { } @Override public boolean equals(Object o) { if (!(o instanceof AppInfoHolder)) { return false; } AppInfoHolder that = (AppInfoHolder) o; return this.appInfo.packageName.equals(that.appInfo.packageName); } } /** * NameComparator */ private static final class NameComparator implements Comparator<AppInfoHolder> { Collator clt = Collator.getInstance(); int direction; NameComparator(int direction) { this.direction = direction; } public int compare(AppInfoHolder obj1, AppInfoHolder obj2) { String s1 = obj1.label == null ? obj1.appInfo.packageName : obj1.label.toString(); String s2 = obj2.label == null ? obj2.appInfo.packageName : obj2.label.toString(); return clt.compare(s1, s2) * direction; } } /** * SizeComparator */ private static final class SizeComparator implements Comparator<AppInfoHolder> { int type; int direction; SizeComparator(int type, int direction) { this.type = type; this.direction = direction; } public int compare(AppInfoHolder obj1, AppInfoHolder obj2) { switch (type) { case ORDER_TYPE_CODE_SIZE: return (obj1.codeSize == obj2.codeSize ? 0 : (obj1.codeSize < obj2.codeSize ? -1 : 1)) * direction; case ORDER_TYPE_DATA_SIZE: return (obj1.dataSize == obj2.dataSize ? 0 : (obj1.dataSize < obj2.dataSize ? -1 : 1)) * direction; case ORDER_TYPE_CACHE_SIZE: return (obj1.cacheSize == obj2.cacheSize ? 0 : (obj1.cacheSize < obj2.cacheSize ? -1 : 1)) * direction; case ORDER_TYPE_TOTAL_SIZE: long s1 = obj1.codeSize + obj1.dataSize + obj1.cacheSize; long s2 = obj2.codeSize + obj2.dataSize + obj2.cacheSize; return (s1 == s2 ? 0 : (s1 < s2 ? -1 : 1)) * direction; } return 0; } } /** * AppCache */ private static final class AppCache { ArrayList<AppInfoHolder> appList; HashMap<String, AppInfoHolder> appLookup; AppCache() { appList = new ArrayList<AppInfoHolder>(); appLookup = new HashMap<String, AppInfoHolder>(); } synchronized void clear() { appList.clear(); appLookup.clear(); } synchronized ArrayList<AppInfoHolder> generateLocalList() { ArrayList<AppInfoHolder> local = new ArrayList<AppInfoHolder>(); local.addAll(appList); return local; } synchronized void update(ArrayList<AppInfoHolder> apps) { appList.retainAll(apps); for (int i = 0, size = apps.size(); i < size; i++) { AppInfoHolder ai = apps.get(i); AppInfoHolder oai = appLookup.get(ai.appInfo.packageName); if (oai == null) { oai = ai; appLookup.put(ai.appInfo.packageName, ai); } else { oai.appInfo = ai.appInfo; oai.version = ai.version; oai.isPrivate = ai.isPrivate; oai.checked = ai.checked; oai.versionCode = ai.versionCode; } if (!appList.contains(oai)) { appList.add(oai); } } } synchronized void reOrder(int type, final int direction) { switch (type) { case ORDER_TYPE_NAME: Collections.sort(appList, new NameComparator(direction)); break; case ORDER_TYPE_CODE_SIZE: case ORDER_TYPE_DATA_SIZE: case ORDER_TYPE_CACHE_SIZE: case ORDER_TYPE_TOTAL_SIZE: Collections.sort(appList, new SizeComparator(type, direction)); break; case ORDER_TYPE_INSTALL_DATE: Collections.sort(appList, new Comparator<AppInfoHolder>() { public int compare(AppInfoHolder obj1, AppInfoHolder obj2) { long d1 = 0; long d2 = 0; if (obj1.appInfo.sourceDir != null) { d1 = new File(obj1.appInfo.sourceDir).lastModified(); } if (obj2.appInfo.sourceDir != null) { d2 = new File(obj2.appInfo.sourceDir).lastModified(); } return (d1 == d2 ? 0 : (d1 < d2 ? -1 : 1)) * direction; } }); break; case ORDER_TYPE_BACKUP_STATE: Collections.sort(appList, new Comparator<AppInfoHolder>() { public int compare(AppInfoHolder obj1, AppInfoHolder obj2) { return (obj1.backupState - obj2.backupState) * direction; } }); break; } } } /** * AppSettings */ public static final class AppSettings extends PreferenceActivity { @Override protected void onCreate(Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); setPreferenceScreen(getPreferenceManager().createPreferenceScreen(this)); PreferenceCategory pc = new PreferenceCategory(this); pc.setTitle(R.string.preference); getPreferenceScreen().addPreference(pc); Preference perfExportDir = new Preference(this); perfExportDir.setKey(PREF_KEY_APP_EXPORT_DIR); perfExportDir.setTitle(R.string.export_dir); pc.addPreference(perfExportDir); Preference perfFilter = new Preference(this); perfFilter.setKey(PREF_KEY_FILTER_APP_TYPE); perfFilter.setTitle(R.string.filter_title); pc.addPreference(perfFilter); CheckBoxPreference perfShowSize = new CheckBoxPreference(this); perfShowSize.setKey(PREF_KEY_SHOW_SIZE); perfShowSize.setTitle(R.string.show_app_size); perfShowSize.setSummary(R.string.show_app_size_sum); pc.addPreference(perfShowSize); CheckBoxPreference perfShowDate = new CheckBoxPreference(this); perfShowDate.setKey(PREF_KEY_SHOW_DATE); perfShowDate.setTitle(R.string.show_app_date); perfShowDate.setSummary(R.string.show_app_date_sum); pc.addPreference(perfShowDate); CheckBoxPreference perfShowIcon = new CheckBoxPreference(this); perfShowIcon.setKey(PREF_KEY_SHOW_ICON); perfShowIcon.setTitle(R.string.show_app_icon); perfShowIcon.setSummary(R.string.show_app_icon_sum); pc.addPreference(perfShowIcon); CheckBoxPreference perfShowBackup = new CheckBoxPreference(this); perfShowBackup.setKey(PREF_KEY_SHOW_BACKUP_STATE); perfShowBackup.setTitle(R.string.show_backup_state); perfShowBackup.setSummary(R.string.show_backup_state_sum); pc.addPreference(perfShowBackup); Preference perfDefaultAction = new Preference(this); perfDefaultAction.setKey(PREF_KEY_DEFAULT_TAP_ACTION); perfDefaultAction.setTitle(R.string.default_tap_action); pc.addPreference(perfDefaultAction); pc = new PreferenceCategory(this); pc.setTitle(R.string.sort); getPreferenceScreen().addPreference(pc); Preference perfSortType = new Preference(this); perfSortType.setKey(PREF_KEY_SORT_ORDER_TYPE); perfSortType.setTitle(R.string.sort_type); pc.addPreference(perfSortType); Preference perfSortDirection = new Preference(this); perfSortDirection.setKey(PREF_KEY_SORT_DIRECTION); perfSortDirection.setTitle(R.string.sort_direction); pc.addPreference(perfSortDirection); refreshBackupFolder(); refreshAppType(); refreshDefaultAction(); refreshSortType(); refreshSortDirection(); refreshBooleanOption(PREF_KEY_SHOW_SIZE); refreshBooleanOption(PREF_KEY_SHOW_DATE); refreshBooleanOption(PREF_KEY_SHOW_ICON); refreshBooleanOption(PREF_KEY_SHOW_BACKUP_STATE); setResult(RESULT_OK, getIntent()); } void refreshBooleanOption(String key) { boolean val = getIntent().getBooleanExtra(key, true); ((CheckBoxPreference) findPreference(key)).setChecked(val); } void refreshBackupFolder() { findPreference(PREF_KEY_APP_EXPORT_DIR).setSummary(getIntent().getStringExtra(PREF_KEY_APP_EXPORT_DIR)); } void refreshAppType() { int type = getIntent().getIntExtra(PREF_KEY_FILTER_APP_TYPE, APP_TYPE_ALL); int res = R.string.all_apps; if (type == APP_TYPE_SYS) { res = R.string.sys_apps; } else if (type == APP_TYPE_USER) { res = R.string.user_apps; } findPreference(PREF_KEY_FILTER_APP_TYPE).setSummary(res); } void refreshDefaultAction() { int type = getIntent().getIntExtra(PREF_KEY_DEFAULT_TAP_ACTION, ACTION_MENU); String label = null; switch (type) { case ACTION_MANAGE: label = getString(R.string.manage); break; case ACTION_LAUNCH: label = getString(R.string.run); break; case ACTION_SEARCH: label = getString(R.string.search_market); break; case ACTION_DETAILS: label = getString(R.string.details); break; case ACTION_MENU: label = getString(R.string.show_menu); break; } findPreference(PREF_KEY_DEFAULT_TAP_ACTION).setSummary(label); } void refreshSortType() { int type = getIntent().getIntExtra(PREF_KEY_SORT_ORDER_TYPE, ORDER_TYPE_NAME); String label = null; switch (type) { case ORDER_TYPE_NAME: label = getString(R.string.name); break; case ORDER_TYPE_CODE_SIZE: label = getString(R.string.code_size); break; case ORDER_TYPE_DATA_SIZE: label = getString(R.string.data_size); break; case ORDER_TYPE_CACHE_SIZE: label = getString(R.string.cache_size); break; case ORDER_TYPE_TOTAL_SIZE: label = getString(R.string.total_size); break; case ORDER_TYPE_INSTALL_DATE: label = getString(R.string.installed_date); break; case ORDER_TYPE_BACKUP_STATE: label = getString(R.string.backup_state); } findPreference(PREF_KEY_SORT_ORDER_TYPE).setSummary(label); } void refreshSortDirection() { int type = getIntent().getIntExtra(PREF_KEY_SORT_DIRECTION, ORDER_ASC); String label = type == ORDER_ASC ? getString(R.string.ascending) : getString(R.string.descending); findPreference(PREF_KEY_SORT_DIRECTION).setSummary(label); } @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { final Intent it = getIntent(); if (PREF_KEY_APP_EXPORT_DIR.equals(preference.getKey())) { final EditText txt = new EditText(this); txt.setText(it.getStringExtra(PREF_KEY_APP_EXPORT_DIR)); OnClickListener listener = new OnClickListener() { public void onClick(DialogInterface dialog, int which) { String path = txt.getText().toString(); if (path != null) { path = path.trim(); if (path.length() == 0) { path = null; } } if (path == null) { path = DEFAULT_EXPORT_FOLDER; } it.putExtra(PREF_KEY_APP_EXPORT_DIR, path); dialog.dismiss(); refreshBackupFolder(); } }; new AlertDialog.Builder(this).setTitle(R.string.export_dir) .setPositiveButton(android.R.string.ok, listener) .setNegativeButton(android.R.string.cancel, null).setView(txt).create().show(); return true; } else if (PREF_KEY_FILTER_APP_TYPE.equals(preference.getKey())) { OnClickListener listener = new OnClickListener() { public void onClick(DialogInterface dialog, int which) { it.putExtra(PREF_KEY_FILTER_APP_TYPE, which); dialog.dismiss(); refreshAppType(); } }; new AlertDialog.Builder(this).setTitle(R.string.filter_title).setNeutralButton(R.string.close, null) .setSingleChoiceItems( new CharSequence[] { getText(R.string.all_apps), getText(R.string.sys_apps), getText(R.string.user_apps) }, it.getIntExtra(PREF_KEY_FILTER_APP_TYPE, APP_TYPE_ALL), listener) .create().show(); return true; } else if (PREF_KEY_SHOW_SIZE.equals(preference.getKey())) { it.putExtra(PREF_KEY_SHOW_SIZE, ((CheckBoxPreference) findPreference(PREF_KEY_SHOW_SIZE)).isChecked()); return true; } else if (PREF_KEY_SHOW_DATE.equals(preference.getKey())) { it.putExtra(PREF_KEY_SHOW_DATE, ((CheckBoxPreference) findPreference(PREF_KEY_SHOW_DATE)).isChecked()); return true; } else if (PREF_KEY_SHOW_ICON.equals(preference.getKey())) { it.putExtra(PREF_KEY_SHOW_ICON, ((CheckBoxPreference) findPreference(PREF_KEY_SHOW_ICON)).isChecked()); return true; } else if (PREF_KEY_SHOW_BACKUP_STATE.equals(preference.getKey())) { it.putExtra(PREF_KEY_SHOW_BACKUP_STATE, ((CheckBoxPreference) findPreference(PREF_KEY_SHOW_BACKUP_STATE)).isChecked()); return true; } else if (PREF_KEY_DEFAULT_TAP_ACTION.equals(preference.getKey())) { OnClickListener listener = new OnClickListener() { public void onClick(DialogInterface dialog, int which) { it.putExtra(PREF_KEY_DEFAULT_TAP_ACTION, which); dialog.dismiss(); refreshDefaultAction(); } }; new AlertDialog.Builder(this).setTitle(R.string.default_tap_action) .setNeutralButton(R.string.close, null) .setSingleChoiceItems( new String[] { getString(R.string.show_menu), getString(R.string.manage), getString(R.string.run), getString(R.string.search_market), getString(R.string.details), }, it.getIntExtra(PREF_KEY_DEFAULT_TAP_ACTION, ACTION_MENU), listener) .create().show(); return true; } else if (PREF_KEY_SORT_ORDER_TYPE.equals(preference.getKey())) { OnClickListener listener = new OnClickListener() { public void onClick(DialogInterface dialog, int which) { it.putExtra(PREF_KEY_SORT_ORDER_TYPE, which); dialog.dismiss(); refreshSortType(); } }; new AlertDialog.Builder(this).setTitle(R.string.sort_type).setNeutralButton(R.string.close, null) .setSingleChoiceItems( new String[] { getString(R.string.name), getString(R.string.code_size), getString(R.string.data_size), getString(R.string.cache_size), getString(R.string.total_size), getString(R.string.installed_date), getString(R.string.backup_state), }, it.getIntExtra(PREF_KEY_SORT_ORDER_TYPE, ORDER_TYPE_NAME), listener) .create().show(); return true; } else if (PREF_KEY_SORT_DIRECTION.equals(preference.getKey())) { OnClickListener listener = new OnClickListener() { public void onClick(DialogInterface dialog, int which) { it.putExtra(PREF_KEY_SORT_DIRECTION, which == 0 ? ORDER_ASC : ORDER_DESC); dialog.dismiss(); refreshSortDirection(); } }; new AlertDialog.Builder(this).setTitle(R.string.sort_direction) .setNeutralButton(R.string.close, null) .setSingleChoiceItems( new String[] { getString(R.string.ascending), getString(R.string.descending), }, it.getIntExtra(PREF_KEY_SORT_DIRECTION, ORDER_ASC) == ORDER_ASC ? 0 : 1, listener) .create().show(); return true; } return false; } } }