1 /* 2 * Copyright (C) 2014 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 package com.android.server.hdmi; 17 18 import android.os.Handler; 19 import android.os.Looper; 20 import android.os.Message; 21 import android.util.Pair; 22 import android.util.Slog; 23 24 import com.android.internal.annotations.VisibleForTesting; 25 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback; 26 27 import java.util.ArrayList; 28 import java.util.List; 29 30 /** 31 * Encapsulates a sequence of CEC command exchange for a certain feature. 32 * <p> 33 * Many CEC features are accomplished by CEC devices on the bus exchanging more than one 34 * command. {@link HdmiCecFeatureAction} represents the life cycle of the communication, manages the 35 * state as the process progresses, and if necessary, returns the result to the caller which 36 * initiates the action, through the callback given at the creation of the object. All the actual 37 * action classes inherit FeatureAction. 38 * <p> 39 * More than one FeatureAction objects can be up and running simultaneously, maintained by 40 * {@link HdmiCecLocalDevice}. Each action is passed a new command arriving from the bus, and either 41 * consumes it if the command is what the action expects, or yields it to other action. Declared as 42 * package private, accessed by {@link HdmiControlService} only. 43 */ 44 abstract class HdmiCecFeatureAction { 45 private static final String TAG = "HdmiCecFeatureAction"; 46 47 // Timer handler message used for timeout event 48 protected static final int MSG_TIMEOUT = 100; 49 50 // Default state used in common by all the feature actions. 51 protected static final int STATE_NONE = 0; 52 53 // Internal state indicating the progress of action. 54 protected int mState = STATE_NONE; 55 56 private final HdmiControlService mService; 57 private final HdmiCecLocalDevice mSource; 58 59 // Timer that manages timeout events. 60 protected ActionTimer mActionTimer; 61 62 private ArrayList<Pair<HdmiCecFeatureAction, Runnable>> mOnFinishedCallbacks; 63 HdmiCecFeatureAction(HdmiCecLocalDevice source)64 HdmiCecFeatureAction(HdmiCecLocalDevice source) { 65 mSource = source; 66 mService = mSource.getService(); 67 mActionTimer = createActionTimer(mService.getServiceLooper()); 68 } 69 70 @VisibleForTesting setActionTimer(ActionTimer actionTimer)71 void setActionTimer(ActionTimer actionTimer) { 72 mActionTimer = actionTimer; 73 } 74 75 /** 76 * Called after the action is created. Initialization or first step to take 77 * for the action can be done in this method. Shall update {@code mState} to 78 * indicate that the action has started. 79 * 80 * @return true if the operation is successful; otherwise false. 81 */ start()82 abstract boolean start(); 83 84 /** 85 * Process the command. Called whenever a new command arrives. 86 * 87 * @param cmd command to process 88 * @return true if the command was consumed in the process; Otherwise false. 89 */ processCommand(HdmiCecMessage cmd)90 abstract boolean processCommand(HdmiCecMessage cmd); 91 92 /** 93 * Called when the action should handle the timer event it created before. 94 * 95 * <p>CEC standard mandates each command transmission should be responded within 96 * certain period of time. The method is called when the timer it created as it transmitted 97 * a command gets expired. Inner logic should take an appropriate action. 98 * 99 * @param state the state associated with the time when the timer was created 100 */ handleTimerEvent(int state)101 abstract void handleTimerEvent(int state); 102 103 /** 104 * Timer handler interface used for FeatureAction classes. 105 */ 106 interface ActionTimer { 107 /** 108 * Send a timer message. 109 * 110 * Also carries the state of the action when the timer is created. Later this state is 111 * compared to the one the action is in when it receives the timer to let the action tell 112 * the right timer to handle. 113 * 114 * @param state state of the action is in 115 * @param delayMillis amount of delay for the timer 116 */ sendTimerMessage(int state, long delayMillis)117 void sendTimerMessage(int state, long delayMillis); 118 119 /** 120 * Removes any pending timer message. 121 */ clearTimerMessage()122 void clearTimerMessage(); 123 } 124 125 private class ActionTimerHandler extends Handler implements ActionTimer { 126 ActionTimerHandler(Looper looper)127 public ActionTimerHandler(Looper looper) { 128 super(looper); 129 } 130 131 @Override sendTimerMessage(int state, long delayMillis)132 public void sendTimerMessage(int state, long delayMillis) { 133 // The third argument(0) is not used. 134 sendMessageDelayed(obtainMessage(MSG_TIMEOUT, state, 0), delayMillis); 135 } 136 137 @Override clearTimerMessage()138 public void clearTimerMessage() { 139 removeMessages(MSG_TIMEOUT); 140 } 141 142 @Override handleMessage(Message msg)143 public void handleMessage(Message msg) { 144 switch (msg.what) { 145 case MSG_TIMEOUT: 146 handleTimerEvent(msg.arg1); 147 break; 148 default: 149 Slog.w(TAG, "Unsupported message:" + msg.what); 150 break; 151 } 152 } 153 } 154 createActionTimer(Looper looper)155 private ActionTimer createActionTimer(Looper looper) { 156 return new ActionTimerHandler(looper); 157 } 158 159 // Add a new timer. The timer event will come to mActionTimer.handleMessage() in 160 // delayMillis. addTimer(int state, int delayMillis)161 protected void addTimer(int state, int delayMillis) { 162 mActionTimer.sendTimerMessage(state, delayMillis); 163 } 164 started()165 boolean started() { 166 return mState != STATE_NONE; 167 } 168 sendCommand(HdmiCecMessage cmd)169 protected final void sendCommand(HdmiCecMessage cmd) { 170 mService.sendCecCommand(cmd); 171 } 172 sendCommand(HdmiCecMessage cmd, HdmiControlService.SendMessageCallback callback)173 protected final void sendCommand(HdmiCecMessage cmd, 174 HdmiControlService.SendMessageCallback callback) { 175 mService.sendCecCommand(cmd, callback); 176 } 177 addAndStartAction(HdmiCecFeatureAction action)178 protected final void addAndStartAction(HdmiCecFeatureAction action) { 179 mSource.addAndStartAction(action); 180 } 181 getActions(final Class<T> clazz)182 protected final <T extends HdmiCecFeatureAction> List<T> getActions(final Class<T> clazz) { 183 return mSource.getActions(clazz); 184 } 185 getCecMessageCache()186 protected final HdmiCecMessageCache getCecMessageCache() { 187 return mSource.getCecMessageCache(); 188 } 189 190 /** 191 * Remove the action from the action queue. This is called after the action finishes 192 * its role. 193 * 194 * @param action 195 */ removeAction(HdmiCecFeatureAction action)196 protected final void removeAction(HdmiCecFeatureAction action) { 197 mSource.removeAction(action); 198 } 199 removeAction(final Class<T> clazz)200 protected final <T extends HdmiCecFeatureAction> void removeAction(final Class<T> clazz) { 201 mSource.removeActionExcept(clazz, null); 202 } 203 removeActionExcept(final Class<T> clazz, final HdmiCecFeatureAction exception)204 protected final <T extends HdmiCecFeatureAction> void removeActionExcept(final Class<T> clazz, 205 final HdmiCecFeatureAction exception) { 206 mSource.removeActionExcept(clazz, exception); 207 } 208 pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount)209 protected final void pollDevices(DevicePollingCallback callback, int pickStrategy, 210 int retryCount) { 211 mService.pollDevices(callback, getSourceAddress(), pickStrategy, retryCount); 212 } 213 214 /** 215 * Clean up action's state. 216 * 217 * <p>Declared as package-private. Only {@link HdmiControlService} can access it. 218 */ clear()219 void clear() { 220 mState = STATE_NONE; 221 // Clear all timers. 222 mActionTimer.clearTimerMessage(); 223 } 224 225 /** 226 * Finish up the action. Reset the state, and remove itself from the action queue. 227 */ finish()228 protected void finish() { 229 finish(true); 230 } 231 finish(boolean removeSelf)232 void finish(boolean removeSelf) { 233 clear(); 234 if (removeSelf) { 235 removeAction(this); 236 } 237 if (mOnFinishedCallbacks != null) { 238 for (Pair<HdmiCecFeatureAction, Runnable> actionCallbackPair: mOnFinishedCallbacks) { 239 if (actionCallbackPair.first.mState != STATE_NONE) { 240 actionCallbackPair.second.run(); 241 } 242 } 243 mOnFinishedCallbacks = null; 244 } 245 } 246 localDevice()247 protected final HdmiCecLocalDevice localDevice() { 248 return mSource; 249 } 250 playback()251 protected final HdmiCecLocalDevicePlayback playback() { 252 return (HdmiCecLocalDevicePlayback) mSource; 253 } 254 source()255 protected final HdmiCecLocalDeviceSource source() { 256 return (HdmiCecLocalDeviceSource) mSource; 257 } 258 tv()259 protected final HdmiCecLocalDeviceTv tv() { 260 return (HdmiCecLocalDeviceTv) mSource; 261 } 262 audioSystem()263 protected final HdmiCecLocalDeviceAudioSystem audioSystem() { 264 return (HdmiCecLocalDeviceAudioSystem) mSource; 265 } 266 getSourceAddress()267 protected final int getSourceAddress() { 268 return mSource.getDeviceInfo().getLogicalAddress(); 269 } 270 getSourcePath()271 protected final int getSourcePath() { 272 return mSource.getDeviceInfo().getPhysicalAddress(); 273 } 274 sendUserControlPressedAndReleased(int targetAddress, int uiCommand)275 protected final void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) { 276 mSource.sendUserControlPressedAndReleased(targetAddress, uiCommand); 277 } 278 addOnFinishedCallback(HdmiCecFeatureAction action, Runnable runnable)279 protected final void addOnFinishedCallback(HdmiCecFeatureAction action, Runnable runnable) { 280 if (mOnFinishedCallbacks == null) { 281 mOnFinishedCallbacks = new ArrayList<>(); 282 } 283 mOnFinishedCallbacks.add(Pair.create(action, runnable)); 284 } 285 } 286