1 /*
2  * Copyright (C) 2015 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 android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
20 
21 import static com.android.car.CarServiceUtils.getHandlerThread;
22 import static com.android.car.CarServiceUtils.isEventOfType;
23 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
24 
25 import android.car.ICarPerUserService;
26 import android.car.builtin.util.Slogf;
27 import android.car.user.CarUserManager.UserLifecycleListener;
28 import android.car.user.UserLifecycleEventFilter;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.ServiceConnection;
33 import android.os.Handler;
34 import android.os.IBinder;
35 import android.os.UserHandle;
36 import android.util.Log;
37 import android.util.proto.ProtoOutputStream;
38 
39 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
40 import com.android.car.internal.util.IndentingPrintWriter;
41 import com.android.car.user.CarUserService;
42 import com.android.internal.annotations.GuardedBy;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 
47 /**
48  * A Helper class that helps with the following:
49  * 1. Provide methods to Bind/Unbind to the {@link CarPerUserService} as the current User
50  * 2. Set up a listener to UserSwitch Broadcasts and call clients that have registered callbacks.
51  *
52  */
53 public class CarPerUserServiceHelper implements CarServiceBase {
54 
55     private static final String TAG = CarLog.tagFor(CarPerUserServiceHelper.class);
56     private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
57 
58     private final Context mContext;
59     private final CarUserService mUserService;
60     private final Handler mHandler;
61 
62     private ICarPerUserService mCarPerUserService;
63     // listener to call on a ServiceConnection to CarPerUserService
64     private List<ServiceCallback> mServiceCallbacks;
65     private final Object mServiceBindLock = new Object();
66     @GuardedBy("mServiceBindLock")
67     private boolean mBound;
68 
CarPerUserServiceHelper(Context context, CarUserService userService)69     public CarPerUserServiceHelper(Context context, CarUserService userService) {
70         mContext = context;
71         mServiceCallbacks = new ArrayList<>();
72         mUserService = userService;
73         mHandler = new Handler(getHandlerThread(
74                 CarPerUserServiceHelper.class.getSimpleName()).getLooper());
75         UserLifecycleEventFilter userSwitchingEventFilter = new UserLifecycleEventFilter.Builder()
76                 .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING).build();
77         mUserService.addUserLifecycleListener(userSwitchingEventFilter, mUserLifecycleListener);
78     }
79 
80     @Override
init()81     public void init() {
82         synchronized (mServiceBindLock) {
83             bindToCarPerUserService();
84         }
85     }
86 
87     @Override
release()88     public void release() {
89         synchronized (mServiceBindLock) {
90             unbindFromCarPerUserService();
91             mUserService.removeUserLifecycleListener(mUserLifecycleListener);
92         }
93     }
94 
95     private final UserLifecycleListener mUserLifecycleListener = event -> {
96         if (!isEventOfType(TAG, event, USER_LIFECYCLE_EVENT_TYPE_SWITCHING)) {
97             return;
98         }
99         if (DBG) {
100             Slogf.d(TAG, "onEvent(" + event + ")");
101         }
102         List<ServiceCallback> callbacks;
103         int userId = event.getUserId();
104         if (DBG) {
105             Slogf.d(TAG, "User Switch Happened. New User" + userId);
106         }
107 
108         // Before unbinding, notify the callbacks about unbinding from the service
109         // so the callbacks can clean up their state through the binder before the service is
110         // killed.
111         synchronized (mServiceBindLock) {
112             // copy the callbacks
113             callbacks = new ArrayList<>(mServiceCallbacks);
114         }
115         // call them
116         for (ServiceCallback callback : callbacks) {
117             callback.onPreUnbind();
118         }
119         // unbind from the service running as the previous user.
120         unbindFromCarPerUserService();
121         // bind to the service running as the new user
122         bindToCarPerUserService();
123     };
124 
125     /**
126      * ServiceConnection to detect connecting/disconnecting to {@link CarPerUserService}
127      */
128     private final ServiceConnection mUserServiceConnection = new ServiceConnection() {
129         // Handle ServiceConnection on a separate thread because the tasks performed on service
130         // connected/disconnected take long time to complete and block the executing thread.
131         // Executing these tasks on the main thread will result in CarService ANR.
132 
133         // On connecting to the service, get the binder object to the CarBluetoothService
134         @Override
135         public void onServiceConnected(ComponentName componentName, IBinder service) {
136             mHandler.post(() -> {
137                 List<ServiceCallback> callbacks;
138                 if (DBG) {
139                     Slogf.d(TAG, "Connected to User Service");
140                 }
141                 mCarPerUserService = ICarPerUserService.Stub.asInterface(service);
142                 if (mCarPerUserService != null) {
143                     synchronized (mServiceBindLock) {
144                         // copy the callbacks
145                         callbacks = new ArrayList<>(mServiceCallbacks);
146                     }
147                     // call them
148                     for (ServiceCallback callback : callbacks) {
149                         callback.onServiceConnected(mCarPerUserService);
150                     }
151                 }
152             });
153         }
154 
155         @Override
156         public void onServiceDisconnected(ComponentName componentName) {
157             mHandler.post(() -> {
158                 List<ServiceCallback> callbacks;
159                 if (DBG) {
160                     Slogf.d(TAG, "Disconnected from User Service");
161                 }
162                 synchronized (mServiceBindLock) {
163                     // copy the callbacks
164                     callbacks = new ArrayList<>(mServiceCallbacks);
165                 }
166                 // call them
167                 for (ServiceCallback callback : callbacks) {
168                     callback.onServiceDisconnected();
169                 }
170             });
171         }
172     };
173 
174     /**
175      * Bind to the CarPerUserService {@link CarPerUserService} which is created to run as the
176      * Current User.
177      */
bindToCarPerUserService()178     private void bindToCarPerUserService() {
179         if (DBG) {
180             Slogf.d(TAG, "Binding to User service");
181         }
182         // This crosses both process and package boundary.
183         Intent startIntent = BuiltinPackageDependency.addClassNameToIntent(mContext, new Intent(),
184                 BuiltinPackageDependency.CAR_USER_PER_SERVICE_CLASS);
185         synchronized (mServiceBindLock) {
186             mBound = true;
187             boolean bindSuccess = mContext.bindServiceAsUser(startIntent, mUserServiceConnection,
188                     mContext.BIND_AUTO_CREATE, UserHandle.CURRENT);
189             // If valid connection not obtained, unbind
190             if (!bindSuccess) {
191                 Slogf.e(TAG, "bindToCarPerUserService() failed to get valid connection");
192                 unbindFromCarPerUserService();
193             }
194         }
195     }
196 
197     /**
198      * Unbind from the {@link CarPerUserService} running as the Current user.
199      */
unbindFromCarPerUserService()200     private void unbindFromCarPerUserService() {
201         synchronized (mServiceBindLock) {
202             // mBound flag makes sure we are unbinding only when the service is bound.
203             if (mBound) {
204                 if (DBG) {
205                     Slogf.d(TAG, "Unbinding from User Service");
206                 }
207                 mContext.unbindService(mUserServiceConnection);
208                 mBound = false;
209             }
210         }
211     }
212 
213     /**
214      * Register a listener that gets called on Connection state changes to the
215      * {@link CarPerUserService}
216      * @param listener - Callback to invoke on user switch event.
217      */
registerServiceCallback(ServiceCallback listener)218     public void registerServiceCallback(ServiceCallback listener) {
219         if (listener != null) {
220             if (DBG) {
221                 Slogf.d(TAG, "Registering CarPerUserService Listener");
222             }
223             synchronized (mServiceBindLock) {
224                 mServiceCallbacks.add(listener);
225             }
226         }
227     }
228 
229     /**
230      * Unregister the Service Listener
231      * @param listener - Callback method to unregister
232      */
unregisterServiceCallback(ServiceCallback listener)233     public void unregisterServiceCallback(ServiceCallback listener) {
234         if (DBG) {
235             Slogf.d(TAG, "Unregistering CarPerUserService Listener");
236         }
237         if (listener != null) {
238             synchronized (mServiceBindLock) {
239                 mServiceCallbacks.remove(listener);
240             }
241         }
242     }
243 
244     /**
245      * Listener to the CarPerUserService connection status that clients need to implement.
246      */
247     public interface ServiceCallback {
248         /**
249          * Invoked when a service connects.
250          *
251          * @param carPerUserService the instance of ICarPerUserService.
252          */
onServiceConnected(ICarPerUserService carPerUserService)253         void onServiceConnected(ICarPerUserService carPerUserService);
254 
255         /**
256          * Invoked before an unbind call is going to be made.
257          */
onPreUnbind()258         void onPreUnbind();
259 
260         /**
261          * Invoked when a service is crashed or disconnected.
262          */
onServiceDisconnected()263         void onServiceDisconnected();
264     }
265 
266     @Override
267     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter pw)268     public final void dump(IndentingPrintWriter pw) {
269         pw.println("CarPerUserServiceHelper");
270         pw.increaseIndent();
271         synchronized (mServiceBindLock) {
272             pw.printf("bound: %b\n", mBound);
273             if (mServiceCallbacks == null) {
274                 pw.println("no callbacks");
275             } else {
276                 int size = mServiceCallbacks.size();
277                 pw.printf("%d callback%s\n", size, (size > 1 ? "s" : ""));
278             }
279         }
280         pw.decreaseIndent();
281     }
282 
283     @Override
284     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)285     public void dumpProto(ProtoOutputStream proto) {}
286 }
287