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