1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tv.settings.library.device.apps; 18 19 import static android.content.pm.ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA; 20 import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; 21 22 import static com.android.tv.settings.library.ManagerUtil.STATE_APP_MANAGEMENT; 23 import static com.android.tv.settings.library.device.apps.EnableDisablePreferenceController.KEY_ENABLE_DISABLE; 24 25 import android.app.Activity; 26 import android.app.ActivityManager; 27 import android.content.ActivityNotFoundException; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.pm.IPackageDataObserver; 31 import android.content.pm.PackageManager; 32 import android.content.pm.ResolveInfo; 33 import android.hardware.usb.IUsbManager; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.os.IBinder; 37 import android.os.RemoteException; 38 import android.os.ServiceManager; 39 import android.os.UserHandle; 40 import android.util.Log; 41 import android.widget.Toast; 42 43 import com.android.tv.settings.library.ManagerUtil; 44 import com.android.tv.settings.library.PreferenceCompat; 45 import com.android.tv.settings.library.UIUpdateCallback; 46 import com.android.tv.settings.library.data.PreferenceControllerState; 47 import com.android.tv.settings.library.util.AbstractPreferenceController; 48 import com.android.tv.settings.library.util.ResourcesUtil; 49 50 import java.util.ArrayList; 51 import java.util.List; 52 53 /** State to handle app management settings screen. */ 54 public class AppManagementState extends PreferenceControllerState { 55 private static final String TAG = "AppManagementState"; 56 // Intent action implemented by apps that have open source licenses to display under settings 57 private static final String VIEW_LICENSES_ACTION = "com.android.tv.settings.VIEW_LICENSES"; 58 private static final String ARG_PACKAGE_NAME = "packageName"; 59 60 private static final String KEY_VERSION = "version"; 61 private static final String KEY_OPEN = "open"; 62 private static final String KEY_LICENSES = "licenses"; 63 private static final String KEY_PERMISSIONS = "permissions"; 64 65 // Result code identifiers 66 static final int REQUEST_UNINSTALL = 1; 67 static final int REQUEST_MANAGE_SPACE = 2; 68 static final int REQUEST_UNINSTALL_UPDATES = 3; 69 static final int REQUEST_CLEAR_DATA = 4; 70 static final int REQUEST_CLEAR_CACHE = 5; 71 static final int REQUEST_CLEAR_DEFAULTS = 6; 72 73 private PackageManager mPackageManager; 74 private String mPackageName; 75 private ApplicationsState mApplicationsState; 76 private ApplicationsState.Session mSession; 77 private ApplicationsState.AppEntry mEntry; 78 private final ApplicationsState.Callbacks mCallbacks = new ApplicationsStateCallbacks(); 79 80 private ForceStopPreferenceController mForceStopPreferenceController; 81 private UninstallPreferenceController mUninstallPreferenceController; 82 private EnableDisablePreferenceController mEnableDisablePreferenceController; 83 private AppStoragePreferenceController mAppStoragePreferenceController; 84 private ClearDataPreferenceController mClearDataPreferenceController; 85 private ClearCachePreferenceController mClearCachePreferenceController; 86 private ClearDefaultsPreferenceController mClearDefaultsPreferenceController; 87 private NotificationsPreferenceController mNotificationsPreferenceController; 88 private final Handler mHandler = new Handler(); 89 AppManagementState(Context context, UIUpdateCallback callback)90 public AppManagementState(Context context, 91 UIUpdateCallback callback) { 92 super(context, callback); 93 } 94 prepareArgs(Bundle args, String packageName)95 public static void prepareArgs(Bundle args, String packageName) { 96 args.putString(ARG_PACKAGE_NAME, packageName); 97 } 98 99 @Override onCreate(Bundle extras)100 public void onCreate(Bundle extras) { 101 mPackageName = extras.getString(ARG_PACKAGE_NAME); 102 103 Activity activity = (Activity) mContext; 104 mPackageManager = activity.getPackageManager(); 105 mApplicationsState = ApplicationsState.getInstance(activity.getApplication()); 106 mSession = mApplicationsState.newSession(mCallbacks, getLifecycle()); 107 mEntry = mApplicationsState.getEntry(mPackageName, UserHandle.myUserId()); 108 super.onCreate(extras); 109 } 110 111 @Override onResume()112 public void onResume() { 113 super.onResume(); 114 115 if (mEntry == null) { 116 Log.w(TAG, "App not found, trying to bail out"); 117 mUIUpdateCallback.notifyNavigateBackward(getStateIdentifier()); 118 } 119 120 if (mClearDefaultsPreferenceController != null) { 121 mClearDefaultsPreferenceController.updateAndNotify(); 122 } 123 if (mEnableDisablePreferenceController != null) { 124 mEnableDisablePreferenceController.updateAndNotify(); 125 } 126 updatePrefs(); 127 } 128 129 130 @Override getStateIdentifier()131 public int getStateIdentifier() { 132 return STATE_APP_MANAGEMENT; 133 } 134 135 @Override onCreatePreferenceControllers(Context context)136 protected List<AbstractPreferenceController> onCreatePreferenceControllers(Context context) { 137 mForceStopPreferenceController = new ForceStopPreferenceController( 138 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry, 139 mPreferenceCompatManager); 140 mUninstallPreferenceController = new UninstallPreferenceController( 141 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry, 142 mPreferenceCompatManager); 143 mEnableDisablePreferenceController = new EnableDisablePreferenceController( 144 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry, 145 mPreferenceCompatManager); 146 mAppStoragePreferenceController = new AppStoragePreferenceController( 147 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry, 148 mPreferenceCompatManager); 149 mClearDataPreferenceController = new ClearDataPreferenceController( 150 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry, 151 mPreferenceCompatManager); 152 mClearCachePreferenceController = new ClearCachePreferenceController( 153 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry, 154 mPreferenceCompatManager); 155 mClearDefaultsPreferenceController = new ClearDefaultsPreferenceController( 156 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry, 157 mPreferenceCompatManager); 158 mNotificationsPreferenceController = new NotificationsPreferenceController( 159 mContext, mUIUpdateCallback, getStateIdentifier(), mEntry, 160 mPreferenceCompatManager); 161 List<AbstractPreferenceController> list = new ArrayList<>(); 162 list.add(mForceStopPreferenceController); 163 list.add(mUninstallPreferenceController); 164 list.add(mEnableDisablePreferenceController); 165 list.add(mAppStoragePreferenceController); 166 list.add(mClearCachePreferenceController); 167 list.add(mClearDataPreferenceController); 168 list.add(mClearDefaultsPreferenceController); 169 list.add(mNotificationsPreferenceController); 170 return list; 171 } 172 173 @Override onPreferenceTreeClick(String[] key, boolean status)174 public boolean onPreferenceTreeClick(String[] key, boolean status) { 175 if (KEY_ENABLE_DISABLE.equals(key[0])) { 176 // disable the preference to prevent double clicking 177 mEnableDisablePreferenceController.setEnabled(false); 178 } 179 try { 180 return super.onPreferenceTreeClick(key, status); 181 } catch (ActivityNotFoundException e) { 182 Log.e(TAG, "Could not find activity to launch", e); 183 Toast.makeText(mContext, 184 ResourcesUtil.getString( 185 mContext, "device_apps_app_management_not_available"), 186 Toast.LENGTH_SHORT).show(); 187 } 188 return false; 189 } 190 191 @Override onActivityResult(int requestCode, int resultCode, Intent data)192 public void onActivityResult(int requestCode, int resultCode, Intent data) { 193 if (mEntry == null) { 194 return; 195 } 196 switch (requestCode) { 197 case REQUEST_UNINSTALL: 198 final int deleteResult = data != null 199 ? data.getIntExtra(Intent.EXTRA_INSTALL_RESULT, 0) : 0; 200 if (deleteResult == PackageManager.DELETE_SUCCEEDED) { 201 final int userId = UserHandle.getUserId(mEntry.info.uid); 202 mApplicationsState.removePackage(mPackageName, userId); 203 mUIUpdateCallback.notifyNavigateBackward(getStateIdentifier()); 204 } else { 205 Log.e(TAG, "Uninstall failed with result " + deleteResult); 206 } 207 break; 208 case REQUEST_MANAGE_SPACE: 209 mClearDataPreferenceController.setClearingData(false); 210 if (resultCode == Activity.RESULT_OK) { 211 final int userId = UserHandle.getUserId(mEntry.info.uid); 212 mApplicationsState.requestSize(mPackageName, userId); 213 } else { 214 Log.w(TAG, "Failed to clear data!"); 215 } 216 break; 217 case REQUEST_UNINSTALL_UPDATES: 218 mUninstallPreferenceController.updateAndNotify(); 219 break; 220 case REQUEST_CLEAR_DATA: 221 if (resultCode == Activity.RESULT_OK) { 222 clearData(); 223 } 224 break; 225 case REQUEST_CLEAR_CACHE: 226 if (resultCode == Activity.RESULT_OK) { 227 clearCache(); 228 } 229 break; 230 case REQUEST_CLEAR_DEFAULTS: 231 if (resultCode == Activity.RESULT_OK) { 232 clearDefaults(); 233 } 234 break; 235 default: 236 break; 237 } 238 } 239 clearDefaults()240 private void clearDefaults() { 241 PackageManager packageManager = mContext.getPackageManager(); 242 packageManager.clearPackagePreferredActivities(mPackageName); 243 try { 244 final IBinder usbBinder = ServiceManager.getService(Context.USB_SERVICE); 245 IUsbManager.Stub.asInterface(usbBinder) 246 .clearDefaults(mPackageName, UserHandle.myUserId()); 247 } catch (RemoteException e) { 248 // Ignore 249 } 250 } 251 clearData()252 private void clearData() { 253 if (!clearDataAllowed()) { 254 Log.e(TAG, "Attempt to clear data failed. Clear data is disabled for " + mPackageName); 255 return; 256 } 257 258 mClearDataPreferenceController.setClearingData(true); 259 String spaceManagementActivityName = mEntry.info.manageSpaceActivityName; 260 if (spaceManagementActivityName != null) { 261 if (!ActivityManager.isUserAMonkey()) { 262 Intent intent = new Intent(Intent.ACTION_DEFAULT); 263 intent.setClassName(mEntry.info.packageName, spaceManagementActivityName); 264 ((Activity) mContext).startActivityForResult(intent, 265 ManagerUtil.calculateCompoundCode(getStateIdentifier(), 266 REQUEST_MANAGE_SPACE)); 267 } 268 } else { 269 // Disabling clear cache preference while clearing data is in progress. See b/77815256 270 // for details. 271 mClearCachePreferenceController.setClearingCache(true); 272 ActivityManager am = mContext.getSystemService(ActivityManager.class); 273 boolean success = am.clearApplicationUserData( 274 mEntry.info.packageName, new IPackageDataObserver.Stub() { 275 public void onRemoveCompleted( 276 final String packageName, final boolean succeeded) { 277 mHandler.post(new Runnable() { 278 @Override 279 public void run() { 280 mClearDataPreferenceController.setClearingData(false); 281 mClearCachePreferenceController.setClearingCache(false); 282 dataCleared(succeeded); 283 } 284 }); 285 } 286 }); 287 if (!success) { 288 mClearDataPreferenceController.setClearingData(false); 289 dataCleared(false); 290 } 291 } 292 mClearDataPreferenceController.updateAndNotify(); 293 } 294 dataCleared(boolean succeeded)295 private void dataCleared(boolean succeeded) { 296 if (succeeded) { 297 final int userId = UserHandle.getUserId(mEntry.info.uid); 298 mApplicationsState.requestSize(mPackageName, userId); 299 } else { 300 Log.w(TAG, "Failed to clear data!"); 301 mClearDataPreferenceController.update(); 302 } 303 } 304 305 clearCache()306 private void clearCache() { 307 mClearCachePreferenceController.setClearingCache(true); 308 mPackageManager.deleteApplicationCacheFiles(mEntry.info.packageName, 309 new IPackageDataObserver.Stub() { 310 public void onRemoveCompleted(final String packageName, 311 final boolean succeeded) { 312 mHandler.post(new Runnable() { 313 @Override 314 public void run() { 315 mClearCachePreferenceController.setClearingCache(false); 316 cacheCleared(succeeded); 317 } 318 }); 319 } 320 }); 321 mClearCachePreferenceController.update(); 322 } 323 cacheCleared(boolean succeeded)324 private void cacheCleared(boolean succeeded) { 325 if (succeeded) { 326 final int userId = UserHandle.getUserId(mEntry.info.uid); 327 mApplicationsState.requestSize(mPackageName, userId); 328 } else { 329 Log.w(TAG, "Failed to clear cache!"); 330 mClearCachePreferenceController.update(); 331 } 332 } 333 updatePrefs()334 private void updatePrefs() { 335 // Version 336 PreferenceCompat versionPreference = mPreferenceCompatManager 337 .getOrCreatePrefCompat(KEY_VERSION); 338 if (versionPreference == null) { 339 versionPreference.setSelectable(false); 340 } 341 versionPreference.setTitle( 342 ResourcesUtil.getString(mContext, "device_apps_app_management_version", 343 mEntry.getVersion(mContext))); 344 versionPreference.setSummary(mPackageName); 345 mUIUpdateCallback.notifyUpdate(getStateIdentifier(), versionPreference); 346 347 // Open 348 PreferenceCompat openPreference = mPreferenceCompatManager.getOrCreatePrefCompat(KEY_OPEN); 349 Intent appLaunchIntent = 350 mPackageManager.getLeanbackLaunchIntentForPackage(mEntry.info.packageName); 351 if (appLaunchIntent == null) { 352 appLaunchIntent = mPackageManager.getLaunchIntentForPackage(mEntry.info.packageName); 353 } 354 if (appLaunchIntent != null) { 355 openPreference.setIntent(appLaunchIntent); 356 openPreference.setTitle( 357 ResourcesUtil.getString(mContext, "device_apps_app_management_open")); 358 openPreference.setVisible(true); 359 } else { 360 openPreference.setVisible(false); 361 } 362 mUIUpdateCallback.notifyUpdate(getStateIdentifier(), openPreference); 363 364 // Force stop 365 if (mForceStopPreferenceController != null) { 366 mForceStopPreferenceController.setEntry(mEntry); 367 } 368 369 // Uninstall 370 if (mUninstallPreferenceController != null) { 371 mUninstallPreferenceController.setEntry(mEntry); 372 } 373 374 // Disable/Enable 375 if (mEnableDisablePreferenceController != null) { 376 mEnableDisablePreferenceController.setEntry(mEntry); 377 mEnableDisablePreferenceController.setEnabled(true); 378 } 379 380 // Storage used 381 if (mAppStoragePreferenceController != null) { 382 mAppStoragePreferenceController.setEntry(mEntry); 383 } 384 385 // Clear data 386 if (clearDataAllowed() && mClearDataPreferenceController != null) { 387 mClearDataPreferenceController.setEntry(mEntry); 388 } 389 390 // Clear cache 391 if (mClearCachePreferenceController != null) { 392 mClearCachePreferenceController.setEntry(mEntry); 393 } 394 395 // Clear defaults 396 if (mClearDefaultsPreferenceController != null) { 397 mClearDefaultsPreferenceController.setEntry(mEntry); 398 } 399 400 // Notifications 401 if (mNotificationsPreferenceController == null) { 402 mNotificationsPreferenceController.setEntry(mEntry); 403 } 404 405 // Open Source Licenses 406 PreferenceCompat licensesPreference = mPreferenceCompatManager.getOrCreatePrefCompat( 407 KEY_LICENSES); 408 // Check if app has open source licenses to display 409 Intent licenseIntent = new Intent(VIEW_LICENSES_ACTION); 410 licenseIntent.setPackage(mEntry.info.packageName); 411 ResolveInfo resolveInfo = resolveIntent(licenseIntent); 412 if (resolveInfo == null) { 413 licensesPreference.setVisible(false); 414 } else { 415 Intent intent = new Intent(licenseIntent); 416 intent.setClassName(resolveInfo.activityInfo.packageName, 417 resolveInfo.activityInfo.name); 418 licensesPreference.setIntent(intent); 419 licensesPreference.setTitle(ResourcesUtil.getString(mContext, 420 "device_apps_app_management_licenses")); 421 licensesPreference.setVisible(true); 422 } 423 mUIUpdateCallback.notifyUpdate(getStateIdentifier(), licensesPreference); 424 425 // Permissions 426 PreferenceCompat permissionsPreference = mPreferenceCompatManager.getOrCreatePrefCompat( 427 KEY_PERMISSIONS); 428 permissionsPreference.setTitle(ResourcesUtil.getString(mContext, 429 "device_apps_app_management_permissions")); 430 permissionsPreference.setIntent(new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS) 431 .putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName)); 432 mUIUpdateCallback.notifyUpdate(getStateIdentifier(), permissionsPreference); 433 } 434 435 private class ApplicationsStateCallbacks implements ApplicationsState.Callbacks { 436 437 @Override onRunningStateChanged(boolean running)438 public void onRunningStateChanged(boolean running) { 439 if (mForceStopPreferenceController != null) { 440 mForceStopPreferenceController.update(); 441 } 442 } 443 444 @Override onPackageListChanged()445 public void onPackageListChanged() { 446 if (mEntry == null || mEntry.info == null) { 447 return; 448 } 449 final int userId = UserHandle.getUserId(mEntry.info.uid); 450 mEntry = mApplicationsState.getEntry(mPackageName, userId); 451 if (mEntry == null) { 452 mUIUpdateCallback.notifyNavigateBackward(getStateIdentifier()); 453 } 454 updatePrefs(); 455 } 456 457 @Override onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps)458 public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) { 459 } 460 461 @Override onPackageIconChanged()462 public void onPackageIconChanged() { 463 } 464 465 @Override onPackageSizeChanged(String packageName)466 public void onPackageSizeChanged(String packageName) { 467 if (mAppStoragePreferenceController == null) { 468 // Nothing to do here. 469 return; 470 } 471 mAppStoragePreferenceController.updateAndNotify(); 472 if (mClearCachePreferenceController != null) { 473 mClearCachePreferenceController.updateAndNotify(); 474 } 475 476 if (mClearDataPreferenceController != null) { 477 mClearDataPreferenceController.updateAndNotify(); 478 } 479 } 480 481 @Override onAllSizesComputed()482 public void onAllSizesComputed() { 483 if (mAppStoragePreferenceController == null) { 484 // Nothing to do here. 485 return; 486 } 487 mAppStoragePreferenceController.updateAndNotify(); 488 if (mClearCachePreferenceController != null) { 489 mClearCachePreferenceController.updateAndNotify(); 490 } 491 492 if (mClearDataPreferenceController != null) { 493 mClearDataPreferenceController.updateAndNotify(); 494 } 495 } 496 497 @Override onLauncherInfoChanged()498 public void onLauncherInfoChanged() { 499 updatePrefs(); 500 } 501 502 @Override onLoadEntriesCompleted()503 public void onLoadEntriesCompleted() { 504 mEntry = mApplicationsState.getEntry(mPackageName, UserHandle.myUserId()); 505 updatePrefs(); 506 if (mAppStoragePreferenceController == null) { 507 // Nothing to do here. 508 return; 509 } 510 mAppStoragePreferenceController.updateAndNotify(); 511 if (mClearCachePreferenceController != null) { 512 mClearCachePreferenceController.updateAndNotify(); 513 } 514 515 if (mClearDataPreferenceController != null) { 516 mClearDataPreferenceController.updateAndNotify(); 517 } 518 } 519 } 520 resolveIntent(Intent intent)521 private ResolveInfo resolveIntent(Intent intent) { 522 List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent, 0); 523 return (resolveInfos == null || resolveInfos.size() <= 0) ? null : resolveInfos.get(0); 524 } 525 526 /** 527 * Clearing data can only be disabled for system apps. For all non-system apps it is enabled. 528 * System apps disable it explicitly via the android:allowClearUserData tag. 529 **/ clearDataAllowed()530 private boolean clearDataAllowed() { 531 boolean sysApp = (mEntry.info.flags & FLAG_SYSTEM) == FLAG_SYSTEM; 532 boolean allowClearData = 533 (mEntry.info.flags & FLAG_ALLOW_CLEAR_USER_DATA) == FLAG_ALLOW_CLEAR_USER_DATA; 534 return !sysApp || allowClearData; 535 } 536 } 537