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