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