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 
     17 package com.android.server.hdmi;
     18 
     19 import android.hardware.hdmi.HdmiDeviceInfo;
     20 import android.util.Slog;
     21 
     22 import com.android.internal.util.Preconditions;
     23 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
     24 
     25 import java.io.UnsupportedEncodingException;
     26 import java.util.ArrayList;
     27 import java.util.List;
     28 
     29 /**
     30  * Feature action that handles device discovery sequences.
     31  * Device discovery is launched when TV device is woken from "Standby" state
     32  * or enabled "Control for Hdmi" from disabled state.
     33  *
     34  * <p>Device discovery goes through the following steps.
     35  * <ol>
     36  *   <li>Poll all non-local devices by sending &lt;Polling Message&gt;
     37  *   <li>Gather "Physical address" and "device type" of all acknowledged devices
     38  *   <li>Gather "OSD (display) name" of all acknowledge devices
     39  *   <li>Gather "Vendor id" of all acknowledge devices
     40  * </ol>
     41  */
     42 final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
     43     private static final String TAG = "DeviceDiscoveryAction";
     44 
     45     // State in which the action is waiting for device polling.
     46     private static final int STATE_WAITING_FOR_DEVICE_POLLING = 1;
     47     // State in which the action is waiting for gathering physical address of non-local devices.
     48     private static final int STATE_WAITING_FOR_PHYSICAL_ADDRESS = 2;
     49     // State in which the action is waiting for gathering osd name of non-local devices.
     50     private static final int STATE_WAITING_FOR_OSD_NAME = 3;
     51     // State in which the action is waiting for gathering vendor id of non-local devices.
     52     private static final int STATE_WAITING_FOR_VENDOR_ID = 4;
     53 
     54     /**
     55      * Interface used to report result of device discovery.
     56      */
     57     interface DeviceDiscoveryCallback {
     58         /**
     59          * Called when device discovery is done.
     60          *
     61          * @param deviceInfos a list of all non-local devices. It can be empty list.
     62          */
     63         void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos);
     64     }
     65 
     66     // An internal container used to keep track of device information during
     67     // this action.
     68     private static final class DeviceInfo {
     69         private final int mLogicalAddress;
     70 
     71         private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
     72         private int mPortId = Constants.INVALID_PORT_ID;
     73         private int mVendorId = Constants.UNKNOWN_VENDOR_ID;
     74         private String mDisplayName = "";
     75         private int mDeviceType = HdmiDeviceInfo.DEVICE_INACTIVE;
     76 
     77         private DeviceInfo(int logicalAddress) {
     78             mLogicalAddress = logicalAddress;
     79         }
     80 
     81         private HdmiDeviceInfo toHdmiDeviceInfo() {
     82             return new HdmiDeviceInfo(mLogicalAddress, mPhysicalAddress, mPortId, mDeviceType,
     83                     mVendorId, mDisplayName);
     84         }
     85     }
     86 
     87     private final ArrayList<DeviceInfo> mDevices = new ArrayList<>();
     88     private final DeviceDiscoveryCallback mCallback;
     89     private int mProcessedDeviceCount = 0;
     90 
     91     /**
     92      * Constructor.
     93      *
     94      * @param source an instance of {@link HdmiCecLocalDevice}.
     95      */
     96     DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback) {
     97         super(source);
     98         mCallback = Preconditions.checkNotNull(callback);
     99     }
    100 
    101     @Override
    102     boolean start() {
    103         mDevices.clear();
    104         mState = STATE_WAITING_FOR_DEVICE_POLLING;
    105 
    106         pollDevices(new DevicePollingCallback() {
    107             @Override
    108             public void onPollingFinished(List<Integer> ackedAddress) {
    109                 if (ackedAddress.isEmpty()) {
    110                     Slog.v(TAG, "No device is detected.");
    111                     wrapUpAndFinish();
    112                     return;
    113                 }
    114 
    115                 Slog.v(TAG, "Device detected: " + ackedAddress);
    116                 allocateDevices(ackedAddress);
    117                 startPhysicalAddressStage();
    118             }
    119         }, Constants.POLL_ITERATION_REVERSE_ORDER
    120             | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.DEVICE_POLLING_RETRY);
    121         return true;
    122     }
    123 
    124     private void allocateDevices(List<Integer> addresses) {
    125         for (Integer i : addresses) {
    126             DeviceInfo info = new DeviceInfo(i);
    127             mDevices.add(info);
    128         }
    129     }
    130 
    131     private void startPhysicalAddressStage() {
    132         Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size());
    133         mProcessedDeviceCount = 0;
    134         mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS;
    135 
    136         checkAndProceedStage();
    137     }
    138 
    139     private boolean verifyValidLogicalAddress(int address) {
    140         return address >= Constants.ADDR_TV && address < Constants.ADDR_UNREGISTERED;
    141     }
    142 
    143     private void queryPhysicalAddress(int address) {
    144         if (!verifyValidLogicalAddress(address)) {
    145             checkAndProceedStage();
    146             return;
    147         }
    148 
    149         mActionTimer.clearTimerMessage();
    150 
    151         // Check cache first and send request if not exist.
    152         if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS)) {
    153             return;
    154         }
    155         sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address));
    156         addTimer(mState, HdmiConfig.TIMEOUT_MS);
    157     }
    158 
    159     private void startOsdNameStage() {
    160         Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size());
    161         mProcessedDeviceCount = 0;
    162         mState = STATE_WAITING_FOR_OSD_NAME;
    163 
    164         checkAndProceedStage();
    165     }
    166 
    167     private void queryOsdName(int address) {
    168         if (!verifyValidLogicalAddress(address)) {
    169             checkAndProceedStage();
    170             return;
    171         }
    172 
    173         mActionTimer.clearTimerMessage();
    174 
    175         if (mayProcessMessageIfCached(address, Constants.MESSAGE_SET_OSD_NAME)) {
    176             return;
    177         }
    178         sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address));
    179         addTimer(mState, HdmiConfig.TIMEOUT_MS);
    180     }
    181 
    182     private void startVendorIdStage() {
    183         Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size());
    184 
    185         mProcessedDeviceCount = 0;
    186         mState = STATE_WAITING_FOR_VENDOR_ID;
    187 
    188         checkAndProceedStage();
    189     }
    190 
    191     private void queryVendorId(int address) {
    192         if (!verifyValidLogicalAddress(address)) {
    193             checkAndProceedStage();
    194             return;
    195         }
    196 
    197         mActionTimer.clearTimerMessage();
    198 
    199         if (mayProcessMessageIfCached(address, Constants.MESSAGE_DEVICE_VENDOR_ID)) {
    200             return;
    201         }
    202         sendCommand(
    203                 HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address));
    204         addTimer(mState, HdmiConfig.TIMEOUT_MS);
    205     }
    206 
    207     private boolean mayProcessMessageIfCached(int address, int opcode) {
    208         HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode);
    209         if (message != null) {
    210             processCommand(message);
    211             return true;
    212         }
    213         return false;
    214     }
    215 
    216     @Override
    217     boolean processCommand(HdmiCecMessage cmd) {
    218         switch (mState) {
    219             case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
    220                 if (cmd.getOpcode() == Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS) {
    221                     handleReportPhysicalAddress(cmd);
    222                     return true;
    223                 }
    224                 return false;
    225             case STATE_WAITING_FOR_OSD_NAME:
    226                 if (cmd.getOpcode() == Constants.MESSAGE_SET_OSD_NAME) {
    227                     handleSetOsdName(cmd);
    228                     return true;
    229                 }
    230                 return false;
    231             case STATE_WAITING_FOR_VENDOR_ID:
    232                 if (cmd.getOpcode() == Constants.MESSAGE_DEVICE_VENDOR_ID) {
    233                     handleVendorId(cmd);
    234                     return true;
    235                 }
    236                 return false;
    237             case STATE_WAITING_FOR_DEVICE_POLLING:
    238                 // Fall through.
    239             default:
    240                 return false;
    241         }
    242     }
    243 
    244     private void handleReportPhysicalAddress(HdmiCecMessage cmd) {
    245         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
    246 
    247         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
    248         if (current.mLogicalAddress != cmd.getSource()) {
    249             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
    250                     cmd.getSource());
    251             return;
    252         }
    253 
    254         byte params[] = cmd.getParams();
    255         current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
    256         current.mPortId = getPortId(current.mPhysicalAddress);
    257         current.mDeviceType = params[2] & 0xFF;
    258 
    259         tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType,
    260                     current.mPhysicalAddress);
    261 
    262         increaseProcessedDeviceCount();
    263         checkAndProceedStage();
    264     }
    265 
    266     private int getPortId(int physicalAddress) {
    267         return tv().getPortId(physicalAddress);
    268     }
    269 
    270     private void handleSetOsdName(HdmiCecMessage cmd) {
    271         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
    272 
    273         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
    274         if (current.mLogicalAddress != cmd.getSource()) {
    275             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
    276                     cmd.getSource());
    277             return;
    278         }
    279 
    280         String displayName = null;
    281         try {
    282             displayName = new String(cmd.getParams(), "US-ASCII");
    283         } catch (UnsupportedEncodingException e) {
    284             Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
    285             // If failed to get display name, use the default name of device.
    286             displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
    287         }
    288         current.mDisplayName = displayName;
    289         increaseProcessedDeviceCount();
    290         checkAndProceedStage();
    291     }
    292 
    293     private void handleVendorId(HdmiCecMessage cmd) {
    294         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
    295 
    296         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
    297         if (current.mLogicalAddress != cmd.getSource()) {
    298             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
    299                     cmd.getSource());
    300             return;
    301         }
    302 
    303         byte[] params = cmd.getParams();
    304         int vendorId = HdmiUtils.threeBytesToInt(params);
    305         current.mVendorId = vendorId;
    306         increaseProcessedDeviceCount();
    307         checkAndProceedStage();
    308     }
    309 
    310     private void increaseProcessedDeviceCount() {
    311         mProcessedDeviceCount++;
    312     }
    313 
    314     private void removeDevice(int index) {
    315         mDevices.remove(index);
    316     }
    317 
    318     private void wrapUpAndFinish() {
    319         Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------");
    320         ArrayList<HdmiDeviceInfo> result = new ArrayList<>();
    321         for (DeviceInfo info : mDevices) {
    322             HdmiDeviceInfo cecDeviceInfo = info.toHdmiDeviceInfo();
    323             Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo);
    324             result.add(cecDeviceInfo);
    325         }
    326         Slog.v(TAG, "--------------------------------------------");
    327         mCallback.onDeviceDiscoveryDone(result);
    328         finish();
    329     }
    330 
    331     private void checkAndProceedStage() {
    332         if (mDevices.isEmpty()) {
    333             wrapUpAndFinish();
    334             return;
    335         }
    336 
    337         // If finished current stage, move on to next stage.
    338         if (mProcessedDeviceCount == mDevices.size()) {
    339             mProcessedDeviceCount = 0;
    340             switch (mState) {
    341                 case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
    342                     startOsdNameStage();
    343                     return;
    344                 case STATE_WAITING_FOR_OSD_NAME:
    345                     startVendorIdStage();
    346                     return;
    347                 case STATE_WAITING_FOR_VENDOR_ID:
    348                     wrapUpAndFinish();
    349                     return;
    350                 default:
    351                     return;
    352             }
    353         } else {
    354             int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
    355             switch (mState) {
    356                 case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
    357                     queryPhysicalAddress(address);
    358                     return;
    359                 case STATE_WAITING_FOR_OSD_NAME:
    360                     queryOsdName(address);
    361                     return;
    362                 case STATE_WAITING_FOR_VENDOR_ID:
    363                     queryVendorId(address);
    364                 default:
    365                     return;
    366             }
    367         }
    368     }
    369 
    370     @Override
    371     void handleTimerEvent(int state) {
    372         if (mState == STATE_NONE || mState != state) {
    373             return;
    374         }
    375 
    376         Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount);
    377         removeDevice(mProcessedDeviceCount);
    378         checkAndProceedStage();
    379     }
    380 }
    381