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