InstalledAppDetails.java revision 309c5dcee18ced447d049d5de882a9586694e04c
1/**
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy
6 * of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16
17package com.android.settings.applications;
18
19import com.android.settings.R;
20import com.android.settings.Utils;
21import com.android.settings.applications.ApplicationsState.AppEntry;
22
23import android.app.Activity;
24import android.app.ActivityManager;
25import android.app.AlertDialog;
26import android.app.Dialog;
27import android.app.DialogFragment;
28import android.app.Fragment;
29import android.app.INotificationManager;
30import android.app.NotificationManager;
31import android.app.admin.DevicePolicyManager;
32import android.appwidget.AppWidgetManager;
33import android.content.BroadcastReceiver;
34import android.content.ComponentName;
35import android.content.Context;
36import android.content.DialogInterface;
37import android.content.Intent;
38import android.content.IntentFilter;
39import android.content.pm.ApplicationInfo;
40import android.content.pm.IPackageDataObserver;
41import android.content.pm.IPackageMoveObserver;
42import android.content.pm.PackageInfo;
43import android.content.pm.PackageManager;
44import android.content.pm.ResolveInfo;
45import android.content.pm.PackageManager.NameNotFoundException;
46import android.hardware.usb.IUsbManager;
47import android.net.Uri;
48import android.os.AsyncTask;
49import android.os.Bundle;
50import android.os.Environment;
51import android.os.Handler;
52import android.os.IBinder;
53import android.os.Message;
54import android.os.RemoteException;
55import android.os.ServiceManager;
56import android.preference.PreferenceActivity;
57import android.text.SpannableString;
58import android.text.TextUtils;
59import android.text.format.Formatter;
60import android.text.style.BulletSpan;
61import android.util.Log;
62
63import java.lang.ref.WeakReference;
64import java.util.ArrayList;
65import java.util.List;
66import android.view.LayoutInflater;
67import android.view.View;
68import android.view.ViewGroup;
69import android.widget.AppSecurityPermissions;
70import android.widget.Button;
71import android.widget.CheckBox;
72import android.widget.CompoundButton;
73import android.widget.ImageView;
74import android.widget.LinearLayout;
75import android.widget.Switch;
76import android.widget.TextView;
77
78/**
79 * Activity to display application information from Settings. This activity presents
80 * extended information associated with a package like code, data, total size, permissions
81 * used by the application and also the set of default launchable activities.
82 * For system applications, an option to clear user data is displayed only if data size is > 0.
83 * System applications that do not want clear user data do not have this option.
84 * For non-system applications, there is no option to clear data. Instead there is an option to
85 * uninstall the application.
86 */
87public class InstalledAppDetails extends Fragment
88        implements View.OnClickListener, CompoundButton.OnCheckedChangeListener,
89        ApplicationsState.Callbacks {
90    private static final String TAG="InstalledAppDetails";
91    static final boolean SUPPORT_DISABLE_APPS = true;
92    private static final boolean localLOGV = false;
93
94    public static final String ARG_PACKAGE_NAME = "package";
95
96    private PackageManager mPm;
97    private IUsbManager mUsbManager;
98    private AppWidgetManager mAppWidgetManager;
99    private DevicePolicyManager mDpm;
100    private ApplicationsState mState;
101    private ApplicationsState.Session mSession;
102    private ApplicationsState.AppEntry mAppEntry;
103    private PackageInfo mPackageInfo;
104    private CanBeOnSdCardChecker mCanBeOnSdCardChecker;
105    private View mRootView;
106    private Button mUninstallButton;
107    private boolean mMoveInProgress = false;
108    private boolean mUpdatedSysApp = false;
109    private Button mActivitiesButton;
110    private View mScreenCompatSection;
111    private CheckBox mAskCompatibilityCB;
112    private CheckBox mEnableCompatibilityCB;
113    private boolean mCanClearData = true;
114    private TextView mAppVersion;
115    private TextView mTotalSize;
116    private TextView mAppSize;
117    private TextView mDataSize;
118    private TextView mExternalCodeSize;
119    private TextView mExternalDataSize;
120    private ClearUserDataObserver mClearDataObserver;
121    // Views related to cache info
122    private TextView mCacheSize;
123    private Button mClearCacheButton;
124    private ClearCacheObserver mClearCacheObserver;
125    private Button mForceStopButton;
126    private Button mClearDataButton;
127    private Button mMoveAppButton;
128    private Switch mNotificationSwitch;
129
130    private PackageMoveObserver mPackageMoveObserver;
131
132    private boolean mHaveSizes = false;
133    private long mLastCodeSize = -1;
134    private long mLastDataSize = -1;
135    private long mLastExternalCodeSize = -1;
136    private long mLastExternalDataSize = -1;
137    private long mLastCacheSize = -1;
138    private long mLastTotalSize = -1;
139
140    //internal constants used in Handler
141    private static final int OP_SUCCESSFUL = 1;
142    private static final int OP_FAILED = 2;
143    private static final int CLEAR_USER_DATA = 1;
144    private static final int CLEAR_CACHE = 3;
145    private static final int PACKAGE_MOVE = 4;
146
147    // invalid size value used initially and also when size retrieval through PackageManager
148    // fails for whatever reason
149    private static final int SIZE_INVALID = -1;
150
151    // Resource strings
152    private CharSequence mInvalidSizeStr;
153    private CharSequence mComputingStr;
154
155    // Dialog identifiers used in showDialog
156    private static final int DLG_BASE = 0;
157    private static final int DLG_CLEAR_DATA = DLG_BASE + 1;
158    private static final int DLG_FACTORY_RESET = DLG_BASE + 2;
159    private static final int DLG_APP_NOT_FOUND = DLG_BASE + 3;
160    private static final int DLG_CANNOT_CLEAR_DATA = DLG_BASE + 4;
161    private static final int DLG_FORCE_STOP = DLG_BASE + 5;
162    private static final int DLG_MOVE_FAILED = DLG_BASE + 6;
163    private static final int DLG_DISABLE = DLG_BASE + 7;
164
165    private Handler mHandler = new Handler() {
166        public void handleMessage(Message msg) {
167            // If the fragment is gone, don't process any more messages.
168            if (getView() == null) {
169                return;
170            }
171            switch (msg.what) {
172                case CLEAR_USER_DATA:
173                    processClearMsg(msg);
174                    break;
175                case CLEAR_CACHE:
176                    // Refresh size info
177                    mState.requestSize(mAppEntry.info.packageName);
178                    break;
179                case PACKAGE_MOVE:
180                    processMoveMsg(msg);
181                    break;
182                default:
183                    break;
184            }
185        }
186    };
187
188    class ClearUserDataObserver extends IPackageDataObserver.Stub {
189       public void onRemoveCompleted(final String packageName, final boolean succeeded) {
190           final Message msg = mHandler.obtainMessage(CLEAR_USER_DATA);
191           msg.arg1 = succeeded?OP_SUCCESSFUL:OP_FAILED;
192           mHandler.sendMessage(msg);
193        }
194    }
195
196    class ClearCacheObserver extends IPackageDataObserver.Stub {
197        public void onRemoveCompleted(final String packageName, final boolean succeeded) {
198            final Message msg = mHandler.obtainMessage(CLEAR_CACHE);
199            msg.arg1 = succeeded ? OP_SUCCESSFUL:OP_FAILED;
200            mHandler.sendMessage(msg);
201         }
202     }
203
204    class PackageMoveObserver extends IPackageMoveObserver.Stub {
205        public void packageMoved(String packageName, int returnCode) throws RemoteException {
206            final Message msg = mHandler.obtainMessage(PACKAGE_MOVE);
207            msg.arg1 = returnCode;
208            mHandler.sendMessage(msg);
209        }
210    }
211
212    private String getSizeStr(long size) {
213        if (size == SIZE_INVALID) {
214            return mInvalidSizeStr.toString();
215        }
216        return Formatter.formatFileSize(getActivity(), size);
217    }
218
219    private void initDataButtons() {
220        if ((mAppEntry.info.flags&(ApplicationInfo.FLAG_SYSTEM
221                | ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA))
222                == ApplicationInfo.FLAG_SYSTEM
223                || mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
224            mClearDataButton.setText(R.string.clear_user_data_text);
225            mClearDataButton.setEnabled(false);
226            mCanClearData = false;
227        } else {
228            if (mAppEntry.info.manageSpaceActivityName != null) {
229                mClearDataButton.setText(R.string.manage_space_text);
230            } else {
231                mClearDataButton.setText(R.string.clear_user_data_text);
232            }
233            mClearDataButton.setOnClickListener(this);
234        }
235    }
236
237    private CharSequence getMoveErrMsg(int errCode) {
238        switch (errCode) {
239            case PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE:
240                return getActivity().getString(R.string.insufficient_storage);
241            case PackageManager.MOVE_FAILED_DOESNT_EXIST:
242                return getActivity().getString(R.string.does_not_exist);
243            case PackageManager.MOVE_FAILED_FORWARD_LOCKED:
244                return getActivity().getString(R.string.app_forward_locked);
245            case PackageManager.MOVE_FAILED_INVALID_LOCATION:
246                return getActivity().getString(R.string.invalid_location);
247            case PackageManager.MOVE_FAILED_SYSTEM_PACKAGE:
248                return getActivity().getString(R.string.system_package);
249            case PackageManager.MOVE_FAILED_INTERNAL_ERROR:
250                return "";
251        }
252        return "";
253    }
254
255    private void initMoveButton() {
256        if (Environment.isExternalStorageEmulated()) {
257            mMoveAppButton.setVisibility(View.INVISIBLE);
258            return;
259        }
260        boolean dataOnly = false;
261        dataOnly = (mPackageInfo == null) && (mAppEntry != null);
262        boolean moveDisable = true;
263        if (dataOnly) {
264            mMoveAppButton.setText(R.string.move_app);
265        } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
266            mMoveAppButton.setText(R.string.move_app_to_internal);
267            // Always let apps move to internal storage from sdcard.
268            moveDisable = false;
269        } else {
270            mMoveAppButton.setText(R.string.move_app_to_sdcard);
271            mCanBeOnSdCardChecker.init();
272            moveDisable = !mCanBeOnSdCardChecker.check(mAppEntry.info);
273        }
274        if (moveDisable) {
275            mMoveAppButton.setEnabled(false);
276        } else {
277            mMoveAppButton.setOnClickListener(this);
278            mMoveAppButton.setEnabled(true);
279        }
280    }
281
282    private void initUninstallButtons() {
283        mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
284        boolean enabled = true;
285        if (mUpdatedSysApp) {
286            mUninstallButton.setText(R.string.app_factory_reset);
287        } else {
288            if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
289                enabled = false;
290                if (SUPPORT_DISABLE_APPS) {
291                    try {
292                        // Try to prevent the user from bricking their phone
293                        // by not allowing disabling of apps signed with the
294                        // system cert and any launcher app in the system.
295                        PackageInfo sys = mPm.getPackageInfo("android",
296                                PackageManager.GET_SIGNATURES);
297                        Intent intent = new Intent(Intent.ACTION_MAIN);
298                        intent.addCategory(Intent.CATEGORY_HOME);
299                        intent.setPackage(mAppEntry.info.packageName);
300                        List<ResolveInfo> homes = mPm.queryIntentActivities(intent, 0);
301                        if ((homes != null && homes.size() > 0) ||
302                                (mPackageInfo != null && mPackageInfo.signatures != null &&
303                                        sys.signatures[0].equals(mPackageInfo.signatures[0]))) {
304                            // Disable button for core system applications.
305                            mUninstallButton.setText(R.string.disable_text);
306                        } else if (mAppEntry.info.enabled) {
307                            mUninstallButton.setText(R.string.disable_text);
308                            enabled = true;
309                        } else {
310                            mUninstallButton.setText(R.string.enable_text);
311                            enabled = true;
312                        }
313                    } catch (PackageManager.NameNotFoundException e) {
314                        Log.w(TAG, "Unable to get package info", e);
315                    }
316                }
317            } else {
318                mUninstallButton.setText(R.string.uninstall_text);
319            }
320        }
321        // If this is a device admin, it can't be uninstall or disabled.
322        // We do this here so the text of the button is still set correctly.
323        if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
324            enabled = false;
325        }
326        mUninstallButton.setEnabled(enabled);
327        if (enabled) {
328            // Register listener
329            mUninstallButton.setOnClickListener(this);
330        }
331    }
332
333    private void initNotificationButton() {
334        INotificationManager nm = INotificationManager.Stub.asInterface(
335                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
336        boolean enabled = true; // default on
337        try {
338            enabled = nm.areNotificationsEnabledForPackage(mAppEntry.info.packageName);
339        } catch (android.os.RemoteException ex) {
340            // this does not bode well
341        }
342        mNotificationSwitch.setChecked(enabled);
343        mNotificationSwitch.setOnCheckedChangeListener(this);
344    }
345
346    /** Called when the activity is first created. */
347    @Override
348    public void onCreate(Bundle icicle) {
349        super.onCreate(icicle);
350
351        mState = ApplicationsState.getInstance(getActivity().getApplication());
352        mSession = mState.newSession(this);
353        mPm = getActivity().getPackageManager();
354        IBinder b = ServiceManager.getService(Context.USB_SERVICE);
355        mUsbManager = IUsbManager.Stub.asInterface(b);
356        mAppWidgetManager = AppWidgetManager.getInstance(getActivity());
357        mDpm = (DevicePolicyManager)getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
358
359        mCanBeOnSdCardChecker = new CanBeOnSdCardChecker();
360    }
361
362    @Override
363    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
364        View view = mRootView = inflater.inflate(R.layout.installed_app_details, null);
365
366        mComputingStr = getActivity().getText(R.string.computing_size);
367
368        // Set default values on sizes
369        mTotalSize = (TextView)view.findViewById(R.id.total_size_text);
370        mAppSize = (TextView)view.findViewById(R.id.application_size_text);
371        mDataSize = (TextView)view.findViewById(R.id.data_size_text);
372        mExternalCodeSize = (TextView)view.findViewById(R.id.external_code_size_text);
373        mExternalDataSize = (TextView)view.findViewById(R.id.external_data_size_text);
374
375        // Get Control button panel
376        View btnPanel = view.findViewById(R.id.control_buttons_panel);
377        mForceStopButton = (Button) btnPanel.findViewById(R.id.left_button);
378        mForceStopButton.setText(R.string.force_stop);
379        mUninstallButton = (Button)btnPanel.findViewById(R.id.right_button);
380        mForceStopButton.setEnabled(false);
381
382        // Initialize clear data and move install location buttons
383        View data_buttons_panel = view.findViewById(R.id.data_buttons_panel);
384        mClearDataButton = (Button) data_buttons_panel.findViewById(R.id.left_button);
385        mMoveAppButton = (Button) data_buttons_panel.findViewById(R.id.right_button);
386
387        // Cache section
388        mCacheSize = (TextView) view.findViewById(R.id.cache_size_text);
389        mClearCacheButton = (Button) view.findViewById(R.id.clear_cache_button);
390
391        mActivitiesButton = (Button)view.findViewById(R.id.clear_activities_button);
392
393        // Screen compatibility control
394        mScreenCompatSection = view.findViewById(R.id.screen_compatibility_section);
395        mAskCompatibilityCB = (CheckBox)view.findViewById(R.id.ask_compatibility_cb);
396        mEnableCompatibilityCB = (CheckBox)view.findViewById(R.id.enable_compatibility_cb);
397
398        mNotificationSwitch = (Switch) view.findViewById(R.id.notification_switch);
399
400        return view;
401    }
402
403    // Utility method to set applicaiton label and icon.
404    private void setAppLabelAndIcon(PackageInfo pkgInfo) {
405        View appSnippet = mRootView.findViewById(R.id.app_snippet);
406        ImageView icon = (ImageView) appSnippet.findViewById(R.id.app_icon);
407        mState.ensureIcon(mAppEntry);
408        icon.setImageDrawable(mAppEntry.icon);
409        // Set application name.
410        TextView label = (TextView) appSnippet.findViewById(R.id.app_name);
411        label.setText(mAppEntry.label);
412        // Version number of application
413        mAppVersion = (TextView) appSnippet.findViewById(R.id.app_size);
414
415        if (pkgInfo != null && pkgInfo.versionName != null) {
416            mAppVersion.setVisibility(View.VISIBLE);
417            mAppVersion.setText(getActivity().getString(R.string.version_text,
418                    String.valueOf(pkgInfo.versionName)));
419        } else {
420            mAppVersion.setVisibility(View.INVISIBLE);
421        }
422    }
423
424    @Override
425    public void onResume() {
426        super.onResume();
427
428        mSession.resume();
429        if (!refreshUi()) {
430            setIntentAndFinish(true, true);
431        }
432    }
433
434    @Override
435    public void onPause() {
436        super.onPause();
437        mSession.pause();
438    }
439
440    @Override
441    public void onAllSizesComputed() {
442    }
443
444    @Override
445    public void onPackageIconChanged() {
446    }
447
448    @Override
449    public void onPackageListChanged() {
450        refreshUi();
451    }
452
453    @Override
454    public void onRebuildComplete(ArrayList<AppEntry> apps) {
455    }
456
457    @Override
458    public void onPackageSizeChanged(String packageName) {
459        if (packageName.equals(mAppEntry.info.packageName)) {
460            refreshSizeInfo();
461        }
462    }
463
464    @Override
465    public void onRunningStateChanged(boolean running) {
466    }
467
468    private boolean refreshUi() {
469        if (mMoveInProgress) {
470            return true;
471        }
472        final Bundle args = getArguments();
473        String packageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null;
474        if (packageName == null) {
475            Intent intent = (args == null) ?
476                    getActivity().getIntent() : (Intent) args.getParcelable("intent");
477            if (intent != null) {
478                packageName = intent.getData().getSchemeSpecificPart();
479            }
480        }
481        mAppEntry = mState.getEntry(packageName);
482
483        if (mAppEntry == null) {
484            return false; // onCreate must have failed, make sure to exit
485        }
486
487        // Get application info again to refresh changed properties of application
488        try {
489            mPackageInfo = mPm.getPackageInfo(mAppEntry.info.packageName,
490                    PackageManager.GET_DISABLED_COMPONENTS |
491                    PackageManager.GET_UNINSTALLED_PACKAGES |
492                    PackageManager.GET_SIGNATURES);
493        } catch (NameNotFoundException e) {
494            Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e);
495            return false; // onCreate must have failed, make sure to exit
496        }
497
498        // Get list of preferred activities
499        List<ComponentName> prefActList = new ArrayList<ComponentName>();
500
501        // Intent list cannot be null. so pass empty list
502        List<IntentFilter> intentList = new ArrayList<IntentFilter>();
503        mPm.getPreferredActivities(intentList, prefActList, packageName);
504        if (localLOGV)
505            Log.i(TAG, "Have " + prefActList.size() + " number of activities in preferred list");
506        boolean hasUsbDefaults = false;
507        try {
508            hasUsbDefaults = mUsbManager.hasDefaults(packageName);
509        } catch (RemoteException e) {
510            Log.e(TAG, "mUsbManager.hasDefaults", e);
511        }
512        boolean hasBindAppWidgetPermission =
513                mAppWidgetManager.hasBindAppWidgetPermission(mAppEntry.info.packageName);
514
515        TextView autoLaunchTitleView = (TextView) mRootView.findViewById(R.id.auto_launch_title);
516        TextView autoLaunchView = (TextView) mRootView.findViewById(R.id.auto_launch);
517        boolean autoLaunchEnabled = prefActList.size() > 0 || hasUsbDefaults;
518        if (!autoLaunchEnabled && !hasBindAppWidgetPermission) {
519            resetLaunchDefaultsUi(autoLaunchTitleView, autoLaunchView);
520        } else {
521            boolean useBullets = hasBindAppWidgetPermission && autoLaunchEnabled;
522
523            if (hasBindAppWidgetPermission) {
524                autoLaunchTitleView.setText(R.string.auto_launch_label_generic);
525            } else {
526                autoLaunchTitleView.setText(R.string.auto_launch_label);
527            }
528
529            CharSequence text = null;
530            int bulletIndent = getResources()
531                    .getDimensionPixelSize(R.dimen.installed_app_details_bullet_offset);
532            if (autoLaunchEnabled) {
533                CharSequence autoLaunchEnableText = getText(R.string.auto_launch_enable_text);
534                SpannableString s = new SpannableString(autoLaunchEnableText);
535                if (useBullets) {
536                    s.setSpan(new BulletSpan(bulletIndent), 0, autoLaunchEnableText.length(), 0);
537                }
538                text = (text == null) ?
539                        TextUtils.concat(s, "\n") : TextUtils.concat(text, "\n", s, "\n");
540            }
541            if (hasBindAppWidgetPermission) {
542                CharSequence alwaysAllowBindAppWidgetsText =
543                        getText(R.string.always_allow_bind_appwidgets_text);
544                SpannableString s = new SpannableString(alwaysAllowBindAppWidgetsText);
545                if (useBullets) {
546                    s.setSpan(new BulletSpan(bulletIndent),
547                            0, alwaysAllowBindAppWidgetsText.length(), 0);
548                }
549                text = (text == null) ?
550                        TextUtils.concat(s, "\n") : TextUtils.concat(text, "\n", s, "\n");
551            }
552            autoLaunchView.setText(text);
553            mActivitiesButton.setEnabled(true);
554            mActivitiesButton.setOnClickListener(this);
555        }
556
557        // Screen compatibility section.
558        ActivityManager am = (ActivityManager)
559                getActivity().getSystemService(Context.ACTIVITY_SERVICE);
560        int compatMode = am.getPackageScreenCompatMode(packageName);
561        // For now these are always off; this is the old UI model which we
562        // are no longer using.
563        if (false && (compatMode == ActivityManager.COMPAT_MODE_DISABLED
564                || compatMode == ActivityManager.COMPAT_MODE_ENABLED)) {
565            mScreenCompatSection.setVisibility(View.VISIBLE);
566            mAskCompatibilityCB.setChecked(am.getPackageAskScreenCompat(packageName));
567            mAskCompatibilityCB.setOnCheckedChangeListener(this);
568            mEnableCompatibilityCB.setChecked(compatMode == ActivityManager.COMPAT_MODE_ENABLED);
569            mEnableCompatibilityCB.setOnCheckedChangeListener(this);
570        } else {
571            mScreenCompatSection.setVisibility(View.GONE);
572        }
573
574        // Security permissions section
575        LinearLayout permsView = (LinearLayout) mRootView.findViewById(R.id.permissions_section);
576        AppSecurityPermissions asp = new AppSecurityPermissions(getActivity(), packageName);
577        if (asp.getPermissionCount() > 0) {
578            permsView.setVisibility(View.VISIBLE);
579            // Make the security sections header visible
580            LinearLayout securityList = (LinearLayout) permsView.findViewById(
581                    R.id.security_settings_list);
582            securityList.removeAllViews();
583            securityList.addView(asp.getPermissionsView());
584        } else {
585            permsView.setVisibility(View.GONE);
586        }
587
588        checkForceStop();
589        setAppLabelAndIcon(mPackageInfo);
590        refreshButtons();
591        refreshSizeInfo();
592        return true;
593    }
594
595    private void resetLaunchDefaultsUi(TextView title, TextView autoLaunchView) {
596        title.setText(R.string.auto_launch_label);
597        autoLaunchView.setText(R.string.auto_launch_disable_text);
598        // Disable clear activities button
599        mActivitiesButton.setEnabled(false);
600    }
601
602    private void setIntentAndFinish(boolean finish, boolean appChanged) {
603        if(localLOGV) Log.i(TAG, "appChanged="+appChanged);
604        Intent intent = new Intent();
605        intent.putExtra(ManageApplications.APP_CHG, appChanged);
606        PreferenceActivity pa = (PreferenceActivity)getActivity();
607        pa.finishPreferencePanel(this, Activity.RESULT_OK, intent);
608    }
609
610    private void refreshSizeInfo() {
611        if (mAppEntry.size == ApplicationsState.SIZE_INVALID
612                || mAppEntry.size == ApplicationsState.SIZE_UNKNOWN) {
613            mLastCodeSize = mLastDataSize = mLastCacheSize = mLastTotalSize = -1;
614            if (!mHaveSizes) {
615                mAppSize.setText(mComputingStr);
616                mDataSize.setText(mComputingStr);
617                mCacheSize.setText(mComputingStr);
618                mTotalSize.setText(mComputingStr);
619            }
620            mClearDataButton.setEnabled(false);
621            mClearCacheButton.setEnabled(false);
622
623        } else {
624            mHaveSizes = true;
625            if (mLastCodeSize != mAppEntry.codeSize) {
626                mLastCodeSize = mAppEntry.codeSize;
627                mAppSize.setText(getSizeStr(mAppEntry.codeSize));
628            }
629            if (mLastDataSize != mAppEntry.dataSize) {
630                mLastDataSize = mAppEntry.dataSize;
631                mDataSize.setText(getSizeStr(mAppEntry.dataSize));
632            }
633            if (mLastExternalCodeSize != mAppEntry.externalCodeSize) {
634                mLastExternalCodeSize = mAppEntry.externalCodeSize;
635                mExternalCodeSize.setText(getSizeStr(mAppEntry.externalCodeSize));
636            }
637            if (mLastExternalDataSize != mAppEntry.externalDataSize) {
638                mLastExternalDataSize = mAppEntry.externalDataSize;
639                mExternalDataSize.setText(getSizeStr(mAppEntry.externalDataSize));
640            }
641            if (mLastCacheSize != mAppEntry.cacheSize) {
642                mLastCacheSize = mAppEntry.cacheSize;
643                mCacheSize.setText(getSizeStr(mAppEntry.cacheSize));
644            }
645            if (mLastTotalSize != mAppEntry.size) {
646                mLastTotalSize = mAppEntry.size;
647                mTotalSize.setText(getSizeStr(mAppEntry.size));
648            }
649
650            if (mAppEntry.dataSize <= 0 || !mCanClearData) {
651                mClearDataButton.setEnabled(false);
652            } else {
653                mClearDataButton.setEnabled(true);
654                mClearDataButton.setOnClickListener(this);
655            }
656            if (mAppEntry.cacheSize <= 0) {
657                mClearCacheButton.setEnabled(false);
658            } else {
659                mClearCacheButton.setEnabled(true);
660                mClearCacheButton.setOnClickListener(this);
661            }
662        }
663    }
664
665    /*
666     * Private method to handle clear message notification from observer when
667     * the async operation from PackageManager is complete
668     */
669    private void processClearMsg(Message msg) {
670        int result = msg.arg1;
671        String packageName = mAppEntry.info.packageName;
672        mClearDataButton.setText(R.string.clear_user_data_text);
673        if(result == OP_SUCCESSFUL) {
674            Log.i(TAG, "Cleared user data for package : "+packageName);
675            mState.requestSize(mAppEntry.info.packageName);
676        } else {
677            mClearDataButton.setEnabled(true);
678        }
679        checkForceStop();
680    }
681
682    private void refreshButtons() {
683        if (!mMoveInProgress) {
684            initUninstallButtons();
685            initDataButtons();
686            initMoveButton();
687            initNotificationButton();
688        } else {
689            mMoveAppButton.setText(R.string.moving);
690            mMoveAppButton.setEnabled(false);
691            mUninstallButton.setEnabled(false);
692        }
693    }
694
695    private void processMoveMsg(Message msg) {
696        int result = msg.arg1;
697        String packageName = mAppEntry.info.packageName;
698        // Refresh the button attributes.
699        mMoveInProgress = false;
700        if (result == PackageManager.MOVE_SUCCEEDED) {
701            Log.i(TAG, "Moved resources for " + packageName);
702            // Refresh size information again.
703            mState.requestSize(mAppEntry.info.packageName);
704        } else {
705            showDialogInner(DLG_MOVE_FAILED, result);
706        }
707        refreshUi();
708    }
709
710    /*
711     * Private method to initiate clearing user data when the user clicks the clear data
712     * button for a system package
713     */
714    private  void initiateClearUserData() {
715        mClearDataButton.setEnabled(false);
716        // Invoke uninstall or clear user data based on sysPackage
717        String packageName = mAppEntry.info.packageName;
718        Log.i(TAG, "Clearing user data for package : " + packageName);
719        if (mClearDataObserver == null) {
720            mClearDataObserver = new ClearUserDataObserver();
721        }
722        ActivityManager am = (ActivityManager)
723                getActivity().getSystemService(Context.ACTIVITY_SERVICE);
724        boolean res = am.clearApplicationUserData(packageName, mClearDataObserver);
725        if (!res) {
726            // Clearing data failed for some obscure reason. Just log error for now
727            Log.i(TAG, "Couldnt clear application user data for package:"+packageName);
728            showDialogInner(DLG_CANNOT_CLEAR_DATA, 0);
729        } else {
730            mClearDataButton.setText(R.string.recompute_size);
731        }
732    }
733
734    private void showDialogInner(int id, int moveErrorCode) {
735        DialogFragment newFragment = MyAlertDialogFragment.newInstance(id, moveErrorCode);
736        newFragment.setTargetFragment(this, 0);
737        newFragment.show(getFragmentManager(), "dialog " + id);
738    }
739
740    public static class MyAlertDialogFragment extends DialogFragment {
741
742        public static MyAlertDialogFragment newInstance(int id, int moveErrorCode) {
743            MyAlertDialogFragment frag = new MyAlertDialogFragment();
744            Bundle args = new Bundle();
745            args.putInt("id", id);
746            args.putInt("moveError", moveErrorCode);
747            frag.setArguments(args);
748            return frag;
749        }
750
751        InstalledAppDetails getOwner() {
752            return (InstalledAppDetails)getTargetFragment();
753        }
754
755        @Override
756        public Dialog onCreateDialog(Bundle savedInstanceState) {
757            int id = getArguments().getInt("id");
758            int moveErrorCode = getArguments().getInt("moveError");
759            switch (id) {
760                case DLG_CLEAR_DATA:
761                    return new AlertDialog.Builder(getActivity())
762                    .setTitle(getActivity().getText(R.string.clear_data_dlg_title))
763                    .setIcon(android.R.drawable.ic_dialog_alert)
764                    .setMessage(getActivity().getText(R.string.clear_data_dlg_text))
765                    .setPositiveButton(R.string.dlg_ok,
766                            new DialogInterface.OnClickListener() {
767                        public void onClick(DialogInterface dialog, int which) {
768                            // Clear user data here
769                            getOwner().initiateClearUserData();
770                        }
771                    })
772                    .setNegativeButton(R.string.dlg_cancel, null)
773                    .create();
774                case DLG_FACTORY_RESET:
775                    return new AlertDialog.Builder(getActivity())
776                    .setTitle(getActivity().getText(R.string.app_factory_reset_dlg_title))
777                    .setIcon(android.R.drawable.ic_dialog_alert)
778                    .setMessage(getActivity().getText(R.string.app_factory_reset_dlg_text))
779                    .setPositiveButton(R.string.dlg_ok,
780                            new DialogInterface.OnClickListener() {
781                        public void onClick(DialogInterface dialog, int which) {
782                            // Clear user data here
783                            getOwner().uninstallPkg(getOwner().mAppEntry.info.packageName);
784                        }
785                    })
786                    .setNegativeButton(R.string.dlg_cancel, null)
787                    .create();
788                case DLG_APP_NOT_FOUND:
789                    return new AlertDialog.Builder(getActivity())
790                    .setTitle(getActivity().getText(R.string.app_not_found_dlg_title))
791                    .setIcon(android.R.drawable.ic_dialog_alert)
792                    .setMessage(getActivity().getText(R.string.app_not_found_dlg_title))
793                    .setNeutralButton(getActivity().getText(R.string.dlg_ok),
794                            new DialogInterface.OnClickListener() {
795                        public void onClick(DialogInterface dialog, int which) {
796                            //force to recompute changed value
797                            getOwner().setIntentAndFinish(true, true);
798                        }
799                    })
800                    .create();
801                case DLG_CANNOT_CLEAR_DATA:
802                    return new AlertDialog.Builder(getActivity())
803                    .setTitle(getActivity().getText(R.string.clear_failed_dlg_title))
804                    .setIcon(android.R.drawable.ic_dialog_alert)
805                    .setMessage(getActivity().getText(R.string.clear_failed_dlg_text))
806                    .setNeutralButton(R.string.dlg_ok,
807                            new DialogInterface.OnClickListener() {
808                        public void onClick(DialogInterface dialog, int which) {
809                            getOwner().mClearDataButton.setEnabled(false);
810                            //force to recompute changed value
811                            getOwner().setIntentAndFinish(false, false);
812                        }
813                    })
814                    .create();
815                case DLG_FORCE_STOP:
816                    return new AlertDialog.Builder(getActivity())
817                    .setTitle(getActivity().getText(R.string.force_stop_dlg_title))
818                    .setIcon(android.R.drawable.ic_dialog_alert)
819                    .setMessage(getActivity().getText(R.string.force_stop_dlg_text))
820                    .setPositiveButton(R.string.dlg_ok,
821                        new DialogInterface.OnClickListener() {
822                        public void onClick(DialogInterface dialog, int which) {
823                            // Force stop
824                            getOwner().forceStopPackage(getOwner().mAppEntry.info.packageName);
825                        }
826                    })
827                    .setNegativeButton(R.string.dlg_cancel, null)
828                    .create();
829                case DLG_MOVE_FAILED:
830                    CharSequence msg = getActivity().getString(R.string.move_app_failed_dlg_text,
831                            getOwner().getMoveErrMsg(moveErrorCode));
832                    return new AlertDialog.Builder(getActivity())
833                    .setTitle(getActivity().getText(R.string.move_app_failed_dlg_title))
834                    .setIcon(android.R.drawable.ic_dialog_alert)
835                    .setMessage(msg)
836                    .setNeutralButton(R.string.dlg_ok, null)
837                    .create();
838                case DLG_DISABLE:
839                    return new AlertDialog.Builder(getActivity())
840                    .setTitle(getActivity().getText(R.string.app_disable_dlg_title))
841                    .setIcon(android.R.drawable.ic_dialog_alert)
842                    .setMessage(getActivity().getText(R.string.app_disable_dlg_text))
843                    .setPositiveButton(R.string.dlg_ok,
844                        new DialogInterface.OnClickListener() {
845                        public void onClick(DialogInterface dialog, int which) {
846                            // Disable the app
847                            new DisableChanger(getOwner(), getOwner().mAppEntry.info,
848                                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
849                            .execute((Object)null);
850                        }
851                    })
852                    .setNegativeButton(R.string.dlg_cancel, null)
853                    .create();
854            }
855            throw new IllegalArgumentException("unknown id " + id);
856        }
857    }
858
859    private void uninstallPkg(String packageName) {
860         // Create new intent to launch Uninstaller activity
861        Uri packageURI = Uri.parse("package:"+packageName);
862        Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI);
863        startActivity(uninstallIntent);
864        setIntentAndFinish(true, true);
865    }
866
867    private void forceStopPackage(String pkgName) {
868        ActivityManager am = (ActivityManager)getActivity().getSystemService(
869                Context.ACTIVITY_SERVICE);
870        am.forceStopPackage(pkgName);
871        mState.invalidatePackage(pkgName);
872        ApplicationsState.AppEntry newEnt = mState.getEntry(pkgName);
873        if (newEnt != null) {
874            mAppEntry = newEnt;
875        }
876        checkForceStop();
877    }
878
879    private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
880        @Override
881        public void onReceive(Context context, Intent intent) {
882            updateForceStopButton(getResultCode() != Activity.RESULT_CANCELED);
883        }
884    };
885
886    private void updateForceStopButton(boolean enabled) {
887        mForceStopButton.setEnabled(enabled);
888        mForceStopButton.setOnClickListener(InstalledAppDetails.this);
889    }
890
891    private void checkForceStop() {
892        if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
893            // User can't force stop device admin.
894            updateForceStopButton(false);
895        } else if ((mAppEntry.info.flags&ApplicationInfo.FLAG_STOPPED) == 0) {
896            // If the app isn't explicitly stopped, then always show the
897            // force stop button.
898            updateForceStopButton(true);
899        } else {
900            Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
901                    Uri.fromParts("package", mAppEntry.info.packageName, null));
902            intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { mAppEntry.info.packageName });
903            intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid);
904            getActivity().sendOrderedBroadcast(intent, null, mCheckKillProcessesReceiver, null,
905                    Activity.RESULT_CANCELED, null, null);
906        }
907    }
908
909    static class DisableChanger extends AsyncTask<Object, Object, Object> {
910        final PackageManager mPm;
911        final WeakReference<InstalledAppDetails> mActivity;
912        final ApplicationInfo mInfo;
913        final int mState;
914
915        DisableChanger(InstalledAppDetails activity, ApplicationInfo info, int state) {
916            mPm = activity.mPm;
917            mActivity = new WeakReference<InstalledAppDetails>(activity);
918            mInfo = info;
919            mState = state;
920        }
921
922        @Override
923        protected Object doInBackground(Object... params) {
924            mPm.setApplicationEnabledSetting(mInfo.packageName, mState, 0);
925            return null;
926        }
927    }
928
929    /*
930     * Method implementing functionality of buttons clicked
931     * @see android.view.View.OnClickListener#onClick(android.view.View)
932     */
933    public void onClick(View v) {
934        String packageName = mAppEntry.info.packageName;
935        if(v == mUninstallButton) {
936            if (mUpdatedSysApp) {
937                showDialogInner(DLG_FACTORY_RESET, 0);
938            } else {
939                if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
940                    if (mAppEntry.info.enabled) {
941                        showDialogInner(DLG_DISABLE, 0);
942                    } else {
943                        new DisableChanger(this, mAppEntry.info,
944                                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
945                        .execute((Object)null);
946                    }
947                } else {
948                    uninstallPkg(packageName);
949                }
950            }
951        } else if(v == mActivitiesButton) {
952            mPm.clearPackagePreferredActivities(packageName);
953            try {
954                mUsbManager.clearDefaults(packageName);
955            } catch (RemoteException e) {
956                Log.e(TAG, "mUsbManager.clearDefaults", e);
957            }
958            mAppWidgetManager.setBindAppWidgetPermission(packageName, false);
959            TextView autoLaunchTitleView =
960                    (TextView) mRootView.findViewById(R.id.auto_launch_title);
961            TextView autoLaunchView = (TextView) mRootView.findViewById(R.id.auto_launch);
962            resetLaunchDefaultsUi(autoLaunchTitleView, autoLaunchView);
963        } else if(v == mClearDataButton) {
964            if (mAppEntry.info.manageSpaceActivityName != null) {
965                if (!Utils.isMonkeyRunning()) {
966                    Intent intent = new Intent(Intent.ACTION_DEFAULT);
967                    intent.setClassName(mAppEntry.info.packageName,
968                            mAppEntry.info.manageSpaceActivityName);
969                    startActivityForResult(intent, -1);
970                }
971            } else {
972                showDialogInner(DLG_CLEAR_DATA, 0);
973            }
974        } else if (v == mClearCacheButton) {
975            // Lazy initialization of observer
976            if (mClearCacheObserver == null) {
977                mClearCacheObserver = new ClearCacheObserver();
978            }
979            mPm.deleteApplicationCacheFiles(packageName, mClearCacheObserver);
980        } else if (v == mForceStopButton) {
981            showDialogInner(DLG_FORCE_STOP, 0);
982            //forceStopPackage(mAppInfo.packageName);
983        } else if (v == mMoveAppButton) {
984            if (mPackageMoveObserver == null) {
985                mPackageMoveObserver = new PackageMoveObserver();
986            }
987            int moveFlags = (mAppEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0 ?
988                    PackageManager.MOVE_INTERNAL : PackageManager.MOVE_EXTERNAL_MEDIA;
989            mMoveInProgress = true;
990            refreshButtons();
991            mPm.movePackage(mAppEntry.info.packageName, mPackageMoveObserver, moveFlags);
992        }
993    }
994
995    @Override
996    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
997        String packageName = mAppEntry.info.packageName;
998        ActivityManager am = (ActivityManager)
999                getActivity().getSystemService(Context.ACTIVITY_SERVICE);
1000        if (buttonView == mAskCompatibilityCB) {
1001            am.setPackageAskScreenCompat(packageName, isChecked);
1002        } else if (buttonView == mEnableCompatibilityCB) {
1003            am.setPackageScreenCompatMode(packageName, isChecked ?
1004                    ActivityManager.COMPAT_MODE_ENABLED : ActivityManager.COMPAT_MODE_DISABLED);
1005        } else if (buttonView == mNotificationSwitch) {
1006            INotificationManager nm = INotificationManager.Stub.asInterface(
1007                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
1008            try {
1009                nm.setNotificationsEnabledForPackage(packageName, isChecked);
1010            } catch (android.os.RemoteException ex) {
1011                mNotificationSwitch.setChecked(!isChecked); // revert
1012            }
1013        }
1014    }
1015}
1016
1017