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