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