1 /*
2  * Copyright (C) 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 
17 package android.app.slice;
18 
19 import android.annotation.NonNull;
20 import android.annotation.StringDef;
21 import android.app.PendingIntent;
22 import android.app.RemoteInput;
23 import android.graphics.drawable.Icon;
24 import android.os.Bundle;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.text.TextUtils;
28 import android.util.Pair;
29 import android.widget.RemoteViews;
30 
31 import com.android.internal.util.ArrayUtils;
32 
33 import java.lang.annotation.Retention;
34 import java.lang.annotation.RetentionPolicy;
35 import java.util.Arrays;
36 import java.util.List;
37 
38 
39 /**
40  * A SliceItem is a single unit in the tree structure of a {@link Slice}.
41  *
42  * A SliceItem a piece of content and some hints about what that content
43  * means or how it should be displayed. The types of content can be:
44  * <li>{@link #FORMAT_SLICE}</li>
45  * <li>{@link #FORMAT_TEXT}</li>
46  * <li>{@link #FORMAT_IMAGE}</li>
47  * <li>{@link #FORMAT_ACTION}</li>
48  * <li>{@link #FORMAT_INT}</li>
49  * <li>{@link #FORMAT_LONG}</li>
50  * <li>{@link #FORMAT_REMOTE_INPUT}</li>
51  * <li>{@link #FORMAT_BUNDLE}</li>
52  *
53  * The hints that a {@link SliceItem} are a set of strings which annotate
54  * the content. The hints that are guaranteed to be understood by the system
55  * are defined on {@link Slice}.
56  */
57 public final class SliceItem implements Parcelable {
58 
59     private static final String TAG = "SliceItem";
60 
61     /**
62      * @hide
63      */
64     @StringDef(prefix = { "FORMAT_" }, value = {
65             FORMAT_SLICE,
66             FORMAT_TEXT,
67             FORMAT_IMAGE,
68             FORMAT_ACTION,
69             FORMAT_INT,
70             FORMAT_LONG,
71             FORMAT_REMOTE_INPUT,
72             FORMAT_BUNDLE,
73     })
74     @Retention(RetentionPolicy.SOURCE)
75     public @interface SliceType {}
76 
77     /**
78      * A {@link SliceItem} that contains a {@link Slice}
79      */
80     public static final String FORMAT_SLICE = "slice";
81     /**
82      * A {@link SliceItem} that contains a {@link CharSequence}
83      */
84     public static final String FORMAT_TEXT = "text";
85     /**
86      * A {@link SliceItem} that contains an {@link Icon}
87      */
88     public static final String FORMAT_IMAGE = "image";
89     /**
90      * A {@link SliceItem} that contains a {@link PendingIntent}
91      *
92      * Note: Actions contain 2 pieces of data, In addition to the pending intent, the
93      * item contains a {@link Slice} that the action applies to.
94      */
95     public static final String FORMAT_ACTION = "action";
96     /**
97      * A {@link SliceItem} that contains an int.
98      */
99     public static final String FORMAT_INT = "int";
100     /**
101      * A {@link SliceItem} that contains a long.
102      */
103     public static final String FORMAT_LONG = "long";
104     /**
105      * @deprecated TO BE REMOVED
106      * @removed
107      */
108     @Deprecated
109     public static final String FORMAT_TIMESTAMP = FORMAT_LONG;
110     /**
111      * A {@link SliceItem} that contains a {@link RemoteInput}.
112      */
113     public static final String FORMAT_REMOTE_INPUT = "input";
114     /**
115      * A {@link SliceItem} that contains a {@link Bundle}.
116      */
117     public static final String FORMAT_BUNDLE = "bundle";
118 
119     /**
120      * @hide
121      */
122     protected @Slice.SliceHint
123     String[] mHints;
124     private final String mFormat;
125     private final String mSubType;
126     private final Object mObj;
127 
128     /**
129      * @hide
130      */
SliceItem(Object obj, @SliceType String format, String subType, List<String> hints)131     public SliceItem(Object obj, @SliceType String format, String subType,
132             List<String> hints) {
133         this(obj, format, subType, hints.toArray(new String[hints.size()]));
134     }
135 
136     /**
137      * @hide
138      */
SliceItem(Object obj, @SliceType String format, String subType, @Slice.SliceHint String[] hints)139     public SliceItem(Object obj, @SliceType String format, String subType,
140             @Slice.SliceHint String[] hints) {
141         mHints = hints;
142         mFormat = format;
143         mSubType = subType;
144         mObj = obj;
145     }
146 
147     /**
148      * @hide
149      */
SliceItem(PendingIntent intent, Slice slice, String format, String subType, @Slice.SliceHint String[] hints)150     public SliceItem(PendingIntent intent, Slice slice, String format, String subType,
151             @Slice.SliceHint String[] hints) {
152         this(new Pair<>(intent, slice), format, subType, hints);
153     }
154 
155     /**
156      * Gets all hints associated with this SliceItem.
157      * @return Array of hints.
158      */
getHints()159     public @NonNull @Slice.SliceHint List<String> getHints() {
160         return Arrays.asList(mHints);
161     }
162 
163     /**
164      * Get the format of this SliceItem.
165      * <p>
166      * The format will be one of the following types supported by the platform:
167      * <li>{@link #FORMAT_SLICE}</li>
168      * <li>{@link #FORMAT_TEXT}</li>
169      * <li>{@link #FORMAT_IMAGE}</li>
170      * <li>{@link #FORMAT_ACTION}</li>
171      * <li>{@link #FORMAT_INT}</li>
172      * <li>{@link #FORMAT_LONG}</li>
173      * <li>{@link #FORMAT_REMOTE_INPUT}</li>
174      * <li>{@link #FORMAT_BUNDLE}</li>
175      * @see #getSubType() ()
176      */
getFormat()177     public String getFormat() {
178         return mFormat;
179     }
180 
181     /**
182      * Get the sub-type of this SliceItem.
183      * <p>
184      * Subtypes provide additional information about the type of this information beyond basic
185      * interpretations inferred by {@link #getFormat()}. For example a slice may contain
186      * many {@link #FORMAT_TEXT} items, but only some of them may be {@link Slice#SUBTYPE_MESSAGE}.
187      * @see #getFormat()
188      */
getSubType()189     public String getSubType() {
190         return mSubType;
191     }
192 
193     /**
194      * @return The text held by this {@link #FORMAT_TEXT} SliceItem
195      */
getText()196     public CharSequence getText() {
197         return (CharSequence) mObj;
198     }
199 
200     /**
201      * @return The parcelable held by this {@link #FORMAT_BUNDLE} SliceItem
202      */
getBundle()203     public Bundle getBundle() {
204         return (Bundle) mObj;
205     }
206 
207     /**
208      * @return The icon held by this {@link #FORMAT_IMAGE} SliceItem
209      */
getIcon()210     public Icon getIcon() {
211         return (Icon) mObj;
212     }
213 
214     /**
215      * @return The pending intent held by this {@link #FORMAT_ACTION} SliceItem
216      */
getAction()217     public PendingIntent getAction() {
218         return ((Pair<PendingIntent, Slice>) mObj).first;
219     }
220 
221     /**
222      * @hide This isn't final
223      */
getRemoteView()224     public RemoteViews getRemoteView() {
225         return (RemoteViews) mObj;
226     }
227 
228     /**
229      * @return The remote input held by this {@link #FORMAT_REMOTE_INPUT} SliceItem
230      */
getRemoteInput()231     public RemoteInput getRemoteInput() {
232         return (RemoteInput) mObj;
233     }
234 
235     /**
236      * @return The color held by this {@link #FORMAT_INT} SliceItem
237      */
getInt()238     public int getInt() {
239         return (Integer) mObj;
240     }
241 
242     /**
243      * @return The slice held by this {@link #FORMAT_ACTION} or {@link #FORMAT_SLICE} SliceItem
244      */
getSlice()245     public Slice getSlice() {
246         if (FORMAT_ACTION.equals(getFormat())) {
247             return ((Pair<PendingIntent, Slice>) mObj).second;
248         }
249         return (Slice) mObj;
250     }
251 
252     /**
253      * @return The long held by this {@link #FORMAT_LONG} SliceItem
254      */
getLong()255     public long getLong() {
256         return (Long) mObj;
257     }
258 
259     /**
260      * @deprecated replaced by {@link #getLong()}
261      * @removed
262      */
263     @Deprecated
getTimestamp()264     public long getTimestamp() {
265         return (Long) mObj;
266     }
267 
268     /**
269      * @param hint The hint to check for
270      * @return true if this item contains the given hint
271      */
hasHint(@lice.SliceHint String hint)272     public boolean hasHint(@Slice.SliceHint String hint) {
273         return ArrayUtils.contains(mHints, hint);
274     }
275 
276     /**
277      * @hide
278      */
SliceItem(Parcel in)279     public SliceItem(Parcel in) {
280         mHints = in.readStringArray();
281         mFormat = in.readString();
282         mSubType = in.readString();
283         mObj = readObj(mFormat, in);
284     }
285 
286     @Override
describeContents()287     public int describeContents() {
288         return 0;
289     }
290 
291     @Override
writeToParcel(Parcel dest, int flags)292     public void writeToParcel(Parcel dest, int flags) {
293         dest.writeStringArray(mHints);
294         dest.writeString(mFormat);
295         dest.writeString(mSubType);
296         writeObj(dest, flags, mObj, mFormat);
297     }
298 
299     /**
300      * @hide
301      */
hasHints(@lice.SliceHint String[] hints)302     public boolean hasHints(@Slice.SliceHint String[] hints) {
303         if (hints == null) return true;
304         for (String hint : hints) {
305             if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) {
306                 return false;
307             }
308         }
309         return true;
310     }
311 
312     /**
313      * @hide
314      */
hasAnyHints(@lice.SliceHint String[] hints)315     public boolean hasAnyHints(@Slice.SliceHint String[] hints) {
316         if (hints == null) return false;
317         for (String hint : hints) {
318             if (ArrayUtils.contains(mHints, hint)) {
319                 return true;
320             }
321         }
322         return false;
323     }
324 
getBaseType(String type)325     private static String getBaseType(String type) {
326         int index = type.indexOf('/');
327         if (index >= 0) {
328             return type.substring(0, index);
329         }
330         return type;
331     }
332 
writeObj(Parcel dest, int flags, Object obj, String type)333     private static void writeObj(Parcel dest, int flags, Object obj, String type) {
334         switch (getBaseType(type)) {
335             case FORMAT_SLICE:
336             case FORMAT_IMAGE:
337             case FORMAT_REMOTE_INPUT:
338             case FORMAT_BUNDLE:
339                 ((Parcelable) obj).writeToParcel(dest, flags);
340                 break;
341             case FORMAT_ACTION:
342                 ((Pair<PendingIntent, Slice>) obj).first.writeToParcel(dest, flags);
343                 ((Pair<PendingIntent, Slice>) obj).second.writeToParcel(dest, flags);
344                 break;
345             case FORMAT_TEXT:
346                 TextUtils.writeToParcel((CharSequence) obj, dest, flags);
347                 break;
348             case FORMAT_INT:
349                 dest.writeInt((Integer) obj);
350                 break;
351             case FORMAT_TIMESTAMP:
352                 dest.writeLong((Long) obj);
353                 break;
354         }
355     }
356 
readObj(String type, Parcel in)357     private static Object readObj(String type, Parcel in) {
358         switch (getBaseType(type)) {
359             case FORMAT_SLICE:
360                 return Slice.CREATOR.createFromParcel(in);
361             case FORMAT_TEXT:
362                 return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
363             case FORMAT_IMAGE:
364                 return Icon.CREATOR.createFromParcel(in);
365             case FORMAT_ACTION:
366                 return new Pair<>(
367                         PendingIntent.CREATOR.createFromParcel(in),
368                         Slice.CREATOR.createFromParcel(in));
369             case FORMAT_INT:
370                 return in.readInt();
371             case FORMAT_TIMESTAMP:
372                 return in.readLong();
373             case FORMAT_REMOTE_INPUT:
374                 return RemoteInput.CREATOR.createFromParcel(in);
375             case FORMAT_BUNDLE:
376                 return Bundle.CREATOR.createFromParcel(in);
377         }
378         throw new RuntimeException("Unsupported type " + type);
379     }
380 
381     public static final @android.annotation.NonNull Creator<SliceItem> CREATOR = new Creator<SliceItem>() {
382         @Override
383         public SliceItem createFromParcel(Parcel in) {
384             return new SliceItem(in);
385         }
386 
387         @Override
388         public SliceItem[] newArray(int size) {
389             return new SliceItem[size];
390         }
391     };
392 }
393