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