summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/hdmi/NewDeviceAction.java
blob: 207408515c91263c05b69a322e5315540c05fc6c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.server.hdmi;

import android.hardware.hdmi.HdmiDeviceInfo;
import android.util.Slog;

import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
import java.io.UnsupportedEncodingException;

/**
 * Feature action that discovers the information of a newly found logical device.
 *
 * This action is created when receiving <Report Physical Address>, a CEC command a newly
 * connected HDMI-CEC device broadcasts to announce its advent. Additional commands are issued in
 * this action to gather more information on the device such as OSD name and device vendor ID.
 *
 * <p>The result is made in the form of {@link HdmiDeviceInfo} object, and passed to service
 * for the management through its life cycle.
 *
 * <p>Package-private, accessed by {@link HdmiControlService} only.
 */
final class NewDeviceAction extends HdmiCecFeatureAction {

    private static final String TAG = "NewDeviceAction";

    // State in which the action sent <Give OSD Name> and is waiting for <Set OSD Name>
    // that contains the name of the device for display on screen.
    static final int STATE_WAITING_FOR_SET_OSD_NAME = 1;

    // State in which the action sent <Give Device Vendor ID> and is waiting for
    // <Device Vendor ID> that contains the vendor ID of the device.
    static final int STATE_WAITING_FOR_DEVICE_VENDOR_ID = 2;

    private final int mDeviceLogicalAddress;
    private final int mDevicePhysicalAddress;

    private int mVendorId;
    private String mDisplayName;

    /**
     * Constructor.
     *
     * @param source {@link HdmiCecLocalDevice} instance
     * @param deviceLogicalAddress logical address of the device in interest
     * @param devicePhysicalAddress physical address of the device in interest
     */
    NewDeviceAction(HdmiCecLocalDevice source, int deviceLogicalAddress,
            int devicePhysicalAddress) {
        super(source);
        mDeviceLogicalAddress = deviceLogicalAddress;
        mDevicePhysicalAddress = devicePhysicalAddress;
        mVendorId = Constants.UNKNOWN_VENDOR_ID;
    }

    @Override
    public boolean start() {
        mState = STATE_WAITING_FOR_SET_OSD_NAME;
        if (mayProcessCommandIfCached(mDeviceLogicalAddress, Constants.MESSAGE_SET_OSD_NAME)) {
            return true;
        }

        sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(),
                mDeviceLogicalAddress));
        addTimer(mState, HdmiConfig.TIMEOUT_MS);
        return true;
    }

    @Override
    public boolean processCommand(HdmiCecMessage cmd) {
        // For the logical device in interest, we want two more pieces of information -
        // osd name and vendor id. They are requested in sequence. In case we don't
        // get the expected responses (either by timeout or by receiving <feature abort> command),
        // set them to a default osd name and unknown vendor id respectively.
        int opcode = cmd.getOpcode();
        int src = cmd.getSource();
        byte[] params = cmd.getParams();

        if (mDeviceLogicalAddress != src) {
            return false;
        }

        if (mState == STATE_WAITING_FOR_SET_OSD_NAME) {
            if (opcode == Constants.MESSAGE_SET_OSD_NAME) {
                try {
                    mDisplayName = new String(params, "US-ASCII");
                } catch (UnsupportedEncodingException e) {
                    Slog.e(TAG, "Failed to get OSD name: " + e.getMessage());
                }
                requestVendorId();
                return true;
            } else if (opcode == Constants.MESSAGE_FEATURE_ABORT) {
                int requestOpcode = params[0] & 0xFF;
                if (requestOpcode == Constants.MESSAGE_GIVE_OSD_NAME) {
                    requestVendorId();
                    return true;
                }
            }
        } else if (mState == STATE_WAITING_FOR_DEVICE_VENDOR_ID) {
            if (opcode == Constants.MESSAGE_DEVICE_VENDOR_ID) {
                mVendorId = HdmiUtils.threeBytesToInt(params);
                addDeviceInfo();
                finish();
                return true;
            } else if (opcode == Constants.MESSAGE_FEATURE_ABORT) {
                int requestOpcode = params[0] & 0xFF;
                if (requestOpcode == Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID) {
                    addDeviceInfo();
                    finish();
                    return true;
                }
            }
        }
        return false;
    }

    private boolean mayProcessCommandIfCached(int destAddress, int opcode) {
        HdmiCecMessage message = getCecMessageCache().getMessage(destAddress, opcode);
        if (message != null) {
            return processCommand(message);
        }
        return false;
    }

    private void requestVendorId() {
        // At first, transit to waiting status for <Device Vendor Id>.
        mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID;
        // If the message is already in cache, process it.
        if (mayProcessCommandIfCached(mDeviceLogicalAddress,
                Constants.MESSAGE_DEVICE_VENDOR_ID)) {
            return;
        }
        sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(),
                mDeviceLogicalAddress));
        addTimer(mState, HdmiConfig.TIMEOUT_MS);
    }

    private void addDeviceInfo() {
        if (mDisplayName == null) {
            mDisplayName = HdmiUtils.getDefaultDeviceName(mDeviceLogicalAddress);
        }
        HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo(
                mDeviceLogicalAddress, mDevicePhysicalAddress,
                tv().getPortId(mDevicePhysicalAddress),
                HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress),
                mVendorId, mDisplayName);
        tv().addCecDevice(deviceInfo);

        if (HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress)
                == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
            tv().onNewAvrAdded(deviceInfo);
        }
    }

    @Override
    public void handleTimerEvent(int state) {
        if (mState == STATE_NONE || mState != state) {
            return;
        }
        if (state == STATE_WAITING_FOR_SET_OSD_NAME) {
            // Osd name request timed out. Try vendor id
            requestVendorId();
        } else if (state == STATE_WAITING_FOR_DEVICE_VENDOR_ID) {
            // vendor id timed out. Go ahead creating the device info what we've got so far.
            addDeviceInfo();
            finish();
        }
    }

    boolean isActionOf(ActiveSource activeSource) {
        return (mDeviceLogicalAddress == activeSource.logicalAddress)
                && (mDevicePhysicalAddress == activeSource.physicalAddress);
    }
}