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