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