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