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