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