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.BroadcastReceiver; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.content.pm.PackageManager; 25 import android.content.pm.ResolveInfo; 26 import android.net.Uri; 27 import android.os.Handler; 28 import android.os.IBinder; 29 import android.os.UserHandle; 30 import android.service.quicksettings.IQSTileService; 31 import android.service.quicksettings.Tile; 32 import android.service.quicksettings.TileService; 33 import android.support.annotation.VisibleForTesting; 34 import android.util.Log; 35 36 import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener; 37 38 import java.util.List; 39 import java.util.Objects; 40 41 /** 42 * Manages the priority which lets {@link TileServices} make decisions about which tiles 43 * to bind. Also holds on to and manages the {@link TileLifecycleManager}, informing it 44 * of when it is allowed to bind based on decisions frome the {@link TileServices}. 45 */ 46 public class TileServiceManager { 47 48 private static final long MIN_BIND_TIME = 5000; 49 private static final long UNBIND_DELAY = 30000; 50 51 public static final boolean DEBUG = true; 52 53 private static final String TAG = "TileServiceManager"; 54 55 @VisibleForTesting 56 static final String PREFS_FILE = "CustomTileModes"; 57 58 private final TileServices mServices; 59 private final TileLifecycleManager mStateManager; 60 private final Handler mHandler; 61 private boolean mBindRequested; 62 private boolean mBindAllowed; 63 private boolean mBound; 64 private int mPriority; 65 private boolean mJustBound; 66 private long mLastUpdate; 67 private boolean mShowingDialog; 68 // Whether we have a pending bind going out to the service without a response yet. 69 // This defaults to true to ensure tiles start out unavailable. 70 private boolean mPendingBind = true; 71 TileServiceManager(TileServices tileServices, Handler handler, ComponentName component, Tile tile)72 TileServiceManager(TileServices tileServices, Handler handler, ComponentName component, 73 Tile tile) { 74 this(tileServices, handler, new TileLifecycleManager(handler, 75 tileServices.getContext(), tileServices, tile, new Intent().setComponent(component), 76 new UserHandle(ActivityManager.getCurrentUser()))); 77 } 78 79 @VisibleForTesting TileServiceManager(TileServices tileServices, Handler handler, TileLifecycleManager tileLifecycleManager)80 TileServiceManager(TileServices tileServices, Handler handler, 81 TileLifecycleManager tileLifecycleManager) { 82 mServices = tileServices; 83 mHandler = handler; 84 mStateManager = tileLifecycleManager; 85 86 IntentFilter filter = new IntentFilter(); 87 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 88 filter.addDataScheme("package"); 89 Context context = mServices.getContext(); 90 context.registerReceiverAsUser(mUninstallReceiver, 91 new UserHandle(ActivityManager.getCurrentUser()), filter, null, mHandler); 92 ComponentName component = tileLifecycleManager.getComponent(); 93 if (!TileLifecycleManager.isTileAdded(context, component)) { 94 TileLifecycleManager.setTileAdded(context, component, true); 95 mStateManager.onTileAdded(); 96 mStateManager.flushMessagesAndUnbind(); 97 } 98 } 99 setTileChangeListener(TileChangeListener changeListener)100 public void setTileChangeListener(TileChangeListener changeListener) { 101 mStateManager.setTileChangeListener(changeListener); 102 } 103 isActiveTile()104 public boolean isActiveTile() { 105 return mStateManager.isActiveTile(); 106 } 107 setShowingDialog(boolean dialog)108 public void setShowingDialog(boolean dialog) { 109 mShowingDialog = dialog; 110 } 111 getTileService()112 public IQSTileService getTileService() { 113 return mStateManager; 114 } 115 getToken()116 public IBinder getToken() { 117 return mStateManager.getToken(); 118 } 119 setBindRequested(boolean bindRequested)120 public void setBindRequested(boolean bindRequested) { 121 if (mBindRequested == bindRequested) return; 122 mBindRequested = bindRequested; 123 if (mBindAllowed && mBindRequested && !mBound) { 124 mHandler.removeCallbacks(mUnbind); 125 bindService(); 126 } else { 127 mServices.recalculateBindAllowance(); 128 } 129 if (mBound && !mBindRequested) { 130 mHandler.postDelayed(mUnbind, UNBIND_DELAY); 131 } 132 } 133 setLastUpdate(long lastUpdate)134 public void setLastUpdate(long lastUpdate) { 135 mLastUpdate = lastUpdate; 136 if (mBound && isActiveTile()) { 137 mStateManager.onStopListening(); 138 setBindRequested(false); 139 } 140 mServices.recalculateBindAllowance(); 141 } 142 handleDestroy()143 public void handleDestroy() { 144 setBindAllowed(false); 145 mServices.getContext().unregisterReceiver(mUninstallReceiver); 146 mStateManager.handleDestroy(); 147 } 148 setBindAllowed(boolean allowed)149 public void setBindAllowed(boolean allowed) { 150 if (mBindAllowed == allowed) return; 151 mBindAllowed = allowed; 152 if (!mBindAllowed && mBound) { 153 unbindService(); 154 } else if (mBindAllowed && mBindRequested && !mBound) { 155 bindService(); 156 } 157 } 158 hasPendingBind()159 public boolean hasPendingBind() { 160 return mPendingBind; 161 } 162 clearPendingBind()163 public void clearPendingBind() { 164 mPendingBind = false; 165 } 166 bindService()167 private void bindService() { 168 if (mBound) { 169 Log.e(TAG, "Service already bound"); 170 return; 171 } 172 mPendingBind = true; 173 mBound = true; 174 mJustBound = true; 175 mHandler.postDelayed(mJustBoundOver, MIN_BIND_TIME); 176 mStateManager.setBindService(true); 177 } 178 unbindService()179 private void unbindService() { 180 if (!mBound) { 181 Log.e(TAG, "Service not bound"); 182 return; 183 } 184 mBound = false; 185 mJustBound = false; 186 mStateManager.setBindService(false); 187 } 188 calculateBindPriority(long currentTime)189 public void calculateBindPriority(long currentTime) { 190 if (mStateManager.hasPendingClick()) { 191 // Pending click is the most important thing, need to put this service at the top of 192 // the list to be bound. 193 mPriority = Integer.MAX_VALUE; 194 } else if (mShowingDialog) { 195 // Hang on to services that are showing dialogs so they don't die. 196 mPriority = Integer.MAX_VALUE - 1; 197 } else if (mJustBound) { 198 // If we just bound, lets not thrash on binding/unbinding too much, this is second most 199 // important. 200 mPriority = Integer.MAX_VALUE - 2; 201 } else if (!mBindRequested) { 202 // Don't care about binding right now, put us last. 203 mPriority = Integer.MIN_VALUE; 204 } else { 205 // Order based on whether this was just updated. 206 long timeSinceUpdate = currentTime - mLastUpdate; 207 // Fit compare into integer space for simplicity. Make sure to leave MAX_VALUE and 208 // MAX_VALUE - 1 for the more important states above. 209 if (timeSinceUpdate > Integer.MAX_VALUE - 3) { 210 mPriority = Integer.MAX_VALUE - 3; 211 } else { 212 mPriority = (int) timeSinceUpdate; 213 } 214 } 215 } 216 getBindPriority()217 public int getBindPriority() { 218 return mPriority; 219 } 220 221 private final Runnable mUnbind = new Runnable() { 222 @Override 223 public void run() { 224 if (mBound && !mBindRequested) { 225 unbindService(); 226 } 227 } 228 }; 229 230 @VisibleForTesting 231 final Runnable mJustBoundOver = new Runnable() { 232 @Override 233 public void run() { 234 mJustBound = false; 235 mServices.recalculateBindAllowance(); 236 } 237 }; 238 239 private final BroadcastReceiver mUninstallReceiver = new BroadcastReceiver() { 240 @Override 241 public void onReceive(Context context, Intent intent) { 242 if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { 243 return; 244 } 245 246 Uri data = intent.getData(); 247 String pkgName = data.getEncodedSchemeSpecificPart(); 248 final ComponentName component = mStateManager.getComponent(); 249 if (!Objects.equals(pkgName, component.getPackageName())) { 250 return; 251 } 252 253 // If the package is being updated, verify the component still exists. 254 if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { 255 Intent queryIntent = new Intent(TileService.ACTION_QS_TILE); 256 queryIntent.setPackage(pkgName); 257 PackageManager pm = context.getPackageManager(); 258 List<ResolveInfo> services = pm.queryIntentServicesAsUser( 259 queryIntent, 0, ActivityManager.getCurrentUser()); 260 for (ResolveInfo info : services) { 261 if (Objects.equals(info.serviceInfo.packageName, component.getPackageName()) 262 && Objects.equals(info.serviceInfo.name, component.getClassName())) { 263 return; 264 } 265 } 266 } 267 268 mServices.getHost().removeTile(component); 269 } 270 }; 271 } 272