1 /* 2 * Copyright 2018 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 androidx.slice.core; 18 19 import static android.app.slice.Slice.HINT_LARGE; 20 import static android.app.slice.Slice.HINT_NO_TINT; 21 import static android.app.slice.Slice.HINT_SELECTED; 22 import static android.app.slice.Slice.HINT_SHORTCUT; 23 import static android.app.slice.Slice.HINT_TITLE; 24 import static android.app.slice.Slice.SUBTYPE_CONTENT_DESCRIPTION; 25 import static android.app.slice.Slice.SUBTYPE_PRIORITY; 26 import static android.app.slice.Slice.SUBTYPE_TOGGLE; 27 import static android.app.slice.SliceItem.FORMAT_ACTION; 28 import static android.app.slice.SliceItem.FORMAT_IMAGE; 29 import static android.app.slice.SliceItem.FORMAT_INT; 30 import static android.app.slice.SliceItem.FORMAT_TEXT; 31 32 import static androidx.annotation.RestrictTo.Scope.LIBRARY; 33 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 34 import static androidx.slice.core.SliceHints.ICON_IMAGE; 35 import static androidx.slice.core.SliceHints.LARGE_IMAGE; 36 import static androidx.slice.core.SliceHints.SMALL_IMAGE; 37 import static androidx.slice.core.SliceHints.UNKNOWN_IMAGE; 38 39 import android.app.PendingIntent; 40 import android.graphics.drawable.Icon; 41 42 import androidx.annotation.IntRange; 43 import androidx.annotation.NonNull; 44 import androidx.annotation.Nullable; 45 import androidx.annotation.RestrictTo; 46 import androidx.core.graphics.drawable.IconCompat; 47 import androidx.slice.Slice; 48 import androidx.slice.SliceItem; 49 50 /** 51 * Class representing an action, supports tappable icons, custom toggle icons, and default toggles. 52 * @hide 53 */ 54 @RestrictTo(LIBRARY_GROUP) 55 public class SliceActionImpl implements SliceAction { 56 57 private PendingIntent mAction; 58 private IconCompat mIcon; 59 private int mImageMode = UNKNOWN_IMAGE; 60 private CharSequence mTitle; 61 private CharSequence mContentDescription; 62 private boolean mIsToggle; 63 private boolean mIsChecked; 64 private int mPriority = -1; 65 private SliceItem mSliceItem; 66 private SliceItem mActionItem; 67 68 /** 69 * Construct a SliceAction representing a tappable icon. 70 * 71 * @param action the pending intent to invoke for this action. 72 * @param actionIcon the icon to display for this action. 73 * @param actionTitle the title for this action, also used for content description if one hasn't 74 * been set via {@link #setContentDescription(CharSequence)}. 75 */ 76 public SliceActionImpl(@NonNull PendingIntent action, @NonNull IconCompat actionIcon, 77 @NonNull CharSequence actionTitle) { 78 this(action, actionIcon, ICON_IMAGE, actionTitle); 79 } 80 81 /** 82 * Construct a SliceAction representing a tappable icon. Use this method to specify the 83 * format of the image, {@link SliceHints#ICON_IMAGE} will be presented as a tintable icon. 84 * Note that there is no difference between {@link SliceHints#SMALL_IMAGE} and 85 * {@link SliceHints#LARGE_IMAGE} for actions; these will just be represented as an 86 * non-tintable image. 87 * 88 * @param action the pending intent to invoke for this action. 89 * @param actionIcon the icon to display for this action. 90 * @param imageMode the mode this icon should be displayed in. 91 * @param actionTitle the title for this action, also used for content description if one hasn't 92 * been set via {@link #setContentDescription(CharSequence)}. 93 * 94 * @see SliceHints#ICON_IMAGE 95 * @see SliceHints#SMALL_IMAGE 96 * @see SliceHints#LARGE_IMAGE 97 */ 98 public SliceActionImpl(@NonNull PendingIntent action, @NonNull IconCompat actionIcon, 99 @SliceHints.ImageMode int imageMode, @NonNull CharSequence actionTitle) { 100 mAction = action; 101 mIcon = actionIcon; 102 mTitle = actionTitle; 103 mImageMode = imageMode; 104 } 105 106 /** 107 * Construct a SliceAction representing a custom toggle icon. 108 * 109 * @param action the pending intent to invoke for this toggle. 110 * @param actionIcon the icon to display for this toggle, should have a checked and unchecked 111 * state. 112 * @param actionTitle the title for this toggle, also used for content description if one hasn't 113 * been set via {@link #setContentDescription(CharSequence)}. 114 * @param isChecked the state of the toggle. 115 */ 116 public SliceActionImpl(@NonNull PendingIntent action, @NonNull IconCompat actionIcon, 117 @NonNull CharSequence actionTitle, boolean isChecked) { 118 this(action, actionIcon, ICON_IMAGE, actionTitle); 119 mIsChecked = isChecked; 120 mIsToggle = true; 121 } 122 123 /** 124 * Construct a SliceAction representing a default toggle. 125 * 126 * @param action the pending intent to invoke for this toggle. 127 * @param actionTitle the title for this toggle, also used for content description if one hasn't 128 * been set via {@link #setContentDescription(CharSequence)}. 129 * @param isChecked the state of the toggle. 130 */ 131 public SliceActionImpl(@NonNull PendingIntent action, @NonNull CharSequence actionTitle, 132 boolean isChecked) { 133 mAction = action; 134 mTitle = actionTitle; 135 mIsToggle = true; 136 mIsChecked = isChecked; 137 } 138 139 /** 140 * Constructs a SliceAction based off of a {@link SliceItem}. Expects a specific format 141 * for the item. 142 * 143 * @param slice the slice item to construct the action out of. 144 * 145 * @hide 146 */ 147 @RestrictTo(LIBRARY) 148 public SliceActionImpl(SliceItem slice) { 149 mSliceItem = slice; 150 SliceItem actionItem = SliceQuery.find(slice, FORMAT_ACTION); 151 if (actionItem == null) { 152 // Can't have action slice without action 153 return; 154 } 155 mActionItem = actionItem; 156 SliceItem iconItem = SliceQuery.find(actionItem.getSlice(), FORMAT_IMAGE); 157 if (iconItem != null) { 158 mIcon = iconItem.getIcon(); 159 mImageMode = iconItem.hasHint(HINT_NO_TINT) 160 ? iconItem.hasHint(HINT_LARGE) ? LARGE_IMAGE : SMALL_IMAGE 161 : ICON_IMAGE; 162 } 163 SliceItem titleItem = SliceQuery.find(actionItem.getSlice(), FORMAT_TEXT, HINT_TITLE, 164 null /* nonHints */); 165 if (titleItem != null) { 166 mTitle = titleItem.getText(); 167 } 168 SliceItem cdItem = SliceQuery.findSubtype(actionItem.getSlice(), FORMAT_TEXT, 169 SUBTYPE_CONTENT_DESCRIPTION); 170 if (cdItem != null) { 171 mContentDescription = cdItem.getText(); 172 } 173 mIsToggle = SUBTYPE_TOGGLE.equals(actionItem.getSubType()); 174 if (mIsToggle) { 175 mIsChecked = actionItem.hasHint(HINT_SELECTED); 176 } 177 SliceItem priority = SliceQuery.findSubtype(actionItem.getSlice(), FORMAT_INT, 178 SUBTYPE_PRIORITY); 179 mPriority = priority != null ? priority.getInt() : -1; 180 } 181 182 /** 183 * @param description the content description for this action. 184 */ 185 @Nullable 186 @Override 187 public SliceActionImpl setContentDescription(@NonNull CharSequence description) { 188 mContentDescription = description; 189 return this; 190 } 191 192 /** 193 * @param isChecked whether the state of this action is checked or not; only used for toggle 194 * actions. 195 */ 196 @Override 197 public SliceActionImpl setChecked(boolean isChecked) { 198 mIsChecked = isChecked; 199 return this; 200 } 201 202 /** 203 * Sets the priority of this action, with the lowest priority having the highest ranking. 204 */ 205 @Override 206 public SliceActionImpl setPriority(@IntRange(from = 0) int priority) { 207 mPriority = priority; 208 return this; 209 } 210 211 /** 212 * @return the {@link PendingIntent} associated with this action. 213 */ 214 @NonNull 215 @Override 216 public PendingIntent getAction() { 217 return mAction != null ? mAction : mActionItem.getAction(); 218 } 219 220 /** 221 * @hide 222 */ 223 @RestrictTo(LIBRARY_GROUP) 224 public SliceItem getActionItem() { 225 return mActionItem; 226 } 227 228 /** 229 * @return the {@link Icon} to display for this action. This can be null when the action 230 * represented is a default toggle. 231 */ 232 @Nullable 233 @Override 234 public IconCompat getIcon() { 235 return mIcon; 236 } 237 238 /** 239 * @return the title for this action. 240 */ 241 @NonNull 242 @Override 243 public CharSequence getTitle() { 244 return mTitle; 245 } 246 247 /** 248 * @return the content description to use for this action. 249 */ 250 @Nullable 251 @Override 252 public CharSequence getContentDescription() { 253 return mContentDescription; 254 } 255 256 /** 257 * @return the priority associated with this action, -1 if unset. 258 */ 259 @Override 260 public int getPriority() { 261 return mPriority; 262 } 263 264 /** 265 * @return whether this action represents a toggle (i.e. has a checked and unchecked state). 266 */ 267 @Override 268 public boolean isToggle() { 269 return mIsToggle; 270 } 271 272 /** 273 * @return whether the state of this action is checked or not; only used for toggle actions. 274 */ 275 @Override 276 public boolean isChecked() { 277 return mIsChecked; 278 } 279 280 /** 281 * @return the image mode to use for this action. 282 */ 283 @Override 284 public @SliceHints.ImageMode int getImageMode() { 285 return mImageMode; 286 } 287 288 /** 289 * @return whether this action is a toggle using the standard switch control. 290 */ 291 @Override 292 public boolean isDefaultToggle() { 293 return mIsToggle && mIcon == null; 294 } 295 296 /** 297 * @return the SliceItem used to construct this action, this is only populated if the action was 298 * constructed with {@link #SliceActionImpl(SliceItem)}. 299 */ 300 @Nullable 301 public SliceItem getSliceItem() { 302 return mSliceItem; 303 } 304 305 /** 306 * @param builder this should be a new builder that has any additional hints the action might 307 * need. 308 * @return the slice representation of this action. 309 */ 310 @NonNull 311 public Slice buildSlice(@NonNull Slice.Builder builder) { 312 Slice.Builder sb = new Slice.Builder(builder); 313 if (mIcon != null) { 314 @Slice.SliceHint String[] hints = mImageMode == ICON_IMAGE 315 ? new String[] {} 316 : new String[] {HINT_NO_TINT}; 317 sb.addIcon(mIcon, null, hints); 318 } 319 if (mTitle != null) { 320 sb.addText(mTitle, null, HINT_TITLE); 321 } 322 if (mContentDescription != null) { 323 sb.addText(mContentDescription, SUBTYPE_CONTENT_DESCRIPTION); 324 } 325 if (mIsToggle && mIsChecked) { 326 sb.addHints(HINT_SELECTED); 327 } 328 if (mPriority != -1) { 329 sb.addInt(mPriority, SUBTYPE_PRIORITY); 330 } 331 String subtype = mIsToggle ? SUBTYPE_TOGGLE : null; 332 builder.addHints(HINT_SHORTCUT); 333 builder.addAction(mAction, sb.build(), subtype); 334 return builder.build(); 335 } 336 } 337