1 /* 2 * Copyright (C) 2016 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.example.android.shortcutsample; 17 18 import android.content.Context; 19 import android.content.Intent; 20 import android.content.pm.ShortcutInfo; 21 import android.content.pm.ShortcutManager; 22 import android.graphics.drawable.Icon; 23 import android.net.Uri; 24 import android.os.AsyncTask; 25 import android.os.PersistableBundle; 26 import android.util.Log; 27 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.HashSet; 31 import java.util.List; 32 import java.util.function.BooleanSupplier; 33 34 public class ShortcutHelper { 35 private static final String TAG = Main.TAG; 36 37 private static final String EXTRA_LAST_REFRESH = 38 "com.example.android.shortcutsample.EXTRA_LAST_REFRESH"; 39 40 private static final long REFRESH_INTERVAL_MS = 60 * 60 * 1000; 41 42 private final Context mContext; 43 44 private final ShortcutManager mShortcutManager; 45 46 public ShortcutHelper(Context context) { 47 mContext = context; 48 mShortcutManager = mContext.getSystemService(ShortcutManager.class); 49 } 50 51 public void maybeRestoreAllDynamicShortcuts() { 52 if (mShortcutManager.getDynamicShortcuts().size() == 0) { 53 // NOTE: If this application is always supposed to have dynamic shortcuts, then publish 54 // them here. 55 // Note when an application is "restored" on a new device, all dynamic shortcuts 56 // will *not* be restored but the pinned shortcuts *will*. 57 } 58 } 59 60 public void reportShortcutUsed(String id) { 61 mShortcutManager.reportShortcutUsed(id); 62 } 63 64 /** 65 * Use this when interacting with ShortcutManager to show consistent error messages. 66 */ 67 private void callShortcutManager(BooleanSupplier r) { 68 try { 69 if (!r.getAsBoolean()) { 70 Utils.showToast(mContext, "Call to ShortcutManager is rate-limited"); 71 } 72 } catch (Exception e) { 73 Log.e(TAG, "Caught Exception", e); 74 Utils.showToast(mContext, "Error while calling ShortcutManager: " + e.toString()); 75 } 76 } 77 78 /** 79 * Return all mutable shortcuts from this app self. 80 */ 81 public List<ShortcutInfo> getShortcuts() { 82 // Load mutable dynamic shortcuts and pinned shortcuts and put them into a single list 83 // removing duplicates. 84 85 final List<ShortcutInfo> ret = new ArrayList<>(); 86 final HashSet<String> seenKeys = new HashSet<>(); 87 88 // Check existing shortcuts shortcuts 89 for (ShortcutInfo shortcut : mShortcutManager.getDynamicShortcuts()) { 90 if (!shortcut.isImmutable()) { 91 ret.add(shortcut); 92 seenKeys.add(shortcut.getId()); 93 } 94 } 95 for (ShortcutInfo shortcut : mShortcutManager.getPinnedShortcuts()) { 96 if (!shortcut.isImmutable() && !seenKeys.contains(shortcut.getId())) { 97 ret.add(shortcut); 98 seenKeys.add(shortcut.getId()); 99 } 100 } 101 return ret; 102 } 103 104 /** 105 * Called when the activity starts. Looks for shortcuts that have been pushed and refreshes 106 * them (but the refresh part isn't implemented yet...). 107 */ 108 public void refreshShortcuts(boolean force) { 109 new AsyncTask<Void, Void, Void>() { 110 @Override 111 protected Void doInBackground(Void... params) { 112 Log.i(TAG, "refreshingShortcuts..."); 113 114 final long now = System.currentTimeMillis(); 115 final long staleThreshold = force ? now : now - REFRESH_INTERVAL_MS; 116 117 // Check all existing dynamic and pinned shortcut, and if their last refresh 118 // time is older than a certain threshold, update them. 119 120 final List<ShortcutInfo> updateList = new ArrayList<>(); 121 122 for (ShortcutInfo shortcut : getShortcuts()) { 123 if (shortcut.isImmutable()) { 124 continue; 125 } 126 127 final PersistableBundle extras = shortcut.getExtras(); 128 if (extras != null && extras.getLong(EXTRA_LAST_REFRESH) >= staleThreshold) { 129 // Shortcut still fresh. 130 continue; 131 } 132 Log.i(TAG, "Refreshing shortcut: " + shortcut.getId()); 133 134 final ShortcutInfo.Builder b = new ShortcutInfo.Builder( 135 mContext, shortcut.getId()); 136 137 setSiteInformation(b, shortcut.getIntent().getData()); 138 setExtras(b); 139 140 updateList.add(b.build()); 141 } 142 // Call update. 143 if (updateList.size() > 0) { 144 callShortcutManager(() -> mShortcutManager.updateShortcuts(updateList)); 145 } 146 147 return null; 148 } 149 }.execute(); 150 } 151 152 private ShortcutInfo createShortcutForUrl(String urlAsString) { 153 Log.i(TAG, "createShortcutForUrl: " + urlAsString); 154 155 final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mContext, urlAsString); 156 157 final Uri uri = Uri.parse(urlAsString); 158 b.setIntent(new Intent(Intent.ACTION_VIEW, uri)); 159 160 setSiteInformation(b, uri); 161 setExtras(b); 162 163 return b.build(); 164 } 165 166 private ShortcutInfo.Builder setSiteInformation(ShortcutInfo.Builder b, Uri uri) { 167 // TODO Get the actual site <title> and use it. 168 // TODO Set the current locale to accept-language to get localized title. 169 b.setShortLabel(uri.getHost()); 170 b.setLongLabel(uri.toString()); 171 172 // TODO Fetch the favicon from the URI and sets to the icon. 173 b.setIcon(Icon.createWithResource(mContext, R.drawable.link)); 174 175 return b; 176 } 177 178 private ShortcutInfo.Builder setExtras(ShortcutInfo.Builder b) { 179 final PersistableBundle extras = new PersistableBundle(); 180 extras.putLong(EXTRA_LAST_REFRESH, System.currentTimeMillis()); 181 b.setExtras(extras); 182 return b; 183 } 184 185 private String normalizeUrl(String urlAsString) { 186 if (urlAsString.startsWith("http://") || urlAsString.startsWith("https://")) { 187 return urlAsString; 188 } else { 189 return "http://" + urlAsString; 190 } 191 } 192 193 public void addWebSiteShortcut(String urlAsString) { 194 final String uriFinal = urlAsString; 195 callShortcutManager(() -> { 196 final ShortcutInfo shortcut = createShortcutForUrl(normalizeUrl(uriFinal)); 197 return mShortcutManager.addDynamicShortcuts(Arrays.asList(shortcut)); 198 }); 199 } 200 201 public void removeShortcut(ShortcutInfo shortcut) { 202 mShortcutManager.removeDynamicShortcuts(Arrays.asList(shortcut.getId())); 203 } 204 205 public void disableShortcut(ShortcutInfo shortcut) { 206 mShortcutManager.disableShortcuts(Arrays.asList(shortcut.getId())); 207 } 208 209 public void enableShortcut(ShortcutInfo shortcut) { 210 mShortcutManager.enableShortcuts(Arrays.asList(shortcut.getId())); 211 } 212 } 213