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