1 /*
2  * Copyright (c) 2016, 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 package com.android.car.stream;
17 
18 import android.app.PendingIntent;
19 import android.graphics.Bitmap;
20 import android.os.Bundle;
21 import android.util.Log;
22 
23 import java.text.SimpleDateFormat;
24 import java.util.Date;
25 
26 /**
27  * A parcelable that is used for communication between various stream components.
28  * The card is uniquely identified by the type and id.
29  */
30 public class StreamCard extends AbstractBundleable {
31     private final static String TAG = "StreamCard";
32 
33     private final static String TYPE_KEY = "type";
34     private final static String ID_KEY = "id";
35     private final static String PRIORITY_KEY = "priority";
36     private final static String TIMESTAMP_KEY = "timestamp";
37     private final static String CONTENT_PENDING_INTENT_KEY = "content_pending_intent";
38     private final static String PRIMARY_ICON_KEY = "primary_icon";
39     private final static String SECONDARY_ICON_KEY = "secondary_icon";
40     private final static String PRIMARY_TEXT_KEY = "primary_text";
41     private final static String SECONDARY_TEXT_KEY = "secondary_text";
42     private final static String DESCRIPTION_KEY = "description";
43     private final static String CARD_EXTENSION_BUNDLE_KEY = "extension_bundle";
44     private final static String CARD_EXTENSION_CLASS_KEY = "extension_key";
45 
46     /** The type of the card */
47     private int mType;
48     /** The unique id of this card */
49     private long mId;
50     /** Priority or rank compared to other cards */
51     private int mPriority;
52     /** Time at which the StreamCard was generated */
53     private long mTimestamp;
54     /** Primary action to be taken when the entire card is clicked */
55     private PendingIntent mContentPendingIntent;
56     /** Primary icon of the stream card */
57     private Bitmap mPrimaryIcon;
58     /** Secondary icon of the stream card */
59     private Bitmap mSecondaryIcon;
60     /** Primary text of the stream card */
61     private CharSequence mPrimaryText;
62     /** Secondary text of the stream card */
63     private CharSequence mSecondaryText;
64     /** Additional meta information for the card */
65     private StreamCardExtension mCardExtension;
66 
67     /** String description of the {@link StreamCard} */
68     private String mDescription;
69 
70     public static final Creator<StreamCard> CREATOR = new BundleableCreator<>(StreamCard.class);
71 
StreamCard()72     public StreamCard() {
73     }
74 
StreamCard(int type, long id, int priority, long timestamp, PendingIntent contentPendingIntent, Bitmap primaryIcon, Bitmap secondaryIcon, CharSequence primaryText, CharSequence secondaryText, String description, StreamCardExtension cardExtension)75     private StreamCard(int type, long id, int priority, long timestamp,
76             PendingIntent contentPendingIntent, Bitmap primaryIcon, Bitmap secondaryIcon,
77             CharSequence primaryText, CharSequence secondaryText, String description,
78             StreamCardExtension cardExtension) {
79         mType = type;
80         mId = id;
81         mPriority = priority;
82         mTimestamp = timestamp;
83         mContentPendingIntent = contentPendingIntent;
84         mPrimaryIcon = primaryIcon;
85         mSecondaryIcon = secondaryIcon;
86         mPrimaryText = primaryText;
87         mSecondaryText = secondaryText;
88         mDescription = description;
89         mCardExtension = cardExtension;
90     }
91 
92     @Override
writeToBundle(Bundle bundle)93     protected void writeToBundle(Bundle bundle) {
94         bundle.putInt(TYPE_KEY, mType);
95         bundle.putLong(ID_KEY, mId);
96         bundle.putInt(PRIORITY_KEY, mPriority);
97         bundle.putLong(TIMESTAMP_KEY, mTimestamp);
98         bundle.putParcelable(CONTENT_PENDING_INTENT_KEY, mContentPendingIntent);
99         // Note there is a 1MB limit on the transaction size of the binder, if the bitmaps
100         // are too large, pass through a resource or URI
101         bundle.putParcelable(PRIMARY_ICON_KEY, mPrimaryIcon);
102         bundle.putParcelable(SECONDARY_ICON_KEY, mSecondaryIcon);
103         bundle.putString(DESCRIPTION_KEY, mDescription);
104         bundle.putCharSequence(PRIMARY_TEXT_KEY, mPrimaryText);
105         bundle.putCharSequence(SECONDARY_TEXT_KEY, mSecondaryText);
106 
107         if (mCardExtension != null) {
108             Bundle extension = new Bundle();
109             mCardExtension.writeToBundle(extension);
110             bundle.putString(CARD_EXTENSION_CLASS_KEY, mCardExtension.getClass().getName());
111             bundle.putBundle(CARD_EXTENSION_BUNDLE_KEY, extension);
112         }
113     }
114 
115     @Override
readFromBundle(Bundle bundle)116     protected void readFromBundle(Bundle bundle) {
117         mType = bundle.getInt(TYPE_KEY);
118         mId = bundle.getLong(ID_KEY);
119         mPriority = bundle.getInt(PRIORITY_KEY);
120         mTimestamp = bundle.getLong(TIMESTAMP_KEY, System.currentTimeMillis());
121         mContentPendingIntent = bundle.getParcelable(CONTENT_PENDING_INTENT_KEY);
122         mPrimaryIcon = bundle.getParcelable(PRIMARY_ICON_KEY);
123         mSecondaryIcon = bundle.getParcelable(SECONDARY_ICON_KEY);
124         mDescription = bundle.getString(DESCRIPTION_KEY);
125         mPrimaryText = bundle.getCharSequence(PRIMARY_TEXT_KEY);
126         mSecondaryText = bundle.getCharSequence(SECONDARY_TEXT_KEY);
127         mCardExtension = extractExtension(bundle);
128     }
129 
extractExtension(Bundle bundle)130     private StreamCardExtension extractExtension(Bundle bundle) {
131         String className = bundle.getString(CARD_EXTENSION_CLASS_KEY);
132         Bundle extensionBundle = bundle.getBundle(CARD_EXTENSION_BUNDLE_KEY);
133         if (className == null || extensionBundle == null) {
134             return null;
135         }
136 
137         StreamCardExtension extension = null;
138         try {
139             Class clazz = getClass().getClassLoader().loadClass(className);
140             extension = (StreamCardExtension) clazz.newInstance();
141             extension.readFromBundle(extensionBundle);
142         } catch (Exception e) {
143             Log.e(TAG, "Failed to instantiate " + className, e);
144         }
145         return extension;
146     }
147 
148     @Override
toString()149     public String toString() {
150         String extensionName = null;
151         if (mCardExtension != null) {
152             extensionName = mCardExtension.getClass().getName();
153         }
154 
155         StringBuilder sb = new StringBuilder();
156         sb.append("\nType: " + mType);
157         sb.append("\nId: " + mId);
158         sb.append("\nContent Pending Intent: " + mContentPendingIntent);
159         sb.append("\nDescription: " + mDescription);
160         sb.append("\nCard Extension: " + extensionName);
161         sb.append("\nTimestamp: "
162                 + SimpleDateFormat.getDateInstance().format(new Date(mTimestamp)));
163 
164         return sb.toString();
165     }
166 
setPriority(int priority)167     public void setPriority(int priority) {
168         mPriority = priority;
169     }
170 
getPriority()171     public int getPriority() {
172         return mPriority;
173     }
174 
getDescription()175     public String getDescription() {
176         return mDescription;
177     }
178 
getId()179     public long getId() {
180         return mId;
181     }
182 
getType()183     public int getType() {
184         return mType;
185     }
186 
getTimestamp()187     public long getTimestamp() {
188         return mTimestamp;
189     }
190 
getPrimaryText()191     public CharSequence getPrimaryText() {
192         return mPrimaryText;
193     }
194 
getSecondaryText()195     public CharSequence getSecondaryText() {
196         return mSecondaryText;
197     }
198 
getPrimaryIcon()199     public Bitmap getPrimaryIcon() {
200         return mPrimaryIcon;
201     }
202 
getSecondaryIcon()203     public Bitmap getSecondaryIcon() {
204         return mSecondaryIcon;
205     }
206 
getContentPendingIntent()207     public PendingIntent getContentPendingIntent() {
208         return mContentPendingIntent;
209     }
210 
getCardExtension()211     public AbstractBundleable getCardExtension() {
212         return mCardExtension;
213     }
214 
215     /**
216      * Builder to generate a {@link StreamCard}
217      */
218     public static class Builder {
219         private int type;
220         private long id;
221         private int priority;
222         private long timestamp;
223         private Bitmap primaryIcon;
224         private Bitmap secondaryIcon;
225         private CharSequence primaryText;
226         private CharSequence secondaryText;
227         private String description;
228         private PendingIntent contentPendingIntent;
229         private StreamCardExtension cardExtension;
230 
Builder(int type, long id, long timestamp)231         public Builder(int type, long id, long timestamp) {
232             this.type = type;
233             this.id = id;
234             this.timestamp = timestamp;
235         }
236 
build()237         public StreamCard build() {
238             return new StreamCard(type, id, priority, timestamp, contentPendingIntent, primaryIcon,
239                     secondaryIcon, primaryText, secondaryText, description, cardExtension);
240         }
241 
242         /**
243          * Set the primary text of this Stream Card.
244          */
setPrimaryText(CharSequence text)245         public Builder setPrimaryText(CharSequence text) {
246             this.primaryText = text;
247             return this;
248         }
249 
250         /**
251          * Set the secondary text of this Stream Card.
252          */
setSecondaryText(CharSequence text)253         public Builder setSecondaryText(CharSequence text) {
254             this.secondaryText = text;
255             return this;
256         }
257 
258         /**
259          * Set the pending intent to be executed when the card is clicked.
260          */
setClickAction(PendingIntent action)261         public Builder setClickAction(PendingIntent action) {
262             this.contentPendingIntent = action;
263             return this;
264         }
265 
266         /**
267          * Set the bitmap for the primary icon.
268          */
setPrimaryIcon(Bitmap bitmap)269         public Builder setPrimaryIcon(Bitmap bitmap){
270             this.primaryIcon = bitmap;
271             return this;
272         }
273 
274         /**
275          * Set the bitmap for the secondary icon.
276          */
setSecondaryIcon(Bitmap bitmap)277         public Builder setSecondaryIcon(Bitmap bitmap){
278             this.secondaryIcon = bitmap;
279             return this;
280         }
281 
282         /**
283          * Set the description of the card.
284          */
setDescription(String description)285         public Builder setDescription(String description) {
286             this.description = description;
287             return this;
288         }
289 
290         /**
291          * Set the extension of the card.
292          */
setCardExtension(StreamCardExtension extension)293         public Builder setCardExtension(StreamCardExtension extension) {
294             this.cardExtension = extension;
295             return this;
296         }
297     }
298 }
299