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 package android.service.quicksettings;
17 
18 import android.annotation.Nullable;
19 import android.app.PendingIntent;
20 import android.graphics.drawable.Icon;
21 import android.os.IBinder;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.os.RemoteException;
25 import android.text.TextUtils;
26 import android.util.Log;
27 
28 /**
29  * A Tile holds the state of a tile that will be displayed
30  * in Quick Settings.
31  *
32  * A tile in Quick Settings exists as an icon with an accompanied label.
33  * It also may have content description for accessibility usability.
34  * The style and layout of the tile may change to match a given
35  * device.
36  */
37 public final class Tile implements Parcelable {
38 
39     private static final String TAG = "Tile";
40 
41     /**
42      * An unavailable state indicates that for some reason this tile is not currently
43      * available to the user, and will have no click action.  The tile's icon will be
44      * tinted differently to reflect this state.
45      */
46     public static final int STATE_UNAVAILABLE = 0;
47 
48     /**
49      * This represents a tile that is currently in a disabled state but is still interactable.
50      *
51      * A disabled state indicates that the tile is not currently active (e.g. wifi disconnected or
52      * bluetooth disabled), but is still interactable by the user to modify this state.  Tiles
53      * that have boolean states should use this to represent one of their states.  The tile's
54      * icon will be tinted differently to reflect this state, but still be distinct from unavailable.
55      */
56     public static final int STATE_INACTIVE = 1;
57 
58     /**
59      * This represents a tile that is currently active. (e.g. wifi is connected, bluetooth is on,
60      * cast is casting).  This is the default state.
61      */
62     public static final int STATE_ACTIVE = 2;
63 
64     private IBinder mToken;
65     private Icon mIcon;
66     private CharSequence mLabel;
67     private CharSequence mDefaultLabel;
68     private CharSequence mSubtitle;
69     private CharSequence mContentDescription;
70     private CharSequence mStateDescription;
71     private PendingIntent mPendingIntent;
72     // Default to inactive until clients of the new API can update.
73     private int mState = STATE_INACTIVE;
74 
75     private IQSService mService;
76 
77     /**
78      * @hide
79      */
Tile(Parcel source)80     public Tile(Parcel source) {
81         readFromParcel(source);
82     }
83 
84     /**
85      * @hide
86      */
Tile()87     public Tile() {
88     }
89 
90     /**
91      * @hide
92      */
setService(IQSService service, IBinder stub)93     public void setService(IQSService service, IBinder stub) {
94         mService = service;
95         mToken = stub;
96     }
97 
98     /**
99      * The current state of the tile.
100      *
101      * @see #STATE_UNAVAILABLE
102      * @see #STATE_INACTIVE
103      * @see #STATE_ACTIVE
104      */
getState()105     public int getState() {
106         return mState;
107     }
108 
109     /**
110      * Sets the current state for the tile.
111      *
112      * Does not take effect until {@link #updateTile()} is called.
113      *
114      * @param state One of {@link #STATE_UNAVAILABLE}, {@link #STATE_INACTIVE},
115      * {@link #STATE_ACTIVE}
116      */
setState(int state)117     public void setState(int state) {
118         mState = state;
119     }
120 
121     /**
122      * Gets the current icon for the tile.
123      */
getIcon()124     public Icon getIcon() {
125         return mIcon;
126     }
127 
128     /**
129      * Sets the current icon for the tile.
130      *
131      * This icon is expected to be white on alpha, and may be
132      * tinted by the system to match it's theme.
133      *
134      * Does not take effect until {@link #updateTile()} is called.
135      *
136      * @param icon New icon to show.
137      */
setIcon(Icon icon)138     public void setIcon(Icon icon) {
139         this.mIcon = icon;
140     }
141 
142     /**
143      * Gets the current label for the tile.
144      */
getLabel()145     public CharSequence getLabel() {
146         return mLabel != null ? mLabel : mDefaultLabel;
147     }
148 
149     /**
150      * @hide
151      * @return
152      */
getCustomLabel()153     public CharSequence getCustomLabel() {
154         return mLabel;
155     }
156 
157     /**
158      * @hide
159      */
setDefaultLabel(CharSequence defaultLabel)160     public void setDefaultLabel(CharSequence defaultLabel) {
161         mDefaultLabel = defaultLabel;
162     }
163 
164     /**
165      * Sets the current label for the tile.
166      *
167      * Does not take effect until {@link #updateTile()} is called.
168      *
169      * @param label New label to show.
170      */
setLabel(CharSequence label)171     public void setLabel(CharSequence label) {
172         this.mLabel = label;
173     }
174 
175     /**
176      * Gets the current subtitle for the tile.
177      */
178     @Nullable
getSubtitle()179     public CharSequence getSubtitle() {
180         return mSubtitle;
181     }
182 
183     /**
184      * Set the subtitle for the tile. Will be displayed as the secondary label.
185      * @param subtitle the subtitle to show.
186      */
setSubtitle(@ullable CharSequence subtitle)187     public void setSubtitle(@Nullable CharSequence subtitle) {
188         this.mSubtitle = subtitle;
189     }
190 
191     /**
192      * Gets the current content description for the tile.
193      */
getContentDescription()194     public CharSequence getContentDescription() {
195         return mContentDescription;
196     }
197 
198     /**
199      * Gets the current state description for the tile.
200      */
201     @Nullable
getStateDescription()202     public CharSequence getStateDescription() {
203         return mStateDescription;
204     }
205 
206     /**
207      * Sets the current content description for the tile.
208      *
209      * Does not take effect until {@link #updateTile()} is called.
210      *
211      * @param contentDescription New content description to use.
212      */
setContentDescription(CharSequence contentDescription)213     public void setContentDescription(CharSequence contentDescription) {
214         this.mContentDescription = contentDescription;
215     }
216 
217     /**
218      * Sets the current state description for the tile.
219      *
220      * Does not take effect until {@link #updateTile()} is called.
221      *
222      * @param stateDescription New state description to use.
223      */
setStateDescription(@ullable CharSequence stateDescription)224     public void setStateDescription(@Nullable CharSequence stateDescription) {
225         this.mStateDescription = stateDescription;
226     }
227 
228     @Override
describeContents()229     public int describeContents() {
230         return 0;
231     }
232 
233     /**
234      * Pushes the state of the Tile to Quick Settings to be displayed.
235      */
updateTile()236     public void updateTile() {
237         try {
238             mService.updateQsTile(this, mToken);
239         } catch (RemoteException e) {
240             Log.e(TAG, "Couldn't update tile");
241         }
242     }
243 
244     /**
245      * Gets the Activity {@link PendingIntent} to be launched when the tile is clicked.
246      */
247     @Nullable
getActivityLaunchForClick()248     public PendingIntent getActivityLaunchForClick() {
249         return mPendingIntent;
250     }
251 
252     /**
253      * Sets an Activity {@link PendingIntent} to be launched when the tile is clicked.
254      *
255      * The last value set here will be launched when the user clicks in the tile, instead of
256      * forwarding the `onClick` message to the {@link TileService}. Set to {@code null} to handle
257      * the `onClick` in the `TileService`
258      * (This is the default behavior if this method is never called.)
259      * @param pendingIntent a PendingIntent for an activity to be launched onclick, or {@code null}
260      *                      to handle the clicks in the `TileService`.
261      */
setActivityLaunchForClick(@ullable PendingIntent pendingIntent)262     public void setActivityLaunchForClick(@Nullable PendingIntent pendingIntent) {
263         if (pendingIntent != null && !pendingIntent.isActivity()) {
264             throw new IllegalArgumentException();
265         } else {
266             mPendingIntent = pendingIntent;
267         }
268     }
269 
270     @Override
writeToParcel(Parcel dest, int flags)271     public void writeToParcel(Parcel dest, int flags) {
272         if (mIcon != null) {
273             dest.writeByte((byte) 1);
274             mIcon.writeToParcel(dest, flags);
275         } else {
276             dest.writeByte((byte) 0);
277         }
278         if (mPendingIntent != null) {
279             dest.writeByte((byte) 1);
280             mPendingIntent.writeToParcel(dest, flags);
281         } else {
282             dest.writeByte((byte) 0);
283         }
284         dest.writeInt(mState);
285         TextUtils.writeToParcel(mLabel, dest, flags);
286         TextUtils.writeToParcel(mDefaultLabel, dest, flags);
287         TextUtils.writeToParcel(mSubtitle, dest, flags);
288         TextUtils.writeToParcel(mContentDescription, dest, flags);
289         TextUtils.writeToParcel(mStateDescription, dest, flags);
290     }
291 
readFromParcel(Parcel source)292     private void readFromParcel(Parcel source) {
293         if (source.readByte() != 0) {
294             mIcon = Icon.CREATOR.createFromParcel(source);
295         } else {
296             mIcon = null;
297         }
298         if (source.readByte() != 0) {
299             mPendingIntent = PendingIntent.CREATOR.createFromParcel(source);
300         } else {
301             mPendingIntent = null;
302         }
303         mState = source.readInt();
304         mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
305         mDefaultLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
306         mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
307         mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
308         mStateDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
309     }
310 
311     public static final @android.annotation.NonNull Creator<Tile> CREATOR = new Creator<Tile>() {
312         @Override
313         public Tile createFromParcel(Parcel source) {
314             return new Tile(source);
315         }
316 
317         @Override
318         public Tile[] newArray(int size) {
319             return new Tile[size];
320         }
321     };
322 }