CustomTile.java revision c767a1f988d6847bc4873d57ec4485c54120fe2c
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 */
16package com.android.systemui.qs.external;
17
18import android.content.ComponentName;
19import android.content.pm.PackageManager;
20import android.content.pm.ServiceInfo;
21import android.graphics.drawable.Drawable;
22import android.os.Binder;
23import android.os.IBinder;
24import android.os.RemoteException;
25import android.service.quicksettings.IQSTileService;
26import android.service.quicksettings.Tile;
27import android.service.quicksettings.TileService;
28import android.text.SpannableStringBuilder;
29import android.text.style.ForegroundColorSpan;
30import android.util.Log;
31import android.view.IWindowManager;
32import android.view.WindowManager;
33import android.view.WindowManagerGlobal;
34
35import com.android.internal.logging.MetricsLogger;
36import com.android.internal.logging.MetricsProto.MetricsEvent;
37import com.android.systemui.R;
38import com.android.systemui.qs.QSTile;
39import com.android.systemui.statusbar.phone.QSTileHost;
40
41public class CustomTile extends QSTile<QSTile.State> {
42    public static final String PREFIX = "custom(";
43
44    private static final boolean DEBUG = false;
45
46    // We don't want to thrash binding and unbinding if the user opens and closes the panel a lot.
47    // So instead we have a period of waiting.
48    private static final long UNBIND_DELAY = 30000;
49
50    private final ComponentName mComponent;
51    private final Tile mTile;
52    private final IWindowManager mWindowManager;
53    private final IBinder mToken = new Binder();
54    private final IQSTileService mService;
55    private final TileServiceManager mServiceManager;
56
57    private boolean mListening;
58    private boolean mBound;
59    private boolean mIsTokenGranted;
60    private boolean mIsShowingDialog;
61
62    private CustomTile(QSTileHost host, String action) {
63        super(host);
64        mWindowManager = WindowManagerGlobal.getWindowManagerService();
65        mComponent = ComponentName.unflattenFromString(action);
66        mServiceManager = host.getTileServices().getTileWrapper(this);
67        mService = mServiceManager.getTileService();
68        mTile = new Tile(mComponent);
69        try {
70            PackageManager pm = mContext.getPackageManager();
71            ServiceInfo info = pm.getServiceInfo(mComponent, 0);
72            mTile.setIcon(android.graphics.drawable.Icon
73                    .createWithResource(mComponent.getPackageName(), info.icon));
74            mTile.setLabel(info.loadLabel(pm));
75        } catch (Exception e) {
76        }
77        try {
78            mService.setQSTile(mTile);
79        } catch (RemoteException e) {
80            // Called through wrapper, won't happen here.
81        }
82    }
83
84    public ComponentName getComponent() {
85        return mComponent;
86    }
87
88    public Tile getQsTile() {
89        return mTile;
90    }
91
92    public void updateState(Tile tile) {
93        mTile.setIcon(tile.getIcon());
94        mTile.setLabel(tile.getLabel());
95        mTile.setContentDescription(tile.getContentDescription());
96        mTile.setState(tile.getState());
97    }
98
99    public void onDialogShown() {
100        mIsShowingDialog = true;
101    }
102
103    public void onDialogHidden() {
104        mIsShowingDialog = false;
105        try {
106            if (DEBUG) Log.d(TAG, "Removing token");
107            mWindowManager.removeWindowToken(mToken);
108        } catch (RemoteException e) {
109        }
110    }
111
112    @Override
113    public void setListening(boolean listening) {
114        if (mListening == listening) return;
115        mListening = listening;
116        try {
117            if (listening) {
118                if (mServiceManager.getType() == TileService.TILE_MODE_PASSIVE) {
119                    mServiceManager.setBindRequested(true);
120                    mService.onStartListening();
121                }
122            } else {
123                mService.onStopListening();
124                if (mIsTokenGranted && !mIsShowingDialog) {
125                    try {
126                        if (DEBUG) Log.d(TAG, "Removing token");
127                        mWindowManager.removeWindowToken(mToken);
128                    } catch (RemoteException e) {
129                    }
130                    mIsTokenGranted = false;
131                }
132                mIsShowingDialog = false;
133                mServiceManager.setBindRequested(false);
134            }
135        } catch (RemoteException e) {
136            // Called through wrapper, won't happen here.
137        }
138    }
139
140    @Override
141    protected void handleDestroy() {
142        super.handleDestroy();
143        if (mIsTokenGranted) {
144            try {
145                if (DEBUG) Log.d(TAG, "Removing token");
146                mWindowManager.removeWindowToken(mToken);
147            } catch (RemoteException e) {
148            }
149        }
150        mHost.getTileServices().freeService(this, mServiceManager);
151    }
152
153    @Override
154    protected State newTileState() {
155        return new State();
156    }
157
158    @Override
159    protected void handleUserSwitch(int newUserId) {
160        super.handleUserSwitch(newUserId);
161    }
162
163    @Override
164    protected void handleClick() {
165        if (mTile.getState() == Tile.STATE_UNAVAILABLE) {
166            return;
167        }
168        try {
169            if (DEBUG) Log.d(TAG, "Adding token");
170            mWindowManager.addWindowToken(mToken, WindowManager.LayoutParams.TYPE_QS_DIALOG);
171            mIsTokenGranted = true;
172        } catch (RemoteException e) {
173        }
174        try {
175            if (mServiceManager.getType() == TileService.TILE_MODE_ACTIVE) {
176                mServiceManager.setBindRequested(true);
177                mService.onStartListening();
178            }
179            mService.onClick(mToken);
180        } catch (RemoteException e) {
181            // Called through wrapper, won't happen here.
182        }
183        MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
184    }
185
186    @Override
187    protected void handleLongClick() {
188    }
189
190    @Override
191    protected void handleUpdateState(State state, Object arg) {
192        Drawable drawable = mTile.getIcon().loadDrawable(mContext);
193        int color = mContext.getColor(getColor(mTile.getState()));
194        drawable.setTint(color);
195        state.icon = new DrawableIcon(drawable);
196        state.label = mTile.getLabel();
197        if (mTile.getState() == Tile.STATE_UNAVAILABLE) {
198            state.label = new SpannableStringBuilder().append(state.label,
199                    new ForegroundColorSpan(color),
200                    SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE);
201        }
202        if (mTile.getContentDescription() != null) {
203            state.contentDescription = mTile.getContentDescription();
204        } else {
205            state.contentDescription = state.label;
206        }
207    }
208
209    @Override
210    public int getMetricsCategory() {
211        return MetricsEvent.QS_CUSTOM;
212    }
213
214    public void startUnlockAndRun() {
215        mHost.startRunnableDismissingKeyguard(new Runnable() {
216            @Override
217            public void run() {
218                try {
219                    mService.onUnlockComplete();
220                } catch (RemoteException e) {
221                }
222            }
223        });
224    }
225
226    private static int getColor(int state) {
227        switch (state) {
228            case Tile.STATE_UNAVAILABLE:
229                return R.color.qs_tile_tint_unavailable;
230            case Tile.STATE_INACTIVE:
231                return R.color.qs_tile_tint_inactive;
232            case Tile.STATE_ACTIVE:
233                return R.color.qs_tile_tint_active;
234        }
235        return 0;
236    }
237
238    public static String toSpec(ComponentName name) {
239        return PREFIX + name.flattenToShortString() + ")";
240    }
241
242    public static ComponentName getComponentFromSpec(String spec) {
243        final String action = spec.substring(PREFIX.length(), spec.length() - 1);
244        if (action.isEmpty()) {
245            throw new IllegalArgumentException("Empty custom tile spec action");
246        }
247        return ComponentName.unflattenFromString(action);
248    }
249
250    public static QSTile<?> create(QSTileHost host, String spec) {
251        if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
252            throw new IllegalArgumentException("Bad custom tile spec: " + spec);
253        }
254        final String action = spec.substring(PREFIX.length(), spec.length() - 1);
255        if (action.isEmpty()) {
256            throw new IllegalArgumentException("Empty custom tile spec action");
257        }
258        return new CustomTile(host, action);
259    }
260}
261