HdmiCecFeatureAction.java revision a7221ce87683fab16603290378408ce92f02e88a
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 */
16package com.android.server.hdmi;
17
18import android.os.Handler;
19import android.os.Looper;
20import android.os.Message;
21import android.util.Pair;
22import android.util.Slog;
23
24import com.android.internal.annotations.VisibleForTesting;
25import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
26
27import java.util.ArrayList;
28import 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 */
44abstract class HdmiCecFeatureAction {
45    private static final String TAG = "HdmiCecFeatureAction";
46    // As all actions run in the same thread (service thread), it's fine to have single logger.
47    // TODO: create global logger for each threads and use them.
48    protected static final HdmiLogger DLOGGER = new HdmiLogger(TAG);
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    HdmiCecFeatureAction(HdmiCecLocalDevice source) {
68        mSource = source;
69        mService = mSource.getService();
70        mActionTimer = createActionTimer(mService.getServiceLooper());
71    }
72
73    @VisibleForTesting
74    void setActionTimer(ActionTimer actionTimer) {
75        mActionTimer = actionTimer;
76    }
77
78    /**
79     * Called right after the action is created. Initialization or first step to take
80     * for the action can be done in this method.
81     *
82     * @return true if the operation is successful; otherwise false.
83     */
84    abstract boolean start();
85
86    /**
87     * Process the command. Called whenever a new command arrives.
88     *
89     * @param cmd command to process
90     * @return true if the command was consumed in the process; Otherwise false, which
91     *          indicates that the command shall be handled by other actions.
92     */
93    abstract boolean processCommand(HdmiCecMessage cmd);
94
95    /**
96     * Called when the action should handle the timer event it created before.
97     *
98     * <p>CEC standard mandates each command transmission should be responded within
99     * certain period of time. The method is called when the timer it created as it transmitted
100     * a command gets expired. Inner logic should take an appropriate action.
101     *
102     * @param state the state associated with the time when the timer was created
103     */
104    abstract void handleTimerEvent(int state);
105
106    /**
107     * Timer handler interface used for FeatureAction classes.
108     */
109    interface ActionTimer {
110        /**
111         * Send a timer message.
112         *
113         * Also carries the state of the action when the timer is created. Later this state is
114         * compared to the one the action is in when it receives the timer to let the action tell
115         * the right timer to handle.
116         *
117         * @param state state of the action is in
118         * @param delayMillis amount of delay for the timer
119         */
120        void sendTimerMessage(int state, long delayMillis);
121
122        /**
123         * Removes any pending timer message.
124         */
125        void clearTimerMessage();
126    }
127
128    private class ActionTimerHandler extends Handler implements ActionTimer {
129
130        public ActionTimerHandler(Looper looper) {
131            super(looper);
132        }
133
134        @Override
135        public void sendTimerMessage(int state, long delayMillis) {
136            // The third argument(0) is not used.
137            sendMessageDelayed(obtainMessage(MSG_TIMEOUT, state, 0), delayMillis);
138        }
139
140        @Override
141        public void clearTimerMessage() {
142            removeMessages(MSG_TIMEOUT);
143        }
144
145        @Override
146        public void handleMessage(Message msg) {
147            switch (msg.what) {
148            case MSG_TIMEOUT:
149                handleTimerEvent(msg.arg1);
150                break;
151            default:
152                Slog.w(TAG, "Unsupported message:" + msg.what);
153                break;
154            }
155        }
156    }
157
158    private ActionTimer createActionTimer(Looper looper) {
159        return new ActionTimerHandler(looper);
160    }
161
162    // Add a new timer. The timer event will come to mActionTimer.handleMessage() in
163    // delayMillis.
164    protected void addTimer(int state, int delayMillis) {
165        mActionTimer.sendTimerMessage(state, delayMillis);
166    }
167
168    protected final void sendCommand(HdmiCecMessage cmd) {
169        mService.sendCecCommand(cmd);
170    }
171
172    protected final void sendCommand(HdmiCecMessage cmd,
173            HdmiControlService.SendMessageCallback callback) {
174        mService.sendCecCommand(cmd, callback);
175    }
176
177    protected final void addAndStartAction(HdmiCecFeatureAction action) {
178        mSource.addAndStartAction(action);
179    }
180
181    protected final <T extends HdmiCecFeatureAction> List<T> getActions(final Class<T> clazz) {
182        return mSource.getActions(clazz);
183    }
184
185    protected final HdmiCecMessageCache getCecMessageCache() {
186        return mSource.getCecMessageCache();
187    }
188
189    /**
190     * Remove the action from the action queue. This is called after the action finishes
191     * its role.
192     *
193     * @param action
194     */
195    protected final void removeAction(HdmiCecFeatureAction action) {
196        mSource.removeAction(action);
197    }
198
199    protected final <T extends HdmiCecFeatureAction> void removeAction(final Class<T> clazz) {
200        mSource.removeActionExcept(clazz, null);
201    }
202
203    protected final <T extends HdmiCecFeatureAction> void removeActionExcept(final Class<T> clazz,
204            final HdmiCecFeatureAction exception) {
205        mSource.removeActionExcept(clazz, exception);
206    }
207
208    protected final void pollDevices(DevicePollingCallback callback, int pickStrategy,
209            int retryCount) {
210        mService.pollDevices(callback, getSourceAddress(), pickStrategy, retryCount);
211    }
212
213    /**
214     * Clean up action's state.
215     *
216     * <p>Declared as package-private. Only {@link HdmiControlService} can access it.
217     */
218    void clear() {
219        mState = STATE_NONE;
220        // Clear all timers.
221        mActionTimer.clearTimerMessage();
222    }
223
224    /**
225     * Finish up the action. Reset the state, and remove itself from the action queue.
226     */
227    protected void finish() {
228        finish(true);
229    }
230
231    void finish(boolean removeSelf) {
232        clear();
233        if (removeSelf) {
234            removeAction(this);
235        }
236        if (mOnFinishedCallbacks != null) {
237            for (Pair<HdmiCecFeatureAction, Runnable> actionCallbackPair: mOnFinishedCallbacks) {
238                if (actionCallbackPair.first.mState != STATE_NONE) {
239                    actionCallbackPair.second.run();
240                }
241            }
242            mOnFinishedCallbacks = null;
243        }
244    }
245
246    protected final HdmiCecLocalDevice localDevice() {
247        return mSource;
248    }
249
250    protected final HdmiCecLocalDevicePlayback playback() {
251        return (HdmiCecLocalDevicePlayback) mSource;
252    }
253
254    protected final HdmiCecLocalDeviceTv tv() {
255        return (HdmiCecLocalDeviceTv) mSource;
256    }
257
258    protected final int getSourceAddress() {
259        return mSource.getDeviceInfo().getLogicalAddress();
260    }
261
262    protected final int getSourcePath() {
263        return mSource.getDeviceInfo().getPhysicalAddress();
264    }
265
266    protected final void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) {
267        sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(
268                getSourceAddress(), targetAddress, uiCommand));
269        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
270                getSourceAddress(), targetAddress));
271    }
272
273    protected final void addOnFinishedCallback(HdmiCecFeatureAction action, Runnable runnable) {
274        if (mOnFinishedCallbacks == null) {
275            mOnFinishedCallbacks = new ArrayList<>();
276        }
277        mOnFinishedCallbacks.add(Pair.create(action, runnable));
278    }
279}
280