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