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