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
17package com.android.systemui.usb;
18
19import android.app.Notification;
20import android.app.Notification.Action;
21import android.app.NotificationManager;
22import android.app.PendingIntent;
23import android.content.BroadcastReceiver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.pm.PackageManager;
28import android.content.pm.PackageManager.MoveCallback;
29import android.os.Bundle;
30import android.os.Handler;
31import android.os.UserHandle;
32import android.os.storage.DiskInfo;
33import android.os.storage.StorageEventListener;
34import android.os.storage.StorageManager;
35import android.os.storage.VolumeInfo;
36import android.os.storage.VolumeRecord;
37import android.text.TextUtils;
38import android.text.format.DateUtils;
39import android.util.Log;
40import android.util.SparseArray;
41
42import com.android.internal.R;
43import com.android.systemui.SystemUI;
44
45import java.util.List;
46
47public class StorageNotification extends SystemUI {
48    private static final String TAG = "StorageNotification";
49
50    private static final int PUBLIC_ID = 0x53505542; // SPUB
51    private static final int PRIVATE_ID = 0x53505256; // SPRV
52    private static final int DISK_ID = 0x5344534b; // SDSK
53    private static final int MOVE_ID = 0x534d4f56; // SMOV
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, PRIVATE_ID, UserHandle.ALL);
94        }
95
96        @Override
97        public void onDiskScanned(DiskInfo disk, int volumeCount) {
98            onDiskScannedInternal(disk, volumeCount);
99        }
100    };
101
102    private final BroadcastReceiver mSnoozeReceiver = new BroadcastReceiver() {
103        @Override
104        public void onReceive(Context context, Intent intent) {
105            // TODO: kick this onto background thread
106            final String fsUuid = intent.getStringExtra(VolumeRecord.EXTRA_FS_UUID);
107            mStorageManager.setVolumeSnoozed(fsUuid, true);
108        }
109    };
110
111    private final BroadcastReceiver mFinishReceiver = new BroadcastReceiver() {
112        @Override
113        public void onReceive(Context context, Intent intent) {
114            // When finishing the adoption wizard, clean up any notifications
115            // for moving primary storage
116            mNotificationManager.cancelAsUser(null, MOVE_ID, UserHandle.ALL);
117        }
118    };
119
120    private final MoveCallback mMoveCallback = new MoveCallback() {
121        @Override
122        public void onCreated(int moveId, Bundle extras) {
123            final MoveInfo move = new MoveInfo();
124            move.moveId = moveId;
125            move.extras = extras;
126            if (extras != null) {
127                move.packageName = extras.getString(Intent.EXTRA_PACKAGE_NAME);
128                move.label = extras.getString(Intent.EXTRA_TITLE);
129                move.volumeUuid = extras.getString(VolumeRecord.EXTRA_FS_UUID);
130            }
131            mMoves.put(moveId, move);
132        }
133
134        @Override
135        public void onStatusChanged(int moveId, int status, long estMillis) {
136            final MoveInfo move = mMoves.get(moveId);
137            if (move == null) {
138                Log.w(TAG, "Ignoring unknown move " + moveId);
139                return;
140            }
141
142            if (PackageManager.isMoveStatusFinished(status)) {
143                onMoveFinished(move, status);
144            } else {
145                onMoveProgress(move, status, estMillis);
146            }
147        }
148    };
149
150    @Override
151    public void start() {
152        mNotificationManager = mContext.getSystemService(NotificationManager.class);
153
154        mStorageManager = mContext.getSystemService(StorageManager.class);
155        mStorageManager.registerListener(mListener);
156
157        mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME),
158                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);
159        mContext.registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_WIZARD),
160                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);
161
162        // Kick current state into place
163        final List<DiskInfo> disks = mStorageManager.getDisks();
164        for (DiskInfo disk : disks) {
165            onDiskScannedInternal(disk, disk.volumeCount);
166        }
167
168        final List<VolumeInfo> vols = mStorageManager.getVolumes();
169        for (VolumeInfo vol : vols) {
170            onVolumeStateChangedInternal(vol);
171        }
172
173        mContext.getPackageManager().registerMoveCallback(mMoveCallback, new Handler());
174
175        updateMissingPrivateVolumes();
176    }
177
178    private void updateMissingPrivateVolumes() {
179        final List<VolumeRecord> recs = mStorageManager.getVolumeRecords();
180        for (VolumeRecord rec : recs) {
181            if (rec.getType() != VolumeInfo.TYPE_PRIVATE) continue;
182
183            final String fsUuid = rec.getFsUuid();
184            final VolumeInfo info = mStorageManager.findVolumeByUuid(fsUuid);
185            if ((info != null && info.isMountedWritable()) || rec.isSnoozed()) {
186                // Yay, private volume is here, or user snoozed
187                mNotificationManager.cancelAsUser(fsUuid, PRIVATE_ID, UserHandle.ALL);
188
189            } else {
190                // Boo, annoy the user to reinsert the private volume
191                final CharSequence title = mContext.getString(R.string.ext_media_missing_title,
192                        rec.getNickname());
193                final CharSequence text = mContext.getString(R.string.ext_media_missing_message);
194
195                final Notification notif = new Notification.Builder(mContext)
196                        .setSmallIcon(R.drawable.ic_sd_card_48dp)
197                        .setColor(mContext.getColor(R.color.system_notification_accent_color))
198                        .setContentTitle(title)
199                        .setContentText(text)
200                        .setContentIntent(buildForgetPendingIntent(rec))
201                        .setStyle(new Notification.BigTextStyle().bigText(text))
202                        .setVisibility(Notification.VISIBILITY_PUBLIC)
203                        .setLocalOnly(true)
204                        .setCategory(Notification.CATEGORY_SYSTEM)
205                        .setDeleteIntent(buildSnoozeIntent(fsUuid))
206                        .build();
207
208                mNotificationManager.notifyAsUser(fsUuid, PRIVATE_ID, notif, UserHandle.ALL);
209            }
210        }
211    }
212
213    private void onDiskScannedInternal(DiskInfo disk, int volumeCount) {
214        if (volumeCount == 0 && disk.size > 0) {
215            // No supported volumes found, give user option to format
216            final CharSequence title = mContext.getString(
217                    R.string.ext_media_unsupported_notification_title, disk.getDescription());
218            final CharSequence text = mContext.getString(
219                    R.string.ext_media_unsupported_notification_message, disk.getDescription());
220
221            final Notification notif = new Notification.Builder(mContext)
222                    .setSmallIcon(getSmallIcon(disk, VolumeInfo.STATE_UNMOUNTABLE))
223                    .setColor(mContext.getColor(R.color.system_notification_accent_color))
224                    .setContentTitle(title)
225                    .setContentText(text)
226                    .setContentIntent(buildInitPendingIntent(disk))
227                    .setStyle(new Notification.BigTextStyle().bigText(text))
228                    .setVisibility(Notification.VISIBILITY_PUBLIC)
229                    .setLocalOnly(true)
230                    .setCategory(Notification.CATEGORY_ERROR)
231                    .build();
232
233            mNotificationManager.notifyAsUser(disk.getId(), DISK_ID, notif, UserHandle.ALL);
234
235        } else {
236            // Yay, we have volumes!
237            mNotificationManager.cancelAsUser(disk.getId(), DISK_ID, UserHandle.ALL);
238        }
239    }
240
241    private void onVolumeStateChangedInternal(VolumeInfo vol) {
242        switch (vol.getType()) {
243            case VolumeInfo.TYPE_PRIVATE:
244                onPrivateVolumeStateChangedInternal(vol);
245                break;
246            case VolumeInfo.TYPE_PUBLIC:
247                onPublicVolumeStateChangedInternal(vol);
248                break;
249        }
250    }
251
252    private void onPrivateVolumeStateChangedInternal(VolumeInfo vol) {
253        Log.d(TAG, "Notifying about private volume: " + vol.toString());
254
255        updateMissingPrivateVolumes();
256    }
257
258    private void onPublicVolumeStateChangedInternal(VolumeInfo vol) {
259        Log.d(TAG, "Notifying about public volume: " + vol.toString());
260
261        final Notification notif;
262        switch (vol.getState()) {
263            case VolumeInfo.STATE_UNMOUNTED:
264                notif = onVolumeUnmounted(vol);
265                break;
266            case VolumeInfo.STATE_CHECKING:
267                notif = onVolumeChecking(vol);
268                break;
269            case VolumeInfo.STATE_MOUNTED:
270            case VolumeInfo.STATE_MOUNTED_READ_ONLY:
271                notif = onVolumeMounted(vol);
272                break;
273            case VolumeInfo.STATE_FORMATTING:
274                notif = onVolumeFormatting(vol);
275                break;
276            case VolumeInfo.STATE_EJECTING:
277                notif = onVolumeEjecting(vol);
278                break;
279            case VolumeInfo.STATE_UNMOUNTABLE:
280                notif = onVolumeUnmountable(vol);
281                break;
282            case VolumeInfo.STATE_REMOVED:
283                notif = onVolumeRemoved(vol);
284                break;
285            case VolumeInfo.STATE_BAD_REMOVAL:
286                notif = onVolumeBadRemoval(vol);
287                break;
288            default:
289                notif = null;
290                break;
291        }
292
293        if (notif != null) {
294            mNotificationManager.notifyAsUser(vol.getId(), PUBLIC_ID, notif, UserHandle.ALL);
295        } else {
296            mNotificationManager.cancelAsUser(vol.getId(), PUBLIC_ID, UserHandle.ALL);
297        }
298    }
299
300    private Notification onVolumeUnmounted(VolumeInfo vol) {
301        // Ignored
302        return null;
303    }
304
305    private Notification onVolumeChecking(VolumeInfo vol) {
306        final DiskInfo disk = vol.getDisk();
307        final CharSequence title = mContext.getString(
308                R.string.ext_media_checking_notification_title, disk.getDescription());
309        final CharSequence text = mContext.getString(
310                R.string.ext_media_checking_notification_message, disk.getDescription());
311
312        return buildNotificationBuilder(vol, title, text)
313                .setCategory(Notification.CATEGORY_PROGRESS)
314                .setPriority(Notification.PRIORITY_LOW)
315                .setOngoing(true)
316                .build();
317    }
318
319    private Notification onVolumeMounted(VolumeInfo vol) {
320        final VolumeRecord rec = mStorageManager.findRecordByUuid(vol.getFsUuid());
321        final DiskInfo disk = vol.getDisk();
322
323        // Don't annoy when user dismissed in past.  (But make sure the disk is adoptable; we
324        // used to allow snoozing non-adoptable disks too.)
325        if (rec.isSnoozed() && disk.isAdoptable()) {
326            return null;
327        }
328
329        if (disk.isAdoptable() && !rec.isInited()) {
330            final CharSequence title = disk.getDescription();
331            final CharSequence text = mContext.getString(
332                    R.string.ext_media_new_notification_message, disk.getDescription());
333
334            final PendingIntent initIntent = buildInitPendingIntent(vol);
335            return buildNotificationBuilder(vol, title, text)
336                    .addAction(new Action(R.drawable.ic_settings_24dp,
337                            mContext.getString(R.string.ext_media_init_action), initIntent))
338                    .addAction(new Action(R.drawable.ic_eject_24dp,
339                            mContext.getString(R.string.ext_media_unmount_action),
340                            buildUnmountPendingIntent(vol)))
341                    .setContentIntent(initIntent)
342                    .setDeleteIntent(buildSnoozeIntent(vol.getFsUuid()))
343                    .setCategory(Notification.CATEGORY_SYSTEM)
344                    .build();
345
346        } else {
347            final CharSequence title = disk.getDescription();
348            final CharSequence text = mContext.getString(
349                    R.string.ext_media_ready_notification_message, disk.getDescription());
350
351            final PendingIntent browseIntent = buildBrowsePendingIntent(vol);
352            final Notification.Builder builder = buildNotificationBuilder(vol, title, text)
353                    .addAction(new Action(R.drawable.ic_folder_24dp,
354                            mContext.getString(R.string.ext_media_browse_action),
355                            browseIntent))
356                    .addAction(new Action(R.drawable.ic_eject_24dp,
357                            mContext.getString(R.string.ext_media_unmount_action),
358                            buildUnmountPendingIntent(vol)))
359                    .setContentIntent(browseIntent)
360                    .setCategory(Notification.CATEGORY_SYSTEM)
361                    .setPriority(Notification.PRIORITY_LOW);
362            // Non-adoptable disks can't be snoozed.
363            if (disk.isAdoptable()) {
364                builder.setDeleteIntent(buildSnoozeIntent(vol.getFsUuid()));
365            }
366
367            return builder.build();
368        }
369    }
370
371    private Notification onVolumeFormatting(VolumeInfo vol) {
372        // Ignored
373        return null;
374    }
375
376    private Notification onVolumeEjecting(VolumeInfo vol) {
377        final DiskInfo disk = vol.getDisk();
378        final CharSequence title = mContext.getString(
379                R.string.ext_media_unmounting_notification_title, disk.getDescription());
380        final CharSequence text = mContext.getString(
381                R.string.ext_media_unmounting_notification_message, disk.getDescription());
382
383        return buildNotificationBuilder(vol, title, text)
384                .setCategory(Notification.CATEGORY_PROGRESS)
385                .setPriority(Notification.PRIORITY_LOW)
386                .setOngoing(true)
387                .build();
388    }
389
390    private Notification onVolumeUnmountable(VolumeInfo vol) {
391        final DiskInfo disk = vol.getDisk();
392        final CharSequence title = mContext.getString(
393                R.string.ext_media_unmountable_notification_title, disk.getDescription());
394        final CharSequence text = mContext.getString(
395                R.string.ext_media_unmountable_notification_message, disk.getDescription());
396
397        return buildNotificationBuilder(vol, title, text)
398                .setContentIntent(buildInitPendingIntent(vol))
399                .setCategory(Notification.CATEGORY_ERROR)
400                .build();
401    }
402
403    private Notification onVolumeRemoved(VolumeInfo vol) {
404        if (!vol.isPrimary()) {
405            // Ignore non-primary media
406            return null;
407        }
408
409        final DiskInfo disk = vol.getDisk();
410        final CharSequence title = mContext.getString(
411                R.string.ext_media_nomedia_notification_title, disk.getDescription());
412        final CharSequence text = mContext.getString(
413                R.string.ext_media_nomedia_notification_message, disk.getDescription());
414
415        return buildNotificationBuilder(vol, title, text)
416                .setCategory(Notification.CATEGORY_ERROR)
417                .build();
418    }
419
420    private Notification onVolumeBadRemoval(VolumeInfo vol) {
421        if (!vol.isPrimary()) {
422            // Ignore non-primary media
423            return null;
424        }
425
426        final DiskInfo disk = vol.getDisk();
427        final CharSequence title = mContext.getString(
428                R.string.ext_media_badremoval_notification_title, disk.getDescription());
429        final CharSequence text = mContext.getString(
430                R.string.ext_media_badremoval_notification_message, disk.getDescription());
431
432        return buildNotificationBuilder(vol, title, text)
433                .setCategory(Notification.CATEGORY_ERROR)
434                .build();
435    }
436
437    private void onMoveProgress(MoveInfo move, int status, long estMillis) {
438        final CharSequence title;
439        if (!TextUtils.isEmpty(move.label)) {
440            title = mContext.getString(R.string.ext_media_move_specific_title, move.label);
441        } else {
442            title = mContext.getString(R.string.ext_media_move_title);
443        }
444
445        final CharSequence text;
446        if (estMillis < 0) {
447            text = null;
448        } else {
449            text = DateUtils.formatDuration(estMillis);
450        }
451
452        final PendingIntent intent;
453        if (move.packageName != null) {
454            intent = buildWizardMovePendingIntent(move);
455        } else {
456            intent = buildWizardMigratePendingIntent(move);
457        }
458
459        final Notification notif = new Notification.Builder(mContext)
460                .setSmallIcon(R.drawable.ic_sd_card_48dp)
461                .setColor(mContext.getColor(R.color.system_notification_accent_color))
462                .setContentTitle(title)
463                .setContentText(text)
464                .setContentIntent(intent)
465                .setStyle(new Notification.BigTextStyle().bigText(text))
466                .setVisibility(Notification.VISIBILITY_PUBLIC)
467                .setLocalOnly(true)
468                .setCategory(Notification.CATEGORY_PROGRESS)
469                .setPriority(Notification.PRIORITY_LOW)
470                .setProgress(100, status, false)
471                .setOngoing(true)
472                .build();
473
474        mNotificationManager.notifyAsUser(move.packageName, MOVE_ID, notif, UserHandle.ALL);
475    }
476
477    private void onMoveFinished(MoveInfo move, int status) {
478        if (move.packageName != null) {
479            // We currently ignore finished app moves; just clear the last
480            // published progress
481            mNotificationManager.cancelAsUser(move.packageName, MOVE_ID, UserHandle.ALL);
482            return;
483        }
484
485        final VolumeInfo privateVol = mContext.getPackageManager().getPrimaryStorageCurrentVolume();
486        final String descrip = mStorageManager.getBestVolumeDescription(privateVol);
487
488        final CharSequence title;
489        final CharSequence text;
490        if (status == PackageManager.MOVE_SUCCEEDED) {
491            title = mContext.getString(R.string.ext_media_move_success_title);
492            text = mContext.getString(R.string.ext_media_move_success_message, descrip);
493        } else {
494            title = mContext.getString(R.string.ext_media_move_failure_title);
495            text = mContext.getString(R.string.ext_media_move_failure_message);
496        }
497
498        // Jump back into the wizard flow if we moved to a real disk
499        final PendingIntent intent;
500        if (privateVol != null && privateVol.getDisk() != null) {
501            intent = buildWizardReadyPendingIntent(privateVol.getDisk());
502        } else if (privateVol != null) {
503            intent = buildVolumeSettingsPendingIntent(privateVol);
504        } else {
505            intent = null;
506        }
507
508        final Notification notif = new Notification.Builder(mContext)
509                .setSmallIcon(R.drawable.ic_sd_card_48dp)
510                .setColor(mContext.getColor(R.color.system_notification_accent_color))
511                .setContentTitle(title)
512                .setContentText(text)
513                .setContentIntent(intent)
514                .setStyle(new Notification.BigTextStyle().bigText(text))
515                .setVisibility(Notification.VISIBILITY_PUBLIC)
516                .setLocalOnly(true)
517                .setCategory(Notification.CATEGORY_SYSTEM)
518                .setPriority(Notification.PRIORITY_LOW)
519                .setAutoCancel(true)
520                .build();
521
522        mNotificationManager.notifyAsUser(move.packageName, MOVE_ID, notif, UserHandle.ALL);
523    }
524
525    private int getSmallIcon(DiskInfo disk, int state) {
526        if (disk.isSd()) {
527            switch (state) {
528                case VolumeInfo.STATE_CHECKING:
529                case VolumeInfo.STATE_EJECTING:
530                    return R.drawable.ic_sd_card_48dp;
531                default:
532                    return R.drawable.ic_sd_card_48dp;
533            }
534        } else if (disk.isUsb()) {
535            return R.drawable.ic_usb_48dp;
536        } else {
537            return R.drawable.ic_sd_card_48dp;
538        }
539    }
540
541    private Notification.Builder buildNotificationBuilder(VolumeInfo vol, CharSequence title,
542            CharSequence text) {
543        return new Notification.Builder(mContext)
544                .setSmallIcon(getSmallIcon(vol.getDisk(), vol.getState()))
545                .setColor(mContext.getColor(R.color.system_notification_accent_color))
546                .setContentTitle(title)
547                .setContentText(text)
548                .setStyle(new Notification.BigTextStyle().bigText(text))
549                .setVisibility(Notification.VISIBILITY_PUBLIC)
550                .setLocalOnly(true);
551    }
552
553    private PendingIntent buildInitPendingIntent(DiskInfo disk) {
554        final Intent intent = new Intent();
555        intent.setClassName("com.android.settings",
556                "com.android.settings.deviceinfo.StorageWizardInit");
557        intent.putExtra(DiskInfo.EXTRA_DISK_ID, disk.getId());
558
559        final int requestKey = disk.getId().hashCode();
560        return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
561                PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
562    }
563
564    private PendingIntent buildInitPendingIntent(VolumeInfo vol) {
565        final Intent intent = new Intent();
566        intent.setClassName("com.android.settings",
567                "com.android.settings.deviceinfo.StorageWizardInit");
568        intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
569
570        final int requestKey = vol.getId().hashCode();
571        return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
572                PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
573    }
574
575    private PendingIntent buildUnmountPendingIntent(VolumeInfo vol) {
576        final Intent intent = new Intent();
577        intent.setClassName("com.android.settings",
578                "com.android.settings.deviceinfo.StorageUnmountReceiver");
579        intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
580
581        final int requestKey = vol.getId().hashCode();
582        return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent,
583                PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT);
584    }
585
586    private PendingIntent buildBrowsePendingIntent(VolumeInfo vol) {
587        final Intent intent = vol.buildBrowseIntent();
588
589        final int requestKey = vol.getId().hashCode();
590        return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
591                PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
592    }
593
594    private PendingIntent buildVolumeSettingsPendingIntent(VolumeInfo vol) {
595        final Intent intent = new Intent();
596        switch (vol.getType()) {
597            case VolumeInfo.TYPE_PRIVATE:
598                intent.setClassName("com.android.settings",
599                        "com.android.settings.Settings$PrivateVolumeSettingsActivity");
600                break;
601            case VolumeInfo.TYPE_PUBLIC:
602                intent.setClassName("com.android.settings",
603                        "com.android.settings.Settings$PublicVolumeSettingsActivity");
604                break;
605            default:
606                return null;
607        }
608        intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
609
610        final int requestKey = vol.getId().hashCode();
611        return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
612                PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
613    }
614
615    private PendingIntent buildSnoozeIntent(String fsUuid) {
616        final Intent intent = new Intent(ACTION_SNOOZE_VOLUME);
617        intent.putExtra(VolumeRecord.EXTRA_FS_UUID, fsUuid);
618
619        final int requestKey = fsUuid.hashCode();
620        return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent,
621                PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT);
622    }
623
624    private PendingIntent buildForgetPendingIntent(VolumeRecord rec) {
625        final Intent intent = new Intent();
626        intent.setClassName("com.android.settings",
627                "com.android.settings.Settings$PrivateVolumeForgetActivity");
628        intent.putExtra(VolumeRecord.EXTRA_FS_UUID, rec.getFsUuid());
629
630        final int requestKey = rec.getFsUuid().hashCode();
631        return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
632                PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
633    }
634
635    private PendingIntent buildWizardMigratePendingIntent(MoveInfo move) {
636        final Intent intent = new Intent();
637        intent.setClassName("com.android.settings",
638                "com.android.settings.deviceinfo.StorageWizardMigrateProgress");
639        intent.putExtra(PackageManager.EXTRA_MOVE_ID, move.moveId);
640
641        final VolumeInfo vol = mStorageManager.findVolumeByQualifiedUuid(move.volumeUuid);
642        intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
643
644        return PendingIntent.getActivityAsUser(mContext, move.moveId, intent,
645                PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
646    }
647
648    private PendingIntent buildWizardMovePendingIntent(MoveInfo move) {
649        final Intent intent = new Intent();
650        intent.setClassName("com.android.settings",
651                "com.android.settings.deviceinfo.StorageWizardMoveProgress");
652        intent.putExtra(PackageManager.EXTRA_MOVE_ID, move.moveId);
653
654        return PendingIntent.getActivityAsUser(mContext, move.moveId, intent,
655                PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
656    }
657
658    private PendingIntent buildWizardReadyPendingIntent(DiskInfo disk) {
659        final Intent intent = new Intent();
660        intent.setClassName("com.android.settings",
661                "com.android.settings.deviceinfo.StorageWizardReady");
662        intent.putExtra(DiskInfo.EXTRA_DISK_ID, disk.getId());
663
664        final int requestKey = disk.getId().hashCode();
665        return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
666                PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
667    }
668}
669