InstalledAppDetails.java revision e458832c765ed5c3de836384338c7626c80b5686
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 // Get Control button panel 389 View btnPanel = view.findViewById(R.id.control_buttons_panel); 390 mForceStopButton = (Button) btnPanel.findViewById(R.id.left_button); 391 mForceStopButton.setText(R.string.force_stop); 392 mUninstallButton = (Button)btnPanel.findViewById(R.id.right_button); 393 mForceStopButton.setEnabled(false); 394 395 // Initialize clear data and move install location buttons 396 View data_buttons_panel = view.findViewById(R.id.data_buttons_panel); 397 mClearDataButton = (Button) data_buttons_panel.findViewById(R.id.left_button); 398 mMoveAppButton = (Button) data_buttons_panel.findViewById(R.id.right_button); 399 400 // Cache section 401 mCacheSize = (TextView) view.findViewById(R.id.cache_size_text); 402 mClearCacheButton = (Button) view.findViewById(R.id.clear_cache_button); 403 404 mActivitiesButton = (Button)view.findViewById(R.id.clear_activities_button); 405 406 // Screen compatibility control 407 mScreenCompatSection = view.findViewById(R.id.screen_compatibility_section); 408 mAskCompatibilityCB = (CheckBox)view.findViewById(R.id.ask_compatibility_cb); 409 mEnableCompatibilityCB = (CheckBox)view.findViewById(R.id.enable_compatibility_cb); 410 411 mNotificationSwitch = (CompoundButton) view.findViewById(R.id.notification_switch); 412 413 return view; 414 } 415 416 // Utility method to set applicaiton label and icon. 417 private void setAppLabelAndIcon(PackageInfo pkgInfo) { 418 View appSnippet = mRootView.findViewById(R.id.app_snippet); 419 ImageView icon = (ImageView) appSnippet.findViewById(R.id.app_icon); 420 mState.ensureIcon(mAppEntry); 421 icon.setImageDrawable(mAppEntry.icon); 422 // Set application name. 423 TextView label = (TextView) appSnippet.findViewById(R.id.app_name); 424 label.setText(mAppEntry.label); 425 // Version number of application 426 mAppVersion = (TextView) appSnippet.findViewById(R.id.app_size); 427 428 if (pkgInfo != null && pkgInfo.versionName != null) { 429 mAppVersion.setVisibility(View.VISIBLE); 430 mAppVersion.setText(getActivity().getString(R.string.version_text, 431 String.valueOf(pkgInfo.versionName))); 432 } else { 433 mAppVersion.setVisibility(View.INVISIBLE); 434 } 435 } 436 437 @Override 438 public void onResume() { 439 super.onResume(); 440 441 mSession.resume(); 442 if (!refreshUi()) { 443 setIntentAndFinish(true, true); 444 } 445 } 446 447 @Override 448 public void onPause() { 449 super.onPause(); 450 mSession.pause(); 451 } 452 453 @Override 454 public void onAllSizesComputed() { 455 } 456 457 @Override 458 public void onPackageIconChanged() { 459 } 460 461 @Override 462 public void onPackageListChanged() { 463 refreshUi(); 464 } 465 466 @Override 467 public void onRebuildComplete(ArrayList<AppEntry> apps) { 468 } 469 470 @Override 471 public void onPackageSizeChanged(String packageName) { 472 if (packageName.equals(mAppEntry.info.packageName)) { 473 refreshSizeInfo(); 474 } 475 } 476 477 @Override 478 public void onRunningStateChanged(boolean running) { 479 } 480 481 private boolean refreshUi() { 482 if (mMoveInProgress) { 483 return true; 484 } 485 final Bundle args = getArguments(); 486 String packageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null; 487 if (packageName == null) { 488 Intent intent = (args == null) ? 489 getActivity().getIntent() : (Intent) args.getParcelable("intent"); 490 if (intent != null) { 491 packageName = intent.getData().getSchemeSpecificPart(); 492 } 493 } 494 mAppEntry = mState.getEntry(packageName); 495 496 if (mAppEntry == null) { 497 return false; // onCreate must have failed, make sure to exit 498 } 499 500 // Get application info again to refresh changed properties of application 501 try { 502 mPackageInfo = mPm.getPackageInfo(mAppEntry.info.packageName, 503 PackageManager.GET_DISABLED_COMPONENTS | 504 PackageManager.GET_UNINSTALLED_PACKAGES | 505 PackageManager.GET_SIGNATURES); 506 } catch (NameNotFoundException e) { 507 Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e); 508 return false; // onCreate must have failed, make sure to exit 509 } 510 511 // Get list of preferred activities 512 List<ComponentName> prefActList = new ArrayList<ComponentName>(); 513 514 // Intent list cannot be null. so pass empty list 515 List<IntentFilter> intentList = new ArrayList<IntentFilter>(); 516 mPm.getPreferredActivities(intentList, prefActList, packageName); 517 if (localLOGV) 518 Log.i(TAG, "Have " + prefActList.size() + " number of activities in preferred list"); 519 boolean hasUsbDefaults = false; 520 try { 521 hasUsbDefaults = mUsbManager.hasDefaults(packageName); 522 } catch (RemoteException e) { 523 Log.e(TAG, "mUsbManager.hasDefaults", e); 524 } 525 boolean hasBindAppWidgetPermission = 526 mAppWidgetManager.hasBindAppWidgetPermission(mAppEntry.info.packageName); 527 528 TextView autoLaunchTitleView = (TextView) mRootView.findViewById(R.id.auto_launch_title); 529 TextView autoLaunchView = (TextView) mRootView.findViewById(R.id.auto_launch); 530 boolean autoLaunchEnabled = prefActList.size() > 0 || hasUsbDefaults; 531 if (!autoLaunchEnabled && !hasBindAppWidgetPermission) { 532 resetLaunchDefaultsUi(autoLaunchTitleView, autoLaunchView); 533 } else { 534 boolean useBullets = hasBindAppWidgetPermission && autoLaunchEnabled; 535 536 if (hasBindAppWidgetPermission) { 537 autoLaunchTitleView.setText(R.string.auto_launch_label_generic); 538 } else { 539 autoLaunchTitleView.setText(R.string.auto_launch_label); 540 } 541 542 CharSequence text = null; 543 int bulletIndent = getResources() 544 .getDimensionPixelSize(R.dimen.installed_app_details_bullet_offset); 545 if (autoLaunchEnabled) { 546 CharSequence autoLaunchEnableText = getText(R.string.auto_launch_enable_text); 547 SpannableString s = new SpannableString(autoLaunchEnableText); 548 if (useBullets) { 549 s.setSpan(new BulletSpan(bulletIndent), 0, autoLaunchEnableText.length(), 0); 550 } 551 text = (text == null) ? 552 TextUtils.concat(s, "\n") : TextUtils.concat(text, "\n", s, "\n"); 553 } 554 if (hasBindAppWidgetPermission) { 555 CharSequence alwaysAllowBindAppWidgetsText = 556 getText(R.string.always_allow_bind_appwidgets_text); 557 SpannableString s = new SpannableString(alwaysAllowBindAppWidgetsText); 558 if (useBullets) { 559 s.setSpan(new BulletSpan(bulletIndent), 560 0, alwaysAllowBindAppWidgetsText.length(), 0); 561 } 562 text = (text == null) ? 563 TextUtils.concat(s, "\n") : TextUtils.concat(text, "\n", s, "\n"); 564 } 565 autoLaunchView.setText(text); 566 mActivitiesButton.setEnabled(true); 567 mActivitiesButton.setOnClickListener(this); 568 } 569 570 // Screen compatibility section. 571 ActivityManager am = (ActivityManager) 572 getActivity().getSystemService(Context.ACTIVITY_SERVICE); 573 int compatMode = am.getPackageScreenCompatMode(packageName); 574 // For now these are always off; this is the old UI model which we 575 // are no longer using. 576 if (false && (compatMode == ActivityManager.COMPAT_MODE_DISABLED 577 || compatMode == ActivityManager.COMPAT_MODE_ENABLED)) { 578 mScreenCompatSection.setVisibility(View.VISIBLE); 579 mAskCompatibilityCB.setChecked(am.getPackageAskScreenCompat(packageName)); 580 mAskCompatibilityCB.setOnCheckedChangeListener(this); 581 mEnableCompatibilityCB.setChecked(compatMode == ActivityManager.COMPAT_MODE_ENABLED); 582 mEnableCompatibilityCB.setOnCheckedChangeListener(this); 583 } else { 584 mScreenCompatSection.setVisibility(View.GONE); 585 } 586 587 // Security permissions section 588 LinearLayout permsView = (LinearLayout) mRootView.findViewById(R.id.permissions_section); 589 AppSecurityPermissions asp = new AppSecurityPermissions(getActivity(), packageName); 590 if (asp.getPermissionCount() > 0) { 591 permsView.setVisibility(View.VISIBLE); 592 // Make the security sections header visible 593 LinearLayout securityList = (LinearLayout) permsView.findViewById( 594 R.id.security_settings_list); 595 securityList.removeAllViews(); 596 securityList.addView(asp.getPermissionsView()); 597 // If this app is running under a shared user ID with other apps, 598 // update the description to explain this. 599 String[] packages = mPm.getPackagesForUid(mPackageInfo.applicationInfo.uid); 600 if (packages != null && packages.length > 1) { 601 ArrayList<CharSequence> pnames = new ArrayList<CharSequence>(); 602 for (int i=0; i<packages.length; i++) { 603 String pkg = packages[i]; 604 if (mPackageInfo.packageName.equals(pkg)) { 605 continue; 606 } 607 try { 608 ApplicationInfo ainfo = mPm.getApplicationInfo(pkg, 0); 609 pnames.add(ainfo.loadLabel(mPm)); 610 } catch (PackageManager.NameNotFoundException e) { 611 } 612 } 613 final int N = pnames.size(); 614 if (N > 0) { 615 final Resources res = getActivity().getResources(); 616 String appListStr; 617 if (N == 1) { 618 appListStr = pnames.get(0).toString(); 619 } else if (N == 2) { 620 appListStr = res.getString(R.string.join_two_items, pnames.get(0), 621 pnames.get(1)); 622 } else { 623 appListStr = pnames.get(N-2).toString(); 624 for (int i=N-3; i>=0; i--) { 625 appListStr = res.getString(i == 0 ? R.string.join_many_items_first 626 : R.string.join_many_items_middle, pnames.get(i), appListStr); 627 } 628 appListStr = res.getString(R.string.join_many_items_last, 629 appListStr, pnames.get(N-1)); 630 } 631 TextView descr = (TextView) mRootView.findViewById( 632 R.id.security_settings_desc); 633 descr.setText(res.getString(R.string.security_settings_desc_multi, 634 mPackageInfo.applicationInfo.loadLabel(mPm), appListStr)); 635 } 636 } 637 } else { 638 permsView.setVisibility(View.GONE); 639 } 640 641 checkForceStop(); 642 setAppLabelAndIcon(mPackageInfo); 643 refreshButtons(); 644 refreshSizeInfo(); 645 return true; 646 } 647 648 private void resetLaunchDefaultsUi(TextView title, TextView autoLaunchView) { 649 title.setText(R.string.auto_launch_label); 650 autoLaunchView.setText(R.string.auto_launch_disable_text); 651 // Disable clear activities button 652 mActivitiesButton.setEnabled(false); 653 } 654 655 private void setIntentAndFinish(boolean finish, boolean appChanged) { 656 if(localLOGV) Log.i(TAG, "appChanged="+appChanged); 657 Intent intent = new Intent(); 658 intent.putExtra(ManageApplications.APP_CHG, appChanged); 659 PreferenceActivity pa = (PreferenceActivity)getActivity(); 660 pa.finishPreferencePanel(this, Activity.RESULT_OK, intent); 661 } 662 663 private void refreshSizeInfo() { 664 if (mAppEntry.size == ApplicationsState.SIZE_INVALID 665 || mAppEntry.size == ApplicationsState.SIZE_UNKNOWN) { 666 mLastCodeSize = mLastDataSize = mLastCacheSize = mLastTotalSize = -1; 667 if (!mHaveSizes) { 668 mAppSize.setText(mComputingStr); 669 mDataSize.setText(mComputingStr); 670 mCacheSize.setText(mComputingStr); 671 mTotalSize.setText(mComputingStr); 672 } 673 mClearDataButton.setEnabled(false); 674 mClearCacheButton.setEnabled(false); 675 676 } else { 677 mHaveSizes = true; 678 if (mLastCodeSize != mAppEntry.codeSize) { 679 mLastCodeSize = mAppEntry.codeSize; 680 mAppSize.setText(getSizeStr(mAppEntry.codeSize)); 681 } 682 if (mLastDataSize != mAppEntry.dataSize) { 683 mLastDataSize = mAppEntry.dataSize; 684 mDataSize.setText(getSizeStr(mAppEntry.dataSize)); 685 } 686 if (mLastExternalCodeSize != mAppEntry.externalCodeSize) { 687 mLastExternalCodeSize = mAppEntry.externalCodeSize; 688 mExternalCodeSize.setText(getSizeStr(mAppEntry.externalCodeSize)); 689 } 690 if (mLastExternalDataSize != mAppEntry.externalDataSize) { 691 mLastExternalDataSize = mAppEntry.externalDataSize; 692 mExternalDataSize.setText(getSizeStr(mAppEntry.externalDataSize)); 693 } 694 if (mLastCacheSize != mAppEntry.cacheSize) { 695 mLastCacheSize = mAppEntry.cacheSize; 696 mCacheSize.setText(getSizeStr(mAppEntry.cacheSize)); 697 } 698 if (mLastTotalSize != mAppEntry.size) { 699 mLastTotalSize = mAppEntry.size; 700 mTotalSize.setText(getSizeStr(mAppEntry.size)); 701 } 702 703 if (mAppEntry.dataSize <= 0 || !mCanClearData) { 704 mClearDataButton.setEnabled(false); 705 } else { 706 mClearDataButton.setEnabled(true); 707 mClearDataButton.setOnClickListener(this); 708 } 709 if (mAppEntry.cacheSize <= 0) { 710 mClearCacheButton.setEnabled(false); 711 } else { 712 mClearCacheButton.setEnabled(true); 713 mClearCacheButton.setOnClickListener(this); 714 } 715 } 716 } 717 718 /* 719 * Private method to handle clear message notification from observer when 720 * the async operation from PackageManager is complete 721 */ 722 private void processClearMsg(Message msg) { 723 int result = msg.arg1; 724 String packageName = mAppEntry.info.packageName; 725 mClearDataButton.setText(R.string.clear_user_data_text); 726 if(result == OP_SUCCESSFUL) { 727 Log.i(TAG, "Cleared user data for package : "+packageName); 728 mState.requestSize(mAppEntry.info.packageName); 729 } else { 730 mClearDataButton.setEnabled(true); 731 } 732 checkForceStop(); 733 } 734 735 private void refreshButtons() { 736 if (!mMoveInProgress) { 737 initUninstallButtons(); 738 initDataButtons(); 739 initMoveButton(); 740 initNotificationButton(); 741 } else { 742 mMoveAppButton.setText(R.string.moving); 743 mMoveAppButton.setEnabled(false); 744 mUninstallButton.setEnabled(false); 745 } 746 } 747 748 private void processMoveMsg(Message msg) { 749 int result = msg.arg1; 750 String packageName = mAppEntry.info.packageName; 751 // Refresh the button attributes. 752 mMoveInProgress = false; 753 if (result == PackageManager.MOVE_SUCCEEDED) { 754 Log.i(TAG, "Moved resources for " + packageName); 755 // Refresh size information again. 756 mState.requestSize(mAppEntry.info.packageName); 757 } else { 758 showDialogInner(DLG_MOVE_FAILED, result); 759 } 760 refreshUi(); 761 } 762 763 /* 764 * Private method to initiate clearing user data when the user clicks the clear data 765 * button for a system package 766 */ 767 private void initiateClearUserData() { 768 mClearDataButton.setEnabled(false); 769 // Invoke uninstall or clear user data based on sysPackage 770 String packageName = mAppEntry.info.packageName; 771 Log.i(TAG, "Clearing user data for package : " + packageName); 772 if (mClearDataObserver == null) { 773 mClearDataObserver = new ClearUserDataObserver(); 774 } 775 ActivityManager am = (ActivityManager) 776 getActivity().getSystemService(Context.ACTIVITY_SERVICE); 777 boolean res = am.clearApplicationUserData(packageName, mClearDataObserver); 778 if (!res) { 779 // Clearing data failed for some obscure reason. Just log error for now 780 Log.i(TAG, "Couldnt clear application user data for package:"+packageName); 781 showDialogInner(DLG_CANNOT_CLEAR_DATA, 0); 782 } else { 783 mClearDataButton.setText(R.string.recompute_size); 784 } 785 } 786 787 private void showDialogInner(int id, int moveErrorCode) { 788 DialogFragment newFragment = MyAlertDialogFragment.newInstance(id, moveErrorCode); 789 newFragment.setTargetFragment(this, 0); 790 newFragment.show(getFragmentManager(), "dialog " + id); 791 } 792 793 public static class MyAlertDialogFragment extends DialogFragment { 794 795 public static MyAlertDialogFragment newInstance(int id, int moveErrorCode) { 796 MyAlertDialogFragment frag = new MyAlertDialogFragment(); 797 Bundle args = new Bundle(); 798 args.putInt("id", id); 799 args.putInt("moveError", moveErrorCode); 800 frag.setArguments(args); 801 return frag; 802 } 803 804 InstalledAppDetails getOwner() { 805 return (InstalledAppDetails)getTargetFragment(); 806 } 807 808 @Override 809 public Dialog onCreateDialog(Bundle savedInstanceState) { 810 int id = getArguments().getInt("id"); 811 int moveErrorCode = getArguments().getInt("moveError"); 812 switch (id) { 813 case DLG_CLEAR_DATA: 814 return new AlertDialog.Builder(getActivity()) 815 .setTitle(getActivity().getText(R.string.clear_data_dlg_title)) 816 .setIcon(android.R.drawable.ic_dialog_alert) 817 .setMessage(getActivity().getText(R.string.clear_data_dlg_text)) 818 .setPositiveButton(R.string.dlg_ok, 819 new DialogInterface.OnClickListener() { 820 public void onClick(DialogInterface dialog, int which) { 821 // Clear user data here 822 getOwner().initiateClearUserData(); 823 } 824 }) 825 .setNegativeButton(R.string.dlg_cancel, null) 826 .create(); 827 case DLG_FACTORY_RESET: 828 return new AlertDialog.Builder(getActivity()) 829 .setTitle(getActivity().getText(R.string.app_factory_reset_dlg_title)) 830 .setIcon(android.R.drawable.ic_dialog_alert) 831 .setMessage(getActivity().getText(R.string.app_factory_reset_dlg_text)) 832 .setPositiveButton(R.string.dlg_ok, 833 new DialogInterface.OnClickListener() { 834 public void onClick(DialogInterface dialog, int which) { 835 // Clear user data here 836 getOwner().uninstallPkg(getOwner().mAppEntry.info.packageName); 837 } 838 }) 839 .setNegativeButton(R.string.dlg_cancel, null) 840 .create(); 841 case DLG_APP_NOT_FOUND: 842 return new AlertDialog.Builder(getActivity()) 843 .setTitle(getActivity().getText(R.string.app_not_found_dlg_title)) 844 .setIcon(android.R.drawable.ic_dialog_alert) 845 .setMessage(getActivity().getText(R.string.app_not_found_dlg_title)) 846 .setNeutralButton(getActivity().getText(R.string.dlg_ok), 847 new DialogInterface.OnClickListener() { 848 public void onClick(DialogInterface dialog, int which) { 849 //force to recompute changed value 850 getOwner().setIntentAndFinish(true, true); 851 } 852 }) 853 .create(); 854 case DLG_CANNOT_CLEAR_DATA: 855 return new AlertDialog.Builder(getActivity()) 856 .setTitle(getActivity().getText(R.string.clear_failed_dlg_title)) 857 .setIcon(android.R.drawable.ic_dialog_alert) 858 .setMessage(getActivity().getText(R.string.clear_failed_dlg_text)) 859 .setNeutralButton(R.string.dlg_ok, 860 new DialogInterface.OnClickListener() { 861 public void onClick(DialogInterface dialog, int which) { 862 getOwner().mClearDataButton.setEnabled(false); 863 //force to recompute changed value 864 getOwner().setIntentAndFinish(false, false); 865 } 866 }) 867 .create(); 868 case DLG_FORCE_STOP: 869 return new AlertDialog.Builder(getActivity()) 870 .setTitle(getActivity().getText(R.string.force_stop_dlg_title)) 871 .setIcon(android.R.drawable.ic_dialog_alert) 872 .setMessage(getActivity().getText(R.string.force_stop_dlg_text)) 873 .setPositiveButton(R.string.dlg_ok, 874 new DialogInterface.OnClickListener() { 875 public void onClick(DialogInterface dialog, int which) { 876 // Force stop 877 getOwner().forceStopPackage(getOwner().mAppEntry.info.packageName); 878 } 879 }) 880 .setNegativeButton(R.string.dlg_cancel, null) 881 .create(); 882 case DLG_MOVE_FAILED: 883 CharSequence msg = getActivity().getString(R.string.move_app_failed_dlg_text, 884 getOwner().getMoveErrMsg(moveErrorCode)); 885 return new AlertDialog.Builder(getActivity()) 886 .setTitle(getActivity().getText(R.string.move_app_failed_dlg_title)) 887 .setIcon(android.R.drawable.ic_dialog_alert) 888 .setMessage(msg) 889 .setNeutralButton(R.string.dlg_ok, null) 890 .create(); 891 case DLG_DISABLE: 892 return new AlertDialog.Builder(getActivity()) 893 .setTitle(getActivity().getText(R.string.app_disable_dlg_title)) 894 .setIcon(android.R.drawable.ic_dialog_alert) 895 .setMessage(getActivity().getText(R.string.app_disable_dlg_text)) 896 .setPositiveButton(R.string.dlg_ok, 897 new DialogInterface.OnClickListener() { 898 public void onClick(DialogInterface dialog, int which) { 899 // Disable the app 900 new DisableChanger(getOwner(), getOwner().mAppEntry.info, 901 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) 902 .execute((Object)null); 903 } 904 }) 905 .setNegativeButton(R.string.dlg_cancel, null) 906 .create(); 907 case DLG_DISABLE_NOTIFICATIONS: 908 return new AlertDialog.Builder(getActivity()) 909 .setTitle(getActivity().getText(R.string.app_disable_notifications_dlg_title)) 910 .setIcon(android.R.drawable.ic_dialog_alert) 911 .setMessage(getActivity().getText(R.string.app_disable_notifications_dlg_text)) 912 .setPositiveButton(R.string.dlg_ok, 913 new DialogInterface.OnClickListener() { 914 public void onClick(DialogInterface dialog, int which) { 915 // Disable the package's notifications 916 getOwner().setNotificationsEnabled(false); 917 } 918 }) 919 .setNegativeButton(R.string.dlg_cancel, 920 new DialogInterface.OnClickListener() { 921 public void onClick(DialogInterface dialog, int which) { 922 // Re-enable the checkbox 923 getOwner().mNotificationSwitch.setChecked(true); 924 } 925 }) 926 .create(); 927 } 928 throw new IllegalArgumentException("unknown id " + id); 929 } 930 } 931 932 private void uninstallPkg(String packageName) { 933 // Create new intent to launch Uninstaller activity 934 Uri packageURI = Uri.parse("package:"+packageName); 935 Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI); 936 startActivity(uninstallIntent); 937 setIntentAndFinish(true, true); 938 } 939 940 private void forceStopPackage(String pkgName) { 941 ActivityManager am = (ActivityManager)getActivity().getSystemService( 942 Context.ACTIVITY_SERVICE); 943 am.forceStopPackage(pkgName); 944 mState.invalidatePackage(pkgName); 945 ApplicationsState.AppEntry newEnt = mState.getEntry(pkgName); 946 if (newEnt != null) { 947 mAppEntry = newEnt; 948 } 949 checkForceStop(); 950 } 951 952 private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() { 953 @Override 954 public void onReceive(Context context, Intent intent) { 955 updateForceStopButton(getResultCode() != Activity.RESULT_CANCELED); 956 } 957 }; 958 959 private void updateForceStopButton(boolean enabled) { 960 mForceStopButton.setEnabled(enabled); 961 mForceStopButton.setOnClickListener(InstalledAppDetails.this); 962 } 963 964 private void checkForceStop() { 965 if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 966 // User can't force stop device admin. 967 updateForceStopButton(false); 968 } else if ((mAppEntry.info.flags&ApplicationInfo.FLAG_STOPPED) == 0) { 969 // If the app isn't explicitly stopped, then always show the 970 // force stop button. 971 updateForceStopButton(true); 972 } else { 973 Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART, 974 Uri.fromParts("package", mAppEntry.info.packageName, null)); 975 intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { mAppEntry.info.packageName }); 976 intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid); 977 getActivity().sendOrderedBroadcast(intent, null, mCheckKillProcessesReceiver, null, 978 Activity.RESULT_CANCELED, null, null); 979 } 980 } 981 982 static class DisableChanger extends AsyncTask<Object, Object, Object> { 983 final PackageManager mPm; 984 final WeakReference<InstalledAppDetails> mActivity; 985 final ApplicationInfo mInfo; 986 final int mState; 987 988 DisableChanger(InstalledAppDetails activity, ApplicationInfo info, int state) { 989 mPm = activity.mPm; 990 mActivity = new WeakReference<InstalledAppDetails>(activity); 991 mInfo = info; 992 mState = state; 993 } 994 995 @Override 996 protected Object doInBackground(Object... params) { 997 mPm.setApplicationEnabledSetting(mInfo.packageName, mState, 0); 998 return null; 999 } 1000 } 1001 1002 private void setNotificationsEnabled(boolean enabled) { 1003 String packageName = mAppEntry.info.packageName; 1004 INotificationManager nm = INotificationManager.Stub.asInterface( 1005 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 1006 try { 1007 final boolean enable = mNotificationSwitch.isChecked(); 1008 nm.setNotificationsEnabledForPackage(packageName, enabled); 1009 } catch (android.os.RemoteException ex) { 1010 mNotificationSwitch.setChecked(!enabled); // revert 1011 } 1012 } 1013 1014 /* 1015 * Method implementing functionality of buttons clicked 1016 * @see android.view.View.OnClickListener#onClick(android.view.View) 1017 */ 1018 public void onClick(View v) { 1019 String packageName = mAppEntry.info.packageName; 1020 if(v == mUninstallButton) { 1021 if (mUpdatedSysApp) { 1022 showDialogInner(DLG_FACTORY_RESET, 0); 1023 } else { 1024 if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 1025 if (mAppEntry.info.enabled) { 1026 showDialogInner(DLG_DISABLE, 0); 1027 } else { 1028 new DisableChanger(this, mAppEntry.info, 1029 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) 1030 .execute((Object)null); 1031 } 1032 } else { 1033 uninstallPkg(packageName); 1034 } 1035 } 1036 } else if(v == mActivitiesButton) { 1037 mPm.clearPackagePreferredActivities(packageName); 1038 try { 1039 mUsbManager.clearDefaults(packageName); 1040 } catch (RemoteException e) { 1041 Log.e(TAG, "mUsbManager.clearDefaults", e); 1042 } 1043 mAppWidgetManager.setBindAppWidgetPermission(packageName, false); 1044 TextView autoLaunchTitleView = 1045 (TextView) mRootView.findViewById(R.id.auto_launch_title); 1046 TextView autoLaunchView = (TextView) mRootView.findViewById(R.id.auto_launch); 1047 resetLaunchDefaultsUi(autoLaunchTitleView, autoLaunchView); 1048 } else if(v == mClearDataButton) { 1049 if (mAppEntry.info.manageSpaceActivityName != null) { 1050 if (!Utils.isMonkeyRunning()) { 1051 Intent intent = new Intent(Intent.ACTION_DEFAULT); 1052 intent.setClassName(mAppEntry.info.packageName, 1053 mAppEntry.info.manageSpaceActivityName); 1054 startActivityForResult(intent, -1); 1055 } 1056 } else { 1057 showDialogInner(DLG_CLEAR_DATA, 0); 1058 } 1059 } else if (v == mClearCacheButton) { 1060 // Lazy initialization of observer 1061 if (mClearCacheObserver == null) { 1062 mClearCacheObserver = new ClearCacheObserver(); 1063 } 1064 mPm.deleteApplicationCacheFiles(packageName, mClearCacheObserver); 1065 } else if (v == mForceStopButton) { 1066 showDialogInner(DLG_FORCE_STOP, 0); 1067 //forceStopPackage(mAppInfo.packageName); 1068 } else if (v == mMoveAppButton) { 1069 if (mPackageMoveObserver == null) { 1070 mPackageMoveObserver = new PackageMoveObserver(); 1071 } 1072 int moveFlags = (mAppEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0 ? 1073 PackageManager.MOVE_INTERNAL : PackageManager.MOVE_EXTERNAL_MEDIA; 1074 mMoveInProgress = true; 1075 refreshButtons(); 1076 mPm.movePackage(mAppEntry.info.packageName, mPackageMoveObserver, moveFlags); 1077 } 1078 } 1079 1080 @Override 1081 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 1082 String packageName = mAppEntry.info.packageName; 1083 ActivityManager am = (ActivityManager) 1084 getActivity().getSystemService(Context.ACTIVITY_SERVICE); 1085 if (buttonView == mAskCompatibilityCB) { 1086 am.setPackageAskScreenCompat(packageName, isChecked); 1087 } else if (buttonView == mEnableCompatibilityCB) { 1088 am.setPackageScreenCompatMode(packageName, isChecked ? 1089 ActivityManager.COMPAT_MODE_ENABLED : ActivityManager.COMPAT_MODE_DISABLED); 1090 } else if (buttonView == mNotificationSwitch) { 1091 if (!isChecked) { 1092 showDialogInner(DLG_DISABLE_NOTIFICATIONS, 0); 1093 } else { 1094 setNotificationsEnabled(true); 1095 } 1096 } 1097 } 1098} 1099 1100