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
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
64    HdmiCecFeatureAction(HdmiCecLocalDevice source) {
65        mSource = source;
66        mService = mSource.getService();
67        mActionTimer = createActionTimer(mService.getServiceLooper());
68    }
69
70    @VisibleForTesting
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     */
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     */
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);
118
119        /**
120         * Removes any pending timer message.
121         */
122        void clearTimerMessage();
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) {
133            // The third argument(0) is not used.
134            sendMessageDelayed(obtainMessage(MSG_TIMEOUT, state, 0), delayMillis);
135        }
136
137        @Override
138        public void clearTimerMessage() {
139            removeMessages(MSG_TIMEOUT);
140        }
141
142        @Override
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
165    boolean started() {
166        return mState != STATE_NONE;
167    }
168
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);
176    }
177
178    protected final void addAndStartAction(HdmiCecFeatureAction action) {
179        mSource.addAndStartAction(action);
180    }
181
182    protected final <T extends HdmiCecFeatureAction> List<T> getActions(final Class<T> clazz) {
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     */
196    protected final void removeAction(HdmiCecFeatureAction action) {
197        mSource.removeAction(action);
198    }
199
200    protected final <T extends HdmiCecFeatureAction> void removeAction(final Class<T> clazz) {
201        mSource.removeActionExcept(clazz, null);
202    }
203
204    protected final <T extends HdmiCecFeatureAction> void removeActionExcept(final Class<T> clazz,
205            final HdmiCecFeatureAction exception) {
206        mSource.removeActionExcept(clazz, exception);
207    }
208
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     */
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     */
228    protected void finish() {
229        finish(true);
230    }
231
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
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();
265    }
266
267    protected final void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) {
268        mSource.sendUserControlPressedAndReleased(targetAddress, uiCommand);
269    }
270
271    protected final void addOnFinishedCallback(HdmiCecFeatureAction action, Runnable runnable) {
272        if (mOnFinishedCallbacks == null) {
273            mOnFinishedCallbacks = new ArrayList<>();
274        }
275        mOnFinishedCallbacks.add(Pair.create(action, runnable));
276    }
277}
278