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