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