/* * Copyright (C) 2014 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.hdmi; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.hdmi.HdmiControlService.DevicePollingCallback; import java.util.ArrayList; import java.util.List; /** * Encapsulates a sequence of CEC command exchange for a certain feature. *

* Many CEC features are accomplished by CEC devices on the bus exchanging more than one * command. {@link HdmiCecFeatureAction} represents the life cycle of the communication, manages the * state as the process progresses, and if necessary, returns the result to the caller which * initiates the action, through the callback given at the creation of the object. All the actual * action classes inherit FeatureAction. *

* More than one FeatureAction objects can be up and running simultaneously, maintained by * {@link HdmiCecLocalDevice}. Each action is passed a new command arriving from the bus, and either * consumes it if the command is what the action expects, or yields it to other action. Declared as * package private, accessed by {@link HdmiControlService} only. */ abstract class HdmiCecFeatureAction { private static final String TAG = "HdmiCecFeatureAction"; // Timer handler message used for timeout event protected static final int MSG_TIMEOUT = 100; // Default state used in common by all the feature actions. protected static final int STATE_NONE = 0; // Internal state indicating the progress of action. protected int mState = STATE_NONE; private final HdmiControlService mService; private final HdmiCecLocalDevice mSource; // Timer that manages timeout events. protected ActionTimer mActionTimer; private ArrayList> mOnFinishedCallbacks; HdmiCecFeatureAction(HdmiCecLocalDevice source) { mSource = source; mService = mSource.getService(); mActionTimer = createActionTimer(mService.getServiceLooper()); } @VisibleForTesting void setActionTimer(ActionTimer actionTimer) { mActionTimer = actionTimer; } /** * Called after the action is created. Initialization or first step to take * for the action can be done in this method. Shall update {@code mState} to * indicate that the action has started. * * @return true if the operation is successful; otherwise false. */ abstract boolean start(); /** * Process the command. Called whenever a new command arrives. * * @param cmd command to process * @return true if the command was consumed in the process; Otherwise false. */ abstract boolean processCommand(HdmiCecMessage cmd); /** * Called when the action should handle the timer event it created before. * *

CEC standard mandates each command transmission should be responded within * certain period of time. The method is called when the timer it created as it transmitted * a command gets expired. Inner logic should take an appropriate action. * * @param state the state associated with the time when the timer was created */ abstract void handleTimerEvent(int state); /** * Timer handler interface used for FeatureAction classes. */ interface ActionTimer { /** * Send a timer message. * * Also carries the state of the action when the timer is created. Later this state is * compared to the one the action is in when it receives the timer to let the action tell * the right timer to handle. * * @param state state of the action is in * @param delayMillis amount of delay for the timer */ void sendTimerMessage(int state, long delayMillis); /** * Removes any pending timer message. */ void clearTimerMessage(); } private class ActionTimerHandler extends Handler implements ActionTimer { public ActionTimerHandler(Looper looper) { super(looper); } @Override public void sendTimerMessage(int state, long delayMillis) { // The third argument(0) is not used. sendMessageDelayed(obtainMessage(MSG_TIMEOUT, state, 0), delayMillis); } @Override public void clearTimerMessage() { removeMessages(MSG_TIMEOUT); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_TIMEOUT: handleTimerEvent(msg.arg1); break; default: Slog.w(TAG, "Unsupported message:" + msg.what); break; } } } private ActionTimer createActionTimer(Looper looper) { return new ActionTimerHandler(looper); } // Add a new timer. The timer event will come to mActionTimer.handleMessage() in // delayMillis. protected void addTimer(int state, int delayMillis) { mActionTimer.sendTimerMessage(state, delayMillis); } boolean started() { return mState != STATE_NONE; } protected final void sendCommand(HdmiCecMessage cmd) { mService.sendCecCommand(cmd); } protected final void sendCommand(HdmiCecMessage cmd, HdmiControlService.SendMessageCallback callback) { mService.sendCecCommand(cmd, callback); } protected final void addAndStartAction(HdmiCecFeatureAction action) { mSource.addAndStartAction(action); } protected final List getActions(final Class clazz) { return mSource.getActions(clazz); } protected final HdmiCecMessageCache getCecMessageCache() { return mSource.getCecMessageCache(); } /** * Remove the action from the action queue. This is called after the action finishes * its role. * * @param action */ protected final void removeAction(HdmiCecFeatureAction action) { mSource.removeAction(action); } protected final void removeAction(final Class clazz) { mSource.removeActionExcept(clazz, null); } protected final void removeActionExcept(final Class clazz, final HdmiCecFeatureAction exception) { mSource.removeActionExcept(clazz, exception); } protected final void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) { mService.pollDevices(callback, getSourceAddress(), pickStrategy, retryCount); } /** * Clean up action's state. * *

Declared as package-private. Only {@link HdmiControlService} can access it. */ void clear() { mState = STATE_NONE; // Clear all timers. mActionTimer.clearTimerMessage(); } /** * Finish up the action. Reset the state, and remove itself from the action queue. */ protected void finish() { finish(true); } void finish(boolean removeSelf) { clear(); if (removeSelf) { removeAction(this); } if (mOnFinishedCallbacks != null) { for (Pair actionCallbackPair: mOnFinishedCallbacks) { if (actionCallbackPair.first.mState != STATE_NONE) { actionCallbackPair.second.run(); } } mOnFinishedCallbacks = null; } } protected final HdmiCecLocalDevice localDevice() { return mSource; } protected final HdmiCecLocalDevicePlayback playback() { return (HdmiCecLocalDevicePlayback) mSource; } protected final HdmiCecLocalDeviceTv tv() { return (HdmiCecLocalDeviceTv) mSource; } protected final int getSourceAddress() { return mSource.getDeviceInfo().getLogicalAddress(); } protected final int getSourcePath() { return mSource.getDeviceInfo().getPhysicalAddress(); } protected final void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) { mSource.sendUserControlPressedAndReleased(targetAddress, uiCommand); } protected final void addOnFinishedCallback(HdmiCecFeatureAction action, Runnable runnable) { if (mOnFinishedCallbacks == null) { mOnFinishedCallbacks = new ArrayList<>(); } mOnFinishedCallbacks.add(Pair.create(action, runnable)); } }