1 /* 2 * Copyright (C) 2015 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.systemui.usb; 18 19 import android.annotation.NonNull; 20 import android.app.Notification; 21 import android.app.Notification.Action; 22 import android.app.NotificationManager; 23 import android.app.PendingIntent; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.pm.PackageManager; 29 import android.content.pm.PackageManager.MoveCallback; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.StrictMode; 33 import android.os.UserHandle; 34 import android.os.storage.DiskInfo; 35 import android.os.storage.StorageEventListener; 36 import android.os.storage.StorageManager; 37 import android.os.storage.VolumeInfo; 38 import android.os.storage.VolumeRecord; 39 import android.provider.Settings; 40 import android.text.TextUtils; 41 import android.text.format.DateUtils; 42 import android.util.Log; 43 import android.util.SparseArray; 44 45 import com.android.internal.R; 46 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; 47 import com.android.systemui.SystemUI; 48 import com.android.systemui.util.NotificationChannels; 49 50 import java.util.List; 51 52 public class StorageNotification extends SystemUI { 53 private static final String TAG = "StorageNotification"; 54 55 private static final String ACTION_SNOOZE_VOLUME = "com.android.systemui.action.SNOOZE_VOLUME"; 56 private static final String ACTION_FINISH_WIZARD = "com.android.systemui.action.FINISH_WIZARD"; 57 58 // TODO: delay some notifications to avoid bumpy fast operations 59 60 private NotificationManager mNotificationManager; 61 private StorageManager mStorageManager; 62 63 private static class MoveInfo { 64 public int moveId; 65 public Bundle extras; 66 public String packageName; 67 public String label; 68 public String volumeUuid; 69 } 70 71 private final SparseArray<MoveInfo> mMoves = new SparseArray<>(); 72 73 private final StorageEventListener mListener = new StorageEventListener() { 74 @Override 75 public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { 76 onVolumeStateChangedInternal(vol); 77 } 78 79 @Override 80 public void onVolumeRecordChanged(VolumeRecord rec) { 81 // Avoid kicking notifications when getting early metadata before 82 // mounted. If already mounted, we're being kicked because of a 83 // nickname or init'ed change. 84 final VolumeInfo vol = mStorageManager.findVolumeByUuid(rec.getFsUuid()); 85 if (vol != null && vol.isMountedReadable()) { 86 onVolumeStateChangedInternal(vol); 87 } 88 } 89 90 @Override 91 public void onVolumeForgotten(String fsUuid) { 92 // Stop annoying the user 93 mNotificationManager.cancelAsUser(fsUuid, SystemMessage.NOTE_STORAGE_PRIVATE, 94 UserHandle.ALL); 95 } 96 97 @Override 98 public void onDiskScanned(DiskInfo disk, int volumeCount) { 99 onDiskScannedInternal(disk, volumeCount); 100 } 101 102 @Override 103 public void onDiskDestroyed(DiskInfo disk) { 104 onDiskDestroyedInternal(disk); 105 } 106 }; 107 108 private final BroadcastReceiver mSnoozeReceiver = new BroadcastReceiver() { 109 @Override 110 public void onReceive(Context context, Intent intent) { 111 // TODO: kick this onto background thread 112 final String fsUuid = intent.getStringExtra(VolumeRecord.EXTRA_FS_UUID); 113 mStorageManager.setVolumeSnoozed(fsUuid, true); 114 } 115 }; 116 117 private final BroadcastReceiver mFinishReceiver = new BroadcastReceiver() { 118 @Override 119 public void onReceive(Context context, Intent intent) { 120 // When finishing the adoption wizard, clean up any notifications 121 // for moving primary storage 122 mNotificationManager.cancelAsUser(null, SystemMessage.NOTE_STORAGE_MOVE, 123 UserHandle.ALL); 124 } 125 }; 126 127 private final MoveCallback mMoveCallback = new MoveCallback() { 128 @Override 129 public void onCreated(int moveId, Bundle extras) { 130 final MoveInfo move = new MoveInfo(); 131 move.moveId = moveId; 132 move.extras = extras; 133 if (extras != null) { 134 move.packageName = extras.getString(Intent.EXTRA_PACKAGE_NAME); 135 move.label = extras.getString(Intent.EXTRA_TITLE); 136 move.volumeUuid = extras.getString(VolumeRecord.EXTRA_FS_UUID); 137 } 138 mMoves.put(moveId, move); 139 } 140 141 @Override 142 public void onStatusChanged(int moveId, int status, long estMillis) { 143 final MoveInfo move = mMoves.get(moveId); 144 if (move == null) { 145 Log.w(TAG, "Ignoring unknown move " + moveId); 146 return; 147 } 148 149 if (PackageManager.isMoveStatusFinished(status)) { 150 onMoveFinished(move, status); 151 } else { 152 onMoveProgress(move, status, estMillis); 153 } 154 } 155 }; 156 157 @Override start()158 public void start() { 159 mNotificationManager = mContext.getSystemService(NotificationManager.class); 160 161 mStorageManager = mContext.getSystemService(StorageManager.class); 162 mStorageManager.registerListener(mListener); 163 164 mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME), 165 android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); 166 mContext.registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_WIZARD), 167 android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); 168 169 // Kick current state into place 170 final List<DiskInfo> disks = mStorageManager.getDisks(); 171 for (DiskInfo disk : disks) { 172 onDiskScannedInternal(disk, disk.volumeCount); 173 } 174 175 final List<VolumeInfo> vols = mStorageManager.getVolumes(); 176 for (VolumeInfo vol : vols) { 177 onVolumeStateChangedInternal(vol); 178 } 179 180 mContext.getPackageManager().registerMoveCallback(mMoveCallback, new Handler()); 181 182 updateMissingPrivateVolumes(); 183 } 184 updateMissingPrivateVolumes()185 private void updateMissingPrivateVolumes() { 186 if (isTv()) { 187 // On TV, TvSettings displays a modal full-screen activity in this case. 188 return; 189 } 190 191 final List<VolumeRecord> recs = mStorageManager.getVolumeRecords(); 192 for (VolumeRecord rec : recs) { 193 if (rec.getType() != VolumeInfo.TYPE_PRIVATE) continue; 194 195 final String fsUuid = rec.getFsUuid(); 196 final VolumeInfo info = mStorageManager.findVolumeByUuid(fsUuid); 197 if ((info != null && info.isMountedWritable()) || rec.isSnoozed()) { 198 // Yay, private volume is here, or user snoozed 199 mNotificationManager.cancelAsUser(fsUuid, SystemMessage.NOTE_STORAGE_PRIVATE, 200 UserHandle.ALL); 201 202 } else { 203 // Boo, annoy the user to reinsert the private volume 204 final CharSequence title = mContext.getString(R.string.ext_media_missing_title, 205 rec.getNickname()); 206 final CharSequence text = mContext.getString(R.string.ext_media_missing_message); 207 208 Notification.Builder builder = 209 new Notification.Builder(mContext, NotificationChannels.STORAGE) 210 .setSmallIcon(R.drawable.ic_sd_card_48dp) 211 .setColor(mContext.getColor( 212 R.color.system_notification_accent_color)) 213 .setContentTitle(title) 214 .setContentText(text) 215 .setContentIntent(buildForgetPendingIntent(rec)) 216 .setStyle(new Notification.BigTextStyle().bigText(text)) 217 .setVisibility(Notification.VISIBILITY_PUBLIC) 218 .setLocalOnly(true) 219 .setCategory(Notification.CATEGORY_SYSTEM) 220 .setDeleteIntent(buildSnoozeIntent(fsUuid)) 221 .extend(new Notification.TvExtender()); 222 SystemUI.overrideNotificationAppName(mContext, builder, false); 223 224 mNotificationManager.notifyAsUser(fsUuid, SystemMessage.NOTE_STORAGE_PRIVATE, 225 builder.build(), UserHandle.ALL); 226 } 227 } 228 } 229 onDiskScannedInternal(DiskInfo disk, int volumeCount)230 private void onDiskScannedInternal(DiskInfo disk, int volumeCount) { 231 if (volumeCount == 0 && disk.size > 0) { 232 // No supported volumes found, give user option to format 233 final CharSequence title = mContext.getString( 234 R.string.ext_media_unsupported_notification_title, disk.getDescription()); 235 final CharSequence text = mContext.getString( 236 R.string.ext_media_unsupported_notification_message, disk.getDescription()); 237 238 Notification.Builder builder = 239 new Notification.Builder(mContext, NotificationChannels.STORAGE) 240 .setSmallIcon(getSmallIcon(disk, VolumeInfo.STATE_UNMOUNTABLE)) 241 .setColor(mContext.getColor(R.color.system_notification_accent_color)) 242 .setContentTitle(title) 243 .setContentText(text) 244 .setContentIntent(buildInitPendingIntent(disk)) 245 .setStyle(new Notification.BigTextStyle().bigText(text)) 246 .setVisibility(Notification.VISIBILITY_PUBLIC) 247 .setLocalOnly(true) 248 .setCategory(Notification.CATEGORY_ERROR) 249 .extend(new Notification.TvExtender()); 250 SystemUI.overrideNotificationAppName(mContext, builder, false); 251 252 mNotificationManager.notifyAsUser(disk.getId(), SystemMessage.NOTE_STORAGE_DISK, 253 builder.build(), UserHandle.ALL); 254 255 } else { 256 // Yay, we have volumes! 257 mNotificationManager.cancelAsUser(disk.getId(), SystemMessage.NOTE_STORAGE_DISK, 258 UserHandle.ALL); 259 } 260 } 261 262 /** 263 * Remove all notifications for a disk when it goes away. 264 * 265 * @param disk The disk that went away. 266 */ onDiskDestroyedInternal(@onNull DiskInfo disk)267 private void onDiskDestroyedInternal(@NonNull DiskInfo disk) { 268 mNotificationManager.cancelAsUser(disk.getId(), SystemMessage.NOTE_STORAGE_DISK, 269 UserHandle.ALL); 270 } 271 onVolumeStateChangedInternal(VolumeInfo vol)272 private void onVolumeStateChangedInternal(VolumeInfo vol) { 273 switch (vol.getType()) { 274 case VolumeInfo.TYPE_PRIVATE: 275 onPrivateVolumeStateChangedInternal(vol); 276 break; 277 case VolumeInfo.TYPE_PUBLIC: 278 onPublicVolumeStateChangedInternal(vol); 279 break; 280 } 281 } 282 onPrivateVolumeStateChangedInternal(VolumeInfo vol)283 private void onPrivateVolumeStateChangedInternal(VolumeInfo vol) { 284 Log.d(TAG, "Notifying about private volume: " + vol.toString()); 285 286 updateMissingPrivateVolumes(); 287 } 288 onPublicVolumeStateChangedInternal(VolumeInfo vol)289 private void onPublicVolumeStateChangedInternal(VolumeInfo vol) { 290 Log.d(TAG, "Notifying about public volume: " + vol.toString()); 291 292 final Notification notif; 293 switch (vol.getState()) { 294 case VolumeInfo.STATE_UNMOUNTED: 295 notif = onVolumeUnmounted(vol); 296 break; 297 case VolumeInfo.STATE_CHECKING: 298 notif = onVolumeChecking(vol); 299 break; 300 case VolumeInfo.STATE_MOUNTED: 301 case VolumeInfo.STATE_MOUNTED_READ_ONLY: 302 notif = onVolumeMounted(vol); 303 break; 304 case VolumeInfo.STATE_FORMATTING: 305 notif = onVolumeFormatting(vol); 306 break; 307 case VolumeInfo.STATE_EJECTING: 308 notif = onVolumeEjecting(vol); 309 break; 310 case VolumeInfo.STATE_UNMOUNTABLE: 311 notif = onVolumeUnmountable(vol); 312 break; 313 case VolumeInfo.STATE_REMOVED: 314 notif = onVolumeRemoved(vol); 315 break; 316 case VolumeInfo.STATE_BAD_REMOVAL: 317 notif = onVolumeBadRemoval(vol); 318 break; 319 default: 320 notif = null; 321 break; 322 } 323 324 if (notif != null) { 325 mNotificationManager.notifyAsUser(vol.getId(), SystemMessage.NOTE_STORAGE_PUBLIC, 326 notif, UserHandle.of(vol.getMountUserId())); 327 } else { 328 mNotificationManager.cancelAsUser(vol.getId(), SystemMessage.NOTE_STORAGE_PUBLIC, 329 UserHandle.of(vol.getMountUserId())); 330 } 331 } 332 onVolumeUnmounted(VolumeInfo vol)333 private Notification onVolumeUnmounted(VolumeInfo vol) { 334 // Ignored 335 return null; 336 } 337 onVolumeChecking(VolumeInfo vol)338 private Notification onVolumeChecking(VolumeInfo vol) { 339 final DiskInfo disk = vol.getDisk(); 340 final CharSequence title = mContext.getString( 341 R.string.ext_media_checking_notification_title, disk.getDescription()); 342 final CharSequence text = mContext.getString( 343 R.string.ext_media_checking_notification_message, disk.getDescription()); 344 345 return buildNotificationBuilder(vol, title, text) 346 .setCategory(Notification.CATEGORY_PROGRESS) 347 .setOngoing(true) 348 .build(); 349 } 350 onVolumeMounted(VolumeInfo vol)351 private Notification onVolumeMounted(VolumeInfo vol) { 352 final VolumeRecord rec = mStorageManager.findRecordByUuid(vol.getFsUuid()); 353 final DiskInfo disk = vol.getDisk(); 354 355 // Don't annoy when user dismissed in past. (But make sure the disk is adoptable; we 356 // used to allow snoozing non-adoptable disks too.) 357 if (rec.isSnoozed() && disk.isAdoptable()) { 358 return null; 359 } 360 361 if (disk.isAdoptable() && !rec.isInited()) { 362 final CharSequence title = disk.getDescription(); 363 final CharSequence text = mContext.getString( 364 R.string.ext_media_new_notification_message, disk.getDescription()); 365 366 final PendingIntent initIntent = buildInitPendingIntent(vol); 367 return buildNotificationBuilder(vol, title, text) 368 .addAction(new Action(R.drawable.ic_settings_24dp, 369 mContext.getString(R.string.ext_media_init_action), initIntent)) 370 .addAction(new Action(R.drawable.ic_eject_24dp, 371 mContext.getString(R.string.ext_media_unmount_action), 372 buildUnmountPendingIntent(vol))) 373 .setContentIntent(initIntent) 374 .setDeleteIntent(buildSnoozeIntent(vol.getFsUuid())) 375 .build(); 376 377 } else { 378 final CharSequence title = disk.getDescription(); 379 final CharSequence text = mContext.getString( 380 R.string.ext_media_ready_notification_message, disk.getDescription()); 381 382 final PendingIntent browseIntent = buildBrowsePendingIntent(vol); 383 final Notification.Builder builder = buildNotificationBuilder(vol, title, text) 384 .addAction(new Action(R.drawable.ic_folder_24dp, 385 mContext.getString(R.string.ext_media_browse_action), 386 browseIntent)) 387 .addAction(new Action(R.drawable.ic_eject_24dp, 388 mContext.getString(R.string.ext_media_unmount_action), 389 buildUnmountPendingIntent(vol))) 390 .setContentIntent(browseIntent) 391 .setCategory(Notification.CATEGORY_SYSTEM); 392 // Non-adoptable disks can't be snoozed. 393 if (disk.isAdoptable()) { 394 builder.setDeleteIntent(buildSnoozeIntent(vol.getFsUuid())); 395 } 396 397 return builder.build(); 398 } 399 } 400 onVolumeFormatting(VolumeInfo vol)401 private Notification onVolumeFormatting(VolumeInfo vol) { 402 // Ignored 403 return null; 404 } 405 onVolumeEjecting(VolumeInfo vol)406 private Notification onVolumeEjecting(VolumeInfo vol) { 407 final DiskInfo disk = vol.getDisk(); 408 final CharSequence title = mContext.getString( 409 R.string.ext_media_unmounting_notification_title, disk.getDescription()); 410 final CharSequence text = mContext.getString( 411 R.string.ext_media_unmounting_notification_message, disk.getDescription()); 412 413 return buildNotificationBuilder(vol, title, text) 414 .setCategory(Notification.CATEGORY_PROGRESS) 415 .setOngoing(true) 416 .build(); 417 } 418 onVolumeUnmountable(VolumeInfo vol)419 private Notification onVolumeUnmountable(VolumeInfo vol) { 420 final DiskInfo disk = vol.getDisk(); 421 final CharSequence title = mContext.getString( 422 R.string.ext_media_unmountable_notification_title, disk.getDescription()); 423 final CharSequence text = mContext.getString( 424 R.string.ext_media_unmountable_notification_message, disk.getDescription()); 425 426 return buildNotificationBuilder(vol, title, text) 427 .setContentIntent(buildInitPendingIntent(vol)) 428 .setCategory(Notification.CATEGORY_ERROR) 429 .build(); 430 } 431 onVolumeRemoved(VolumeInfo vol)432 private Notification onVolumeRemoved(VolumeInfo vol) { 433 if (!vol.isPrimary()) { 434 // Ignore non-primary media 435 return null; 436 } 437 438 final DiskInfo disk = vol.getDisk(); 439 final CharSequence title = mContext.getString( 440 R.string.ext_media_nomedia_notification_title, disk.getDescription()); 441 final CharSequence text = mContext.getString( 442 R.string.ext_media_nomedia_notification_message, disk.getDescription()); 443 444 return buildNotificationBuilder(vol, title, text) 445 .setCategory(Notification.CATEGORY_ERROR) 446 .build(); 447 } 448 onVolumeBadRemoval(VolumeInfo vol)449 private Notification onVolumeBadRemoval(VolumeInfo vol) { 450 if (!vol.isPrimary()) { 451 // Ignore non-primary media 452 return null; 453 } 454 455 final DiskInfo disk = vol.getDisk(); 456 final CharSequence title = mContext.getString( 457 R.string.ext_media_badremoval_notification_title, disk.getDescription()); 458 final CharSequence text = mContext.getString( 459 R.string.ext_media_badremoval_notification_message, disk.getDescription()); 460 461 return buildNotificationBuilder(vol, title, text) 462 .setCategory(Notification.CATEGORY_ERROR) 463 .build(); 464 } 465 onMoveProgress(MoveInfo move, int status, long estMillis)466 private void onMoveProgress(MoveInfo move, int status, long estMillis) { 467 final CharSequence title; 468 if (!TextUtils.isEmpty(move.label)) { 469 title = mContext.getString(R.string.ext_media_move_specific_title, move.label); 470 } else { 471 title = mContext.getString(R.string.ext_media_move_title); 472 } 473 474 final CharSequence text; 475 if (estMillis < 0) { 476 text = null; 477 } else { 478 text = DateUtils.formatDuration(estMillis); 479 } 480 481 final PendingIntent intent; 482 if (move.packageName != null) { 483 intent = buildWizardMovePendingIntent(move); 484 } else { 485 intent = buildWizardMigratePendingIntent(move); 486 } 487 488 Notification.Builder builder = 489 new Notification.Builder(mContext, NotificationChannels.STORAGE) 490 .setSmallIcon(R.drawable.ic_sd_card_48dp) 491 .setColor(mContext.getColor(R.color.system_notification_accent_color)) 492 .setContentTitle(title) 493 .setContentText(text) 494 .setContentIntent(intent) 495 .setStyle(new Notification.BigTextStyle().bigText(text)) 496 .setVisibility(Notification.VISIBILITY_PUBLIC) 497 .setLocalOnly(true) 498 .setCategory(Notification.CATEGORY_PROGRESS) 499 .setProgress(100, status, false) 500 .setOngoing(true); 501 SystemUI.overrideNotificationAppName(mContext, builder, false); 502 503 mNotificationManager.notifyAsUser(move.packageName, SystemMessage.NOTE_STORAGE_MOVE, 504 builder.build(), UserHandle.ALL); 505 } 506 onMoveFinished(MoveInfo move, int status)507 private void onMoveFinished(MoveInfo move, int status) { 508 if (move.packageName != null) { 509 // We currently ignore finished app moves; just clear the last 510 // published progress 511 mNotificationManager.cancelAsUser(move.packageName, SystemMessage.NOTE_STORAGE_MOVE, 512 UserHandle.ALL); 513 return; 514 } 515 516 final VolumeInfo privateVol = mContext.getPackageManager().getPrimaryStorageCurrentVolume(); 517 final String descrip = mStorageManager.getBestVolumeDescription(privateVol); 518 519 final CharSequence title; 520 final CharSequence text; 521 if (status == PackageManager.MOVE_SUCCEEDED) { 522 title = mContext.getString(R.string.ext_media_move_success_title); 523 text = mContext.getString(R.string.ext_media_move_success_message, descrip); 524 } else { 525 title = mContext.getString(R.string.ext_media_move_failure_title); 526 text = mContext.getString(R.string.ext_media_move_failure_message); 527 } 528 529 // Jump back into the wizard flow if we moved to a real disk 530 final PendingIntent intent; 531 if (privateVol != null && privateVol.getDisk() != null) { 532 intent = buildWizardReadyPendingIntent(privateVol.getDisk()); 533 } else if (privateVol != null) { 534 intent = buildVolumeSettingsPendingIntent(privateVol); 535 } else { 536 intent = null; 537 } 538 539 Notification.Builder builder = 540 new Notification.Builder(mContext, NotificationChannels.STORAGE) 541 .setSmallIcon(R.drawable.ic_sd_card_48dp) 542 .setColor(mContext.getColor(R.color.system_notification_accent_color)) 543 .setContentTitle(title) 544 .setContentText(text) 545 .setContentIntent(intent) 546 .setStyle(new Notification.BigTextStyle().bigText(text)) 547 .setVisibility(Notification.VISIBILITY_PUBLIC) 548 .setLocalOnly(true) 549 .setCategory(Notification.CATEGORY_SYSTEM) 550 .setAutoCancel(true); 551 SystemUI.overrideNotificationAppName(mContext, builder, false); 552 553 mNotificationManager.notifyAsUser(move.packageName, SystemMessage.NOTE_STORAGE_MOVE, 554 builder.build(), UserHandle.ALL); 555 } 556 getSmallIcon(DiskInfo disk, int state)557 private int getSmallIcon(DiskInfo disk, int state) { 558 if (disk.isSd()) { 559 switch (state) { 560 case VolumeInfo.STATE_CHECKING: 561 case VolumeInfo.STATE_EJECTING: 562 return R.drawable.ic_sd_card_48dp; 563 default: 564 return R.drawable.ic_sd_card_48dp; 565 } 566 } else if (disk.isUsb()) { 567 return R.drawable.ic_usb_48dp; 568 } else { 569 return R.drawable.ic_sd_card_48dp; 570 } 571 } 572 buildNotificationBuilder(VolumeInfo vol, CharSequence title, CharSequence text)573 private Notification.Builder buildNotificationBuilder(VolumeInfo vol, CharSequence title, 574 CharSequence text) { 575 Notification.Builder builder = 576 new Notification.Builder(mContext, NotificationChannels.STORAGE) 577 .setSmallIcon(getSmallIcon(vol.getDisk(), vol.getState())) 578 .setColor(mContext.getColor(R.color.system_notification_accent_color)) 579 .setContentTitle(title) 580 .setContentText(text) 581 .setStyle(new Notification.BigTextStyle().bigText(text)) 582 .setVisibility(Notification.VISIBILITY_PUBLIC) 583 .setLocalOnly(true) 584 .extend(new Notification.TvExtender()); 585 overrideNotificationAppName(mContext, builder, false); 586 return builder; 587 } 588 buildInitPendingIntent(DiskInfo disk)589 private PendingIntent buildInitPendingIntent(DiskInfo disk) { 590 final Intent intent = new Intent(); 591 if (isTv()) { 592 intent.setPackage("com.android.tv.settings"); 593 intent.setAction("com.android.tv.settings.action.NEW_STORAGE"); 594 } else { 595 intent.setClassName("com.android.settings", 596 "com.android.settings.deviceinfo.StorageWizardInit"); 597 } 598 intent.putExtra(DiskInfo.EXTRA_DISK_ID, disk.getId()); 599 600 final int requestKey = disk.getId().hashCode(); 601 return PendingIntent.getActivityAsUser(mContext, requestKey, intent, 602 PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); 603 } 604 buildInitPendingIntent(VolumeInfo vol)605 private PendingIntent buildInitPendingIntent(VolumeInfo vol) { 606 final Intent intent = new Intent(); 607 if (isTv()) { 608 intent.setPackage("com.android.tv.settings"); 609 intent.setAction("com.android.tv.settings.action.NEW_STORAGE"); 610 } else { 611 intent.setClassName("com.android.settings", 612 "com.android.settings.deviceinfo.StorageWizardInit"); 613 } 614 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); 615 616 final int requestKey = vol.getId().hashCode(); 617 return PendingIntent.getActivityAsUser(mContext, requestKey, intent, 618 PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); 619 } 620 buildUnmountPendingIntent(VolumeInfo vol)621 private PendingIntent buildUnmountPendingIntent(VolumeInfo vol) { 622 final Intent intent = new Intent(); 623 if (isTv()) { 624 intent.setPackage("com.android.tv.settings"); 625 intent.setAction("com.android.tv.settings.action.UNMOUNT_STORAGE"); 626 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); 627 628 final int requestKey = vol.getId().hashCode(); 629 return PendingIntent.getActivityAsUser(mContext, requestKey, intent, 630 PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); 631 } else if (isAutomotive()) { 632 intent.setClassName("com.android.car.settings", 633 "com.android.car.settings.storage.StorageUnmountReceiver"); 634 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); 635 636 final int requestKey = vol.getId().hashCode(); 637 return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent, 638 PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT); 639 } else { 640 intent.setClassName("com.android.settings", 641 "com.android.settings.deviceinfo.StorageUnmountReceiver"); 642 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); 643 644 final int requestKey = vol.getId().hashCode(); 645 return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent, 646 PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT); 647 } 648 } 649 buildBrowsePendingIntent(VolumeInfo vol)650 private PendingIntent buildBrowsePendingIntent(VolumeInfo vol) { 651 final StrictMode.VmPolicy oldPolicy = StrictMode.allowVmViolations(); 652 try { 653 final Intent intent = vol.buildBrowseIntentForUser(vol.getMountUserId()); 654 655 final int requestKey = vol.getId().hashCode(); 656 return PendingIntent.getActivityAsUser(mContext, requestKey, intent, 657 PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); 658 } finally { 659 StrictMode.setVmPolicy(oldPolicy); 660 } 661 } 662 buildVolumeSettingsPendingIntent(VolumeInfo vol)663 private PendingIntent buildVolumeSettingsPendingIntent(VolumeInfo vol) { 664 final Intent intent = new Intent(); 665 if (isTv()) { 666 intent.setPackage("com.android.tv.settings"); 667 intent.setAction(Settings.ACTION_INTERNAL_STORAGE_SETTINGS); 668 } else { 669 switch (vol.getType()) { 670 case VolumeInfo.TYPE_PRIVATE: 671 intent.setClassName("com.android.settings", 672 "com.android.settings.Settings$PrivateVolumeSettingsActivity"); 673 break; 674 case VolumeInfo.TYPE_PUBLIC: 675 intent.setClassName("com.android.settings", 676 "com.android.settings.Settings$PublicVolumeSettingsActivity"); 677 break; 678 default: 679 return null; 680 } 681 } 682 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); 683 684 final int requestKey = vol.getId().hashCode(); 685 return PendingIntent.getActivityAsUser(mContext, requestKey, intent, 686 PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); 687 } 688 buildSnoozeIntent(String fsUuid)689 private PendingIntent buildSnoozeIntent(String fsUuid) { 690 final Intent intent = new Intent(ACTION_SNOOZE_VOLUME); 691 intent.putExtra(VolumeRecord.EXTRA_FS_UUID, fsUuid); 692 693 final int requestKey = fsUuid.hashCode(); 694 return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent, 695 PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT); 696 } 697 buildForgetPendingIntent(VolumeRecord rec)698 private PendingIntent buildForgetPendingIntent(VolumeRecord rec) { 699 // Not used on TV 700 final Intent intent = new Intent(); 701 intent.setClassName("com.android.settings", 702 "com.android.settings.Settings$PrivateVolumeForgetActivity"); 703 intent.putExtra(VolumeRecord.EXTRA_FS_UUID, rec.getFsUuid()); 704 705 final int requestKey = rec.getFsUuid().hashCode(); 706 return PendingIntent.getActivityAsUser(mContext, requestKey, intent, 707 PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); 708 } 709 buildWizardMigratePendingIntent(MoveInfo move)710 private PendingIntent buildWizardMigratePendingIntent(MoveInfo move) { 711 final Intent intent = new Intent(); 712 if (isTv()) { 713 intent.setPackage("com.android.tv.settings"); 714 intent.setAction("com.android.tv.settings.action.MIGRATE_STORAGE"); 715 } else { 716 intent.setClassName("com.android.settings", 717 "com.android.settings.deviceinfo.StorageWizardMigrateProgress"); 718 } 719 intent.putExtra(PackageManager.EXTRA_MOVE_ID, move.moveId); 720 721 final VolumeInfo vol = mStorageManager.findVolumeByQualifiedUuid(move.volumeUuid); 722 if (vol != null) { 723 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); 724 } 725 return PendingIntent.getActivityAsUser(mContext, move.moveId, intent, 726 PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); 727 } 728 buildWizardMovePendingIntent(MoveInfo move)729 private PendingIntent buildWizardMovePendingIntent(MoveInfo move) { 730 final Intent intent = new Intent(); 731 if (isTv()) { 732 intent.setPackage("com.android.tv.settings"); 733 intent.setAction("com.android.tv.settings.action.MOVE_APP"); 734 } else { 735 intent.setClassName("com.android.settings", 736 "com.android.settings.deviceinfo.StorageWizardMoveProgress"); 737 } 738 intent.putExtra(PackageManager.EXTRA_MOVE_ID, move.moveId); 739 740 return PendingIntent.getActivityAsUser(mContext, move.moveId, intent, 741 PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); 742 } 743 buildWizardReadyPendingIntent(DiskInfo disk)744 private PendingIntent buildWizardReadyPendingIntent(DiskInfo disk) { 745 final Intent intent = new Intent(); 746 if (isTv()) { 747 intent.setPackage("com.android.tv.settings"); 748 intent.setAction(Settings.ACTION_INTERNAL_STORAGE_SETTINGS); 749 } else { 750 intent.setClassName("com.android.settings", 751 "com.android.settings.deviceinfo.StorageWizardReady"); 752 } 753 intent.putExtra(DiskInfo.EXTRA_DISK_ID, disk.getId()); 754 755 final int requestKey = disk.getId().hashCode(); 756 return PendingIntent.getActivityAsUser(mContext, requestKey, intent, 757 PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); 758 } 759 isAutomotive()760 private boolean isAutomotive() { 761 PackageManager packageManager = mContext.getPackageManager(); 762 return packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); 763 } 764 isTv()765 private boolean isTv() { 766 PackageManager packageManager = mContext.getPackageManager(); 767 return packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK); 768 } 769 } 770