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