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