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.UserHandle; 29 import android.service.quicksettings.IQSTileService; 30 import android.service.quicksettings.Tile; 31 import android.service.quicksettings.TileService; 32 import android.support.annotation.VisibleForTesting; 33 import android.util.Log; 34 35 import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener; 36 37 import java.util.List; 38 39 import libcore.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 mServices.getContext().registerReceiverAsUser(mUninstallReceiver, 90 new UserHandle(ActivityManager.getCurrentUser()), filter, null, mHandler); 91 } 92 setTileChangeListener(TileChangeListener changeListener)93 public void setTileChangeListener(TileChangeListener changeListener) { 94 mStateManager.setTileChangeListener(changeListener); 95 } 96 isActiveTile()97 public boolean isActiveTile() { 98 return mStateManager.isActiveTile(); 99 } 100 setShowingDialog(boolean dialog)101 public void setShowingDialog(boolean dialog) { 102 mShowingDialog = dialog; 103 } 104 getTileService()105 public IQSTileService getTileService() { 106 return mStateManager; 107 } 108 setBindRequested(boolean bindRequested)109 public void setBindRequested(boolean bindRequested) { 110 if (mBindRequested == bindRequested) return; 111 mBindRequested = bindRequested; 112 if (mBindAllowed && mBindRequested && !mBound) { 113 mHandler.removeCallbacks(mUnbind); 114 bindService(); 115 } else { 116 mServices.recalculateBindAllowance(); 117 } 118 if (mBound && !mBindRequested) { 119 mHandler.postDelayed(mUnbind, UNBIND_DELAY); 120 } 121 } 122 setLastUpdate(long lastUpdate)123 public void setLastUpdate(long lastUpdate) { 124 mLastUpdate = lastUpdate; 125 if (mBound && isActiveTile()) { 126 mStateManager.onStopListening(); 127 setBindRequested(false); 128 } 129 mServices.recalculateBindAllowance(); 130 } 131 handleDestroy()132 public void handleDestroy() { 133 mServices.getContext().unregisterReceiver(mUninstallReceiver); 134 mStateManager.handleDestroy(); 135 } 136 setBindAllowed(boolean allowed)137 public void setBindAllowed(boolean allowed) { 138 if (mBindAllowed == allowed) return; 139 mBindAllowed = allowed; 140 if (!mBindAllowed && mBound) { 141 unbindService(); 142 } else if (mBindAllowed && mBindRequested && !mBound) { 143 bindService(); 144 } 145 } 146 hasPendingBind()147 public boolean hasPendingBind() { 148 return mPendingBind; 149 } 150 clearPendingBind()151 public void clearPendingBind() { 152 mPendingBind = false; 153 } 154 bindService()155 private void bindService() { 156 if (mBound) { 157 Log.e(TAG, "Service already bound"); 158 return; 159 } 160 mPendingBind = true; 161 mBound = true; 162 mJustBound = true; 163 mHandler.postDelayed(mJustBoundOver, MIN_BIND_TIME); 164 mStateManager.setBindService(true); 165 } 166 unbindService()167 private void unbindService() { 168 if (!mBound) { 169 Log.e(TAG, "Service not bound"); 170 return; 171 } 172 mBound = false; 173 mJustBound = false; 174 mStateManager.setBindService(false); 175 } 176 calculateBindPriority(long currentTime)177 public void calculateBindPriority(long currentTime) { 178 if (mStateManager.hasPendingClick()) { 179 // Pending click is the most important thing, need to put this service at the top of 180 // the list to be bound. 181 mPriority = Integer.MAX_VALUE; 182 } else if (mShowingDialog) { 183 // Hang on to services that are showing dialogs so they don't die. 184 mPriority = Integer.MAX_VALUE - 1; 185 } else if (mJustBound) { 186 // If we just bound, lets not thrash on binding/unbinding too much, this is second most 187 // important. 188 mPriority = Integer.MAX_VALUE - 2; 189 } else if (!mBindRequested) { 190 // Don't care about binding right now, put us last. 191 mPriority = Integer.MIN_VALUE; 192 } else { 193 // Order based on whether this was just updated. 194 long timeSinceUpdate = currentTime - mLastUpdate; 195 // Fit compare into integer space for simplicity. Make sure to leave MAX_VALUE and 196 // MAX_VALUE - 1 for the more important states above. 197 if (timeSinceUpdate > Integer.MAX_VALUE - 3) { 198 mPriority = Integer.MAX_VALUE - 3; 199 } else { 200 mPriority = (int) timeSinceUpdate; 201 } 202 } 203 } 204 getBindPriority()205 public int getBindPriority() { 206 return mPriority; 207 } 208 209 private final Runnable mUnbind = new Runnable() { 210 @Override 211 public void run() { 212 if (mBound && !mBindRequested) { 213 unbindService(); 214 } 215 } 216 }; 217 218 @VisibleForTesting 219 final Runnable mJustBoundOver = new Runnable() { 220 @Override 221 public void run() { 222 mJustBound = false; 223 mServices.recalculateBindAllowance(); 224 } 225 }; 226 227 private final BroadcastReceiver mUninstallReceiver = new BroadcastReceiver() { 228 @Override 229 public void onReceive(Context context, Intent intent) { 230 if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { 231 return; 232 } 233 234 Uri data = intent.getData(); 235 String pkgName = data.getEncodedSchemeSpecificPart(); 236 final ComponentName component = mStateManager.getComponent(); 237 if (!Objects.equal(pkgName, component.getPackageName())) { 238 return; 239 } 240 241 // If the package is being updated, verify the component still exists. 242 if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { 243 Intent queryIntent = new Intent(TileService.ACTION_QS_TILE); 244 queryIntent.setPackage(pkgName); 245 PackageManager pm = context.getPackageManager(); 246 List<ResolveInfo> services = pm.queryIntentServicesAsUser( 247 queryIntent, 0, ActivityManager.getCurrentUser()); 248 for (ResolveInfo info : services) { 249 if (Objects.equal(info.serviceInfo.packageName, component.getPackageName()) 250 && Objects.equal(info.serviceInfo.name, component.getClassName())) { 251 return; 252 } 253 } 254 } 255 256 mServices.getHost().removeTile(component); 257 } 258 }; 259 } 260