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