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