/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.car; import android.car.ICarUserService; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.os.IBinder; import android.os.UserHandle; import android.util.Log; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; import java.util.List; import java.util.ArrayList; /** * A Helper class that helps with the following: * 1. Provide methods to Bind/Unbind to the {@link PerUserCarService} as the current User * 2. Set up a listener to UserSwitch Broadcasts and call clients that have registered callbacks. * */ public class PerUserCarServiceHelper implements CarServiceBase { private static final String TAG = "PerUserCarSvcHelper"; private static boolean DBG = false; private Context mContext; private ICarUserService mCarUserService; // listener to call on a ServiceConnection to PerUserCarService private List mServiceCallbacks; private UserSwitchBroadcastReceiver mReceiver; private IntentFilter mUserSwitchFilter; private static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle"; private final Object mServiceBindLock = new Object(); @GuardedBy("mServiceBindLock") private boolean mBound = false; public PerUserCarServiceHelper(Context context) { mContext = context; mServiceCallbacks = new ArrayList<>(); mReceiver = new UserSwitchBroadcastReceiver(); setupUserSwitchListener(); } @Override public synchronized void init() { bindToPerUserCarService(); } @Override public synchronized void release() { unbindFromPerUserCarService(); } /** * Setting up the intent filter for * 2. UserSwitch events */ private void setupUserSwitchListener() { mUserSwitchFilter = new IntentFilter(); mUserSwitchFilter.addAction(Intent.ACTION_USER_SWITCHED); mContext.registerReceiver(mReceiver, mUserSwitchFilter); if (DBG) { Log.d(TAG, "UserSwitch Listener Registered"); } } /** * UserSwitchBroadcastReceiver receives broadcasts on User account switches. */ public class UserSwitchBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { List callbacks; if (DBG) { Log.d(TAG, "User Switch Happened"); boolean userSwitched = intent.getAction().equals( Intent.ACTION_USER_SWITCHED); int user = intent.getExtras().getInt(EXTRA_USER_HANDLE); if (userSwitched) { Log.d(TAG, "New User " + user); } } // Before unbinding, notify the callbacks about unbinding from the service // so the callbacks can clean up their state through the binder before the service is // killed. synchronized (this) { // copy the callbacks callbacks = new ArrayList<>(mServiceCallbacks); } // call them for (ServiceCallback callback : callbacks) { callback.onPreUnbind(); } // unbind from the service running as the previous user. unbindFromPerUserCarService(); // bind to the service running as the new user bindToPerUserCarService(); } } /** * ServiceConnection to detect connecting/disconnecting to {@link PerUserCarService} */ private final ServiceConnection mUserServiceConnection = new ServiceConnection() { // On connecting to the service, get the binder object to the CarBluetoothService @Override public void onServiceConnected(ComponentName componentName, IBinder service) { List callbacks; if (DBG) { Log.d(TAG, "Connected to User Service"); } mCarUserService = ICarUserService.Stub.asInterface(service); if (mCarUserService != null) { synchronized (this) { // copy the callbacks callbacks = new ArrayList<>(mServiceCallbacks); } // call them for (ServiceCallback callback : callbacks) { callback.onServiceConnected(mCarUserService); } } } @Override public void onServiceDisconnected(ComponentName componentName) { List callbacks; if (DBG) { Log.d(TAG, "Disconnected from User Service"); } synchronized (this) { // copy the callbacks callbacks = new ArrayList<>(mServiceCallbacks); } // call them for (ServiceCallback callback : callbacks) { callback.onServiceDisconnected(); } } }; /** * Bind to the CarUserService {@link PerUserCarService} which is created to run as the Current * User. * */ private void bindToPerUserCarService() { if (DBG) { Log.d(TAG, "Binding to User service"); } Intent startIntent = new Intent(mContext, PerUserCarService.class); synchronized (mServiceBindLock) { mBound = true; boolean bindSuccess = mContext.bindServiceAsUser(startIntent, mUserServiceConnection, mContext.BIND_AUTO_CREATE, UserHandle.CURRENT); // If valid connection not obtained, unbind if (!bindSuccess) { Log.e(TAG, "bindToPerUserCarService() failed to get valid connection"); unbindFromPerUserCarService(); } } } /** * Unbind from the {@link PerUserCarService} running as the Current user. */ private void unbindFromPerUserCarService() { synchronized (mServiceBindLock) { // mBound flag makes sure we are unbinding only when the service is bound. if (mBound) { if (DBG) { Log.d(TAG, "Unbinding from User Service"); } mContext.unbindService(mUserServiceConnection); mBound = false; } } } /** * Register a listener that gets called on Connection state changes to the * {@link PerUserCarService} * @param listener - Callback to invoke on user switch event. */ public void registerServiceCallback(ServiceCallback listener) { if (listener != null) { if (DBG) { Log.d(TAG, "Registering PerUserCarService Listener"); } synchronized (this) { mServiceCallbacks.add(listener); } } } /** * Unregister the Service Listener * @param listener - Callback method to unregister */ public void unregisterServiceCallback(ServiceCallback listener) { if (DBG) { Log.d(TAG, "Unregistering PerUserCarService Listener"); } if (listener != null) { synchronized (this) { mServiceCallbacks.remove(listener); } } } /** * Listener to the PerUserCarService connection status that clients need to implement. */ public interface ServiceCallback { // When Service Connects void onServiceConnected(ICarUserService carUserService); // Before an unbind call is going to be made. void onPreUnbind(); // When Service crashed or disconnected void onServiceDisconnected(); } @Override public synchronized void dump(PrintWriter writer) { } }