OSDN Git Service

46e7277e36d4e928a040746fe4b07d109a29523e
[android-x86/frameworks-base.git] / packages / SystemUI / src / com / android / systemui / qs / external / CustomTile.java
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 com.android.systemui.qs.external;
17
18 import android.app.ActivityManager;
19 import android.content.ComponentName;
20 import android.content.Intent;
21 import android.content.pm.PackageManager;
22 import android.content.pm.ResolveInfo;
23 import android.content.pm.ServiceInfo;
24 import android.graphics.drawable.Drawable;
25 import android.net.Uri;
26 import android.os.Binder;
27 import android.os.IBinder;
28 import android.os.RemoteException;
29 import android.provider.Settings;
30 import android.service.quicksettings.IQSTileService;
31 import android.service.quicksettings.Tile;
32 import android.service.quicksettings.TileService;
33 import android.text.SpannableStringBuilder;
34 import android.text.style.ForegroundColorSpan;
35 import android.util.Log;
36 import android.view.IWindowManager;
37 import android.view.WindowManager;
38 import android.view.WindowManagerGlobal;
39 import com.android.internal.logging.MetricsLogger;
40 import com.android.internal.logging.MetricsProto.MetricsEvent;
41 import com.android.systemui.R;
42 import com.android.systemui.qs.QSTile;
43 import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
44 import com.android.systemui.statusbar.phone.QSTileHost;
45 import libcore.util.Objects;
46
47 public class CustomTile extends QSTile<QSTile.State> implements TileChangeListener {
48     public static final String PREFIX = "custom(";
49
50     private static final boolean DEBUG = false;
51
52     // We don't want to thrash binding and unbinding if the user opens and closes the panel a lot.
53     // So instead we have a period of waiting.
54     private static final long UNBIND_DELAY = 30000;
55
56     private final ComponentName mComponent;
57     private final Tile mTile;
58     private final IWindowManager mWindowManager;
59     private final IBinder mToken = new Binder();
60     private final IQSTileService mService;
61     private final TileServiceManager mServiceManager;
62     private final int mUser;
63     private android.graphics.drawable.Icon mDefaultIcon;
64
65     private boolean mListening;
66     private boolean mBound;
67     private boolean mIsTokenGranted;
68     private boolean mIsShowingDialog;
69
70     private CustomTile(QSTileHost host, String action) {
71         super(host);
72         mWindowManager = WindowManagerGlobal.getWindowManagerService();
73         mComponent = ComponentName.unflattenFromString(action);
74         mTile = new Tile(mComponent);
75         setTileIcon();
76         mServiceManager = host.getTileServices().getTileWrapper(this);
77         mService = mServiceManager.getTileService();
78         mServiceManager.setTileChangeListener(this);
79         mUser = ActivityManager.getCurrentUser();
80     }
81
82     private void setTileIcon() {
83         try {
84             PackageManager pm = mContext.getPackageManager();
85             ServiceInfo info = pm.getServiceInfo(mComponent,
86                     PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE);
87             // Update the icon if its not set or is the default icon.
88             boolean updateIcon = mTile.getIcon() == null
89                     || iconEquals(mTile.getIcon(), mDefaultIcon);
90             mDefaultIcon = info.icon != 0 ? android.graphics.drawable.Icon
91                     .createWithResource(mComponent.getPackageName(), info.icon) : null;
92             if (updateIcon) {
93                 mTile.setIcon(mDefaultIcon);
94             }
95             // Update the label if there is no label.
96             if (mTile.getLabel() == null) {
97                 mTile.setLabel(info.loadLabel(pm));
98             }
99         } catch (Exception e) {
100             mDefaultIcon = null;
101         }
102     }
103
104     /**
105      * Compare two icons, only works for resources.
106      */
107     private boolean iconEquals(android.graphics.drawable.Icon icon1,
108             android.graphics.drawable.Icon icon2) {
109         if (icon1 == icon2) {
110             return true;
111         }
112         if (icon1 == null || icon2 == null) {
113             return false;
114         }
115         if (icon1.getType() != android.graphics.drawable.Icon.TYPE_RESOURCE
116                 || icon2.getType() != android.graphics.drawable.Icon.TYPE_RESOURCE) {
117             return false;
118         }
119         if (icon1.getResId() != icon2.getResId()) {
120             return false;
121         }
122         if (!Objects.equal(icon1.getResPackage(), icon2.getResPackage())) {
123             return false;
124         }
125         return true;
126     }
127
128     @Override
129     public void onTileChanged(ComponentName tile) {
130         setTileIcon();
131     }
132
133     @Override
134     public boolean isAvailable() {
135         return mDefaultIcon != null;
136     }
137
138     public int getUser() {
139         return mUser;
140     }
141
142     public ComponentName getComponent() {
143         return mComponent;
144     }
145
146     public Tile getQsTile() {
147         return mTile;
148     }
149
150     public void updateState(Tile tile) {
151         mTile.setIcon(tile.getIcon());
152         mTile.setLabel(tile.getLabel());
153         mTile.setContentDescription(tile.getContentDescription());
154         mTile.setState(tile.getState());
155     }
156
157     public void onDialogShown() {
158         mIsShowingDialog = true;
159     }
160
161     public void onDialogHidden() {
162         mIsShowingDialog = false;
163         try {
164             if (DEBUG) Log.d(TAG, "Removing token");
165             mWindowManager.removeWindowToken(mToken);
166         } catch (RemoteException e) {
167         }
168     }
169
170     @Override
171     public void setListening(boolean listening) {
172         if (mListening == listening) return;
173         mListening = listening;
174         try {
175             if (listening) {
176                 setTileIcon();
177                 refreshState();
178                 if (!mServiceManager.isActiveTile()) {
179                     mServiceManager.setBindRequested(true);
180                     mService.onStartListening();
181                 }
182             } else {
183                 mService.onStopListening();
184                 if (mIsTokenGranted && !mIsShowingDialog) {
185                     try {
186                         if (DEBUG) Log.d(TAG, "Removing token");
187                         mWindowManager.removeWindowToken(mToken);
188                     } catch (RemoteException e) {
189                     }
190                     mIsTokenGranted = false;
191                 }
192                 mIsShowingDialog = false;
193                 mServiceManager.setBindRequested(false);
194             }
195         } catch (RemoteException e) {
196             // Called through wrapper, won't happen here.
197         }
198     }
199
200     @Override
201     protected void handleDestroy() {
202         super.handleDestroy();
203         if (mIsTokenGranted) {
204             try {
205                 if (DEBUG) Log.d(TAG, "Removing token");
206                 mWindowManager.removeWindowToken(mToken);
207             } catch (RemoteException e) {
208             }
209         }
210         mHost.getTileServices().freeService(this, mServiceManager);
211     }
212
213     @Override
214     public State newTileState() {
215         return new State();
216     }
217
218     @Override
219     public Intent getLongClickIntent() {
220         Intent i = new Intent(TileService.ACTION_QS_TILE_PREFERENCES);
221         i.setPackage(mComponent.getPackageName());
222         i = resolveIntent(i);
223         if (i != null) {
224             return i;
225         }
226         return new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(
227                 Uri.fromParts("package", mComponent.getPackageName(), null));
228     }
229
230     private Intent resolveIntent(Intent i) {
231         ResolveInfo result = mContext.getPackageManager().resolveActivityAsUser(i, 0,
232                 ActivityManager.getCurrentUser());
233         return result != null ? new Intent(TileService.ACTION_QS_TILE_PREFERENCES)
234                 .setClassName(result.activityInfo.packageName, result.activityInfo.name) : null;
235     }
236
237     @Override
238     protected void handleClick() {
239         if (mTile.getState() == Tile.STATE_UNAVAILABLE) {
240             return;
241         }
242         try {
243             if (DEBUG) Log.d(TAG, "Adding token");
244             mWindowManager.addWindowToken(mToken, WindowManager.LayoutParams.TYPE_QS_DIALOG);
245             mIsTokenGranted = true;
246         } catch (RemoteException e) {
247         }
248         try {
249             if (mServiceManager.isActiveTile()) {
250                 mServiceManager.setBindRequested(true);
251                 mService.onStartListening();
252             }
253             mService.onClick(mToken);
254         } catch (RemoteException e) {
255             // Called through wrapper, won't happen here.
256         }
257         MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
258     }
259
260     @Override
261     public CharSequence getTileLabel() {
262         return getState().label;
263     }
264
265     @Override
266     protected void handleUpdateState(State state, Object arg) {
267         int tileState = mTile.getState();
268         if (mServiceManager.hasPendingBind()) {
269             tileState = Tile.STATE_UNAVAILABLE;
270         }
271         Drawable drawable;
272         try {
273             drawable = mTile.getIcon().loadDrawable(mContext);
274         } catch (Exception e) {
275             Log.w(TAG, "Invalid icon, forcing into unavailable state");
276             tileState = Tile.STATE_UNAVAILABLE;
277             drawable = mDefaultIcon.loadDrawable(mContext);
278         }
279         int color = mContext.getColor(getColor(tileState));
280         drawable.setTint(color);
281         state.icon = new DrawableIcon(drawable);
282         state.label = mTile.getLabel();
283         if (tileState == Tile.STATE_UNAVAILABLE) {
284             state.label = new SpannableStringBuilder().append(state.label,
285                     new ForegroundColorSpan(color),
286                     SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE);
287         }
288         if (mTile.getContentDescription() != null) {
289             state.contentDescription = mTile.getContentDescription();
290         } else {
291             state.contentDescription = state.label;
292         }
293     }
294
295     @Override
296     public int getMetricsCategory() {
297         return MetricsEvent.QS_CUSTOM;
298     }
299
300     public void startUnlockAndRun() {
301         mHost.startRunnableDismissingKeyguard(new Runnable() {
302             @Override
303             public void run() {
304                 try {
305                     mService.onUnlockComplete();
306                 } catch (RemoteException e) {
307                 }
308             }
309         });
310     }
311
312     private static int getColor(int state) {
313         switch (state) {
314             case Tile.STATE_UNAVAILABLE:
315                 return R.color.qs_tile_tint_unavailable;
316             case Tile.STATE_INACTIVE:
317                 return R.color.qs_tile_tint_inactive;
318             case Tile.STATE_ACTIVE:
319                 return R.color.qs_tile_tint_active;
320         }
321         return 0;
322     }
323
324     public static String toSpec(ComponentName name) {
325         return PREFIX + name.flattenToShortString() + ")";
326     }
327
328     public static ComponentName getComponentFromSpec(String spec) {
329         final String action = spec.substring(PREFIX.length(), spec.length() - 1);
330         if (action.isEmpty()) {
331             throw new IllegalArgumentException("Empty custom tile spec action");
332         }
333         return ComponentName.unflattenFromString(action);
334     }
335
336     public static QSTile<?> create(QSTileHost host, String spec) {
337         if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
338             throw new IllegalArgumentException("Bad custom tile spec: " + spec);
339         }
340         final String action = spec.substring(PREFIX.length(), spec.length() - 1);
341         if (action.isEmpty()) {
342             throw new IllegalArgumentException("Empty custom tile spec action");
343         }
344         return new CustomTile(host, action);
345     }
346 }