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 17 package com.android.settings.applications; 18 19 import com.android.settings.R; 20 import com.android.settings.Utils; 21 import com.android.settings.applications.ApplicationsState.AppEntry; 22 23 import android.app.Activity; 24 import android.app.ActivityManager; 25 import android.app.AlertDialog; 26 import android.app.Dialog; 27 import android.app.DialogFragment; 28 import android.app.Fragment; 29 import android.app.INotificationManager; 30 import android.app.admin.DevicePolicyManager; 31 import android.appwidget.AppWidgetManager; 32 import android.content.BroadcastReceiver; 33 import android.content.ComponentName; 34 import android.content.Context; 35 import android.content.DialogInterface; 36 import android.content.Intent; 37 import android.content.IntentFilter; 38 import android.content.pm.ApplicationInfo; 39 import android.content.pm.IPackageDataObserver; 40 import android.content.pm.IPackageMoveObserver; 41 import android.content.pm.PackageInfo; 42 import android.content.pm.PackageManager; 43 import android.content.pm.ResolveInfo; 44 import android.content.pm.PackageManager.NameNotFoundException; 45 import android.content.res.Resources; 46 import android.hardware.usb.IUsbManager; 47 import android.net.Uri; 48 import android.os.AsyncTask; 49 import android.os.Bundle; 50 import android.os.Environment; 51 import android.os.Handler; 52 import android.os.IBinder; 53 import android.os.Message; 54 import android.os.RemoteException; 55 import android.os.ServiceManager; 56 import android.preference.PreferenceActivity; 57 import android.text.SpannableString; 58 import android.text.TextUtils; 59 import android.text.format.Formatter; 60 import android.text.style.BulletSpan; 61 import android.util.Log; 62 63 import java.lang.ref.WeakReference; 64 import java.util.ArrayList; 65 import java.util.List; 66 import android.view.LayoutInflater; 67 import android.view.View; 68 import android.view.ViewGroup; 69 import android.widget.AppSecurityPermissions; 70 import android.widget.Button; 71 import android.widget.CheckBox; 72 import android.widget.CompoundButton; 73 import android.widget.ImageView; 74 import android.widget.LinearLayout; 75 import 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 */ 86 public 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.right_button); 398 mMoveAppButton = (Button) data_buttons_panel.findViewById(R.id.left_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 long nonCacheExtDataSize = mAppEntry.externalDataSize - mAppEntry.externalCacheSize; 691 if (mLastExternalDataSize != nonCacheExtDataSize) { 692 mLastExternalDataSize = nonCacheExtDataSize; 693 mExternalDataSize.setText(getSizeStr(nonCacheExtDataSize)); 694 } 695 long cacheSize = mAppEntry.cacheSize + mAppEntry.externalCacheSize; 696 if (mLastCacheSize != cacheSize) { 697 mLastCacheSize = cacheSize; 698 mCacheSize.setText(getSizeStr(cacheSize)); 699 } 700 if (mLastTotalSize != mAppEntry.size) { 701 mLastTotalSize = mAppEntry.size; 702 mTotalSize.setText(getSizeStr(mAppEntry.size)); 703 } 704 705 if ((mAppEntry.dataSize+nonCacheExtDataSize) <= 0 || !mCanClearData) { 706 mClearDataButton.setEnabled(false); 707 } else { 708 mClearDataButton.setEnabled(true); 709 mClearDataButton.setOnClickListener(this); 710 } 711 if (cacheSize <= 0) { 712 mClearCacheButton.setEnabled(false); 713 } else { 714 mClearCacheButton.setEnabled(true); 715 mClearCacheButton.setOnClickListener(this); 716 } 717 } 718 } 719 720 /* 721 * Private method to handle clear message notification from observer when 722 * the async operation from PackageManager is complete 723 */ 724 private void processClearMsg(Message msg) { 725 int result = msg.arg1; 726 String packageName = mAppEntry.info.packageName; 727 mClearDataButton.setText(R.string.clear_user_data_text); 728 if(result == OP_SUCCESSFUL) { 729 Log.i(TAG, "Cleared user data for package : "+packageName); 730 mState.requestSize(mAppEntry.info.packageName); 731 } else { 732 mClearDataButton.setEnabled(true); 733 } 734 checkForceStop(); 735 } 736 737 private void refreshButtons() { 738 if (!mMoveInProgress) { 739 initUninstallButtons(); 740 initDataButtons(); 741 initMoveButton(); 742 initNotificationButton(); 743 } else { 744 mMoveAppButton.setText(R.string.moving); 745 mMoveAppButton.setEnabled(false); 746 mUninstallButton.setEnabled(false); 747 } 748 } 749 750 private void processMoveMsg(Message msg) { 751 int result = msg.arg1; 752 String packageName = mAppEntry.info.packageName; 753 // Refresh the button attributes. 754 mMoveInProgress = false; 755 if (result == PackageManager.MOVE_SUCCEEDED) { 756 Log.i(TAG, "Moved resources for " + packageName); 757 // Refresh size information again. 758 mState.requestSize(mAppEntry.info.packageName); 759 } else { 760 showDialogInner(DLG_MOVE_FAILED, result); 761 } 762 refreshUi(); 763 } 764 765 /* 766 * Private method to initiate clearing user data when the user clicks the clear data 767 * button for a system package 768 */ 769 private void initiateClearUserData() { 770 mClearDataButton.setEnabled(false); 771 // Invoke uninstall or clear user data based on sysPackage 772 String packageName = mAppEntry.info.packageName; 773 Log.i(TAG, "Clearing user data for package : " + packageName); 774 if (mClearDataObserver == null) { 775 mClearDataObserver = new ClearUserDataObserver(); 776 } 777 ActivityManager am = (ActivityManager) 778 getActivity().getSystemService(Context.ACTIVITY_SERVICE); 779 boolean res = am.clearApplicationUserData(packageName, mClearDataObserver); 780 if (!res) { 781 // Clearing data failed for some obscure reason. Just log error for now 782 Log.i(TAG, "Couldnt clear application user data for package:"+packageName); 783 showDialogInner(DLG_CANNOT_CLEAR_DATA, 0); 784 } else { 785 mClearDataButton.setText(R.string.recompute_size); 786 } 787 } 788 789 private void showDialogInner(int id, int moveErrorCode) { 790 DialogFragment newFragment = MyAlertDialogFragment.newInstance(id, moveErrorCode); 791 newFragment.setTargetFragment(this, 0); 792 newFragment.show(getFragmentManager(), "dialog " + id); 793 } 794 795 public static class MyAlertDialogFragment extends DialogFragment { 796 797 public static MyAlertDialogFragment newInstance(int id, int moveErrorCode) { 798 MyAlertDialogFragment frag = new MyAlertDialogFragment(); 799 Bundle args = new Bundle(); 800 args.putInt("id", id); 801 args.putInt("moveError", moveErrorCode); 802 frag.setArguments(args); 803 return frag; 804 } 805 806 InstalledAppDetails getOwner() { 807 return (InstalledAppDetails)getTargetFragment(); 808 } 809 810 @Override 811 public Dialog onCreateDialog(Bundle savedInstanceState) { 812 int id = getArguments().getInt("id"); 813 int moveErrorCode = getArguments().getInt("moveError"); 814 switch (id) { 815 case DLG_CLEAR_DATA: 816 return new AlertDialog.Builder(getActivity()) 817 .setTitle(getActivity().getText(R.string.clear_data_dlg_title)) 818 .setIcon(android.R.drawable.ic_dialog_alert) 819 .setMessage(getActivity().getText(R.string.clear_data_dlg_text)) 820 .setPositiveButton(R.string.dlg_ok, 821 new DialogInterface.OnClickListener() { 822 public void onClick(DialogInterface dialog, int which) { 823 // Clear user data here 824 getOwner().initiateClearUserData(); 825 } 826 }) 827 .setNegativeButton(R.string.dlg_cancel, null) 828 .create(); 829 case DLG_FACTORY_RESET: 830 return new AlertDialog.Builder(getActivity()) 831 .setTitle(getActivity().getText(R.string.app_factory_reset_dlg_title)) 832 .setIcon(android.R.drawable.ic_dialog_alert) 833 .setMessage(getActivity().getText(R.string.app_factory_reset_dlg_text)) 834 .setPositiveButton(R.string.dlg_ok, 835 new DialogInterface.OnClickListener() { 836 public void onClick(DialogInterface dialog, int which) { 837 // Clear user data here 838 getOwner().uninstallPkg(getOwner().mAppEntry.info.packageName); 839 } 840 }) 841 .setNegativeButton(R.string.dlg_cancel, null) 842 .create(); 843 case DLG_APP_NOT_FOUND: 844 return new AlertDialog.Builder(getActivity()) 845 .setTitle(getActivity().getText(R.string.app_not_found_dlg_title)) 846 .setIcon(android.R.drawable.ic_dialog_alert) 847 .setMessage(getActivity().getText(R.string.app_not_found_dlg_title)) 848 .setNeutralButton(getActivity().getText(R.string.dlg_ok), 849 new DialogInterface.OnClickListener() { 850 public void onClick(DialogInterface dialog, int which) { 851 //force to recompute changed value 852 getOwner().setIntentAndFinish(true, true); 853 } 854 }) 855 .create(); 856 case DLG_CANNOT_CLEAR_DATA: 857 return new AlertDialog.Builder(getActivity()) 858 .setTitle(getActivity().getText(R.string.clear_failed_dlg_title)) 859 .setIcon(android.R.drawable.ic_dialog_alert) 860 .setMessage(getActivity().getText(R.string.clear_failed_dlg_text)) 861 .setNeutralButton(R.string.dlg_ok, 862 new DialogInterface.OnClickListener() { 863 public void onClick(DialogInterface dialog, int which) { 864 getOwner().mClearDataButton.setEnabled(false); 865 //force to recompute changed value 866 getOwner().setIntentAndFinish(false, false); 867 } 868 }) 869 .create(); 870 case DLG_FORCE_STOP: 871 return new AlertDialog.Builder(getActivity()) 872 .setTitle(getActivity().getText(R.string.force_stop_dlg_title)) 873 .setIcon(android.R.drawable.ic_dialog_alert) 874 .setMessage(getActivity().getText(R.string.force_stop_dlg_text)) 875 .setPositiveButton(R.string.dlg_ok, 876 new DialogInterface.OnClickListener() { 877 public void onClick(DialogInterface dialog, int which) { 878 // Force stop 879 getOwner().forceStopPackage(getOwner().mAppEntry.info.packageName); 880 } 881 }) 882 .setNegativeButton(R.string.dlg_cancel, null) 883 .create(); 884 case DLG_MOVE_FAILED: 885 CharSequence msg = getActivity().getString(R.string.move_app_failed_dlg_text, 886 getOwner().getMoveErrMsg(moveErrorCode)); 887 return new AlertDialog.Builder(getActivity()) 888 .setTitle(getActivity().getText(R.string.move_app_failed_dlg_title)) 889 .setIcon(android.R.drawable.ic_dialog_alert) 890 .setMessage(msg) 891 .setNeutralButton(R.string.dlg_ok, null) 892 .create(); 893 case DLG_DISABLE: 894 return new AlertDialog.Builder(getActivity()) 895 .setTitle(getActivity().getText(R.string.app_disable_dlg_title)) 896 .setIcon(android.R.drawable.ic_dialog_alert) 897 .setMessage(getActivity().getText(R.string.app_disable_dlg_text)) 898 .setPositiveButton(R.string.dlg_ok, 899 new DialogInterface.OnClickListener() { 900 public void onClick(DialogInterface dialog, int which) { 901 // Disable the app 902 new DisableChanger(getOwner(), getOwner().mAppEntry.info, 903 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) 904 .execute((Object)null); 905 } 906 }) 907 .setNegativeButton(R.string.dlg_cancel, null) 908 .create(); 909 case DLG_DISABLE_NOTIFICATIONS: 910 return new AlertDialog.Builder(getActivity()) 911 .setTitle(getActivity().getText(R.string.app_disable_notifications_dlg_title)) 912 .setIcon(android.R.drawable.ic_dialog_alert) 913 .setMessage(getActivity().getText(R.string.app_disable_notifications_dlg_text)) 914 .setPositiveButton(R.string.dlg_ok, 915 new DialogInterface.OnClickListener() { 916 public void onClick(DialogInterface dialog, int which) { 917 // Disable the package's notifications 918 getOwner().setNotificationsEnabled(false); 919 } 920 }) 921 .setNegativeButton(R.string.dlg_cancel, 922 new DialogInterface.OnClickListener() { 923 public void onClick(DialogInterface dialog, int which) { 924 // Re-enable the checkbox 925 getOwner().mNotificationSwitch.setChecked(true); 926 } 927 }) 928 .create(); 929 } 930 throw new IllegalArgumentException("unknown id " + id); 931 } 932 } 933 934 private void uninstallPkg(String packageName) { 935 // Create new intent to launch Uninstaller activity 936 Uri packageURI = Uri.parse("package:"+packageName); 937 Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI); 938 startActivity(uninstallIntent); 939 setIntentAndFinish(true, true); 940 } 941 942 private void forceStopPackage(String pkgName) { 943 ActivityManager am = (ActivityManager)getActivity().getSystemService( 944 Context.ACTIVITY_SERVICE); 945 am.forceStopPackage(pkgName); 946 mState.invalidatePackage(pkgName); 947 ApplicationsState.AppEntry newEnt = mState.getEntry(pkgName); 948 if (newEnt != null) { 949 mAppEntry = newEnt; 950 } 951 checkForceStop(); 952 } 953 954 private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() { 955 @Override 956 public void onReceive(Context context, Intent intent) { 957 updateForceStopButton(getResultCode() != Activity.RESULT_CANCELED); 958 } 959 }; 960 961 private void updateForceStopButton(boolean enabled) { 962 mForceStopButton.setEnabled(enabled); 963 mForceStopButton.setOnClickListener(InstalledAppDetails.this); 964 } 965 966 private void checkForceStop() { 967 if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { 968 // User can't force stop device admin. 969 updateForceStopButton(false); 970 } else if ((mAppEntry.info.flags&ApplicationInfo.FLAG_STOPPED) == 0) { 971 // If the app isn't explicitly stopped, then always show the 972 // force stop button. 973 updateForceStopButton(true); 974 } else { 975 Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART, 976 Uri.fromParts("package", mAppEntry.info.packageName, null)); 977 intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { mAppEntry.info.packageName }); 978 intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid); 979 getActivity().sendOrderedBroadcast(intent, null, mCheckKillProcessesReceiver, null, 980 Activity.RESULT_CANCELED, null, null); 981 } 982 } 983 984 static class DisableChanger extends AsyncTask<Object, Object, Object> { 985 final PackageManager mPm; 986 final WeakReference<InstalledAppDetails> mActivity; 987 final ApplicationInfo mInfo; 988 final int mState; 989 990 DisableChanger(InstalledAppDetails activity, ApplicationInfo info, int state) { 991 mPm = activity.mPm; 992 mActivity = new WeakReference<InstalledAppDetails>(activity); 993 mInfo = info; 994 mState = state; 995 } 996 997 @Override 998 protected Object doInBackground(Object... params) { 999 mPm.setApplicationEnabledSetting(mInfo.packageName, mState, 0); 1000 return null; 1001 } 1002 } 1003 1004 private void setNotificationsEnabled(boolean enabled) { 1005 String packageName = mAppEntry.info.packageName; 1006 INotificationManager nm = INotificationManager.Stub.asInterface( 1007 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 1008 try { 1009 final boolean enable = mNotificationSwitch.isChecked(); 1010 nm.setNotificationsEnabledForPackage(packageName, enabled); 1011 } catch (android.os.RemoteException ex) { 1012 mNotificationSwitch.setChecked(!enabled); // revert 1013 } 1014 } 1015 1016 /* 1017 * Method implementing functionality of buttons clicked 1018 * @see android.view.View.OnClickListener#onClick(android.view.View) 1019 */ 1020 public void onClick(View v) { 1021 String packageName = mAppEntry.info.packageName; 1022 if(v == mUninstallButton) { 1023 if (mUpdatedSysApp) { 1024 showDialogInner(DLG_FACTORY_RESET, 0); 1025 } else { 1026 if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 1027 if (mAppEntry.info.enabled) { 1028 showDialogInner(DLG_DISABLE, 0); 1029 } else { 1030 new DisableChanger(this, mAppEntry.info, 1031 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) 1032 .execute((Object)null); 1033 } 1034 } else { 1035 uninstallPkg(packageName); 1036 } 1037 } 1038 } else if(v == mActivitiesButton) { 1039 mPm.clearPackagePreferredActivities(packageName); 1040 try { 1041 mUsbManager.clearDefaults(packageName); 1042 } catch (RemoteException e) { 1043 Log.e(TAG, "mUsbManager.clearDefaults", e); 1044 } 1045 mAppWidgetManager.setBindAppWidgetPermission(packageName, false); 1046 TextView autoLaunchTitleView = 1047 (TextView) mRootView.findViewById(R.id.auto_launch_title); 1048 TextView autoLaunchView = (TextView) mRootView.findViewById(R.id.auto_launch); 1049 resetLaunchDefaultsUi(autoLaunchTitleView, autoLaunchView); 1050 } else if(v == mClearDataButton) { 1051 if (mAppEntry.info.manageSpaceActivityName != null) { 1052 if (!Utils.isMonkeyRunning()) { 1053 Intent intent = new Intent(Intent.ACTION_DEFAULT); 1054 intent.setClassName(mAppEntry.info.packageName, 1055 mAppEntry.info.manageSpaceActivityName); 1056 startActivityForResult(intent, -1); 1057 } 1058 } else { 1059 showDialogInner(DLG_CLEAR_DATA, 0); 1060 } 1061 } else if (v == mClearCacheButton) { 1062 // Lazy initialization of observer 1063 if (mClearCacheObserver == null) { 1064 mClearCacheObserver = new ClearCacheObserver(); 1065 } 1066 mPm.deleteApplicationCacheFiles(packageName, mClearCacheObserver); 1067 } else if (v == mForceStopButton) { 1068 showDialogInner(DLG_FORCE_STOP, 0); 1069 //forceStopPackage(mAppInfo.packageName); 1070 } else if (v == mMoveAppButton) { 1071 if (mPackageMoveObserver == null) { 1072 mPackageMoveObserver = new PackageMoveObserver(); 1073 } 1074 int moveFlags = (mAppEntry.info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0 ? 1075 PackageManager.MOVE_INTERNAL : PackageManager.MOVE_EXTERNAL_MEDIA; 1076 mMoveInProgress = true; 1077 refreshButtons(); 1078 mPm.movePackage(mAppEntry.info.packageName, mPackageMoveObserver, moveFlags); 1079 } 1080 } 1081 1082 @Override 1083 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 1084 String packageName = mAppEntry.info.packageName; 1085 ActivityManager am = (ActivityManager) 1086 getActivity().getSystemService(Context.ACTIVITY_SERVICE); 1087 if (buttonView == mAskCompatibilityCB) { 1088 am.setPackageAskScreenCompat(packageName, isChecked); 1089 } else if (buttonView == mEnableCompatibilityCB) { 1090 am.setPackageScreenCompatMode(packageName, isChecked ? 1091 ActivityManager.COMPAT_MODE_ENABLED : ActivityManager.COMPAT_MODE_DISABLED); 1092 } else if (buttonView == mNotificationSwitch) { 1093 if (!isChecked) { 1094 showDialogInner(DLG_DISABLE_NOTIFICATIONS, 0); 1095 } else { 1096 setNotificationsEnabled(true); 1097 } 1098 } 1099 } 1100 } 1101 1102