SampleSliceProvider.java revision ea77b88ead29f5582dac1019325e6f48496b556f
1/* 2 * Copyright 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 17package com.example.androidx.slice.demos; 18 19import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE; 20 21import android.app.PendingIntent; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.content.Intent; 25import android.graphics.drawable.Icon; 26import android.net.Uri; 27import android.net.wifi.WifiManager; 28import android.os.Handler; 29import android.provider.Settings; 30import android.support.annotation.NonNull; 31import android.text.SpannableString; 32import android.text.format.DateUtils; 33import android.text.style.ForegroundColorSpan; 34 35import java.util.Calendar; 36 37import androidx.app.slice.Slice; 38import androidx.app.slice.SliceProvider; 39import androidx.app.slice.builders.GridBuilder; 40import androidx.app.slice.builders.ListBuilder; 41import androidx.app.slice.builders.MessagingSliceBuilder; 42import androidx.app.slice.builders.SliceAction; 43 44/** 45 * Examples of using slice template builders. 46 */ 47public class SampleSliceProvider extends SliceProvider { 48 49 public static final String ACTION_WIFI_CHANGED = 50 "com.example.androidx.slice.action.WIFI_CHANGED"; 51 public static final String ACTION_TOAST = 52 "com.example.androidx.slice.action.TOAST"; 53 public static final String EXTRA_TOAST_MESSAGE = "com.example.androidx.extra.TOAST_MESSAGE"; 54 public static final String ACTION_TOAST_RANGE_VALUE = 55 "com.example.androidx.slice.action.TOAST_RANGE_VALUE"; 56 57 public static final int LOADING_DELAY_MS = 4000; 58 59 public static final String[] URI_PATHS = {"message", "wifi", "note", "ride", "toggle", 60 "toggle2", "contact", "gallery", "weather", "reservation", "loadlist", "loadlist2", 61 "loadgrid", "loadgrid2", "inputrange", "range"}; 62 63 /** 64 * @return Uri with the provided path 65 */ 66 public static Uri getUri(String path, Context context) { 67 return new Uri.Builder() 68 .scheme(ContentResolver.SCHEME_CONTENT) 69 .authority(context.getPackageName()) 70 .appendPath(path) 71 .build(); 72 } 73 74 @Override 75 public boolean onCreateSliceProvider() { 76 return true; 77 } 78 79 @NonNull 80 @Override 81 public Uri onMapIntentToUri(Intent intent) { 82 return getUri("wifi", getContext()); 83 } 84 85 @Override 86 public Slice onBindSlice(Uri sliceUri) { 87 String path = sliceUri.getPath(); 88 switch (path) { 89 // TODO: add list / grid slices with 'see more' options 90 case "/message": 91 return createMessagingSlice(sliceUri); 92 case "/wifi": 93 return createWifiSlice(sliceUri); 94 case "/note": 95 return createNoteSlice(sliceUri); 96 case "/ride": 97 return createRideSlice(sliceUri); 98 case "/toggle": 99 return createCustomToggleSlice(sliceUri); 100 case "/toggle2": 101 return createTwoCustomToggleSlices(sliceUri); 102 case "/contact": 103 return createContact(sliceUri); 104 case "/gallery": 105 return createGallery(sliceUri); 106 case "/weather": 107 return createWeather(sliceUri); 108 case "/reservation": 109 return createReservationSlice(sliceUri); 110 case "/loadlist": 111 return createLoadingSlice(sliceUri, false /* loadAll */, true /* isList */); 112 case "/loadlist2": 113 return createLoadingSlice(sliceUri, true /* loadAll */, true /* isList */); 114 case "/loadgrid": 115 return createLoadingSlice(sliceUri, false /* loadAll */, false /* isList */); 116 case "/loadgrid2": 117 return createLoadingSlice(sliceUri, true /* loadAll */, false /* isList */); 118 case "/inputrange": 119 return createStarRatingInputRange(sliceUri); 120 case "/range": 121 return createDownloadProgressRange(sliceUri); 122 } 123 throw new IllegalArgumentException("Unknown uri " + sliceUri); 124 } 125 126 private Slice createWeather(Uri sliceUri) { 127 SliceAction primaryAction = new SliceAction(getBroadcastIntent(ACTION_TOAST, 128 "open weather app"), Icon.createWithResource(getContext(), R.drawable.weather_1), 129 "Weather is happening!"); 130 return new GridBuilder(getContext(), sliceUri) 131 .setPrimaryAction(primaryAction) 132 .addCell(cb -> cb 133 .addLargeImage(Icon.createWithResource(getContext(), R.drawable.weather_1)) 134 .addText("MON") 135 .addTitleText("69\u00B0")) 136 .addCell(cb -> cb 137 .addLargeImage(Icon.createWithResource(getContext(), R.drawable.weather_2)) 138 .addText("TUE") 139 .addTitleText("71\u00B0")) 140 .addCell(cb -> cb 141 .addLargeImage(Icon.createWithResource(getContext(), R.drawable.weather_3)) 142 .addText("WED") 143 .addTitleText("76\u00B0")) 144 .addCell(cb -> cb 145 .addLargeImage(Icon.createWithResource(getContext(), R.drawable.weather_4)) 146 .addText("THU") 147 .addTitleText("72\u00B0")) 148 .addCell(cb -> cb 149 .addLargeImage(Icon.createWithResource(getContext(), R.drawable.weather_1)) 150 .addText("FRI") 151 .addTitleText("68\u00B0")) 152 .build(); 153 } 154 155 private Slice createGallery(Uri sliceUri) { 156 return new ListBuilder(getContext(), sliceUri) 157 .setColor(0xff4285F4) 158 .addRow(b -> b 159 .setTitle("Family trip to Hawaii") 160 .setSubtitle("Sep 30, 2017 - Oct 2, 2017")) 161 .addAction(new SliceAction( 162 getBroadcastIntent(ACTION_TOAST, "cast photo album"), 163 Icon.createWithResource(getContext(), R.drawable.ic_cast), 164 "Cast photo album")) 165 .addAction(new SliceAction( 166 getBroadcastIntent(ACTION_TOAST, "share photo album"), 167 Icon.createWithResource(getContext(), R.drawable.ic_share), 168 "Share photo album")) 169 .addGrid(b -> b 170 .addCell(cb -> cb 171 .addLargeImage(Icon.createWithResource(getContext(), R.drawable.slices_1))) 172 .addCell(cb -> cb 173 .addLargeImage(Icon.createWithResource(getContext(), R.drawable.slices_2))) 174 .addCell(cb -> cb 175 .addLargeImage(Icon.createWithResource(getContext(), R.drawable.slices_3))) 176 .addCell(cb -> cb 177 .addLargeImage(Icon.createWithResource(getContext(), R.drawable.slices_4)))) 178 .build(); 179 } 180 181 private Slice createContact(Uri sliceUri) { 182 final long lastCalled = System.currentTimeMillis() - 20 * DateUtils.MINUTE_IN_MILLIS; 183 CharSequence lastCalledString = DateUtils.getRelativeTimeSpanString(lastCalled, 184 Calendar.getInstance().getTimeInMillis(), 185 DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE); 186 SliceAction primaryAction = new SliceAction(getBroadcastIntent(ACTION_TOAST, 187 "See contact info"), Icon.createWithResource(getContext(), 188 R.drawable.mady), "Mady"); 189 return new ListBuilder(getContext(), sliceUri) 190 .setColor(0xff3949ab) 191 .setHeader(b -> b 192 .setTitle("Mady Pitza") 193 .setSummarySubtitle("Called " + lastCalledString) 194 .setPrimaryAction(primaryAction)) 195 .addRow(b -> b 196 .setTitleItem(Icon.createWithResource(getContext(), R.drawable.ic_call)) 197 .setTitle("314-259-2653") 198 .setSubtitle("Call lasted 1 hr 17 min") 199 .addEndItem(lastCalled)) 200 .addRow(b -> b 201 .setTitleItem(Icon.createWithResource(getContext(), R.drawable.ic_text)) 202 .setTitle("You: Coooooool see you then") 203 .addEndItem(System.currentTimeMillis() - 40 * DateUtils.MINUTE_IN_MILLIS)) 204 .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "call"), 205 Icon.createWithResource(getContext(), R.drawable.ic_call), "Call mady")) 206 .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "text"), 207 Icon.createWithResource(getContext(), R.drawable.ic_text), "Text mady")) 208 .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "video"), 209 Icon.createWithResource(getContext(), R.drawable.ic_video), 210 "Video call mady")) 211 .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "email"), 212 Icon.createWithResource(getContext(), R.drawable.ic_email), "Email mady")) 213 .build(); 214 } 215 216 private Slice createMessagingSlice(Uri sliceUri) { 217 // TODO: Remote input. 218 return new MessagingSliceBuilder(getContext(), sliceUri) 219 .add(b -> b 220 .addText("yo home \uD83C\uDF55, I emailed you the info") 221 .addTimestamp(System.currentTimeMillis() - 20 * DateUtils.MINUTE_IN_MILLIS) 222 .addSource(Icon.createWithResource(getContext(), R.drawable.mady))) 223 .add(b -> b 224 .addText("just bought my tickets") 225 .addTimestamp(System.currentTimeMillis() - 10 * DateUtils.MINUTE_IN_MILLIS)) 226 .add(b -> b 227 .addText("yay! can't wait for getContext() weekend!\n" 228 + "\uD83D\uDE00") 229 .addTimestamp(System.currentTimeMillis() - 5 * DateUtils.MINUTE_IN_MILLIS) 230 .addSource(Icon.createWithResource(getContext(), R.drawable.mady))) 231 .build(); 232 233 } 234 235 private Slice createNoteSlice(Uri sliceUri) { 236 // TODO: Remote input. 237 return new ListBuilder(getContext(), sliceUri) 238 .setColor(0xfff4b400) 239 .addRow(b -> b.setTitle("Create new note")) 240 .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "create note"), 241 Icon.createWithResource(getContext(), R.drawable.ic_create), 242 "Create note")) 243 .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "voice note"), 244 Icon.createWithResource(getContext(), R.drawable.ic_voice), 245 "Voice note")) 246 .addAction(new SliceAction(getIntent("android.media.action.IMAGE_CAPTURE"), 247 Icon.createWithResource(getContext(), R.drawable.ic_camera), 248 "Photo note")) 249 .build(); 250 } 251 252 private Slice createReservationSlice(Uri sliceUri) { 253 return new ListBuilder(getContext(), sliceUri) 254 .setColor(0xffFF5252) 255 .setHeader(b -> b 256 .setTitle("Upcoming trip to Seattle") 257 .setSubtitle("Feb 1 - 19 | 2 guests")) 258 .addAction(new SliceAction( 259 getBroadcastIntent(ACTION_TOAST, "show location on map"), 260 Icon.createWithResource(getContext(), R.drawable.ic_location), 261 "Show reservation location")) 262 .addAction(new SliceAction(getBroadcastIntent(ACTION_TOAST, "contact host"), 263 Icon.createWithResource(getContext(), R.drawable.ic_text), 264 "Contact host")) 265 .addGrid(b -> b 266 .addCell(cb -> cb 267 .addLargeImage( 268 Icon.createWithResource(getContext(), R.drawable.reservation)))) 269 .addGrid(b -> b 270 .addCell(cb -> cb 271 .addTitleText("Check In") 272 .addText("12:00 PM, Feb 1")) 273 .addCell(cb -> cb 274 .addTitleText("Check Out") 275 .addText("11:00 AM, Feb 19"))) 276 .build(); 277 } 278 279 private Slice createRideSlice(Uri sliceUri) { 280 final ForegroundColorSpan colorSpan = new ForegroundColorSpan(0xff0F9D58); 281 SpannableString headerSubtitle = new SpannableString("Ride in 4 min"); 282 headerSubtitle.setSpan(colorSpan, 8, headerSubtitle.length(), SPAN_EXCLUSIVE_EXCLUSIVE); 283 SpannableString homeSubtitle = new SpannableString("12 miles | 12 min | $9.00"); 284 homeSubtitle.setSpan(colorSpan, 20, homeSubtitle.length(), SPAN_EXCLUSIVE_EXCLUSIVE); 285 SpannableString workSubtitle = new SpannableString("44 miles | 1 hour 45 min | $31.41"); 286 workSubtitle.setSpan(colorSpan, 27, workSubtitle.length(), SPAN_EXCLUSIVE_EXCLUSIVE); 287 288 SliceAction primaryAction = new SliceAction(getBroadcastIntent(ACTION_TOAST, "get ride"), 289 Icon.createWithResource(getContext(), R.drawable.ic_car), "Get Ride"); 290 return new ListBuilder(getContext(), sliceUri) 291 .setColor(0xff0F9D58) 292 .setHeader(b -> b 293 .setTitle("Get ride") 294 .setSubtitle(headerSubtitle) 295 .setSummarySubtitle("Ride to work in 12 min | Ride home in 1 hour 45 min") 296 .setPrimaryAction(primaryAction)) 297 .addRow(b -> b 298 .setTitle("Work") 299 .setSubtitle(workSubtitle) 300 .addEndItem(new SliceAction(getBroadcastIntent(ACTION_TOAST, "work"), 301 Icon.createWithResource(getContext(), R.drawable.ic_work), 302 "Get ride to work"))) 303 .addRow(b -> b 304 .setTitle("Home") 305 .setSubtitle(homeSubtitle) 306 .addEndItem(new SliceAction(getBroadcastIntent(ACTION_TOAST, "home"), 307 Icon.createWithResource(getContext(), R.drawable.ic_home), 308 "Get ride home"))) 309 .build(); 310 } 311 312 private Slice createCustomToggleSlice(Uri sliceUri) { 313 return new ListBuilder(getContext(), sliceUri) 314 .setColor(0xffff4081) 315 .addRow(b -> b 316 .setTitle("Custom toggle") 317 .setSubtitle("It can support two states") 318 .addEndItem(new SliceAction(getBroadcastIntent(ACTION_TOAST, "star toggled"), 319 Icon.createWithResource(getContext(), R.drawable.toggle_star), 320 "Toggle star", true /* isChecked */))) 321 .build(); 322 } 323 324 private Slice createTwoCustomToggleSlices(Uri sliceUri) { 325 return new ListBuilder(getContext(), sliceUri) 326 .setColor(0xffff4081) 327 .addRow(b -> b 328 .setTitle("2 toggles") 329 .setSubtitle("each supports two states") 330 .addEndItem(new SliceAction( 331 getBroadcastIntent(ACTION_TOAST, "first star toggled"), 332 Icon.createWithResource(getContext(), R.drawable.toggle_star), 333 "Toggle star", true /* isChecked */)) 334 .addEndItem(new SliceAction( 335 getBroadcastIntent(ACTION_TOAST, "second star toggled"), 336 Icon.createWithResource(getContext(), R.drawable.toggle_star), 337 "Toggle star", false /* isChecked */))) 338 .build(); 339 } 340 341 private Slice createWifiSlice(Uri sliceUri) { 342 // Get wifi state 343 WifiManager wifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE); 344 int wifiState = wifiManager.getWifiState(); 345 boolean wifiEnabled = false; 346 String state; 347 switch (wifiState) { 348 case WifiManager.WIFI_STATE_DISABLED: 349 case WifiManager.WIFI_STATE_DISABLING: 350 state = "disconnected"; 351 break; 352 case WifiManager.WIFI_STATE_ENABLED: 353 case WifiManager.WIFI_STATE_ENABLING: 354 state = wifiManager.getConnectionInfo().getSSID(); 355 wifiEnabled = true; 356 break; 357 case WifiManager.WIFI_STATE_UNKNOWN: 358 default: 359 state = ""; // just don't show anything? 360 break; 361 } 362 boolean finalWifiEnabled = wifiEnabled; 363 SliceAction primaryAction = new SliceAction(getIntent(Settings.ACTION_WIFI_SETTINGS), 364 Icon.createWithResource(getContext(), R.drawable.ic_wifi), "Wi-fi Settings"); 365 return new ListBuilder(getContext(), sliceUri) 366 .setColor(0xff4285f4) 367 .addRow(b -> b 368 .setTitle("Wi-fi") 369 .setSubtitle(state) 370 .addEndItem(new SliceAction(getBroadcastIntent(ACTION_WIFI_CHANGED, null), 371 "Toggle wifi", finalWifiEnabled)) 372 .setPrimaryAction(primaryAction)) 373 .build(); 374 } 375 376 private Slice createStarRatingInputRange(Uri sliceUri) { 377 return new ListBuilder(getContext(), sliceUri) 378 .setColor(0xffff4081) 379 .addInputRange(c -> c 380 .setTitle("Star rating") 381 .setThumb(Icon.createWithResource(getContext(), R.drawable.ic_star_on)) 382 .setAction(getBroadcastIntent(ACTION_TOAST_RANGE_VALUE, null)) 383 .setMax(5) 384 .setValue(3)) 385 .build(); 386 } 387 388 private Slice createDownloadProgressRange(Uri sliceUri) { 389 return new ListBuilder(getContext(), sliceUri) 390 .setColor(0xffff4081) 391 .addRange(c -> c 392 .setTitle("Download progress") 393 .setMax(100) 394 .setValue(75)) 395 .build(); 396 } 397 398 private Handler mHandler = new Handler(); 399 private Runnable mLoader; 400 private boolean mLoaded = false; 401 402 private Slice createLoadingSlice(Uri sliceUri, boolean loadAll, boolean isList) { 403 if (!mLoaded || mLoader != null) { 404 // Need to load content or we're still loading so just return partial 405 if (!mLoaded) { 406 mLoader = () -> { 407 // Note that we've loaded things 408 mLoader = null; 409 mLoaded = true; 410 // Notify to update the slice 411 getContext().getContentResolver().notifyChange(sliceUri, null); 412 }; 413 mHandler.postDelayed(mLoader, LOADING_DELAY_MS); 414 } 415 if (loadAll) { 416 return isList 417 ? new ListBuilder(getContext(), sliceUri).build() 418 : new GridBuilder(getContext(), sliceUri).build(); 419 } 420 return createPartialSlice(sliceUri, true, isList); 421 } else { 422 mLoaded = false; 423 return createPartialSlice(sliceUri, false, isList); 424 } 425 } 426 427 private Slice createPartialSlice(Uri sliceUri, boolean isPartial, boolean isList) { 428 Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_star_on); 429 PendingIntent intent = getBroadcastIntent(ACTION_TOAST, "star tapped"); 430 PendingIntent intent2 = getBroadcastIntent(ACTION_TOAST, "toggle tapped"); 431 if (isPartial) { 432 if (isList) { 433 return new ListBuilder(getContext(), sliceUri) 434 .addRow(b -> createRow(b, "Slice that has content to load", 435 "Temporary subtitle", icon, intent, true)) 436 .addRow(b -> createRow(b, null, null, null, intent, true)) 437 .addRow(b -> b 438 .setTitle("My title") 439 .addEndItem(new SliceAction(intent2, "Some action", 440 false /* isChecked */), 441 true /* isLoading */)) 442 .build(); 443 } else { 444 return new GridBuilder(getContext(), sliceUri) 445 .addCell(b -> createCell(b, null, null, null, true)) 446 .addCell(b -> createCell(b, "Two stars", null, icon, true)) 447 .addCell(b -> createCell(b, null, null, null, true)) 448 .build(); 449 } 450 } else { 451 if (isList) { 452 return new ListBuilder(getContext(), sliceUri) 453 .addRow(b -> createRow(b, "Slice that has content to load", 454 "Subtitle loaded", icon, intent, false)) 455 .addRow(b -> createRow(b, "Loaded row", "Loaded subtitle", 456 icon, intent, false)) 457 .addRow(b -> b 458 .setTitle("My title") 459 .addEndItem(new SliceAction(intent2, "Some action", 460 false /* isChecked */))) 461 .build(); 462 } else { 463 return new GridBuilder(getContext(), sliceUri) 464 .addCell(b -> createCell(b, "One star", "meh", icon, false)) 465 .addCell(b -> createCell(b, "Two stars", "good", icon, false)) 466 .addCell(b -> createCell(b, "Three stars", "best", icon, false)) 467 .build(); 468 } 469 } 470 } 471 472 private ListBuilder.RowBuilder createRow(ListBuilder.RowBuilder rb, String title, 473 String subtitle, Icon icon, PendingIntent content, boolean isLoading) { 474 SliceAction primaryAction = new SliceAction(content, icon, title); 475 return rb.setTitle(title, isLoading) 476 .setSubtitle(subtitle, isLoading) 477 .addEndItem(icon, isLoading) 478 .setPrimaryAction(primaryAction); 479 } 480 481 private GridBuilder.CellBuilder createCell(GridBuilder.CellBuilder cb, String text1, 482 String text2, Icon icon, boolean isLoading) { 483 return cb.addText(text1, isLoading).addText(text2, isLoading).addImage(icon, isLoading); 484 } 485 486 private PendingIntent getIntent(String action) { 487 Intent intent = new Intent(action); 488 PendingIntent pi = PendingIntent.getActivity(getContext(), 0, intent, 0); 489 return pi; 490 } 491 492 private PendingIntent getBroadcastIntent(String action, String message) { 493 Intent intent = new Intent(action); 494 intent.setClass(getContext(), SliceBroadcastReceiver.class); 495 // Ensure a new PendingIntent is created for each message. 496 int requestCode = 0; 497 if (message != null) { 498 intent.putExtra(EXTRA_TOAST_MESSAGE, message); 499 requestCode = message.hashCode(); 500 } 501 return PendingIntent.getBroadcast(getContext(), requestCode, intent, 502 PendingIntent.FLAG_UPDATE_CURRENT); 503 } 504} 505