1 /*
2  * Copyright (C) 2024 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.car.pm;
18 
19 import android.app.TaskInfo;
20 import android.car.builtin.app.TaskInfoHelper;
21 import android.car.builtin.util.Slogf;
22 import android.car.content.pm.ICarBlockingUiCommandListener;
23 import android.content.Intent;
24 import android.os.RemoteCallbackList;
25 import android.os.RemoteException;
26 import android.util.Log;
27 import android.util.SparseArray;
28 import android.view.Display;
29 
30 import com.android.car.CarLog;
31 import com.android.car.internal.util.IndentingPrintWriter;
32 import com.android.internal.annotations.GuardedBy;
33 import com.android.internal.annotations.VisibleForTesting;
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 
38 /**
39  * Class for controlling the {@link ICarBlockingUiCommandListener}.
40  *
41  * @hide
42  */
43 public final class BlockingUiCommandListenerMediator {
44     private static final String TAG = CarLog.tagFor(BlockingUiCommandListenerMediator.class);
45     private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
46     private static final int NO_LISTENERS_FOR_DISPLAY = 0;
47     private static final int MAX_FINISHING_BLOCKING_UI_LOG_SIZE = 5;
48 
49     private final Object mLock = new Object();
50     /**
51      * Mapping between the display IDs and the BlockingUICommandListeners.
52      */
53     @GuardedBy("mLock")
54     private final SparseArray<RemoteCallbackList<ICarBlockingUiCommandListener>>
55             mDisplayToCarBlockingUiCommandListener = new SparseArray<>();
56     // For dumpsys logging
57     @GuardedBy("mLock")
58     private final List<FinishLogs> mFinishingBlockingUiFinishLogs = new ArrayList<>(
59             MAX_FINISHING_BLOCKING_UI_LOG_SIZE);
60 
61     /**
62      * Registers the {@link ICarBlockingUiCommandListener} listening for the commands to control the
63      * BlockingUI.
64      *
65      * @param listener  listener to register.
66      * @param displayId display Id with which the listener is associated.
67      */
registerBlockingUiCommandListener(ICarBlockingUiCommandListener listener, int displayId)68     public void registerBlockingUiCommandListener(ICarBlockingUiCommandListener listener,
69             int displayId) {
70         synchronized (mLock) {
71             RemoteCallbackList<ICarBlockingUiCommandListener> carBlockingUIRemoteCallbacks =
72                     mDisplayToCarBlockingUiCommandListener.get(displayId);
73             if (carBlockingUIRemoteCallbacks == null) {
74                 carBlockingUIRemoteCallbacks = new RemoteCallbackList<>();
75                 mDisplayToCarBlockingUiCommandListener.put(displayId, carBlockingUIRemoteCallbacks);
76             }
77             carBlockingUIRemoteCallbacks.register(listener);
78         }
79     }
80 
81     /**
82      * Unregisters the {@link ICarBlockingUiCommandListener}.
83      *
84      * @param listener listener to unregister.
85      */
unregisterBlockingUiCommandListener(ICarBlockingUiCommandListener listener)86     public void unregisterBlockingUiCommandListener(ICarBlockingUiCommandListener listener) {
87         synchronized (mLock) {
88             for (int i = 0; i < mDisplayToCarBlockingUiCommandListener.size(); i++) {
89                 int displayId = mDisplayToCarBlockingUiCommandListener.keyAt(i);
90                 RemoteCallbackList<ICarBlockingUiCommandListener> carBlockingUIRemoteCallbacks =
91                         mDisplayToCarBlockingUiCommandListener.get(displayId);
92                 boolean unregistered = carBlockingUIRemoteCallbacks.unregister(listener);
93                 if (unregistered) {
94                     if (carBlockingUIRemoteCallbacks.getRegisteredCallbackCount() == 0) {
95                         mDisplayToCarBlockingUiCommandListener.remove(displayId);
96                     }
97                     return;
98                 }
99             }
100             Slogf.e(TAG, "BlockingUIListener already unregistered");
101         }
102     }
103 
104     /**
105      * Broadcast the finish command to listeners.
106      *
107      * @param taskInfo           the {@link TaskInfo} due to which finish is broadcast to the
108      *                           listeners.
109      * @param lastKnownDisplayId the last known display id where the task changed or vanished.
110      */
finishBlockingUi(TaskInfo taskInfo, int lastKnownDisplayId)111     public void finishBlockingUi(TaskInfo taskInfo, int lastKnownDisplayId) {
112         synchronized (mLock) {
113             int displayId = getDisplayId(taskInfo, lastKnownDisplayId);
114             if (!mDisplayToCarBlockingUiCommandListener.contains(displayId)) {
115                 Slogf.e(TAG, "No BlockingUI listeners for display Id %d", displayId);
116                 return;
117             }
118             addFinishBlockingUiTransitionLogLocked(taskInfo, displayId);
119             RemoteCallbackList<ICarBlockingUiCommandListener> carBlockingUIRemoteCallbacks =
120                     mDisplayToCarBlockingUiCommandListener.get(displayId);
121             int numCallbacks = carBlockingUIRemoteCallbacks.beginBroadcast();
122             if (DBG) {
123                 Slogf.d(TAG, "Broadcasting finishBlockingUi to %d callbacks for %d display ID",
124                         numCallbacks, displayId);
125             }
126             for (int i = 0; i < numCallbacks; i++) {
127                 try {
128                     carBlockingUIRemoteCallbacks.getBroadcastItem(i).finishBlockingUi();
129                 } catch (RemoteException remoteException) {
130                     Slogf.e(TAG, "Error dispatching finishBlockingUi", remoteException);
131                 }
132             }
133             carBlockingUIRemoteCallbacks.finishBroadcast();
134         }
135     }
136 
getDisplayId(TaskInfo taskInfo, int lastKnownDisplayId)137     private static int getDisplayId(TaskInfo taskInfo, int lastKnownDisplayId) {
138         int displayId = TaskInfoHelper.getDisplayId(taskInfo);
139         // Display ID can often be invalid when the task has vanished.
140         if (displayId == Display.INVALID_DISPLAY) {
141             displayId = lastKnownDisplayId;
142         }
143         return displayId;
144     }
145 
146     /**
147      * Returns the number of registered callbacks for the display ID.
148      *
149      * @param displayId display Id with which the listener is associated.
150      * @return number of registered callbacks for the given {@code displayId}.
151      */
152     @VisibleForTesting
getCarBlockingUiCommandListenerRegisteredCallbacksForDisplay(int displayId)153     public int getCarBlockingUiCommandListenerRegisteredCallbacksForDisplay(int displayId) {
154         synchronized (mLock) {
155             if (mDisplayToCarBlockingUiCommandListener.get(displayId) != null) {
156                 return mDisplayToCarBlockingUiCommandListener
157                         .get(displayId).getRegisteredCallbackCount();
158             }
159             return NO_LISTENERS_FOR_DISPLAY;
160         }
161     }
162 
163     /** Log information why the blocking ui was finished. */
164     @GuardedBy("mLock")
addFinishBlockingUiTransitionLogLocked(TaskInfo taskInfo, int displayId)165     private void addFinishBlockingUiTransitionLogLocked(TaskInfo taskInfo, int displayId) {
166         if (mFinishingBlockingUiFinishLogs.size() >= MAX_FINISHING_BLOCKING_UI_LOG_SIZE) {
167             mFinishingBlockingUiFinishLogs.removeFirst();
168         }
169         FinishLogs log = new FinishLogs(taskInfo.taskId, taskInfo.baseIntent, displayId,
170                 System.currentTimeMillis());
171         mFinishingBlockingUiFinishLogs.add(log);
172     }
173 
174     /** Dump the {@code mFinishingBlockingUiFinishLogs}. */
dump(IndentingPrintWriter writer)175     public void dump(IndentingPrintWriter writer) {
176         synchronized (mLock) {
177             writer.println("*BlockingUiCommandListenerMediator*");
178             writer.println("Finishing BlockingUi logs:");
179             for (int i = 0; i < mFinishingBlockingUiFinishLogs.size(); i++) {
180                 writer.println(mFinishingBlockingUiFinishLogs.get(i).toString());
181             }
182         }
183     }
184 
185     /**
186      * An utility class to dump blocking ui finishing logs.
187      */
188     private static final class FinishLogs {
189         private final int mTaskId;
190         private final Intent mBaseIntent;
191         private final int mDisplayId;
192         private final long mTimestampMs;
193 
FinishLogs(int taskId, Intent baseIntent, int displayId, long timestampMs)194         FinishLogs(int taskId, Intent baseIntent, int displayId, long timestampMs) {
195             mTaskId = taskId;
196             mBaseIntent = baseIntent;
197             mDisplayId = displayId;
198             mTimestampMs = timestampMs;
199         }
200 
timeToLog(long timestamp)201         private CharSequence timeToLog(long timestamp) {
202             return android.text.format.DateFormat.format("MM-dd HH:mm:ss", timestamp);
203         }
204 
205         @Override
toString()206         public String toString() {
207             return "Finish BlockingUI, reason: taskId=" + mTaskId + ", baseIntent=" + mBaseIntent
208                     + ", displayId=" + mDisplayId + " at timestamp=" + timeToLog(mTimestampMs);
209         }
210     }
211 }
212