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