1 /*
2  * Copyright (C) 2022 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;
18 
19 import static com.android.car.internal.common.CommonConstants.INVALID_PID;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.UserIdInt;
24 import android.car.app.CarActivityManager;
25 import android.car.builtin.os.UserManagerHelper;
26 import android.car.builtin.util.Slogf;
27 import android.content.ComponentName;
28 import android.os.IBinder;
29 import android.os.RemoteException;
30 import android.os.UserHandle;
31 import android.view.Display;
32 
33 import com.android.car.internal.ICarServiceHelper;
34 import com.android.internal.annotations.GuardedBy;
35 import com.android.internal.annotations.VisibleForTesting;
36 
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.Objects;
40 
41 /**
42  * Utility wrapper to access {@code ICarServiceHelper} inside car service
43  */
44 public final class CarServiceHelperWrapper {
45 
46     private static final String TAG = CarServiceHelperWrapper.class.getSimpleName();
47 
48     // If car service helper is not available for more than this time, we will throw exception.
49     private static final long CAR_SERVICE_HELPER_WAIT_TIME_MS = 20_000;
50 
51     private static final String REMOTE_EXCEPTION_STR = "CarServiceHelper threw RemoteException";
52 
53     private final long mCarServiceHelperWaitTimeoutMs;
54 
55     private final Object mLock = new Object();
56 
57     @GuardedBy("mLock")
58     @Nullable
59     private ICarServiceHelper mICarServiceHelper;
60 
61     @GuardedBy("mLock")
62     @Nullable
63     private ArrayList<Runnable> mConnectionRunnables;
64 
65     /**
66      * Factory method for {@code ICarImpl}.
67      */
create()68     public static CarServiceHelperWrapper create() {
69         return create(CAR_SERVICE_HELPER_WAIT_TIME_MS);
70     }
71 
72     /** Factory method to create with non-default wait timeout. This is for testing only. */
73     @VisibleForTesting
create(long carServiceHelperWaitTimeoutMs)74     public static CarServiceHelperWrapper create(long carServiceHelperWaitTimeoutMs) {
75         CarServiceHelperWrapper wrapper = new CarServiceHelperWrapper(
76                 carServiceHelperWaitTimeoutMs);
77         CarLocalServices.addService(CarServiceHelperWrapper.class, wrapper);
78         return wrapper;
79     }
80 
81     /**
82      * Notifies CarServiceHelper connection. Only for {@code ICarImpl}.
83      */
setCarServiceHelper(@onNull ICarServiceHelper carServiceHelper)84     public void setCarServiceHelper(@NonNull ICarServiceHelper carServiceHelper) {
85         Objects.requireNonNull(carServiceHelper);
86 
87         ArrayList<Runnable> connectionRunnables;
88         synchronized (mLock) {
89             mICarServiceHelper = carServiceHelper;
90             // This is thread safe as mConnectionRunnables is no longer accessed after connection.
91             connectionRunnables = mConnectionRunnables;
92             mConnectionRunnables = null;
93             mLock.notifyAll();
94         }
95         if (connectionRunnables != null) {
96             for (int i = 0; i < connectionRunnables.size(); i++) {
97                 connectionRunnables.get(i).run();
98             }
99         }
100     }
101 
102     /**
103      * Returns a singleton instance.
104      */
getInstance()105     public static CarServiceHelperWrapper getInstance() {
106         return CarLocalServices.getService(CarServiceHelperWrapper.class);
107     }
108 
109     /**
110      * Runs the passed {@code Runnable} when {@code CarServiceHelper} is connected. If it is already
111      * connected, it will run inside the call.
112      */
runOnConnection(Runnable r)113     public void runOnConnection(Runnable r) {
114         boolean alreadyConnected;
115         synchronized (mLock) {
116             alreadyConnected = mICarServiceHelper != null;
117             if (!alreadyConnected) {
118                 if (mConnectionRunnables == null) {
119                     mConnectionRunnables = new ArrayList<>();
120                 }
121                 mConnectionRunnables.add(r);
122             }
123         }
124         if (alreadyConnected) {
125             r.run();
126         }
127     }
128 
129     /**
130      * See {@code ICarServiceHelper}.
131      */
setDisplayAllowlistForUser(@serIdInt int userId, int[] displayIds)132     public void setDisplayAllowlistForUser(@UserIdInt int userId, int[] displayIds) {
133         try {
134             waitForCarServiceHelper().setDisplayAllowlistForUser(userId, displayIds);
135         } catch (RemoteException e) {
136             Slogf.e(TAG, REMOTE_EXCEPTION_STR, e);
137         }
138     }
139 
140     /**
141      * See {@code ICarServiceHelper}.
142      */
setPassengerDisplays(int[] displayIds)143     public void setPassengerDisplays(int[] displayIds) {
144         try {
145             waitForCarServiceHelper().setPassengerDisplays(displayIds);
146         } catch (RemoteException e) {
147             Slogf.e(TAG, REMOTE_EXCEPTION_STR, e);
148         }
149     }
150 
151     /**
152      * See {@code ICarServiceHelper}.
153      */
setSafetyMode(boolean safe)154     public void setSafetyMode(boolean safe) {
155         try {
156             waitForCarServiceHelper().setSafetyMode(safe);
157         } catch (RemoteException e) {
158             Slogf.e(TAG, REMOTE_EXCEPTION_STR, e);
159         }
160     }
161 
162     /**
163      * See {@code ICarServiceHelper}.
164      */
165     @Nullable
createUserEvenWhenDisallowed(String name, String userType, int flags)166     public UserHandle createUserEvenWhenDisallowed(String name, String userType, int flags) {
167         try {
168             return waitForCarServiceHelper().createUserEvenWhenDisallowed(name, userType, flags);
169         } catch (RemoteException e) {
170             Slogf.e(TAG, REMOTE_EXCEPTION_STR, e);
171         }
172         return null;
173     }
174 
175     /**
176      * See {@code ICarServiceHelper}.
177      */
setPersistentActivity(ComponentName activity, int displayId, int featureId)178     public int setPersistentActivity(ComponentName activity, int displayId, int featureId) {
179         try {
180             return waitForCarServiceHelper().setPersistentActivity(activity, displayId, featureId);
181         } catch (RemoteException e) {
182             Slogf.e(TAG, REMOTE_EXCEPTION_STR, e);
183         }
184         return CarActivityManager.RESULT_FAILURE;
185     }
186 
187     /**
188      * See {@code ICarServiceHelper}.
189      */
setPersistentActivitiesOnRootTask(List<ComponentName> activities, IBinder rootTaskToken)190     public void setPersistentActivitiesOnRootTask(List<ComponentName> activities,
191             IBinder rootTaskToken) {
192         try {
193             waitForCarServiceHelper().setPersistentActivitiesOnRootTask(activities, rootTaskToken);
194         } catch (RemoteException e) {
195             Slogf.e(TAG, REMOTE_EXCEPTION_STR, e);
196         }
197     }
198 
199     /**
200      * See {@code ICarServiceHelper}.
201      */
sendInitialUser(UserHandle user)202     public void sendInitialUser(UserHandle user) {
203         try {
204             waitForCarServiceHelper().sendInitialUser(user);
205         } catch (RemoteException e) {
206             Slogf.e(TAG, REMOTE_EXCEPTION_STR, e);
207         }
208     }
209 
210     /**
211      * See {@code ICarServiceHelper}.
212      */
setProcessGroup(int pid, int group)213     public void setProcessGroup(int pid, int group) {
214         try {
215             waitForCarServiceHelper().setProcessGroup(pid, group);
216         } catch (RemoteException e) {
217             Slogf.e(TAG, REMOTE_EXCEPTION_STR, e);
218         }
219     }
220 
221     /**
222      * See {@code ICarServiceHelper}.
223      */
getProcessGroup(int pid)224     public int getProcessGroup(int pid) {
225         try {
226             return waitForCarServiceHelper().getProcessGroup(pid);
227         } catch (RemoteException e) {
228             Slogf.e(TAG, REMOTE_EXCEPTION_STR, e);
229         }
230         return -1; // invalid id
231     }
232 
233     /**
234      * See {@code ICarServiceHelper}.
235      */
getMainDisplayAssignedToUser(int userId)236     public int getMainDisplayAssignedToUser(int userId) {
237         try {
238             return waitForCarServiceHelper().getMainDisplayAssignedToUser(userId);
239         } catch (RemoteException e) {
240             Slogf.e(TAG, REMOTE_EXCEPTION_STR, e);
241         }
242         return Display.INVALID_DISPLAY;
243     }
244 
245     /**
246      * See {@code ICarServiceHelper}.
247      */
getUserAssignedToDisplay(int displayId)248     public int getUserAssignedToDisplay(int displayId) {
249         try {
250             return waitForCarServiceHelper().getUserAssignedToDisplay(displayId);
251         } catch (RemoteException e) {
252             Slogf.e(TAG, REMOTE_EXCEPTION_STR, e);
253         }
254         return UserManagerHelper.USER_NULL;
255     }
256 
257     /**
258      * See {@code ICarServiceHelper}.
259      */
startUserInBackgroundVisibleOnDisplay(int userId, int displayId)260     public boolean startUserInBackgroundVisibleOnDisplay(int userId, int displayId) {
261         try {
262             return waitForCarServiceHelper().startUserInBackgroundVisibleOnDisplay(userId,
263                     displayId);
264         } catch (RemoteException e) {
265             Slogf.e(TAG, REMOTE_EXCEPTION_STR, e);
266         }
267         return false;
268     }
269 
270     /**
271      * See {@code ICarServiceHelper}.
272      */
setProcessProfile(int pid, int uid, @NonNull String profile)273     public void setProcessProfile(int pid, int uid, @NonNull String profile) {
274         try {
275             waitForCarServiceHelper().setProcessProfile(pid, uid, profile);
276         } catch (RemoteException e) {
277             Slogf.e(TAG, REMOTE_EXCEPTION_STR, e);
278         }
279     }
280 
281     /**
282      * See {@code ICarServiceHelper}.
283      */
fetchAidlVhalPid()284     public int fetchAidlVhalPid() {
285         try {
286             return waitForCarServiceHelper().fetchAidlVhalPid();
287         } catch (RemoteException e) {
288             Slogf.e(TAG, REMOTE_EXCEPTION_STR, e);
289         }
290         return INVALID_PID;
291     }
292 
293     /**
294      * @return true if a package requires launching in automotive compatibility mode.
295      */
requiresDisplayCompat(String packageName)296     public boolean requiresDisplayCompat(String packageName) {
297         try {
298             return waitForCarServiceHelper().requiresDisplayCompat(packageName);
299         } catch (RemoteException e) {
300             Slogf.e(TAG, REMOTE_EXCEPTION_STR, e);
301         }
302         return false;
303     }
304 
CarServiceHelperWrapper(long carServiceHelperWaitTimeoutMs)305     private CarServiceHelperWrapper(long carServiceHelperWaitTimeoutMs) {
306         mCarServiceHelperWaitTimeoutMs = carServiceHelperWaitTimeoutMs;
307     }
308 
309     @SuppressWarnings("WaitNotInLoop")
waitForCarServiceHelper()310     private ICarServiceHelper waitForCarServiceHelper() {
311         synchronized (mLock) {
312             if (mICarServiceHelper == null) {
313                 try {
314                     // This only wait once as timeout will crash the car service with exception.
315                     mLock.wait(mCarServiceHelperWaitTimeoutMs);
316                 } catch (InterruptedException e) {
317                     Thread.currentThread().interrupt();
318                     Slogf.w(TAG, "Thread was interrupted while waiting for CarServiceHelper",
319                             e);
320                     // just continue as we cannot return null instance.
321                 }
322                 if (mICarServiceHelper == null) {
323                     throw new IllegalStateException("CarServiceHelper did not connect");
324                 }
325             }
326             return mICarServiceHelper;
327         }
328     }
329 }
330