1 /*
2  * Copyright (C) 2023 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.systemui.car.wm;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
20 
21 import android.app.ActivityManager;
22 import android.app.ActivityTaskManager;
23 import android.car.Car;
24 import android.car.app.CarActivityManager;
25 import android.car.app.CarSystemUIProxy;
26 import android.car.app.CarTaskViewClient;
27 import android.car.app.CarTaskViewHost;
28 import android.content.Context;
29 import android.content.pm.PackageManager;
30 import android.hardware.display.DisplayManager;
31 import android.os.Binder;
32 import android.os.Process;
33 import android.util.ArraySet;
34 import android.util.Slog;
35 import android.view.Display;
36 
37 import androidx.annotation.NonNull;
38 
39 import com.android.systemui.Dumpable;
40 import com.android.systemui.R;
41 import com.android.systemui.car.CarServiceProvider;
42 import com.android.systemui.car.wm.taskview.RemoteCarTaskViewServerImpl;
43 import com.android.systemui.dump.DumpManager;
44 import com.android.wm.shell.ShellTaskOrganizer;
45 import com.android.wm.shell.common.SyncTransactionQueue;
46 import com.android.wm.shell.dagger.WMSingleton;
47 import com.android.wm.shell.taskview.TaskViewTransitions;
48 
49 import java.io.PrintWriter;
50 import java.util.List;
51 
52 import javax.inject.Inject;
53 
54 /**
55  * This class provides a concrete implementation for {@link CarSystemUIProxy}. It hosts all the
56  * system ui interaction that is required by other apps.
57  */
58 @WMSingleton
59 public final class CarSystemUIProxyImpl
60         implements CarSystemUIProxy, CarServiceProvider.CarServiceOnConnectedListener, Dumpable {
61     private static final String TAG = CarSystemUIProxyImpl.class.getSimpleName();
62 
63     private final Context mContext;
64     private final SyncTransactionQueue mSyncQueue;
65     private final ShellTaskOrganizer mTaskOrganizer;
66     private final TaskViewTransitions mTaskViewTransitions;
67     private final ArraySet<RemoteCarTaskViewServerImpl> mRemoteCarTaskViewServerSet =
68             new ArraySet<>();
69     private final DisplayManager mDisplayManager;
70 
71     private boolean mConnected;
72     private CarActivityManager mCarActivityManager;
73 
74     /**
75      * Returns true if {@link CarSystemUIProxyImpl} should be registered, false otherwise.
76      * This could be false because of reasons like:
77      * <ul>
78      *     <li>Current user is not a system user.</li>
79      *     <li>Or {@code config_registerCarSystemUIProxy} is disabled.</li>
80      * </ul>
81      */
shouldRegisterCarSystemUIProxy(Context context)82     public static boolean shouldRegisterCarSystemUIProxy(Context context) {
83         if (!Process.myUserHandle().isSystem()) {
84             Slog.i(TAG, "Non system user.");
85             return false;
86         }
87         if (!context.getResources().getBoolean(R.bool.config_registerCarSystemUIProxy)) {
88             Slog.i(TAG, "config_registerCarSystemUIProxy disabled");
89             return false;
90         }
91         return true;
92     }
93 
94     @Inject
CarSystemUIProxyImpl( Context context, CarServiceProvider carServiceProvider, SyncTransactionQueue syncTransactionQueue, ShellTaskOrganizer taskOrganizer, TaskViewTransitions taskViewTransitions, DumpManager dumpManager)95     CarSystemUIProxyImpl(
96             Context context,
97             CarServiceProvider carServiceProvider,
98             SyncTransactionQueue syncTransactionQueue,
99             ShellTaskOrganizer taskOrganizer,
100             TaskViewTransitions taskViewTransitions,
101             DumpManager dumpManager) {
102         mContext = context;
103         mTaskOrganizer = taskOrganizer;
104         mSyncQueue = syncTransactionQueue;
105         mTaskViewTransitions = taskViewTransitions;
106         mDisplayManager = mContext.getSystemService(DisplayManager.class);
107         dumpManager.registerDumpable(this);
108 
109         if (!shouldRegisterCarSystemUIProxy(mContext)) {
110             Slog.i(TAG, "Not registering CarSystemUIProxy.");
111             return;
112         }
113         carServiceProvider.addListener(this);
114     }
115 
116     /** Returns true if a taskview with a launch root task exists on {@code displayId}. */
isLaunchRootTaskPresent(int displayId)117     public boolean isLaunchRootTaskPresent(int displayId) {
118         for (RemoteCarTaskViewServerImpl remoteCarTaskViewServer : mRemoteCarTaskViewServerSet) {
119             if (remoteCarTaskViewServer.hasLaunchRootTaskOnDisplay(displayId)) {
120                 return true;
121             }
122         }
123         return false;
124     }
125 
126     /** Returns the list of all the task views. */
getAllTaskViews()127     public ArraySet<RemoteCarTaskViewServerImpl> getAllTaskViews() {
128         return mRemoteCarTaskViewServerSet;
129     }
130 
131     @Override
createControlledCarTaskView(CarTaskViewClient carTaskViewClient)132     public CarTaskViewHost createControlledCarTaskView(CarTaskViewClient carTaskViewClient) {
133         return createCarTaskView(carTaskViewClient);
134     }
135 
136     @Override
createCarTaskView(CarTaskViewClient carTaskViewClient)137     public CarTaskViewHost createCarTaskView(CarTaskViewClient carTaskViewClient) {
138         ensureManageSystemUIPermission(mContext);
139         RemoteCarTaskViewServerImpl remoteCarTaskViewServerImpl =
140                 new RemoteCarTaskViewServerImpl(
141                         mContext,
142                         mTaskOrganizer,
143                         mSyncQueue,
144                         carTaskViewClient,
145                         this,
146                         mTaskViewTransitions, mCarActivityManager);
147         mRemoteCarTaskViewServerSet.add(remoteCarTaskViewServerImpl);
148         return remoteCarTaskViewServerImpl.getHostImpl();
149     }
150 
151     /** Clears the taskview from the internal state. */
onCarTaskViewReleased(RemoteCarTaskViewServerImpl remoteCarTaskViewServer)152     public void onCarTaskViewReleased(RemoteCarTaskViewServerImpl remoteCarTaskViewServer) {
153         mRemoteCarTaskViewServerSet.remove(remoteCarTaskViewServer);
154     }
155 
156     @Override
onConnected(Car car)157     public void onConnected(Car car) {
158         mConnected = true;
159         removeExistingTaskViewTasks();
160 
161         mCarActivityManager = car.getCarManager(CarActivityManager.class);
162         mCarActivityManager.registerTaskMonitor();
163         mCarActivityManager.registerCarSystemUIProxy(this);
164     }
165 
166     @Override
dump(@onNull PrintWriter pw, @NonNull String[] args)167     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
168         pw.println("  user:" + mContext.getUserId());
169         pw.println("  shouldRegisterCarSystemUiProxy:" + shouldRegisterCarSystemUIProxy(mContext));
170         pw.println("  mConnected:" + mConnected);
171         pw.println("  mRemoteCarTaskViewServerSet size:" + mRemoteCarTaskViewServerSet.size());
172         pw.println("  mRemoteCarTaskViewServerSet:");
173         for (RemoteCarTaskViewServerImpl remoteCarTaskViewServer : mRemoteCarTaskViewServerSet) {
174             pw.println("    " + remoteCarTaskViewServer);
175         }
176     }
177 
removeExistingTaskViewTasks()178     private void removeExistingTaskViewTasks() {
179         Display[] displays = mDisplayManager.getDisplays();
180         for (int i = 0; i < displays.length; i++) {
181             List<ActivityManager.RunningTaskInfo> taskInfos =
182                     mTaskOrganizer.getRunningTasks(displays[i].getDisplayId());
183             removeMultiWindowTasks(taskInfos);
184         }
185     }
186 
removeMultiWindowTasks(List<ActivityManager.RunningTaskInfo> taskInfos)187     private static void removeMultiWindowTasks(List<ActivityManager.RunningTaskInfo> taskInfos) {
188         ActivityTaskManager atm = ActivityTaskManager.getInstance();
189         for (ActivityManager.RunningTaskInfo taskInfo : taskInfos) {
190             // In Auto, only TaskView tasks have WINDOWING_MODE_MULTI_WINDOW as of now.
191             if (taskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
192                 Slog.d(TAG, "Found a dangling task, removing: " + taskInfo.taskId);
193                 atm.removeTask(taskInfo.taskId);
194             }
195         }
196     }
197 
198     /**
199      * Checks the permission of the calling process. Throws {@link SecurityException} if
200      * {@link Car#PERMISSION_MANAGE_CAR_SYSTEM_UI} is not granted.
201      */
ensureManageSystemUIPermission(Context context)202     public static void ensureManageSystemUIPermission(Context context) {
203         if (Binder.getCallingPid() == android.os.Process.myPid()) {
204             // If called from within CarSystemUI, allow.
205             return;
206         }
207         if (context.checkCallingPermission(Car.PERMISSION_MANAGE_CAR_SYSTEM_UI)
208                 == PackageManager.PERMISSION_GRANTED) {
209             return;
210         }
211         throw new SecurityException("requires permission " + Car.PERMISSION_MANAGE_CAR_SYSTEM_UI);
212     }
213 }
214