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