| /* |
| * Copyright 2017 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.server.telecom; |
| |
| import android.annotation.Nullable; |
| import android.content.ComponentName; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.telecom.Log; |
| import android.telecom.Logging.Session; |
| import android.text.TextUtils; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.stream.Collectors; |
| |
| public class ConnectionServiceFocusManager { |
| private static final String TAG = "ConnectionSvrFocusMgr"; |
| |
| /** Factory interface used to create the {@link ConnectionServiceFocusManager} instance. */ |
| public interface ConnectionServiceFocusManagerFactory { |
| ConnectionServiceFocusManager create(CallsManagerRequester requester, Looper looper); |
| } |
| |
| /** |
| * Interface used by ConnectionServiceFocusManager to communicate with |
| * {@link ConnectionServiceWrapper}. |
| */ |
| public interface ConnectionServiceFocus { |
| /** |
| * Notifies the {@link android.telecom.ConnectionService} that it has lose the connection |
| * service focus. It should release all call resource i.e camera, audio once it lost the |
| * focus. |
| */ |
| void connectionServiceFocusLost(); |
| |
| /** |
| * Notifies the {@link android.telecom.ConnectionService} that it has gain the connection |
| * service focus. It can request the call resource i.e camera, audio as they expected to be |
| * free at the moment. |
| */ |
| void connectionServiceFocusGained(); |
| |
| /** |
| * Sets the ConnectionServiceFocusListener. |
| * |
| * @see {@link ConnectionServiceFocusListener}. |
| */ |
| void setConnectionServiceFocusListener(ConnectionServiceFocusListener listener); |
| |
| /** |
| * Get the {@link ComponentName} of the ConnectionService for logging purposes. |
| * @return the {@link ComponentName}. |
| */ |
| ComponentName getComponentName(); |
| } |
| |
| /** |
| * Interface used to receive the changed of {@link android.telecom.ConnectionService} that |
| * ConnectionServiceFocusManager cares about. |
| */ |
| public interface ConnectionServiceFocusListener { |
| /** |
| * Calls when {@link android.telecom.ConnectionService} has released the call resource. This |
| * usually happen after the {@link android.telecom.ConnectionService} lost the focus. |
| * |
| * @param connectionServiceFocus the {@link android.telecom.ConnectionService} that released |
| * the call resources. |
| */ |
| void onConnectionServiceReleased(ConnectionServiceFocus connectionServiceFocus); |
| |
| /** |
| * Calls when {@link android.telecom.ConnectionService} is disconnected. |
| * |
| * @param connectionServiceFocus the {@link android.telecom.ConnectionService} which is |
| * disconnected. |
| */ |
| void onConnectionServiceDeath(ConnectionServiceFocus connectionServiceFocus); |
| } |
| |
| /** |
| * Interface define to expose few information of {@link Call} that ConnectionServiceFocusManager |
| * cares about. |
| */ |
| public interface CallFocus { |
| /** |
| * Returns the ConnectionService associated with the call. |
| */ |
| ConnectionServiceFocus getConnectionServiceWrapper(); |
| |
| /** |
| * Returns the state of the call. |
| * |
| * @see {@link CallState} |
| */ |
| int getState(); |
| } |
| |
| /** Interface define a call back for focus request event. */ |
| public interface RequestFocusCallback { |
| /** |
| * Invokes after the focus request is done. |
| * |
| * @param call the call associated with the focus request. |
| */ |
| void onRequestFocusDone(CallFocus call); |
| } |
| |
| /** |
| * Interface define to allow the ConnectionServiceFocusManager to communicate with |
| * {@link CallsManager}. |
| */ |
| public interface CallsManagerRequester { |
| /** |
| * Requests {@link CallsManager} to disconnect a {@link ConnectionServiceFocus}. This |
| * usually happen when the connection service doesn't respond to focus lost event. |
| */ |
| void releaseConnectionService(ConnectionServiceFocus connectionService); |
| |
| /** |
| * Sets the {@link com.android.server.telecom.CallsManager.CallsManagerListener} to listen |
| * the call event that ConnectionServiceFocusManager cares about. |
| */ |
| void setCallsManagerListener(CallsManager.CallsManagerListener listener); |
| } |
| |
| private static final int[] PRIORITY_FOCUS_CALL_STATE = new int[] { |
| CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING |
| }; |
| |
| private static final int MSG_REQUEST_FOCUS = 1; |
| private static final int MSG_RELEASE_CONNECTION_FOCUS = 2; |
| private static final int MSG_RELEASE_FOCUS_TIMEOUT = 3; |
| private static final int MSG_CONNECTION_SERVICE_DEATH = 4; |
| private static final int MSG_ADD_CALL = 5; |
| private static final int MSG_REMOVE_CALL = 6; |
| private static final int MSG_CALL_STATE_CHANGED = 7; |
| |
| @VisibleForTesting |
| public static final int RELEASE_FOCUS_TIMEOUT_MS = 5000; |
| |
| private final List<CallFocus> mCalls; |
| |
| private final CallsManagerListenerBase mCallsManagerListener = |
| new CallsManagerListenerBase() { |
| @Override |
| public void onCallAdded(Call call) { |
| if (callShouldBeIgnored(call)) { |
| return; |
| } |
| |
| mEventHandler |
| .obtainMessage(MSG_ADD_CALL, |
| new MessageArgs( |
| Log.createSubsession(), |
| "CSFM.oCA", |
| call)) |
| .sendToTarget(); |
| } |
| |
| @Override |
| public void onCallRemoved(Call call) { |
| if (callShouldBeIgnored(call)) { |
| return; |
| } |
| |
| mEventHandler |
| .obtainMessage(MSG_REMOVE_CALL, |
| new MessageArgs( |
| Log.createSubsession(), |
| "CSFM.oCR", |
| call)) |
| .sendToTarget(); |
| } |
| |
| @Override |
| public void onCallStateChanged(Call call, int oldState, int newState) { |
| if (callShouldBeIgnored(call)) { |
| return; |
| } |
| |
| mEventHandler |
| .obtainMessage(MSG_CALL_STATE_CHANGED, oldState, newState, |
| new MessageArgs( |
| Log.createSubsession(), |
| "CSFM.oCSS", |
| call)) |
| .sendToTarget(); |
| } |
| |
| @Override |
| public void onExternalCallChanged(Call call, boolean isExternalCall) { |
| if (isExternalCall) { |
| mEventHandler |
| .obtainMessage(MSG_REMOVE_CALL, |
| new MessageArgs( |
| Log.createSubsession(), |
| "CSFM.oECC", |
| call)) |
| .sendToTarget(); |
| } else { |
| mEventHandler |
| .obtainMessage(MSG_ADD_CALL, |
| new MessageArgs( |
| Log.createSubsession(), |
| "CSFM.oECC", |
| call)) |
| .sendToTarget(); |
| } |
| } |
| |
| boolean callShouldBeIgnored(Call call) { |
| return call.isExternalCall(); |
| } |
| }; |
| |
| private final ConnectionServiceFocusListener mConnectionServiceFocusListener = |
| new ConnectionServiceFocusListener() { |
| @Override |
| public void onConnectionServiceReleased( |
| ConnectionServiceFocus connectionServiceFocus) { |
| mEventHandler |
| .obtainMessage(MSG_RELEASE_CONNECTION_FOCUS, |
| new MessageArgs( |
| Log.createSubsession(), |
| "CSFM.oCSR", |
| connectionServiceFocus)) |
| .sendToTarget(); |
| } |
| |
| @Override |
| public void onConnectionServiceDeath( |
| ConnectionServiceFocus connectionServiceFocus) { |
| mEventHandler |
| .obtainMessage(MSG_CONNECTION_SERVICE_DEATH, |
| new MessageArgs( |
| Log.createSubsession(), |
| "CSFM.oCSD", |
| connectionServiceFocus)) |
| .sendToTarget(); |
| } |
| }; |
| |
| private ConnectionServiceFocus mCurrentFocus; |
| private CallFocus mCurrentFocusCall; |
| private CallsManagerRequester mCallsManagerRequester; |
| private FocusRequest mCurrentFocusRequest; |
| private FocusManagerHandler mEventHandler; |
| |
| public ConnectionServiceFocusManager( |
| CallsManagerRequester callsManagerRequester, Looper looper) { |
| mCallsManagerRequester = callsManagerRequester; |
| mCallsManagerRequester.setCallsManagerListener(mCallsManagerListener); |
| mEventHandler = new FocusManagerHandler(looper); |
| mCalls = new ArrayList<>(); |
| } |
| |
| /** |
| * Requests the call focus for the given call. The {@code callback} will be invoked once |
| * the request is done. |
| * @param focus the call need to be focus. |
| * @param callback the callback associated with this request. |
| */ |
| public void requestFocus(CallFocus focus, RequestFocusCallback callback) { |
| mEventHandler.obtainMessage(MSG_REQUEST_FOCUS, |
| new MessageArgs( |
| Log.createSubsession(), |
| "CSFM.rF", |
| new FocusRequest(focus, callback))) |
| .sendToTarget(); |
| } |
| |
| /** |
| * Returns the current focus call. The {@link android.telecom.ConnectionService} of the focus |
| * call is the current connection service focus. Also the state of the focus call must be one |
| * of {@link #PRIORITY_FOCUS_CALL_STATE}. |
| */ |
| public CallFocus getCurrentFocusCall() { |
| return mCurrentFocusCall; |
| } |
| |
| /** Returns the current connection service focus. */ |
| public ConnectionServiceFocus getCurrentFocusConnectionService() { |
| return mCurrentFocus; |
| } |
| |
| @VisibleForTesting |
| public Handler getHandler() { |
| return mEventHandler; |
| } |
| |
| @VisibleForTesting |
| public List<CallFocus> getAllCall() { return mCalls; } |
| |
| private void updateConnectionServiceFocus(ConnectionServiceFocus connSvrFocus) { |
| if (!Objects.equals(mCurrentFocus, connSvrFocus)) { |
| if (connSvrFocus != null) { |
| connSvrFocus.setConnectionServiceFocusListener(mConnectionServiceFocusListener); |
| connSvrFocus.connectionServiceFocusGained(); |
| } |
| mCurrentFocus = connSvrFocus; |
| Log.d(this, "updateConnectionServiceFocus connSvr = %s", connSvrFocus); |
| } |
| } |
| |
| private void updateCurrentFocusCall() { |
| mCurrentFocusCall = null; |
| |
| if (mCurrentFocus == null) { |
| return; |
| } |
| |
| List<CallFocus> calls = mCalls |
| .stream() |
| .filter(call -> mCurrentFocus.equals(call.getConnectionServiceWrapper())) |
| .collect(Collectors.toList()); |
| |
| for (int i = 0; i < PRIORITY_FOCUS_CALL_STATE.length; i++) { |
| for (CallFocus call : calls) { |
| if (call.getState() == PRIORITY_FOCUS_CALL_STATE[i]) { |
| mCurrentFocusCall = call; |
| Log.d(this, "updateCurrentFocusCall %s", mCurrentFocusCall); |
| return; |
| } |
| } |
| } |
| |
| Log.d(this, "updateCurrentFocusCall = null"); |
| } |
| |
| private void onRequestFocusDone(FocusRequest focusRequest) { |
| if (focusRequest.callback != null) { |
| focusRequest.callback.onRequestFocusDone(focusRequest.call); |
| } |
| } |
| |
| private void handleRequestFocus(FocusRequest focusRequest) { |
| Log.d(this, "handleRequestFocus req = %s", focusRequest); |
| if (mCurrentFocus == null |
| || mCurrentFocus.equals(focusRequest.call.getConnectionServiceWrapper())) { |
| updateConnectionServiceFocus(focusRequest.call.getConnectionServiceWrapper()); |
| updateCurrentFocusCall(); |
| onRequestFocusDone(focusRequest); |
| } else { |
| mCurrentFocus.connectionServiceFocusLost(); |
| mCurrentFocusRequest = focusRequest; |
| Message msg = mEventHandler.obtainMessage( |
| MSG_RELEASE_FOCUS_TIMEOUT, |
| new MessageArgs( |
| Log.createSubsession(), |
| "CSFM.hRF", |
| focusRequest)); |
| mEventHandler.sendMessageDelayed(msg, RELEASE_FOCUS_TIMEOUT_MS); |
| } |
| } |
| |
| private void handleReleasedFocus(ConnectionServiceFocus connectionServiceFocus) { |
| Log.d(this, "handleReleasedFocus connSvr = %s", connectionServiceFocus); |
| // The ConnectionService can call onConnectionServiceFocusReleased even if it's not the |
| // current focus connection service, nothing will be changed in this case. |
| if (Objects.equals(mCurrentFocus, connectionServiceFocus)) { |
| mEventHandler.removeMessages(MSG_RELEASE_FOCUS_TIMEOUT); |
| ConnectionServiceFocus newCSF = null; |
| if (mCurrentFocusRequest != null) { |
| newCSF = mCurrentFocusRequest.call.getConnectionServiceWrapper(); |
| } |
| updateConnectionServiceFocus(newCSF); |
| updateCurrentFocusCall(); |
| if (mCurrentFocusRequest != null) { |
| onRequestFocusDone(mCurrentFocusRequest); |
| mCurrentFocusRequest = null; |
| } |
| } |
| } |
| |
| private void handleReleasedFocusTimeout(FocusRequest focusRequest) { |
| Log.d(this, "handleReleasedFocusTimeout req = %s", focusRequest); |
| mCallsManagerRequester.releaseConnectionService(mCurrentFocus); |
| updateConnectionServiceFocus(focusRequest.call.getConnectionServiceWrapper()); |
| updateCurrentFocusCall(); |
| onRequestFocusDone(focusRequest); |
| mCurrentFocusRequest = null; |
| } |
| |
| private void handleConnectionServiceDeath(ConnectionServiceFocus connectionServiceFocus) { |
| Log.d(this, "handleConnectionServiceDeath %s", connectionServiceFocus); |
| if (Objects.equals(connectionServiceFocus, mCurrentFocus)) { |
| updateConnectionServiceFocus(null); |
| updateCurrentFocusCall(); |
| } |
| } |
| |
| private void handleAddedCall(CallFocus call) { |
| Log.d(this, "handleAddedCall %s", call); |
| if (!mCalls.contains(call)) { |
| mCalls.add(call); |
| } |
| if (Objects.equals(mCurrentFocus, call.getConnectionServiceWrapper())) { |
| updateCurrentFocusCall(); |
| } |
| } |
| |
| private void handleRemovedCall(CallFocus call) { |
| Log.d(this, "handleRemovedCall %s", call); |
| mCalls.remove(call); |
| if (call.equals(mCurrentFocusCall)) { |
| updateCurrentFocusCall(); |
| } |
| } |
| |
| private void handleCallStateChanged(CallFocus call, int oldState, int newState) { |
| Log.d(this, |
| "handleCallStateChanged %s, oldState = %d, newState = %d", |
| call, |
| oldState, |
| newState); |
| if (mCalls.contains(call) |
| && Objects.equals(mCurrentFocus, call.getConnectionServiceWrapper())) { |
| updateCurrentFocusCall(); |
| } |
| } |
| |
| private final class FocusManagerHandler extends Handler { |
| FocusManagerHandler(Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| Session session = ((MessageArgs) msg.obj).logSession; |
| String shortName = ((MessageArgs) msg.obj).shortName; |
| if (TextUtils.isEmpty(shortName)) { |
| shortName = "hM"; |
| } |
| Log.continueSession(session, shortName); |
| Object msgObj = ((MessageArgs) msg.obj).obj; |
| |
| try { |
| switch (msg.what) { |
| case MSG_REQUEST_FOCUS: |
| handleRequestFocus((FocusRequest) msgObj); |
| break; |
| case MSG_RELEASE_CONNECTION_FOCUS: |
| handleReleasedFocus((ConnectionServiceFocus) msgObj); |
| break; |
| case MSG_RELEASE_FOCUS_TIMEOUT: |
| handleReleasedFocusTimeout((FocusRequest) msgObj); |
| break; |
| case MSG_CONNECTION_SERVICE_DEATH: |
| handleConnectionServiceDeath((ConnectionServiceFocus) msgObj); |
| break; |
| case MSG_ADD_CALL: |
| handleAddedCall((CallFocus) msgObj); |
| break; |
| case MSG_REMOVE_CALL: |
| handleRemovedCall((CallFocus) msgObj); |
| break; |
| case MSG_CALL_STATE_CHANGED: |
| handleCallStateChanged((CallFocus) msgObj, msg.arg1, msg.arg2); |
| break; |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| } |
| |
| private static final class FocusRequest { |
| CallFocus call; |
| @Nullable RequestFocusCallback callback; |
| |
| FocusRequest(CallFocus call, RequestFocusCallback callback) { |
| this.call = call; |
| this.callback = callback; |
| } |
| } |
| |
| private static final class MessageArgs { |
| Session logSession; |
| String shortName; |
| Object obj; |
| |
| MessageArgs(Session logSession, String shortName, Object obj) { |
| this.logSession = logSession; |
| this.shortName = shortName; |
| this.obj = obj; |
| } |
| } |
| } |