Home | History | Annotate | Download | only in core
      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