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