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 android.hardware.hdmi;
     17 
     18 import android.annotation.NonNull;
     19 import android.annotation.SystemApi;
     20 import android.hardware.hdmi.HdmiRecordSources.RecordSource;
     21 import android.hardware.hdmi.HdmiTimerRecordSources.TimerRecordSource;
     22 import android.os.RemoteException;
     23 import android.util.Log;
     24 
     25 import libcore.util.EmptyArray;
     26 
     27 import java.util.Collections;
     28 import java.util.List;
     29 
     30 /**
     31  * HdmiTvClient represents HDMI-CEC logical device of type TV in the Android system
     32  * which acts as TV/Display. It provides with methods that manage, interact with other
     33  * devices on the CEC bus.
     34  *
     35  * @hide
     36  */
     37 @SystemApi
     38 public final class HdmiTvClient extends HdmiClient {
     39     private static final String TAG = "HdmiTvClient";
     40 
     41     /**
     42      * Size of MHL register for vendor command
     43      */
     44     public static final int VENDOR_DATA_SIZE = 16;
     45 
     46     /* package */ HdmiTvClient(IHdmiControlService service) {
     47         super(service);
     48     }
     49 
     50     // Factory method for HdmiTvClient.
     51     // Declared package-private. Accessed by HdmiControlManager only.
     52     /* package */ static HdmiTvClient create(IHdmiControlService service) {
     53         return new HdmiTvClient(service);
     54     }
     55 
     56     @Override
     57     public int getDeviceType() {
     58         return HdmiDeviceInfo.DEVICE_TV;
     59     }
     60 
     61     /**
     62      * Callback interface used to get the result of {@link #deviceSelect}.
     63      */
     64     public interface SelectCallback {
     65         /**
     66          * Called when the operation is finished.
     67          *
     68          * @param result the result value of {@link #deviceSelect}
     69          */
     70         void onComplete(int result);
     71     }
     72 
     73     /**
     74      * Selects a CEC logical device to be a new active source.
     75      *
     76      * @param logicalAddress logical address of the device to select
     77      * @param callback callback to get the result with
     78      * @throws {@link IllegalArgumentException} if the {@code callback} is null
     79      */
     80     public void deviceSelect(int logicalAddress, @NonNull SelectCallback callback) {
     81         if (callback == null) {
     82             throw new IllegalArgumentException("callback must not be null.");
     83         }
     84         try {
     85             mService.deviceSelect(logicalAddress, getCallbackWrapper(callback));
     86         } catch (RemoteException e) {
     87             Log.e(TAG, "failed to select device: ", e);
     88         }
     89     }
     90 
     91     private static IHdmiControlCallback getCallbackWrapper(final SelectCallback callback) {
     92         return new IHdmiControlCallback.Stub() {
     93             @Override
     94             public void onComplete(int result) {
     95                 callback.onComplete(result);
     96             }
     97         };
     98     }
     99 
    100     /**
    101      * Selects a HDMI port to be a new route path.
    102      *
    103      * @param portId HDMI port to select
    104      * @param callback callback to get the result with
    105      * @throws {@link IllegalArgumentException} if the {@code callback} is null
    106      */
    107     public void portSelect(int portId, @NonNull SelectCallback callback) {
    108         if (callback == null) {
    109             throw new IllegalArgumentException("Callback must not be null");
    110         }
    111         try {
    112             mService.portSelect(portId, getCallbackWrapper(callback));
    113         } catch (RemoteException e) {
    114             Log.e(TAG, "failed to select port: ", e);
    115         }
    116     }
    117 
    118     /**
    119      * Callback interface used to get the input change event.
    120      */
    121     public interface InputChangeListener {
    122         /**
    123          * Called when the input was changed.
    124          *
    125          * @param info newly selected HDMI input
    126          */
    127         void onChanged(HdmiDeviceInfo info);
    128     }
    129 
    130     /**
    131      * Sets the listener used to get informed of the input change event.
    132      *
    133      * @param listener listener object
    134      */
    135     public void setInputChangeListener(InputChangeListener listener) {
    136         if (listener == null) {
    137             throw new IllegalArgumentException("listener must not be null.");
    138         }
    139         try {
    140             mService.setInputChangeListener(getListenerWrapper(listener));
    141         } catch (RemoteException e) {
    142             Log.e("TAG", "Failed to set InputChangeListener:", e);
    143         }
    144     }
    145 
    146     private static IHdmiInputChangeListener getListenerWrapper(final InputChangeListener listener) {
    147         return new IHdmiInputChangeListener.Stub() {
    148             @Override
    149             public void onChanged(HdmiDeviceInfo info) {
    150                 listener.onChanged(info);
    151             }
    152         };
    153     }
    154 
    155     /**
    156      * Returns all the CEC devices connected to TV.
    157      *
    158      * @return list of {@link HdmiDeviceInfo} for connected CEC devices.
    159      *         Empty list is returned if there is none.
    160      */
    161     public List<HdmiDeviceInfo> getDeviceList() {
    162         try {
    163             return mService.getDeviceList();
    164         } catch (RemoteException e) {
    165             Log.e("TAG", "Failed to call getDeviceList():", e);
    166             return Collections.<HdmiDeviceInfo>emptyList();
    167         }
    168     }
    169 
    170     /**
    171      * Sets system audio mode.
    172      *
    173      * @param enabled set to {@code true} to enable the mode; otherwise {@code false}
    174      * @param callback callback to get the result with
    175      * @throws {@link IllegalArgumentException} if the {@code callback} is null
    176      */
    177     public void setSystemAudioMode(boolean enabled, SelectCallback callback) {
    178         try {
    179             mService.setSystemAudioMode(enabled, getCallbackWrapper(callback));
    180         } catch (RemoteException e) {
    181             Log.e(TAG, "failed to set system audio mode:", e);
    182         }
    183     }
    184 
    185     /**
    186      * Sets system audio volume.
    187      *
    188      * @param oldIndex current volume index
    189      * @param newIndex volume index to be set
    190      * @param maxIndex maximum volume index
    191      */
    192     public void setSystemAudioVolume(int oldIndex, int newIndex, int maxIndex) {
    193         try {
    194             mService.setSystemAudioVolume(oldIndex, newIndex, maxIndex);
    195         } catch (RemoteException e) {
    196             Log.e(TAG, "failed to set volume: ", e);
    197         }
    198     }
    199 
    200     /**
    201      * Sets system audio mute status.
    202      *
    203      * @param mute {@code true} if muted; otherwise, {@code false}
    204      */
    205     public void setSystemAudioMute(boolean mute) {
    206         try {
    207             mService.setSystemAudioMute(mute);
    208         } catch (RemoteException e) {
    209             Log.e(TAG, "failed to set mute: ", e);
    210         }
    211     }
    212 
    213     /**
    214      * Sets record listener.
    215      *
    216      * @param listener
    217      */
    218     public void setRecordListener(@NonNull HdmiRecordListener listener) {
    219         if (listener == null) {
    220             throw new IllegalArgumentException("listener must not be null.");
    221         }
    222         try {
    223             mService.setHdmiRecordListener(getListenerWrapper(listener));
    224         } catch (RemoteException e) {
    225             Log.e(TAG, "failed to set record listener.", e);
    226         }
    227     }
    228 
    229     /**
    230      * Sends a &lt;Standby&gt; command to other device.
    231      *
    232      * @param deviceId device id to send the command to
    233      */
    234     public void sendStandby(int deviceId) {
    235         try {
    236             mService.sendStandby(getDeviceType(), deviceId);
    237         } catch (RemoteException e) {
    238             Log.e(TAG, "sendStandby threw exception ", e);
    239         }
    240     }
    241 
    242     private static IHdmiRecordListener getListenerWrapper(final HdmiRecordListener callback) {
    243         return new IHdmiRecordListener.Stub() {
    244             @Override
    245             public byte[] getOneTouchRecordSource(int recorderAddress) {
    246                 HdmiRecordSources.RecordSource source =
    247                         callback.onOneTouchRecordSourceRequested(recorderAddress);
    248                 if (source == null) {
    249                     return EmptyArray.BYTE;
    250                 }
    251                 byte[] data = new byte[source.getDataSize(true)];
    252                 source.toByteArray(true, data, 0);
    253                 return data;
    254             }
    255 
    256             @Override
    257             public void onOneTouchRecordResult(int recorderAddress, int result) {
    258                 callback.onOneTouchRecordResult(recorderAddress, result);
    259             }
    260 
    261             @Override
    262             public void onTimerRecordingResult(int recorderAddress, int result) {
    263                 callback.onTimerRecordingResult(recorderAddress,
    264                         HdmiRecordListener.TimerStatusData.parseFrom(result));
    265             }
    266 
    267             @Override
    268             public void onClearTimerRecordingResult(int recorderAddress, int result) {
    269                 callback.onClearTimerRecordingResult(recorderAddress, result);
    270             }
    271         };
    272     }
    273 
    274     /**
    275      * Starts one touch recording with the given recorder address and recorder source.
    276      * <p>
    277      * Usage
    278      * <pre>
    279      * HdmiTvClient tvClient = ....;
    280      * // for own source.
    281      * OwnSource ownSource = HdmiRecordSources.ofOwnSource();
    282      * tvClient.startOneTouchRecord(recorderAddress, ownSource);
    283      * </pre>
    284      */
    285     public void startOneTouchRecord(int recorderAddress, @NonNull RecordSource source) {
    286         if (source == null) {
    287             throw new IllegalArgumentException("source must not be null.");
    288         }
    289 
    290         try {
    291             byte[] data = new byte[source.getDataSize(true)];
    292             source.toByteArray(true, data, 0);
    293             mService.startOneTouchRecord(recorderAddress, data);
    294         } catch (RemoteException e) {
    295             Log.e(TAG, "failed to start record: ", e);
    296         }
    297     }
    298 
    299     /**
    300      * Stops one touch record.
    301      *
    302      * @param recorderAddress recorder address where recoding will be stopped
    303      */
    304     public void stopOneTouchRecord(int recorderAddress) {
    305         try {
    306             mService.stopOneTouchRecord(recorderAddress);
    307         } catch (RemoteException e) {
    308             Log.e(TAG, "failed to stop record: ", e);
    309         }
    310     }
    311 
    312     /**
    313      * Starts timer recording with the given recoder address and recorder source.
    314      * <p>
    315      * Usage
    316      * <pre>
    317      * HdmiTvClient tvClient = ....;
    318      * // create timer info
    319      * TimerInfo timerInfo = HdmiTimerRecourdSources.timerInfoOf(...);
    320      * // for digital source.
    321      * DigitalServiceSource recordSource = HdmiRecordSources.ofDigitalService(...);
    322      * // create timer recording source.
    323      * TimerRecordSource source = HdmiTimerRecourdSources.ofDigitalSource(timerInfo, recordSource);
    324      * tvClient.startTimerRecording(recorderAddress, source);
    325      * </pre>
    326      *
    327      * @param recorderAddress target recorder address
    328      * @param sourceType type of record source. It should be one of
    329      *          {@link HdmiControlManager#TIMER_RECORDING_TYPE_DIGITAL},
    330      *          {@link HdmiControlManager#TIMER_RECORDING_TYPE_ANALOGUE},
    331      *          {@link HdmiControlManager#TIMER_RECORDING_TYPE_EXTERNAL}.
    332      * @param source record source to be used
    333      */
    334     public void startTimerRecording(int recorderAddress, int sourceType, TimerRecordSource source) {
    335         if (source == null) {
    336             throw new IllegalArgumentException("source must not be null.");
    337         }
    338 
    339         checkTimerRecordingSourceType(sourceType);
    340 
    341         try {
    342             byte[] data = new byte[source.getDataSize()];
    343             source.toByteArray(data, 0);
    344             mService.startTimerRecording(recorderAddress, sourceType, data);
    345         } catch (RemoteException e) {
    346             Log.e(TAG, "failed to start record: ", e);
    347         }
    348     }
    349 
    350     private void checkTimerRecordingSourceType(int sourceType) {
    351         switch (sourceType) {
    352             case HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL:
    353             case HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE:
    354             case HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL:
    355                 break;
    356             default:
    357                 throw new IllegalArgumentException("Invalid source type:" + sourceType);
    358         }
    359     }
    360 
    361     /**
    362      * Clears timer recording with the given recorder address and recording source.
    363      * For more details, please refer {@link #startTimerRecording(int, int, TimerRecordSource)}.
    364      */
    365     public void clearTimerRecording(int recorderAddress, int sourceType, TimerRecordSource source) {
    366         if (source == null) {
    367             throw new IllegalArgumentException("source must not be null.");
    368         }
    369 
    370         checkTimerRecordingSourceType(sourceType);
    371         try {
    372             byte[] data = new byte[source.getDataSize()];
    373             source.toByteArray(data, 0);
    374             mService.clearTimerRecording(recorderAddress, sourceType, data);
    375         } catch (RemoteException e) {
    376             Log.e(TAG, "failed to start record: ", e);
    377         }
    378     }
    379 
    380     /**
    381      * Interface used to get incoming MHL vendor command.
    382      */
    383     public interface HdmiMhlVendorCommandListener {
    384         void onReceived(int portId, int offset, int length, byte[] data);
    385     }
    386 
    387     /**
    388      * Sets {@link HdmiMhlVendorCommandListener} to get incoming MHL vendor command.
    389      *
    390      * @param listener to receive incoming MHL vendor command
    391      */
    392     public void setHdmiMhlVendorCommandListener(HdmiMhlVendorCommandListener listener) {
    393         if (listener == null) {
    394             throw new IllegalArgumentException("listener must not be null.");
    395         }
    396         try {
    397             mService.addHdmiMhlVendorCommandListener(getListenerWrapper(listener));
    398         } catch (RemoteException e) {
    399             Log.e(TAG, "failed to set hdmi mhl vendor command listener: ", e);
    400         }
    401     }
    402 
    403     private IHdmiMhlVendorCommandListener getListenerWrapper(
    404             final HdmiMhlVendorCommandListener listener) {
    405         return new IHdmiMhlVendorCommandListener.Stub() {
    406             @Override
    407             public void onReceived(int portId, int offset, int length, byte[] data) {
    408                 listener.onReceived(portId, offset, length, data);
    409             }
    410         };
    411     }
    412 
    413     /**
    414      * Sends MHL vendor command to the device connected to a port of the given portId.
    415      *
    416      * @param portId id of port to send MHL vendor command
    417      * @param offset offset in the in given data
    418      * @param length length of data. offset + length should be bound to length of data.
    419      * @param data container for vendor command data. It should be 16 bytes.
    420      * @throws IllegalArgumentException if the given parameters are invalid
    421      */
    422     public void sendMhlVendorCommand(int portId, int offset, int length, byte[] data) {
    423         if (data == null || data.length != VENDOR_DATA_SIZE) {
    424             throw new IllegalArgumentException("Invalid vendor command data.");
    425         }
    426         if (offset < 0 || offset >= VENDOR_DATA_SIZE) {
    427             throw new IllegalArgumentException("Invalid offset:" + offset);
    428         }
    429         if (length < 0 || offset + length > VENDOR_DATA_SIZE) {
    430             throw new IllegalArgumentException("Invalid length:" + length);
    431         }
    432 
    433         try {
    434             mService.sendMhlVendorCommand(portId, offset, length, data);
    435         } catch (RemoteException e) {
    436             Log.e(TAG, "failed to send vendor command: ", e);
    437         }
    438     }
    439 }
    440