1 /*
2  * Copyright (C) 2017 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 package com.android.documentsui;
17 
18 import androidx.annotation.DrawableRes;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.pm.ShortcutInfo;
22 import android.content.pm.ShortcutManager;
23 import android.graphics.drawable.Icon;
24 
25 import com.android.documentsui.R;
26 import com.android.documentsui.base.Providers;
27 import com.android.documentsui.base.RootInfo;
28 import com.android.documentsui.base.Shared;
29 import com.android.documentsui.files.FilesActivity;
30 import com.android.documentsui.prefs.ScopedPreferences;
31 
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37 
38 /**
39  * Manages dynamic shortcuts.
40  */
41 public final class ShortcutsUpdater {
42 
43     private final ScopedPreferences mPrefs;
44     private final Context mContext;
45 
ShortcutsUpdater(Context context, ScopedPreferences prefs)46     public ShortcutsUpdater(Context context, ScopedPreferences prefs) {
47         mContext = context;
48         mPrefs = prefs;
49     }
50 
update(Collection<RootInfo> roots)51     public void update(Collection<RootInfo> roots) {
52         if (!Shared.isLauncherEnabled(mContext)) {
53             return;
54         }
55 
56         ShortcutManager mgr = mContext.getSystemService(ShortcutManager.class);
57 
58         Map<String, ShortcutInfo> existing = getPinnedShortcuts(mgr);
59         List<ShortcutInfo> devices = getDeviceShortcuts(roots);
60         List<String> deviceIds = new ArrayList<>();
61         for (ShortcutInfo s : devices) {
62             deviceIds.add(s.getId());
63         }
64 
65         mgr.setDynamicShortcuts(devices.subList(0, getNumDynSlots(mgr, devices.size())));
66 
67         // Mark any shortcut that doesn't correspond to a current root as disabled.
68         List<String> disabled = new ArrayList<>();
69         for (String id : existing.keySet()) {
70             // If it isn't in candidates, it isn't a live target, so we disable it.
71             if (!deviceIds.contains(id)) {
72                 disabled.add(id);
73             }
74         }
75 
76         mgr.enableShortcuts(deviceIds);
77         mgr.disableShortcuts(disabled);
78     }
79 
80     /**
81      * Return at most four awesome devices/roots to include as dynamic shortcuts.
82      */
getDeviceShortcuts(Collection<RootInfo> roots)83     private List<ShortcutInfo> getDeviceShortcuts(Collection<RootInfo> roots) {
84         List<ShortcutInfo> devices = new ArrayList<>();
85         for (RootInfo root : roots) {
86             String id = root.getUri().toString();
87             // TODO: Hook up third party providers. For now, there may be dupes when
88             // user has multiple accounts installed, and the plain title doesn't
89             // disambiguate for the user. So, we don't add them.
90             // if (!Providers.isSystemProvider(root.authority)) {
91             //    // add third party providers at the beginning of the list.
92             //    devices.add(createShortcut(root, R.drawable.ic_folder_shortcut));
93             // } else
94             if (root.isAdvanced() && root.authority.equals(Providers.AUTHORITY_STORAGE)) {
95                 // internal storage
96                 if (mPrefs.getShowDeviceRoot()) {
97                     devices.add(0, createShortcut(root, R.drawable.ic_advanced_shortcut));
98                 }
99             } else if (root.isAdvanced()) {
100                 // probably just bugreports provider
101                 devices.add(0, createShortcut(root, R.drawable.ic_folder_shortcut));
102             }
103             // TODO: Hook up USB and MTP devices. In order to do this we need
104             // to fire up a broadcast to listen for ACTION_MEDIA_MOUNTED
105             // and ACTION_MEDIA_REMOVED. But doing so now would require a good
106             // bit of refactoring, rendering out of scope for now. <sadface>.
107             // else if (root.isUsb() || root.isMtp()) {
108             //    // probably just bugreports provider
109             //    devices.add(0, createShortcut(root, R.drawable.ic_usb_shortcut));
110             // }
111         }
112 
113         return devices;
114     }
115 
getPinnedShortcuts(ShortcutManager mgr)116     private Map<String, ShortcutInfo> getPinnedShortcuts(ShortcutManager mgr) {
117         Map<String, ShortcutInfo> pinned = new HashMap<>();
118         for (ShortcutInfo s : mgr.getDynamicShortcuts()) {
119             pinned.put(s.getId(), s);
120         }
121         return pinned;
122     }
123 
getNumDynSlots(ShortcutManager mgr, int numDevices)124     private int getNumDynSlots(ShortcutManager mgr, int numDevices) {
125         int slots = mgr.getMaxShortcutCountPerActivity() - mgr.getManifestShortcuts().size();
126         return numDevices >= slots ? slots : numDevices;
127     }
128 
createShortcut(RootInfo root, @DrawableRes int resId)129     private ShortcutInfo createShortcut(RootInfo root, @DrawableRes int resId) {
130         Intent intent = new Intent(mContext, FilesActivity.class);
131         intent.setAction(Intent.ACTION_VIEW);
132         intent.setData(root.getUri());
133 
134         return new ShortcutInfo.Builder(mContext, root.getUri().toString())
135                 .setShortLabel(root.title)
136                 .setIcon(Icon.createWithResource(mContext, resId))
137                 .setIntent(intent)
138                 .build();
139     }
140 }
141