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