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