1 /* 2 * Copyright (C) 2015 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 17 package android.support.car.app.menu; 18 19 import android.graphics.Bitmap; 20 import android.graphics.drawable.Drawable; 21 import android.os.Bundle; 22 import android.support.car.app.menu.compat.CarMenuConstantsComapt.MenuItemConstants; 23 import android.util.DisplayMetrics; 24 import android.widget.RemoteViews; 25 import java.util.ArrayList; 26 import java.util.List; 27 28 /** 29 * CarMenu is used to pass back the menu items of a sublevel to the CarMenu subscriber. 30 * Use the {@link Builder} to populate the contents of the sublevel. 31 * @hide 32 */ 33 public class CarMenu { 34 private boolean mDetachCalled; 35 private boolean mSendResultCalled; 36 private final DisplayMetrics mMetrics; 37 38 public CarMenu(DisplayMetrics metrics) { 39 mMetrics = metrics; 40 } 41 42 /** 43 * Send the result back to the caller. 44 */ 45 public void sendResult(List<Item> results) { 46 if (mSendResultCalled) { 47 throw new IllegalStateException("sendResult() called twice."); 48 } 49 mSendResultCalled = true; 50 51 List<Bundle> resultBundle = new ArrayList<>(); 52 for (Item item : results) { 53 ItemImpl impl = (ItemImpl) item; 54 if (impl.mIcon != null) { 55 impl.mBundle.putParcelable(MenuItemConstants.KEY_LEFTICON, snapshot(impl.mIcon)); 56 } 57 if (impl.mRightIcon != null) { 58 impl.mBundle.putParcelable( 59 MenuItemConstants.KEY_RIGHTICON, snapshot(impl.mRightIcon)); 60 } 61 resultBundle.add(impl.mBundle); 62 } 63 onResultReady(resultBundle); 64 } 65 66 private Bitmap snapshot(Drawable drawable) { 67 return Utils.snapshot(mMetrics, drawable); 68 } 69 70 /** 71 * Detach this message from the current thread and allow the {@link #sendResult} 72 * call to happen later. This stops blocking the current thread. 73 */ 74 public void detach() { 75 if (mDetachCalled) { 76 throw new IllegalStateException("detach() called when detach() had already" 77 + " been called."); 78 } 79 if (mSendResultCalled) { 80 throw new IllegalStateException("detach() called when sendResult() had already" 81 + " been called"); 82 } 83 mDetachCalled = true; 84 } 85 86 /** 87 * Returns whether results were actually sent. 88 * 89 * @return {@code true} if {@link #sendResult(java.util.List)} or {@link #detach()} has been called. 90 * @hide 91 */ 92 public boolean isDone() { 93 return mDetachCalled || mSendResultCalled; 94 } 95 96 /** 97 * Called when the result is sent, after assertions about not being called twice 98 * have happened. 99 * @hide 100 */ 101 protected void onResultReady(List<Bundle> result) { 102 } 103 104 /** 105 * An individual item in a menu. 106 */ 107 public interface Item { 108 /** 109 * Gets the id of the menu item. 110 * 111 * @return The id of the menu item. 112 */ 113 String getId(); 114 115 /** 116 * Gets the title of the menu item. 117 * 118 * @return The title of the menu item. {@code null} if there is no title. 119 */ 120 String getTitle(); 121 122 /** 123 * Gets the text of the menu item. 124 * 125 * @return The text of the menu item. {@code null} if there is no text. 126 */ 127 String getText(); 128 129 /** 130 * Gets the integer constant for the widget. 131 * 132 * @return Either {@link MenuItemConstants.WidgetTypes#WIDGET_CHECKBOX} or -1, 133 * if no widget was set. 134 */ 135 int getWidget(); 136 137 /** 138 * Gets the widget state. The return value is only valid if a widget was set. 139 * 140 * @return {@code true} if the widget is enabled, {@code false} if the widget is disabled. 141 */ 142 boolean getWidgetState(); 143 144 /** 145 * Gets the flags set for this menu item. 146 * 147 * @return The flags set. 148 */ 149 int getFlags(); 150 } 151 152 /** @hide */ 153 static class ItemImpl implements Item { 154 final Bundle mBundle; 155 final Drawable mIcon; 156 final Drawable mRightIcon; 157 158 ItemImpl(Bundle bundle, Drawable icon, Drawable rightIcon) { 159 mBundle = bundle; 160 mIcon = icon; 161 mRightIcon = rightIcon; 162 } 163 164 @Override 165 public String getId() { 166 return mBundle.getString(MenuItemConstants.KEY_ID); 167 } 168 169 @Override 170 public String getTitle() { 171 return mBundle.getString(MenuItemConstants.KEY_TITLE); 172 } 173 174 @Override 175 public String getText() { 176 return mBundle.getString(MenuItemConstants.KEY_TEXT); 177 } 178 179 @Override 180 public int getWidget() { 181 return mBundle.getInt(MenuItemConstants.KEY_WIDGET, -1); 182 } 183 184 @Override 185 public boolean getWidgetState() { 186 return mBundle.getBoolean(MenuItemConstants.KEY_WIDGET_STATE); 187 } 188 189 @Override 190 public int getFlags() { 191 return mBundle.getInt(MenuItemConstants.KEY_FLAGS); 192 } 193 } 194 195 /** 196 * Builder to build an {@link Item}. Calls to the builder can be chained. 197 */ 198 public static class Builder { 199 private final Bundle mBundle = new Bundle(); 200 // Drawable icons that will later be turned into Bitmaps and inserted into the Bundle 201 private Drawable mIcon; 202 private Drawable mRightIcon; 203 204 /** 205 * Construct a Builder with a specific id. 206 * 207 * @param id Unique id used to identify this menu item. If it is browsable, then it 208 * will also be used to fetch this item's submenu. 209 */ 210 public Builder(String id) { 211 if (id == null) { 212 throw new IllegalStateException("Cannot pass a null id to the Builder."); 213 } 214 mBundle.putString(MenuItemConstants.KEY_ID, id); 215 } 216 217 /** 218 * Sets the title. 219 * 220 * @param title Title to set 221 * @return This to chain calls 222 */ 223 public Builder setTitle(String title) { 224 mBundle.putString(MenuItemConstants.KEY_TITLE, title); 225 return this; 226 } 227 228 /** 229 * Sets the body text. 230 * 231 * @param text Text to set 232 * @return This {@link Builder} to chain calls 233 */ 234 public Builder setText(String text) { 235 mBundle.putString(MenuItemConstants.KEY_TEXT, text); 236 return this; 237 } 238 239 /** 240 * Sets the icon. 241 * 242 * @param bitmap Icon to set 243 * @return This {@link Builder} to chain calls 244 */ 245 public Builder setIcon(Bitmap bitmap) { 246 mBundle.putParcelable(MenuItemConstants.KEY_LEFTICON, bitmap); 247 return this; 248 } 249 250 /** 251 * Sets the icon. 252 * 253 * A snapshot of the {@link android.graphics.drawable.Drawable} is captured at the time the {@link Item} obtained 254 * from this Builder is passed to {@link #sendResult(java.util.List)}. Any changes that are 255 * made to the Drawable after that point will not affect what is displayed by the menu. 256 * 257 * @param drawable Icon to set 258 * @return This {@link Builder} to chain calls 259 */ 260 public Builder setIconFromSnapshot(Drawable drawable) { 261 mIcon = drawable; 262 return this; 263 } 264 265 /** 266 * Sets the right icon. 267 * 268 * @param bitmap Icon to set 269 * @return This {@link Builder} to chain calls 270 */ 271 public Builder setRightIcon(Bitmap bitmap) { 272 mBundle.putParcelable(MenuItemConstants.KEY_RIGHTICON, bitmap); 273 return this; 274 } 275 276 /** 277 * Sets the right icon. 278 * 279 * A snapshot of the {@link android.graphics.drawable.Drawable} is captured at the time the 280 * {@link Item} obtained 281 * from this Builder is passed to {@link #sendResult(java.util.List)}. Any changes that are 282 * made to the Drawable after that point will not affect what is displayed by the menu. 283 * 284 * @param drawable Icon to set 285 * @return This {@link Builder} to chain calls 286 */ 287 public Builder setRightIconFromSnapshot(Drawable drawable) { 288 mRightIcon = drawable; 289 return this; 290 } 291 292 /** 293 * The widget to set. 294 * It can be anyone of the following: button, checkbox, toggle 295 * 296 * @param widget 297 * @return This {@link Builder} to chain calls 298 */ 299 public Builder setWidget(int widget) { 300 mBundle.putInt(MenuItemConstants.KEY_WIDGET, widget); 301 return this; 302 } 303 304 /** 305 * If a widget is set, the state the widget is in. 306 * This is only applicable if the widget is "checkbox" or "toggle" 307 * 308 * @param on If true, a checkbox is checked and a toggle is set to "on". 309 * If false, a checkbox will be unchecked and a toggle will be set to "off". 310 * @return This {@link Builder} to chain calls 311 */ 312 public Builder setWidgetState(boolean on) { 313 mBundle.putBoolean(MenuItemConstants.KEY_WIDGET_STATE, on); 314 return this; 315 } 316 317 /** 318 * Indicates that this is an empty placeholder menu item. 319 * Only title and icon will be available in this situation. 320 * 321 * @param isEmptyPlaceHolder If true, this CarMenu will be a placeholder item for no data 322 * in menu list. 323 * @return This {@link Builder} to chain calls 324 */ 325 public Builder setIsEmptyPlaceHolder(boolean isEmptyPlaceHolder) { 326 mBundle.putBoolean(MenuItemConstants.KEY_EMPTY_PLACEHOLDER, isEmptyPlaceHolder); 327 return this; 328 } 329 330 /** 331 * If the widget is {@link MenuItemConstants#WIDGET_TEXT_VIEW}, then this will allow setting 332 * the right text. 333 * 334 * @param text The text to set 335 * @return this {@link Builder} to chain calls 336 */ 337 public Builder setRightText(String text) { 338 mBundle.putString(MenuItemConstants.KEY_RIGHTTEXT, text); 339 return this; 340 } 341 342 public Builder setRemoteViews(RemoteViews views) { 343 mBundle.putParcelable(MenuItemConstants.KEY_REMOTEVIEWS, views); 344 return this; 345 } 346 347 /** 348 * Sets additional flags for this item. 349 * {@link MenuItemConstants#FLAG_BROWSABLE} is the only one that can be currently set 350 * 351 * @param flags flags to set 352 * @return This {@link Builder} to chain calls 353 */ 354 public Builder setFlags(@MenuItemConstants.MenuItemFlags int flags) { 355 mBundle.putInt(MenuItemConstants.KEY_FLAGS, flags); 356 return this; 357 } 358 359 /** 360 * Add this item to the list of items to be sent when {@link CarMenu#sendResult(java.util.List)} 361 * is called 362 */ 363 public Item build() { 364 return new ItemImpl(mBundle, mIcon, mRightIcon); 365 } 366 } 367 } 368