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