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.pm.shortcutdemo; 17 18 import android.app.Activity; 19 import android.app.Notification; 20 import android.app.Notification.Action; 21 import android.app.Notification.Builder; 22 import android.app.NotificationManager; 23 import android.app.PendingIntent; 24 import android.app.RemoteInput; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.ShortcutInfo; 29 import android.content.pm.ShortcutManager; 30 import android.graphics.BitmapFactory; 31 import android.graphics.drawable.Icon; 32 import android.net.Uri; 33 import android.os.Bundle; 34 import android.text.format.Time; 35 import android.util.ArrayMap; 36 import android.util.Log; 37 import android.util.Pair; 38 import android.view.View; 39 import android.widget.ListView; 40 import android.widget.Toast; 41 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.Collections; 45 import java.util.Comparator; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.Random; 49 import java.util.concurrent.atomic.AtomicInteger; 50 import java.util.function.BooleanSupplier; 51 52 public class ShortcutPublisher extends Activity { 53 public static final String TAG = "ShortcutDemo"; 54 55 private static final String SETUP_SHORTCUT_ID = "setup"; 56 57 private ShortcutManager mShortcutManager; 58 59 private ListView mList; 60 private MyAdapter mAdapter; 61 62 private static final Random sRandom = new Random(); 63 64 private static final AtomicInteger sSequenceNumber = new AtomicInteger(); 65 66 private ComponentName mMyActivity; 67 68 @Override 69 public void onCreate(Bundle savedInstanceState) { 70 super.onCreate(savedInstanceState); 71 72 setContentView(R.layout.main); 73 74 mShortcutManager = getSystemService(ShortcutManager.class); 75 76 mMyActivity = getIntent().getComponent(); 77 if (mMyActivity == null) { 78 mMyActivity = new ComponentName(this, ShortcutPublisher.class); 79 } 80 81 mList = (ListView) findViewById(android.R.id.list); 82 mAdapter = new MyAdapter(this); 83 mList.setAdapter(mAdapter); 84 85 Log.d(TAG, "intent=" + getIntent()); 86 Log.d(TAG, "extras=" + getIntent().getExtras()); 87 } 88 89 @Override 90 protected void onResume() { 91 super.onResume(); 92 93 refreshList(); 94 } 95 96 @Override 97 protected void onDestroy() { 98 super.onDestroy(); 99 } 100 101 private List<ShortcutInfo> getAllShortcuts() { 102 final Map<String, ShortcutInfo> map = new ArrayMap<>(); 103 for (ShortcutInfo si : mShortcutManager.getManifestShortcuts()) { 104 if (!si.getActivity().equals(mMyActivity)) continue; 105 if (!map.containsKey(si.getId())) { 106 map.put(si.getId(), si); 107 } 108 } 109 for (ShortcutInfo si : mShortcutManager.getDynamicShortcuts()) { 110 if (!si.getActivity().equals(mMyActivity)) continue; 111 if (!map.containsKey(si.getId())) { 112 map.put(si.getId(), si); 113 } 114 } 115 for (ShortcutInfo si : mShortcutManager.getPinnedShortcuts()) { 116 if (!si.getActivity().equals(mMyActivity)) continue; 117 if (!map.containsKey(si.getId())) { 118 map.put(si.getId(), si); 119 } 120 } 121 return new ArrayList<>(map.values()); 122 } 123 124 private void refreshList() { 125 final List<ShortcutInfo> list = getAllShortcuts(); 126 Collections.sort(list, mShortcutComparator); 127 mAdapter.setShortcuts(list); 128 } 129 130 private final Comparator<ShortcutInfo> mShortcutComparator = 131 (ShortcutInfo s1, ShortcutInfo s2) -> { 132 int ret = 0; 133 ret = (s1.isDeclaredInManifest() ? 0 : 1) - (s2.isDeclaredInManifest() ? 0 : 1); 134 if (ret != 0) return ret; 135 136 ret = (s1.isDynamic() ? 0 : 1) - (s2.isDynamic() ? 0 : 1); 137 if (ret != 0) return ret; 138 139 ret = s1.getId().compareTo(s2.getId()); 140 if (ret != 0) return ret; 141 142 return 0; 143 }; 144 145 private void dumpCurrentShortcuts() { 146 Log.d(TAG, "Dynamic shortcuts:"); 147 for (ShortcutInfo si : mShortcutManager.getDynamicShortcuts()) { 148 Log.d(TAG, " " + si.toString()); 149 } 150 Log.d(TAG, "Pinned shortcuts:"); 151 for (ShortcutInfo si : mShortcutManager.getPinnedShortcuts()) { 152 Log.d(TAG, " " + si.toString()); 153 } 154 } 155 156 private static void showToast(Context context, String message) { 157 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 158 } 159 160 private static void showThrottledToast(Context context) { 161 showToast(context, 162 "Throttled, use \"adb shell cmd shortcut reset-throttling\" to reset counters"); 163 } 164 165 public static void callApi(Context context, BooleanSupplier call) { 166 try { 167 if (!call.getAsBoolean()) { 168 showThrottledToast(context); 169 } 170 } catch (RuntimeException r) { 171 Log.w(TAG, r.getMessage(), r); 172 showToast(context, r.getMessage()); 173 } 174 } 175 176 private static List<Pair<String, String>> sIntentList = Arrays.asList( 177 Pair.create("Google Search", "http://www.google.com"), 178 Pair.create("Google Mail", "http://mail.google.com"), 179 Pair.create("Google Maps", "http://maps.google.com"), 180 Pair.create("Google Drive", "http://drive.google.com"), 181 Pair.create("Google Photos", "http://photos.google.com"), 182 Pair.create("Google Hangouts", "http://hangouts.google.com"), 183 Pair.create("Google+", "http://plus.google.com") 184 ); 185 186 public static ShortcutInfo.Builder addRandomIntents(Context context, ShortcutInfo.Builder b) { 187 final int i = sRandom.nextInt(sIntentList.size()); 188 b.setShortLabel(sIntentList.get(i).first); 189 b.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(sIntentList.get(i).second))); 190 b.setIcon(Icon.createWithResource(context, R.drawable.icon2)); 191 return b; 192 } 193 194 public void onPublishPressed(View view) { 195 dumpCurrentShortcuts(); 196 final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(getResources(), 197 R.drawable.icon_large_2)); 198 final Icon icon3 = Icon.createWithResource(this, R.drawable.icon_large_3); 199 200 final Intent intent2 = new Intent(Intent.ACTION_VIEW); 201 intent2.setClass(this, ShortcutPublisher.class); 202 203 final Intent intent3 = new Intent(Intent.ACTION_VIEW); 204 intent3.setClass(this, ShortcutPublisher.class); 205 intent3.putExtra("str", "str-value"); 206 intent3.putExtra("nest", new Bundle()); 207 intent3.getBundleExtra("nest").putInt("int", 123); 208 209 final ShortcutInfo si1 = addRandomIntents(this, new ShortcutInfo.Builder(this, "shortcut1")) 210 .setActivity(mMyActivity) 211 .build(); 212 213 final ShortcutInfo si2 = new ShortcutInfo.Builder(this, SETUP_SHORTCUT_ID) 214 .setActivity(mMyActivity) 215 .setShortLabel("Shortcut Demo Main") 216 .setIcon(icon2) 217 .setIntent(intent2) 218 .build(); 219 220 final ShortcutInfo si3 = new ShortcutInfo.Builder(this, "shortcut3") 221 .setActivity(mMyActivity) 222 .setShortLabel("Shortcut Demo Main with extras") 223 .setIcon(icon3) 224 .setIntent(intent3) 225 .build(); 226 227 callApi(this, () -> mShortcutManager.setDynamicShortcuts(Arrays.asList(si1, si2, si3))); 228 refreshList(); 229 } 230 231 public void onDeleteAllPressed(View view) { 232 callApi(this, () -> { 233 mShortcutManager.removeAllDynamicShortcuts(); 234 return true; 235 }); 236 refreshList(); 237 } 238 239 static String formatTime(long time) { 240 Time tobj = new Time(); 241 tobj.set(time); 242 return tobj.format("%Y-%m-%d %H:%M:%S"); 243 } 244 245 public void onAddPressed(View view) { 246 final ShortcutInfo si = addRandomIntents(this, new ShortcutInfo.Builder(this, 247 "shortcut-" + formatTime(System.currentTimeMillis()) + "-" 248 + sSequenceNumber.getAndIncrement())) 249 .setActivity(mMyActivity) 250 .build(); 251 callApi(this, () -> mShortcutManager.addDynamicShortcuts(Arrays.asList(si))); 252 refreshList(); 253 } 254 255 public void onUpdatePressed(View view) { 256 final List updateList = new ArrayList<>(); 257 258 for (ShortcutInfo si : getAllShortcuts()) { 259 if (SETUP_SHORTCUT_ID.equals(si.getId())) continue; 260 if (si.isImmutable()) continue; 261 if (!si.getActivity().equals(mMyActivity)) continue; 262 updateList.add(addRandomIntents(this, new ShortcutInfo.Builder(this, si.getId())) 263 .build()); 264 } 265 callApi(this, () -> mShortcutManager.updateShortcuts(updateList)); 266 refreshList(); 267 } 268 269 void launch(ShortcutInfo si) { 270 startActivity(si.getIntent()); 271 } 272 273 void deleteDynamic(ShortcutInfo si) { 274 mShortcutManager.removeDynamicShortcuts(Arrays.asList(si.getId())); 275 refreshList(); 276 } 277 278 public void onShowNotificationPressed(View v) { 279 final PendingIntent receiverIntent = 280 PendingIntent.getBroadcast(this, 0, 281 new Intent().setComponent(new ComponentName(this, ShortcutReceiver.class)), 282 PendingIntent.FLAG_UPDATE_CURRENT); 283 final RemoteInput ri = new RemoteInput.Builder("result").setLabel("Remote input").build(); 284 285 final Notification.Builder nb = new Builder(this) 286 .setContentText("Test") 287 .setContentTitle(getPackageName()) 288 .setSmallIcon(R.drawable.icon_large_2) 289 .addAction(new Action.Builder(0, "Remote input", receiverIntent) 290 .addRemoteInput(ri) 291 .build()); 292 getSystemService(NotificationManager.class).notify(0, nb.build()); 293 } 294 295 class MyAdapter extends ShortcutAdapter { 296 public MyAdapter(Context context) { 297 super(context); 298 } 299 300 @Override 301 protected int getLayoutId() { 302 return R.layout.list_item; 303 } 304 305 @Override 306 protected int getText1Id() { 307 return R.id.line1; 308 } 309 310 @Override 311 protected int getText2Id() { 312 return R.id.line2; 313 } 314 315 @Override 316 protected int getImageId() { 317 return R.id.image; 318 } 319 320 @Override 321 protected int getLaunchId() { 322 return R.id.launch; 323 } 324 325 @Override 326 protected int getAction2Id() { 327 return R.id.action2; 328 } 329 330 @Override 331 protected boolean showLaunch(ShortcutInfo si) { 332 return true; 333 } 334 335 @Override 336 protected boolean showAction2(ShortcutInfo si) { 337 return si.isDynamic(); // TODO Need disable too. 338 } 339 340 @Override 341 protected String getAction2Text(ShortcutInfo si) { 342 return "Delete"; 343 } 344 345 @Override 346 protected void onLaunchClicked(ShortcutInfo si) { 347 launch(si); 348 } 349 350 @Override 351 protected void onAction2Clicked(ShortcutInfo si) { 352 deleteDynamic(si); 353 } 354 } 355 } 356