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.HdmiDeviceInfo;
19 import android.util.Slog;
20 
21 import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
22 
23 import java.io.UnsupportedEncodingException;
24 
25 /**
26  * Feature action that discovers the information of a newly found logical device.
27  *
28  * This action is created when receiving <Report Physical Address>, a CEC command a newly
29  * connected HDMI-CEC device broadcasts to announce its advent. Additional commands are issued in
30  * this action to gather more information on the device such as OSD name and device vendor ID.
31  *
32  * <p>The result is made in the form of {@link HdmiDeviceInfo} object, and passed to service
33  * for the management through its life cycle.
34  *
35  * <p>Package-private, accessed by {@link HdmiControlService} only.
36  */
37 final class NewDeviceAction extends HdmiCecFeatureAction {
38 
39     private static final String TAG = "NewDeviceAction";
40 
41     // State in which the action sent <Give OSD Name> and is waiting for <Set OSD Name>
42     // that contains the name of the device for display on screen.
43     static final int STATE_WAITING_FOR_SET_OSD_NAME = 1;
44 
45     // State in which the action sent <Give Device Vendor ID> and is waiting for
46     // <Device Vendor ID> that contains the vendor ID of the device.
47     static final int STATE_WAITING_FOR_DEVICE_VENDOR_ID = 2;
48 
49     private final int mDeviceLogicalAddress;
50     private final int mDevicePhysicalAddress;
51     private final int mDeviceType;
52 
53     private int mVendorId;
54     private String mDisplayName;
55     private int mTimeoutRetry;
56 
57     /**
58      * Constructor.
59      *
60      * @param source {@link HdmiCecLocalDevice} instance
61      * @param deviceLogicalAddress logical address of the device in interest
62      * @param devicePhysicalAddress physical address of the device in interest
63      * @param deviceType type of the device
64      */
NewDeviceAction(HdmiCecLocalDevice source, int deviceLogicalAddress, int devicePhysicalAddress, int deviceType)65     NewDeviceAction(HdmiCecLocalDevice source, int deviceLogicalAddress,
66             int devicePhysicalAddress, int deviceType) {
67         super(source);
68         mDeviceLogicalAddress = deviceLogicalAddress;
69         mDevicePhysicalAddress = devicePhysicalAddress;
70         mDeviceType = deviceType;
71         mVendorId = Constants.VENDOR_ID_UNKNOWN;
72     }
73 
74     @Override
start()75     public boolean start() {
76         requestOsdName(true);
77         return true;
78     }
79 
requestOsdName(boolean firstTry)80     private void requestOsdName(boolean firstTry) {
81         if (firstTry) {
82             mTimeoutRetry = 0;
83         }
84         mState = STATE_WAITING_FOR_SET_OSD_NAME;
85         if (mayProcessCommandIfCached(mDeviceLogicalAddress, Constants.MESSAGE_SET_OSD_NAME)) {
86             return;
87         }
88 
89         sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(),
90                 mDeviceLogicalAddress));
91         addTimer(mState, HdmiConfig.TIMEOUT_MS);
92     }
93 
94     @Override
processCommand(HdmiCecMessage cmd)95     public boolean processCommand(HdmiCecMessage cmd) {
96         // For the logical device in interest, we want two more pieces of information -
97         // osd name and vendor id. They are requested in sequence. In case we don't
98         // get the expected responses (either by timeout or by receiving <feature abort> command),
99         // set them to a default osd name and unknown vendor id respectively.
100         int opcode = cmd.getOpcode();
101         int src = cmd.getSource();
102         byte[] params = cmd.getParams();
103 
104         if (mDeviceLogicalAddress != src) {
105             return false;
106         }
107 
108         if (mState == STATE_WAITING_FOR_SET_OSD_NAME) {
109             if (opcode == Constants.MESSAGE_SET_OSD_NAME) {
110                 try {
111                     mDisplayName = new String(params, "US-ASCII");
112                 } catch (UnsupportedEncodingException e) {
113                     Slog.e(TAG, "Failed to get OSD name: " + e.getMessage());
114                 }
115                 requestVendorId(true);
116                 return true;
117             } else if (opcode == Constants.MESSAGE_FEATURE_ABORT) {
118                 int requestOpcode = params[0] & 0xFF;
119                 if (requestOpcode == Constants.MESSAGE_GIVE_OSD_NAME) {
120                     requestVendorId(true);
121                     return true;
122                 }
123             }
124         } else if (mState == STATE_WAITING_FOR_DEVICE_VENDOR_ID) {
125             if (opcode == Constants.MESSAGE_DEVICE_VENDOR_ID) {
126                 mVendorId = HdmiUtils.threeBytesToInt(params);
127                 addDeviceInfo();
128                 finish();
129                 return true;
130             } else if (opcode == Constants.MESSAGE_FEATURE_ABORT) {
131                 int requestOpcode = params[0] & 0xFF;
132                 if (requestOpcode == Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID) {
133                     addDeviceInfo();
134                     finish();
135                     return true;
136                 }
137             }
138         }
139         return false;
140     }
141 
mayProcessCommandIfCached(int destAddress, int opcode)142     private boolean mayProcessCommandIfCached(int destAddress, int opcode) {
143         HdmiCecMessage message = getCecMessageCache().getMessage(destAddress, opcode);
144         if (message != null) {
145             return processCommand(message);
146         }
147         return false;
148     }
149 
requestVendorId(boolean firstTry)150     private void requestVendorId(boolean firstTry) {
151         if (firstTry) {
152             mTimeoutRetry = 0;
153         }
154         // At first, transit to waiting status for <Device Vendor Id>.
155         mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID;
156         // If the message is already in cache, process it.
157         if (mayProcessCommandIfCached(mDeviceLogicalAddress,
158                 Constants.MESSAGE_DEVICE_VENDOR_ID)) {
159             return;
160         }
161         sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(),
162                 mDeviceLogicalAddress));
163         addTimer(mState, HdmiConfig.TIMEOUT_MS);
164     }
165 
addDeviceInfo()166     private void addDeviceInfo() {
167         // The device should be in the device list with default information.
168         if (!localDevice().mService.getHdmiCecNetwork().isInDeviceList(mDeviceLogicalAddress,
169                 mDevicePhysicalAddress)) {
170             Slog.w(TAG, String.format("Device not found (%02x, %04x)",
171                     mDeviceLogicalAddress, mDevicePhysicalAddress));
172             return;
173         }
174         if (mDisplayName == null) {
175             mDisplayName = "";
176         }
177         HdmiDeviceInfo deviceInfo = HdmiDeviceInfo.cecDeviceBuilder()
178                 .setLogicalAddress(mDeviceLogicalAddress)
179                 .setPhysicalAddress(mDevicePhysicalAddress)
180                 .setPortId(tv().getPortId(mDevicePhysicalAddress))
181                 .setDeviceType(mDeviceType)
182                 .setVendorId(mVendorId)
183                 .setDisplayName(mDisplayName)
184                 .build();
185         localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo);
186 
187         // Consume CEC messages we already got for this newly found device.
188         tv().processDelayedMessages(mDeviceLogicalAddress);
189 
190         if (HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM,
191                 mDeviceLogicalAddress)) {
192             tv().onNewAvrAdded(deviceInfo);
193         }
194     }
195 
196     @Override
handleTimerEvent(int state)197     public void handleTimerEvent(int state) {
198         if (mState == STATE_NONE || mState != state) {
199             return;
200         }
201         if (state == STATE_WAITING_FOR_SET_OSD_NAME) {
202             if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
203                 requestOsdName(false);
204                 return;
205             }
206             // Osd name request timed out. Try vendor id
207             requestVendorId(true);
208         } else if (state == STATE_WAITING_FOR_DEVICE_VENDOR_ID) {
209             if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
210                 requestVendorId(false);
211                 return;
212             }
213             // vendor id timed out. Go ahead creating the device info what we've got so far.
214             addDeviceInfo();
215             finish();
216         }
217     }
218 
isActionOf(ActiveSource activeSource)219     boolean isActionOf(ActiveSource activeSource) {
220         return (mDeviceLogicalAddress == activeSource.logicalAddress)
221                 && (mDevicePhysicalAddress == activeSource.physicalAddress);
222     }
223 }
224