Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 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 | |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 18 | import android.os.Handler; |
| 19 | import android.os.Looper; |
| 20 | import android.os.Message; |
Yuncheol Heo | c516d65 | 2014-07-11 18:23:24 +0900 | [diff] [blame] | 21 | import android.util.Pair; |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 22 | import android.util.Slog; |
| 23 | |
| 24 | import com.android.internal.annotations.VisibleForTesting; |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 25 | import com.android.server.hdmi.HdmiControlService.DevicePollingCallback; |
| 26 | |
Yuncheol Heo | c516d65 | 2014-07-11 18:23:24 +0900 | [diff] [blame] | 27 | import java.util.ArrayList; |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 28 | import java.util.List; |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 29 | |
| 30 | /** |
Jungshik Jang | 7df5286 | 2014-08-11 14:35:27 +0900 | [diff] [blame] | 31 | * Encapsulates a sequence of CEC command exchange for a certain feature. |
Jungshik Jang | b509c2e | 2014-08-07 13:45:01 +0900 | [diff] [blame] | 32 | * <p> |
Jungshik Jang | 7df5286 | 2014-08-11 14:35:27 +0900 | [diff] [blame] | 33 | * Many CEC features are accomplished by CEC devices on the bus exchanging more than one |
Jungshik Jang | b509c2e | 2014-08-07 13:45:01 +0900 | [diff] [blame] | 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. |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 43 | */ |
Jungshik Jang | b509c2e | 2014-08-07 13:45:01 +0900 | [diff] [blame] | 44 | abstract class HdmiCecFeatureAction { |
Jungshik Jang | 7df5286 | 2014-08-11 14:35:27 +0900 | [diff] [blame] | 45 | private static final String TAG = "HdmiCecFeatureAction"; |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 46 | |
| 47 | // Timer handler message used for timeout event |
| 48 | protected static final int MSG_TIMEOUT = 100; |
| 49 | |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 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 | |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 56 | private final HdmiControlService mService; |
| 57 | private final HdmiCecLocalDevice mSource; |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 58 | |
| 59 | // Timer that manages timeout events. |
| 60 | protected ActionTimer mActionTimer; |
| 61 | |
Jungshik Jang | b509c2e | 2014-08-07 13:45:01 +0900 | [diff] [blame] | 62 | private ArrayList<Pair<HdmiCecFeatureAction, Runnable>> mOnFinishedCallbacks; |
Yuncheol Heo | c516d65 | 2014-07-11 18:23:24 +0900 | [diff] [blame] | 63 | |
Jungshik Jang | b509c2e | 2014-08-07 13:45:01 +0900 | [diff] [blame] | 64 | HdmiCecFeatureAction(HdmiCecLocalDevice source) { |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 65 | mSource = source; |
| 66 | mService = mSource.getService(); |
| 67 | mActionTimer = createActionTimer(mService.getServiceLooper()); |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 68 | } |
| 69 | |
| 70 | @VisibleForTesting |
| 71 | void setActionTimer(ActionTimer actionTimer) { |
| 72 | mActionTimer = actionTimer; |
| 73 | } |
| 74 | |
| 75 | /** |
Jinsuk Kim | 6f87b4e | 2014-10-10 14:40:29 +0900 | [diff] [blame] | 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. |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 79 | * |
| 80 | * @return true if the operation is successful; otherwise false. |
| 81 | */ |
| 82 | abstract boolean start(); |
| 83 | |
| 84 | /** |
| 85 | * Process the command. Called whenever a new command arrives. |
| 86 | * |
| 87 | * @param cmd command to process |
Jungshik Jang | 5352081c | 2014-09-22 15:14:49 +0900 | [diff] [blame] | 88 | * @return true if the command was consumed in the process; Otherwise false. |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 89 | */ |
| 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 | */ |
| 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 | */ |
| 117 | void sendTimerMessage(int state, long delayMillis); |
Jungshik Jang | 67ea521 | 2014-05-15 14:05:24 +0900 | [diff] [blame] | 118 | |
| 119 | /** |
| 120 | * Removes any pending timer message. |
| 121 | */ |
| 122 | void clearTimerMessage(); |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 123 | } |
| 124 | |
| 125 | private class ActionTimerHandler extends Handler implements ActionTimer { |
| 126 | |
| 127 | public ActionTimerHandler(Looper looper) { |
| 128 | super(looper); |
| 129 | } |
| 130 | |
| 131 | @Override |
| 132 | public void sendTimerMessage(int state, long delayMillis) { |
Jinsuk Kim | b0674f0 | 2014-05-28 17:42:56 +0900 | [diff] [blame] | 133 | // The third argument(0) is not used. |
| 134 | sendMessageDelayed(obtainMessage(MSG_TIMEOUT, state, 0), delayMillis); |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 135 | } |
| 136 | |
| 137 | @Override |
Jungshik Jang | 67ea521 | 2014-05-15 14:05:24 +0900 | [diff] [blame] | 138 | public void clearTimerMessage() { |
| 139 | removeMessages(MSG_TIMEOUT); |
| 140 | } |
| 141 | |
| 142 | @Override |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 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 | |
| 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. |
| 161 | protected void addTimer(int state, int delayMillis) { |
| 162 | mActionTimer.sendTimerMessage(state, delayMillis); |
| 163 | } |
| 164 | |
Jinsuk Kim | 6f87b4e | 2014-10-10 14:40:29 +0900 | [diff] [blame] | 165 | boolean started() { |
| 166 | return mState != STATE_NONE; |
| 167 | } |
| 168 | |
Jungshik Jang | d643f76 | 2014-05-22 19:28:09 +0900 | [diff] [blame] | 169 | protected final void sendCommand(HdmiCecMessage cmd) { |
| 170 | mService.sendCecCommand(cmd); |
| 171 | } |
| 172 | |
| 173 | protected final void sendCommand(HdmiCecMessage cmd, |
| 174 | HdmiControlService.SendMessageCallback callback) { |
| 175 | mService.sendCecCommand(cmd, callback); |
Jungshik Jang | 67ea521 | 2014-05-15 14:05:24 +0900 | [diff] [blame] | 176 | } |
| 177 | |
Jungshik Jang | b509c2e | 2014-08-07 13:45:01 +0900 | [diff] [blame] | 178 | protected final void addAndStartAction(HdmiCecFeatureAction action) { |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 179 | mSource.addAndStartAction(action); |
| 180 | } |
| 181 | |
Jungshik Jang | b509c2e | 2014-08-07 13:45:01 +0900 | [diff] [blame] | 182 | protected final <T extends HdmiCecFeatureAction> List<T> getActions(final Class<T> clazz) { |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 183 | return mSource.getActions(clazz); |
| 184 | } |
| 185 | |
| 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 | */ |
Jungshik Jang | b509c2e | 2014-08-07 13:45:01 +0900 | [diff] [blame] | 196 | protected final void removeAction(HdmiCecFeatureAction action) { |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 197 | mSource.removeAction(action); |
| 198 | } |
| 199 | |
Jungshik Jang | b509c2e | 2014-08-07 13:45:01 +0900 | [diff] [blame] | 200 | protected final <T extends HdmiCecFeatureAction> void removeAction(final Class<T> clazz) { |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 201 | mSource.removeActionExcept(clazz, null); |
| 202 | } |
| 203 | |
Jungshik Jang | b509c2e | 2014-08-07 13:45:01 +0900 | [diff] [blame] | 204 | protected final <T extends HdmiCecFeatureAction> void removeActionExcept(final Class<T> clazz, |
| 205 | final HdmiCecFeatureAction exception) { |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 206 | mSource.removeActionExcept(clazz, exception); |
| 207 | } |
| 208 | |
| 209 | protected final void pollDevices(DevicePollingCallback callback, int pickStrategy, |
| 210 | int retryCount) { |
Jungshik Jang | 1de5142 | 2014-07-03 11:14:26 +0900 | [diff] [blame] | 211 | mService.pollDevices(callback, getSourceAddress(), pickStrategy, retryCount); |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 212 | } |
| 213 | |
Jungshik Jang | 67ea521 | 2014-05-15 14:05:24 +0900 | [diff] [blame] | 214 | /** |
| 215 | * Clean up action's state. |
| 216 | * |
| 217 | * <p>Declared as package-private. Only {@link HdmiControlService} can access it. |
| 218 | */ |
| 219 | void clear() { |
| 220 | mState = STATE_NONE; |
| 221 | // Clear all timers. |
| 222 | mActionTimer.clearTimerMessage(); |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 223 | } |
| 224 | |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 225 | /** |
| 226 | * Finish up the action. Reset the state, and remove itself from the action queue. |
| 227 | */ |
| 228 | protected void finish() { |
Yuncheol Heo | c516d65 | 2014-07-11 18:23:24 +0900 | [diff] [blame] | 229 | finish(true); |
| 230 | } |
| 231 | |
| 232 | void finish(boolean removeSelf) { |
Jungshik Jang | 67ea521 | 2014-05-15 14:05:24 +0900 | [diff] [blame] | 233 | clear(); |
Yuncheol Heo | c516d65 | 2014-07-11 18:23:24 +0900 | [diff] [blame] | 234 | if (removeSelf) { |
| 235 | removeAction(this); |
| 236 | } |
| 237 | if (mOnFinishedCallbacks != null) { |
Jungshik Jang | b509c2e | 2014-08-07 13:45:01 +0900 | [diff] [blame] | 238 | for (Pair<HdmiCecFeatureAction, Runnable> actionCallbackPair: mOnFinishedCallbacks) { |
Yuncheol Heo | c516d65 | 2014-07-11 18:23:24 +0900 | [diff] [blame] | 239 | if (actionCallbackPair.first.mState != STATE_NONE) { |
| 240 | actionCallbackPair.second.run(); |
| 241 | } |
| 242 | } |
| 243 | mOnFinishedCallbacks = null; |
| 244 | } |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 245 | } |
| 246 | |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 247 | protected final HdmiCecLocalDevice localDevice() { |
| 248 | return mSource; |
| 249 | } |
| 250 | |
| 251 | protected final HdmiCecLocalDevicePlayback playback() { |
| 252 | return (HdmiCecLocalDevicePlayback) mSource; |
| 253 | } |
| 254 | |
| 255 | protected final HdmiCecLocalDeviceTv tv() { |
| 256 | return (HdmiCecLocalDeviceTv) mSource; |
| 257 | } |
| 258 | |
| 259 | protected final int getSourceAddress() { |
| 260 | return mSource.getDeviceInfo().getLogicalAddress(); |
| 261 | } |
| 262 | |
| 263 | protected final int getSourcePath() { |
| 264 | return mSource.getDeviceInfo().getPhysicalAddress(); |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 265 | } |
Jungshik Jang | 8fa36b1 | 2014-06-25 15:51:36 +0900 | [diff] [blame] | 266 | |
Yuncheol Heo | c516d65 | 2014-07-11 18:23:24 +0900 | [diff] [blame] | 267 | protected final void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) { |
Jungshik Jang | 2e8f1b6 | 2014-09-03 08:28:02 +0900 | [diff] [blame] | 268 | mSource.sendUserControlPressedAndReleased(targetAddress, uiCommand); |
Jungshik Jang | 8fa36b1 | 2014-06-25 15:51:36 +0900 | [diff] [blame] | 269 | } |
Yuncheol Heo | c516d65 | 2014-07-11 18:23:24 +0900 | [diff] [blame] | 270 | |
Jungshik Jang | b509c2e | 2014-08-07 13:45:01 +0900 | [diff] [blame] | 271 | protected final void addOnFinishedCallback(HdmiCecFeatureAction action, Runnable runnable) { |
Yuncheol Heo | c516d65 | 2014-07-11 18:23:24 +0900 | [diff] [blame] | 272 | if (mOnFinishedCallbacks == null) { |
| 273 | mOnFinishedCallbacks = new ArrayList<>(); |
| 274 | } |
| 275 | mOnFinishedCallbacks.add(Pair.create(action, runnable)); |
| 276 | } |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 277 | } |