1 /* 2 * Copyright (C) 2021 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 17 package com.android.server.devicestate; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.hardware.devicestate.DeviceStateRequest; 23 import android.os.IBinder; 24 25 import java.io.PrintWriter; 26 import java.lang.annotation.Retention; 27 import java.lang.annotation.RetentionPolicy; 28 import java.util.ArrayList; 29 import java.util.List; 30 31 /** 32 * Manages the lifecycle of override requests. 33 * <p> 34 * New requests are added with {@link #addRequest(OverrideRequest)} and are kept active until 35 * either: 36 * <ul> 37 * <li>A new request is added with {@link #addRequest(OverrideRequest)}, in which case the 38 * request will become suspended.</li> 39 * <li>The request is cancelled with {@link #cancelRequest(IBinder)} or as a side effect 40 * of other methods calls, such as {@link #handleProcessDied(int)}.</li> 41 * </ul> 42 */ 43 final class OverrideRequestController { 44 static final int STATUS_UNKNOWN = 0; 45 /** 46 * The request is the top-most request. 47 */ 48 static final int STATUS_ACTIVE = 1; 49 /** 50 * The request is still present but is being superseded by another request. 51 */ 52 static final int STATUS_SUSPENDED = 2; 53 /** 54 * The request is not longer valid. 55 */ 56 static final int STATUS_CANCELED = 3; 57 58 @IntDef(prefix = {"STATUS_"}, value = { 59 STATUS_UNKNOWN, 60 STATUS_ACTIVE, 61 STATUS_SUSPENDED, 62 STATUS_CANCELED 63 }) 64 @Retention(RetentionPolicy.SOURCE) 65 @interface RequestStatus {} 66 statusToString(@equestStatus int status)67 static String statusToString(@RequestStatus int status) { 68 switch (status) { 69 case STATUS_ACTIVE: 70 return "ACTIVE"; 71 case STATUS_SUSPENDED: 72 return "SUSPENDED"; 73 case STATUS_CANCELED: 74 return "CANCELED"; 75 case STATUS_UNKNOWN: 76 return "UNKNOWN"; 77 } 78 throw new IllegalArgumentException("Unknown status: " + status); 79 } 80 81 private final StatusChangeListener mListener; 82 private final List<OverrideRequest> mTmpRequestsToCancel = new ArrayList<>(); 83 84 // List of override requests with the most recent override request at the end. 85 private final ArrayList<OverrideRequest> mRequests = new ArrayList<>(); 86 87 private boolean mStickyRequestsAllowed; 88 // List of override requests that have outlived their process and will only be cancelled through 89 // a call to cancelStickyRequests(). 90 private final ArrayList<OverrideRequest> mStickyRequests = new ArrayList<>(); 91 OverrideRequestController(@onNull StatusChangeListener listener)92 OverrideRequestController(@NonNull StatusChangeListener listener) { 93 mListener = listener; 94 } 95 96 /** 97 * Sets sticky requests as either allowed or disallowed. When sticky requests are allowed a call 98 * to {@link #handleProcessDied(int)} will not result in the request being cancelled 99 * immediately. Instead, the request will be marked sticky and must be cancelled with a call 100 * to {@link #cancelStickyRequests()}. 101 */ setStickyRequestsAllowed(boolean stickyRequestsAllowed)102 void setStickyRequestsAllowed(boolean stickyRequestsAllowed) { 103 mStickyRequestsAllowed = stickyRequestsAllowed; 104 if (!mStickyRequestsAllowed) { 105 cancelStickyRequests(); 106 } 107 } 108 109 /** 110 * Adds a request to the top of the stack and notifies the listener of all changes to request 111 * status as a result of this operation. 112 */ addRequest(@onNull OverrideRequest request)113 void addRequest(@NonNull OverrideRequest request) { 114 mRequests.add(request); 115 mListener.onStatusChanged(request, STATUS_ACTIVE); 116 117 if (mRequests.size() > 1) { 118 OverrideRequest prevRequest = mRequests.get(mRequests.size() - 2); 119 mListener.onStatusChanged(prevRequest, STATUS_SUSPENDED); 120 } 121 } 122 123 /** 124 * Cancels the request with the specified {@code token} and notifies the listener of all changes 125 * to request status as a result of this operation. 126 */ cancelRequest(@onNull IBinder token)127 void cancelRequest(@NonNull IBinder token) { 128 int index = getRequestIndex(token); 129 if (index == -1) { 130 return; 131 } 132 133 OverrideRequest request = mRequests.remove(index); 134 if (index == mRequests.size() && mRequests.size() > 0) { 135 // We removed the current active request so we need to set the new active request 136 // before cancelling this request. 137 OverrideRequest newTop = getLast(mRequests); 138 mListener.onStatusChanged(newTop, STATUS_ACTIVE); 139 } 140 mListener.onStatusChanged(request, STATUS_CANCELED); 141 } 142 143 /** 144 * Cancels all requests that are currently marked sticky and notifies the listener of all 145 * changes to request status as a result of this operation. 146 * 147 * @see #setStickyRequestsAllowed(boolean) 148 */ cancelStickyRequests()149 void cancelStickyRequests() { 150 mTmpRequestsToCancel.clear(); 151 mTmpRequestsToCancel.addAll(mStickyRequests); 152 cancelRequestsLocked(mTmpRequestsToCancel); 153 } 154 155 /** 156 * Returns {@code true} if this controller is current managing a request with the specified 157 * {@code token}, {@code false} otherwise. 158 */ hasRequest(@onNull IBinder token)159 boolean hasRequest(@NonNull IBinder token) { 160 return getRequestIndex(token) != -1; 161 } 162 163 /** 164 * Notifies the controller that the process with the specified {@code pid} has died. The 165 * controller will notify the listener of all changes to request status as a result of this 166 * operation. 167 */ handleProcessDied(int pid)168 void handleProcessDied(int pid) { 169 if (mRequests.isEmpty()) { 170 return; 171 } 172 173 mTmpRequestsToCancel.clear(); 174 OverrideRequest prevActiveRequest = getLast(mRequests); 175 for (OverrideRequest request : mRequests) { 176 if (request.getPid() == pid) { 177 mTmpRequestsToCancel.add(request); 178 } 179 } 180 181 if (mStickyRequestsAllowed) { 182 // Do not cancel the requests now because sticky requests are allowed. These 183 // requests will be cancelled on a call to cancelStickyRequests(). 184 mStickyRequests.addAll(mTmpRequestsToCancel); 185 return; 186 } 187 188 cancelRequestsLocked(mTmpRequestsToCancel); 189 } 190 191 /** 192 * Notifies the controller that the base state has changed. The controller will notify the 193 * listener of all changes to request status as a result of this change. 194 * 195 * @return {@code true} if calling this method has lead to a new active request, {@code false} 196 * otherwise. 197 */ handleBaseStateChanged()198 boolean handleBaseStateChanged() { 199 if (mRequests.isEmpty()) { 200 return false; 201 } 202 203 mTmpRequestsToCancel.clear(); 204 OverrideRequest prevActiveRequest = getLast(mRequests); 205 for (int i = 0; i < mRequests.size(); i++) { 206 OverrideRequest request = mRequests.get(i); 207 if ((request.getFlags() & DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES) != 0) { 208 mTmpRequestsToCancel.add(request); 209 } 210 } 211 212 final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel); 213 return newActiveRequest; 214 } 215 216 /** 217 * Notifies the controller that the set of supported states has changed. The controller will 218 * notify the listener of all changes to request status as a result of this change. 219 * 220 * @return {@code true} if calling this method has lead to a new active request, {@code false} 221 * otherwise. 222 */ handleNewSupportedStates(int[] newSupportedStates)223 boolean handleNewSupportedStates(int[] newSupportedStates) { 224 if (mRequests.isEmpty()) { 225 return false; 226 } 227 228 mTmpRequestsToCancel.clear(); 229 for (int i = 0; i < mRequests.size(); i++) { 230 OverrideRequest request = mRequests.get(i); 231 if (!contains(newSupportedStates, request.getRequestedState())) { 232 mTmpRequestsToCancel.add(request); 233 } 234 } 235 236 final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel); 237 return newActiveRequest; 238 } 239 dumpInternal(PrintWriter pw)240 void dumpInternal(PrintWriter pw) { 241 final int requestCount = mRequests.size(); 242 pw.println(); 243 pw.println("Override requests: size=" + requestCount); 244 for (int i = 0; i < requestCount; i++) { 245 OverrideRequest overrideRequest = mRequests.get(i); 246 int status = (i == requestCount - 1) ? STATUS_ACTIVE : STATUS_SUSPENDED; 247 pw.println(" " + i + ": mPid=" + overrideRequest.getPid() 248 + ", mRequestedState=" + overrideRequest.getRequestedState() 249 + ", mFlags=" + overrideRequest.getFlags() 250 + ", mStatus=" + statusToString(status)); 251 } 252 } 253 254 /** 255 * Handles cancelling a set of requests. If the set of requests to cancel will lead to a new 256 * request becoming active this request will also be notified of its change in state. 257 * 258 * @return {@code true} if calling this method has lead to a new active request, {@code false} 259 * otherwise. 260 */ cancelRequestsLocked(List<OverrideRequest> requestsToCancel)261 private boolean cancelRequestsLocked(List<OverrideRequest> requestsToCancel) { 262 if (requestsToCancel.isEmpty()) { 263 return false; 264 } 265 266 OverrideRequest prevActiveRequest = getLast(mRequests); 267 boolean causedNewRequestToBecomeActive = false; 268 mRequests.removeAll(requestsToCancel); 269 mStickyRequests.removeAll(requestsToCancel); 270 if (!mRequests.isEmpty()) { 271 OverrideRequest newActiveRequest = getLast(mRequests); 272 if (newActiveRequest != prevActiveRequest) { 273 mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE); 274 causedNewRequestToBecomeActive = true; 275 } 276 } 277 278 for (int i = 0; i < requestsToCancel.size(); i++) { 279 mListener.onStatusChanged(requestsToCancel.get(i), STATUS_CANCELED); 280 } 281 return causedNewRequestToBecomeActive; 282 } 283 getRequestIndex(@onNull IBinder token)284 private int getRequestIndex(@NonNull IBinder token) { 285 final int numberOfRequests = mRequests.size(); 286 if (numberOfRequests == 0) { 287 return -1; 288 } 289 290 for (int i = 0; i < numberOfRequests; i++) { 291 OverrideRequest request = mRequests.get(i); 292 if (request.getToken() == token) { 293 return i; 294 } 295 } 296 return -1; 297 } 298 299 @Nullable getLast(List<T> list)300 private static <T> T getLast(List<T> list) { 301 return list.size() > 0 ? list.get(list.size() - 1) : null; 302 } 303 contains(int[] array, int value)304 private static boolean contains(int[] array, int value) { 305 for (int i = 0; i < array.length; i++) { 306 if (array[i] == value) { 307 return true; 308 } 309 } 310 return false; 311 } 312 313 public interface StatusChangeListener { 314 /** 315 * Notifies the listener of a change in request status. If a change within the controller 316 * causes one request to become active and one to become either suspended or cancelled, this 317 * method is guaranteed to be called with the active request first before the suspended or 318 * cancelled request. 319 */ onStatusChanged(@onNull OverrideRequest request, @RequestStatus int newStatus)320 void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus); 321 } 322 } 323