Merge "Check the validity of the port id of HdmiCecMessage only in TV." into lmp-dev
diff --git a/Android.mk b/Android.mk
index 278e67f..0d71150 100644
--- a/Android.mk
+++ b/Android.mk
@@ -155,7 +155,7 @@
 	core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl \
 	core/java/android/hardware/hdmi/IHdmiHotplugEventListener.aidl \
 	core/java/android/hardware/hdmi/IHdmiInputChangeListener.aidl \
-	core/java/android/hardware/hdmi/IHdmiRecordRequestListener.aidl \
+	core/java/android/hardware/hdmi/IHdmiRecordListener.aidl \
 	core/java/android/hardware/hdmi/IHdmiSystemAudioModeChangeListener.aidl \
 	core/java/android/hardware/hdmi/IHdmiVendorCommandListener.aidl \
 	core/java/android/hardware/input/IInputManager.aidl \
diff --git a/api/current.txt b/api/current.txt
index 4e0d311..943fef5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -29093,7 +29093,7 @@
     field public static final int LIMIT_EXCEEDED = 15; // 0xf
     field public static final int LOCAL = 3; // 0x3
     field public static final int LOST_SIGNAL = 14; // 0xe
-    field public static final int MAXIMUM_VALID_VALUE = 42; // 0x2a
+    field public static final int MAXIMUM_VALID_VALUE = 44; // 0x2c
     field public static final int MINIMUM_VALID_VALUE = 0; // 0x0
     field public static final int MMI = 6; // 0x6
     field public static final int NORMAL = 2; // 0x2
@@ -29101,6 +29101,8 @@
     field public static final int NOT_VALID = -1; // 0xffffffff
     field public static final int NO_PHONE_NUMBER_SUPPLIED = 38; // 0x26
     field public static final int NUMBER_UNREACHABLE = 8; // 0x8
+    field public static final int OUTGOING_CANCELED = 44; // 0x2c
+    field public static final int OUTGOING_FAILURE = 43; // 0x2b
     field public static final int OUT_OF_NETWORK = 11; // 0xb
     field public static final int OUT_OF_SERVICE = 18; // 0x12
     field public static final int POWER_OFF = 17; // 0x11
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index b1cbb13..42b8dbf 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -21,6 +21,8 @@
 import android.bluetooth.le.BluetoothLeAdvertiser;
 import android.bluetooth.le.BluetoothLeScanner;
 import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanRecord;
 import android.bluetooth.le.ScanResult;
 import android.bluetooth.le.ScanSettings;
 import android.content.Context;
@@ -371,12 +373,14 @@
      */
     private static BluetoothAdapter sAdapter;
 
+    private static BluetoothLeScanner sBluetoothLeScanner;
+    private static BluetoothLeAdvertiser sBluetoothLeAdvertiser;
+
     private final IBluetoothManager mManagerService;
     private IBluetooth mService;
 
-    private final Map<LeScanCallback, GattCallbackWrapper> mLeScanClients;
-    private final Handler mHandler;  // Handler to post the advertise callback to run on main thread.
     private final Object mLock = new Object();
+    private final Map<LeScanCallback, ScanCallback> mLeScanClients;
 
     /**
      * Get a handle to the default local Bluetooth adapter.
@@ -411,8 +415,7 @@
             mService = managerService.registerAdapter(mManagerCallback);
         } catch (RemoteException e) {Log.e(TAG, "", e);}
         mManagerService = managerService;
-        mLeScanClients = new HashMap<LeScanCallback, GattCallbackWrapper>();
-        mHandler = new Handler(Looper.getMainLooper());
+        mLeScanClients = new HashMap<LeScanCallback, ScanCallback>();
     }
 
     /**
@@ -451,19 +454,40 @@
     }
 
     /**
-     * Returns a {@link BluetoothLeAdvertiser} object for Bluetooth LE Advertising operations.
+     * Returns a {@link BluetoothLeAdvertiser} object for Bluetooth LE Advertising operations, or
+     * null if Bluetooth LE Advertising is not support on this device.
+     * <p>
+     * Use {@link #isMultipleAdvertisementSupported()} to check whether LE Advertising is supported
+     * on this device before calling this method.
      */
     public BluetoothLeAdvertiser getBluetoothLeAdvertiser() {
-        // TODO: Return null if this feature is not supported by hardware.
-        return new BluetoothLeAdvertiser(mManagerService);
+        if (getState() != STATE_ON) {
+            return null;
+        }
+        if (!isMultipleAdvertisementSupported()) {
+            return null;
+        }
+        synchronized(mLock) {
+            if (sBluetoothLeAdvertiser == null) {
+                sBluetoothLeAdvertiser = new BluetoothLeAdvertiser(mManagerService);
+            }
+        }
+        return sBluetoothLeAdvertiser;
     }
 
     /**
      * Returns a {@link BluetoothLeScanner} object for Bluetooth LE scan operations.
      */
     public BluetoothLeScanner getBluetoothLeScanner() {
-        // TODO: Return null if BLE scan is not supported by hardware.
-        return new BluetoothLeScanner(mManagerService);
+        if (getState() != STATE_ON) {
+            return null;
+        }
+        synchronized(mLock) {
+            if (sBluetoothLeScanner == null) {
+                sBluetoothLeScanner = new BluetoothLeScanner(mManagerService);
+            }
+        }
+        return sBluetoothLeScanner;
     }
 
     /**
@@ -1625,13 +1649,17 @@
      *             instead.
      */
     @Deprecated
-    public boolean startLeScan(UUID[] serviceUuids, LeScanCallback callback) {
+    public boolean startLeScan(final UUID[] serviceUuids, final LeScanCallback callback) {
         if (DBG) Log.d(TAG, "startLeScan(): " + serviceUuids);
-
         if (callback == null) {
             if (DBG) Log.e(TAG, "startLeScan: null callback");
             return false;
         }
+        BluetoothLeScanner scanner = getBluetoothLeScanner();
+        if (scanner == null) {
+            if (DBG) Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner");
+            return false;
+        }
 
         synchronized(mLeScanClients) {
             if (mLeScanClients.containsKey(callback)) {
@@ -1646,13 +1674,50 @@
                     return false;
                 }
 
-                UUID uuid = UUID.randomUUID();
-                GattCallbackWrapper wrapper = new GattCallbackWrapper(this, callback, serviceUuids);
-                iGatt.registerClient(new ParcelUuid(uuid), wrapper);
-                if (wrapper.scanStarted()) {
-                    mLeScanClients.put(callback, wrapper);
-                    return true;
+                ScanCallback scanCallback = new ScanCallback() {
+                    @Override
+                    public void onScanResult(int callbackType, ScanResult result) {
+                        if (callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES) {
+                            // Should not happen.
+                            Log.e(TAG, "LE Scan has already started");
+                            return;
+                        }
+                        ScanRecord scanRecord = result.getScanRecord();
+                        if (scanRecord == null) {
+                            return;
+                        }
+                        if (serviceUuids != null) {
+                            List<ParcelUuid> uuids = new ArrayList<ParcelUuid>();
+                            for (UUID uuid : serviceUuids) {
+                                uuids.add(new ParcelUuid(uuid));
+                            }
+                            List<ParcelUuid> scanServiceUuids = scanRecord.getServiceUuids();
+                            if (scanServiceUuids == null || !scanServiceUuids.containsAll(uuids)) {
+                                if (DBG) Log.d(TAG, "uuids does not match");
+                                return;
+                            }
+                        }
+                        callback.onLeScan(result.getDevice(), result.getRssi(),
+                                scanRecord.getBytes());
+                    }
+                };
+                ScanSettings settings = new ScanSettings.Builder()
+                    .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
+                    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+
+                List<ScanFilter> filters = new ArrayList<ScanFilter>();
+                if (serviceUuids != null && serviceUuids.length > 0) {
+                    // Note scan filter does not support matching an UUID array so we put one
+                    // UUID to hardware and match the whole array in callback.
+                    ScanFilter filter = new ScanFilter.Builder().setServiceUuid(
+                            new ParcelUuid(serviceUuids[0])).build();
+                    filters.add(filter);
                 }
+                scanner.startScan(filters, settings, scanCallback);
+
+                mLeScanClients.put(callback, scanCallback);
+                return true;
+
             } catch (RemoteException e) {
                 Log.e(TAG,"",e);
             }
@@ -1672,264 +1737,17 @@
     @Deprecated
     public void stopLeScan(LeScanCallback callback) {
         if (DBG) Log.d(TAG, "stopLeScan()");
-        GattCallbackWrapper wrapper;
-        synchronized(mLeScanClients) {
-            wrapper = mLeScanClients.remove(callback);
-            if (wrapper == null) return;
+        BluetoothLeScanner scanner = getBluetoothLeScanner();
+        if (scanner == null) {
+            return;
         }
-        wrapper.stopLeScan();
-    }
-
-    /**
-     * Bluetooth GATT interface callbacks
-     */
-    private static class GattCallbackWrapper extends IBluetoothGattCallback.Stub {
-        private static final int LE_CALLBACK_REG_TIMEOUT = 2000;
-        private static final int LE_CALLBACK_REG_WAIT_COUNT = 5;
-
-        private final LeScanCallback mLeScanCb;
-
-        // mLeHandle 0: not registered
-        //           -1: scan stopped
-        //           >0: registered and scan started
-        private int mLeHandle;
-        private final UUID[] mScanFilter;
-        private WeakReference<BluetoothAdapter> mBluetoothAdapter;
-
-        public GattCallbackWrapper(BluetoothAdapter bluetoothAdapter,
-                                   LeScanCallback leScanCb, UUID[] uuid) {
-            mBluetoothAdapter = new WeakReference<BluetoothAdapter>(bluetoothAdapter);
-            mLeScanCb = leScanCb;
-            mScanFilter = uuid;
-            mLeHandle = 0;
-        }
-
-        public boolean scanStarted() {
-            return waitForRegisteration(LE_CALLBACK_REG_WAIT_COUNT);
-        }
-
-        private boolean waitForRegisteration(int maxWaitCount) {
-            boolean started = false;
-            synchronized(this) {
-                if (mLeHandle == -1) return false;
-                int count = 0;
-                // wait for callback registration and LE scan to start
-                while (mLeHandle == 0 && count < maxWaitCount) {
-                    try {
-                        wait(LE_CALLBACK_REG_TIMEOUT);
-                    } catch (InterruptedException e) {
-                        Log.e(TAG, "Callback reg wait interrupted: " + e);
-                    }
-                    count++;
-                }
-                started = (mLeHandle > 0);
+        synchronized (mLeScanClients) {
+            ScanCallback scanCallback = mLeScanClients.remove(callback);
+            if (scanCallback == null) {
+                if (DBG) Log.d(TAG, "scan not started yet");
+                return;
             }
-            return started;
-        }
-
-        public void stopLeScan() {
-            synchronized(this) {
-                if (mLeHandle <= 0) {
-                    Log.e(TAG, "Error state, mLeHandle: " + mLeHandle);
-                    return;
-                }
-                BluetoothAdapter adapter = mBluetoothAdapter.get();
-                if (adapter != null) {
-                    try {
-                        IBluetoothGatt iGatt = adapter.getBluetoothManager().getBluetoothGatt();
-                        iGatt.stopScan(mLeHandle, false);
-                        iGatt.unregisterClient(mLeHandle);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Failed to stop scan and unregister" + e);
-                    }
-                } else {
-                    Log.e(TAG, "stopLeScan, BluetoothAdapter is null");
-                }
-                mLeHandle = -1;
-                notifyAll();
-            }
-        }
-
-        /**
-         * Application interface registered - app is ready to go
-         */
-        public void onClientRegistered(int status, int clientIf) {
-            if (DBG) Log.d(TAG, "onClientRegistered() - status=" + status +
-                           " clientIf=" + clientIf);
-            synchronized(this) {
-                if (mLeHandle == -1) {
-                    if (DBG) Log.d(TAG, "onClientRegistered LE scan canceled");
-                }
-
-                if (status == BluetoothGatt.GATT_SUCCESS) {
-                    mLeHandle = clientIf;
-                    IBluetoothGatt iGatt = null;
-                    try {
-                        BluetoothAdapter adapter = mBluetoothAdapter.get();
-                        if (adapter != null) {
-                            iGatt = adapter.getBluetoothManager().getBluetoothGatt();
-                            if (mScanFilter == null) {
-                                iGatt.startScan(mLeHandle, false);
-                            } else {
-                                ParcelUuid[] uuids = new ParcelUuid[mScanFilter.length];
-                                for(int i = 0; i != uuids.length; ++i) {
-                                    uuids[i] = new ParcelUuid(mScanFilter[i]);
-                                }
-                                iGatt.startScanWithUuids(mLeHandle, false, uuids);
-                            }
-                        } else {
-                            Log.e(TAG, "onClientRegistered, BluetoothAdapter null");
-                            mLeHandle = -1;
-                        }
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "fail to start le scan: " + e);
-                        mLeHandle = -1;
-                    }
-                    if (mLeHandle == -1) {
-                        // registration succeeded but start scan or advertise failed
-                        if (iGatt != null) {
-                            try {
-                                iGatt.unregisterClient(mLeHandle);
-                            } catch (RemoteException e) {
-                                Log.e(TAG, "fail to unregister callback: " + mLeHandle +
-                                      " error: " + e);
-                            }
-                        }
-                    }
-                } else {
-                    // registration failed
-                    mLeHandle = -1;
-                }
-                notifyAll();
-            }
-        }
-
-        public void onClientConnectionState(int status, int clientIf,
-                                            boolean connected, String address) {
-            // no op
-        }
-
-        /**
-         * Callback reporting an LE scan result.
-         * @hide
-         */
-        public void onScanResult(String address, int rssi, byte[] advData) {
-            if (VDBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
-
-            // Check null in case the scan has been stopped
-            synchronized(this) {
-                if (mLeHandle <= 0) return;
-            }
-            try {
-                BluetoothAdapter adapter = mBluetoothAdapter.get();
-                if (adapter == null) {
-                    Log.d(TAG, "onScanResult, BluetoothAdapter null");
-                    return;
-                }
-                mLeScanCb.onLeScan(adapter.getRemoteDevice(address), rssi, advData);
-            } catch (Exception ex) {
-                Log.w(TAG, "Unhandled exception: " + ex);
-            }
-        }
-
-        public void onGetService(String address, int srvcType,
-                                 int srvcInstId, ParcelUuid srvcUuid) {
-            // no op
-        }
-
-        public void onGetIncludedService(String address, int srvcType,
-                                         int srvcInstId, ParcelUuid srvcUuid,
-                                         int inclSrvcType, int inclSrvcInstId,
-                                         ParcelUuid inclSrvcUuid) {
-            // no op
-        }
-
-        public void onGetCharacteristic(String address, int srvcType,
-                                        int srvcInstId, ParcelUuid srvcUuid,
-                                        int charInstId, ParcelUuid charUuid,
-                                        int charProps) {
-            // no op
-        }
-
-        public void onGetDescriptor(String address, int srvcType,
-                                    int srvcInstId, ParcelUuid srvcUuid,
-                                    int charInstId, ParcelUuid charUuid,
-                                    int descInstId, ParcelUuid descUuid) {
-            // no op
-        }
-
-        public void onSearchComplete(String address, int status) {
-            // no op
-        }
-
-        public void onCharacteristicRead(String address, int status, int srvcType,
-                                         int srvcInstId, ParcelUuid srvcUuid,
-                                         int charInstId, ParcelUuid charUuid, byte[] value) {
-            // no op
-        }
-
-        public void onCharacteristicWrite(String address, int status, int srvcType,
-                                          int srvcInstId, ParcelUuid srvcUuid,
-                                          int charInstId, ParcelUuid charUuid) {
-            // no op
-        }
-
-        public void onNotify(String address, int srvcType,
-                             int srvcInstId, ParcelUuid srvcUuid,
-                             int charInstId, ParcelUuid charUuid,
-                             byte[] value) {
-            // no op
-        }
-
-        public void onDescriptorRead(String address, int status, int srvcType,
-                                     int srvcInstId, ParcelUuid srvcUuid,
-                                     int charInstId, ParcelUuid charUuid,
-                                     int descInstId, ParcelUuid descrUuid, byte[] value) {
-            // no op
-        }
-
-        public void onDescriptorWrite(String address, int status, int srvcType,
-                                      int srvcInstId, ParcelUuid srvcUuid,
-                                      int charInstId, ParcelUuid charUuid,
-                                      int descInstId, ParcelUuid descrUuid) {
-            // no op
-        }
-
-        public void onExecuteWrite(String address, int status) {
-            // no op
-        }
-
-        public void onReadRemoteRssi(String address, int rssi, int status) {
-            // no op
-        }
-
-        public void onAdvertiseStateChange(int advertiseState, int status) {
-        }
-
-        @Override
-        public void onMultiAdvertiseCallback(int status) {
-            // no op
-        }
-
-        @Override
-        public void onConfigureMTU(String address, int mtu, int status) {
-            // no op
-        }
-
-        @Override
-        public void onConnectionCongested(String address, boolean congested) {
-            // no op
-        }
-
-        @Override
-        public void onBatchScanResults(List<ScanResult> results) {
-            // no op
-        }
-
-        @Override
-        public void onFoundOrLost(boolean onFound, String address,int rssi,
-                byte[] advData) {
-            // no op
+            scanner.stopScan(scanCallback);
         }
     }
 }
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 533be13..edf823e 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -33,10 +33,8 @@
 interface IBluetoothGatt {
     List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
 
-    void startScan(in int appIf, in boolean isServer);
-    void startScanWithUuids(in int appIf, in boolean isServer, in ParcelUuid[] ids);
-    void startScanWithFilters(in int appIf, in boolean isServer,
-                              in ScanSettings settings, in List<ScanFilter> filters);
+    void startScan(in int appIf, in boolean isServer, in ScanSettings settings,
+                   in List<ScanFilter> filters);
     void stopScan(in int appIf, in boolean isServer);
     void flushPendingBatchResults(in int appIf, in boolean isServer);
     void startMultiAdvertising(in int appIf,
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index 980f717..8e7d400 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -151,6 +151,7 @@
         synchronized (mLeScanClients) {
             BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback);
             if (wrapper == null) {
+                if (DBG) Log.d(TAG, "could not find callback wrapper");
                 return;
             }
             wrapper.stopLeScan();
@@ -266,7 +267,7 @@
                 if (status == BluetoothGatt.GATT_SUCCESS) {
                     mClientIf = clientIf;
                     try {
-                        mBluetoothGatt.startScanWithFilters(mClientIf, false, mSettings, mFilters);
+                        mBluetoothGatt.startScan(mClientIf, false, mSettings, mFilters);
                     } catch (RemoteException e) {
                         Log.e(TAG, "fail to start le scan: " + e);
                         mClientIf = -1;
diff --git a/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.java b/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.java
index acf92f1..d663714 100644
--- a/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.java
+++ b/core/java/android/hardware/hdmi/HdmiCecDeviceInfo.java
@@ -67,6 +67,7 @@
     // are immutable value.
     private final int mLogicalAddress;
     private final int mPhysicalAddress;
+    private final int mPortId;
     private final int mDeviceType;
     private final int mVendorId;
     private final String mDisplayName;
@@ -80,11 +81,12 @@
                 public HdmiCecDeviceInfo createFromParcel(Parcel source) {
                     int logicalAddress = source.readInt();
                     int physicalAddress = source.readInt();
+                    int portId = source.readInt();
                     int deviceType = source.readInt();
                     int vendorId = source.readInt();
                     String displayName = source.readString();
-                    return new HdmiCecDeviceInfo(logicalAddress, physicalAddress, deviceType,
-                            vendorId, displayName);
+                    return new HdmiCecDeviceInfo(logicalAddress, physicalAddress, portId,
+                            deviceType, vendorId, displayName);
                 }
 
                 @Override
@@ -98,15 +100,17 @@
      *
      * @param logicalAddress logical address of HDMI-CEC device
      * @param physicalAddress physical address of HDMI-CEC device
+     * @param portId HDMI port ID (1 for HDMI1)
      * @param deviceType type of device
      * @param vendorId vendor id of device. Used for vendor specific command.
      * @param displayName name of device
      * @hide
      */
-    public HdmiCecDeviceInfo(int logicalAddress, int physicalAddress, int deviceType,
+    public HdmiCecDeviceInfo(int logicalAddress, int physicalAddress, int portId, int deviceType,
             int vendorId, String displayName) {
         mLogicalAddress = logicalAddress;
         mPhysicalAddress = physicalAddress;
+        mPortId = portId;
         mDeviceType = deviceType;
         mDisplayName = displayName;
         mVendorId = vendorId;
@@ -127,6 +131,13 @@
     }
 
     /**
+     * Return the port ID.
+     */
+    public int getPortId() {
+        return mPortId;
+    }
+
+    /**
      * Return type of the device. For more details, refer constants between
      * {@link DEVICE_TV} and {@link DEVICE_INACTIVE}.
      */
@@ -179,6 +190,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mLogicalAddress);
         dest.writeInt(mPhysicalAddress);
+        dest.writeInt(mPortId);
         dest.writeInt(mDeviceType);
         dest.writeInt(mVendorId);
         dest.writeString(mDisplayName);
@@ -189,6 +201,7 @@
         StringBuffer s = new StringBuffer();
         s.append("logical_address: ").append(mLogicalAddress).append(", ");
         s.append("physical_address: ").append(mPhysicalAddress).append(", ");
+        s.append("port_id: ").append(mPortId).append(", ");
         s.append("device_type: ").append(mDeviceType).append(", ");
         s.append("vendor_id: ").append(mVendorId).append(", ");
         s.append("display_name: ").append(mDisplayName);
@@ -204,6 +217,7 @@
         HdmiCecDeviceInfo other = (HdmiCecDeviceInfo) obj;
         return mLogicalAddress == other.mLogicalAddress
                 && mPhysicalAddress == other.mPhysicalAddress
+                && mPortId == other.mPortId
                 && mDeviceType == other.mDeviceType
                 && mVendorId == other.mVendorId
                 && mDisplayName.equals(other.mDisplayName);
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 521a439..55db1df 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -69,69 +69,71 @@
     public static final int RESULT_INCORRECT_MODE = 6;
     public static final int RESULT_COMMUNICATION_FAILED = 7;
 
-    // -- Message ids for display osd.
-
-    /** Place holder for recording status message. Indicates the status of a recording. */
-    public static final int MESSAGE_RECORDING_STATUS_MESSAGE_START = 0x100;
+    // --- One Touch Recording success result
     /** Recording currently selected source. Indicates the status of a recording. */
-    public static final int MESSAGE_RECORDING_CURRENTLY_SELECTED_SOURCE = 0x101;
+    public static final int ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE = 0x01;
     /** Recording Digital Service. Indicates the status of a recording. */
-    public static final int MESSAGE_RECORDING_DIGITAL_SERVICE = 0x102;
+    public static final int ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE = 0x02;
     /** Recording Analogue Service. Indicates the status of a recording. */
-    public static final int MESSAGE_RECORDING_ANALOGUE_SERVICE = 0x103;
+    public static final int ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE = 0x03;
     /** Recording External input. Indicates the status of a recording. */
-    public static final int MESSAGE_RECORDING_EXTERNAL_INPUT = 0x104;
+    public static final int ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT = 0x04;
+
+    // --- One Touch Record failure result
     /** No recording – unable to record Digital Service. No suitable tuner. */
-    public static final int MESSAGE_NO_RECORDNIG_UNABLE_DIGITAL_SERVICE = 0x105;
+    public static final int ONE_TOUCH_RECORD_UNABLE_DIGITAL_SERVICE = 0x05;
     /** No recording – unable to record Analogue Service. No suitable tuner. */
-    public static final int MESSAGE_NO_RECORDNIG_UNABLE_ANALOGUE_SERVICE = 0x106;
+    public static final int ONE_TOUCH_RECORD_UNABLE_ANALOGUE_SERVICE = 0x06;
     /**
      * No recording – unable to select required service. as suitable tuner, but the requested
      * parameters are invalid or out of range for that tuner.
      */
-    public static final int MESSAGE_NO_RECORDNIG_UNABLE_SELECTED_SERVICE = 0x107;
+    public static final int ONE_TOUCH_RECORD_UNABLE_SELECTED_SERVICE = 0x07;
     /** No recording – invalid External plug number */
-    public static final int MESSAGE_NO_RECORDNIG_INVALID_EXTERNAL_PLUG_NUMBER = 0x109;
+    public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PLUG_NUMBER = 0x09;
     /** No recording – invalid External Physical Address */
-    public static final int MESSAGE_NO_RECORDNIG_INVALID_EXTERNAL_PHYSICAL_ADDRESS = 0x10A;
+    public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PHYSICAL_ADDRESS = 0x0A;
     /** No recording – CA system not supported */
-    public static final int MESSAGE_NO_RECORDNIG_UNSUPPORTED_CA = 0x10B;
+    public static final int ONE_TOUCH_RECORD_UNSUPPORTED_CA = 0x0B;
     /** No Recording – No or Insufficient CA Entitlements” */
-    public static final int MESSAGE_NO_RECORDNIG_NO_OR_INSUFFICIENT_CA_ENTITLEMENTS = 0x10C;
+    public static final int ONE_TOUCH_RECORD_NO_OR_INSUFFICIENT_CA_ENTITLEMENTS = 0x0C;
     /** No recording – Not allowed to copy source. Source is “copy never”. */
-    public static final int MESSAGE_NO_RECORDNIG_DISALLOW_TO_COPY = 0x10D;
+    public static final int ONE_TOUCH_RECORD_DISALLOW_TO_COPY = 0x0D;
     /** No recording – No further copies allowed */
-    public static final int MESSAGE_NO_RECORDNIG_DISALLOW_TO_FUTHER_COPIES = 0x10E;
+    public static final int ONE_TOUCH_RECORD_DISALLOW_TO_FUTHER_COPIES = 0x0E;
     /** No recording – No media */
-    public static final int MESSAGE_NO_RECORDNIG_NO_MEDIA = 0x110;
+    public static final int ONE_TOUCH_RECORD_NO_MEDIA = 0x10;
     /** No recording – playing */
-    public static final int MESSAGE_NO_RECORDNIG_PLAYING = 0x111;
+    public static final int ONE_TOUCH_RECORD_PLAYING = 0x11;
     /** No recording – already recording */
-    public static final int MESSAGE_NO_RECORDNIG_ALREADY_RECORDING = 0x112;
+    public static final int ONE_TOUCH_RECORD_ALREADY_RECORDING = 0x12;
     /** No recording – media protected */
-    public static final int MESSAGE_NO_RECORDNIG_MEDIA_PROTECTED = 0x113;
+    public static final int ONE_TOUCH_RECORD_MEDIA_PROTECTED = 0x13;
     /** No recording – no source signal */
-    public static final int MESSAGE_NO_RECORDNIG_NO_SOURCE_SIGNAL = 0x114;
+    public static final int ONE_TOUCH_RECORD_NO_SOURCE_SIGNAL = 0x14;
     /** No recording – media problem */
-    public static final int MESSAGE_NO_RECORDNIG_MEDIA_PROBLEM = 0x115;
+    public static final int ONE_TOUCH_RECORD_MEDIA_PROBLEM = 0x15;
     /** No recording – not enough space available */
-    public static final int MESSAGE_NO_RECORDNIG_NOT_ENOUGH_SPACE = 0x116;
+    public static final int ONE_TOUCH_RECORD_NOT_ENOUGH_SPACE = 0x16;
     /** No recording – Parental Lock On */
-    public static final int MESSAGE_NO_RECORDNIG_PARENT_LOCK_ON = 0x117;
+    public static final int ONE_TOUCH_RECORD_PARENT_LOCK_ON = 0x17;
     /** Recording terminated normally */
-    public static final int MESSAGE_RECORDING_TERMINATED_NORMALLY = 0x11A;
+    public static final int ONE_TOUCH_RECORD_RECORDING_TERMINATED_NORMALLY = 0x1A;
     /** Recording has already terminated */
-    public static final int MESSAGE_RECORDING_ALREADY_TERMINATED = 0x11B;
+    public static final int ONE_TOUCH_RECORD_RECORDING_ALREADY_TERMINATED = 0x1B;
     /** No recording – other reason */
-    public static final int MESSAGE_NO_RECORDNIG_OTHER_REASON = 0x11F;
+    public static final int ONE_TOUCH_RECORD_OTHER_REASON = 0x1F;
     // From here extra message for recording that is not mentioned in CEC spec
     /** No recording. Previous recording request in progress. */
-    public static final int MESSAGE_NO_RECORDING_PREVIOUS_RECORDING_IN_PROGRESS = 0x130;
+    public static final int ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS = 0x30;
     /** No recording. Please check recorder and connection. */
-    public static final int MESSAGE_NO_RECORDING_CHECK_RECORDER_CONNECTION = 0x131;
+    public static final int ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION = 0x31;
     /** Cannot record currently displayed source. */
-    public static final int MESSAGE_NO_RECORDING_FAIL_TO_RECORD_DISPLAYED_SCREEN = 0x132;
+    public static final int ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN = 0x32;
+    /** CEC is disabled. */
+    public static final int ONE_TOUCH_RECORD_CEC_DISABLED = 0x33;
 
+    // --- Types for timer recording
     /** Timer recording type for digital service source. */
     public static final int TIMER_RECORDING_TYPE_DIGITAL = 1;
     /** Timer recording type for analogue service source. */
@@ -139,6 +141,14 @@
     /** Timer recording type for external source. */
     public static final int TIMER_RECORDING_TYPE_EXTERNAL = 3;
 
+    // --- Extra result value for timer recording.
+    /** No timer recording - check recorder and connection. */
+    public static final int TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION = 0x01;
+    /** No timer recording - cannot record selected source. */
+    public static final int TIME_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE = 0x02;
+    /** CEC is disabled. */
+    public static final int TIME_RECORDING_RESULT_EXTRA_CEC_DISABLED = 0x33;
+
     // True if we have a logical device of type playback hosted in the system.
     private final boolean mHasPlaybackDevice;
     // True if we have a logical device of type TV hosted in the system.
diff --git a/core/java/android/hardware/hdmi/HdmiRecordListener.java b/core/java/android/hardware/hdmi/HdmiRecordListener.java
new file mode 100644
index 0000000..0b1166b
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiRecordListener.java
@@ -0,0 +1,67 @@
+/*
+ * 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 android.hardware.hdmi;
+
+import android.annotation.SystemApi;
+import android.hardware.hdmi.HdmiRecordSources.RecordSource;
+
+/**
+ * Listener for hdmi record feature including one touch record and timer recording.
+ * @hide
+ */
+@SystemApi
+public abstract class HdmiRecordListener {
+    protected HdmiRecordListener() {}
+
+    /**
+     * Called when TV received one touch record request from record device. The client of this
+     * should use {@link HdmiRecordSources} to return it.
+     *
+     * @param recorderAddress
+     * @return record source to be used for recording. Null if no device is available.
+     */
+    public abstract RecordSource getOneTouchRecordSource(int recorderAddress);
+
+    /**
+     * Called when one touch record is started or failed during initialization.
+     *
+     * @param result result code. For more details, please look at all constants starting with
+     *            "ONE_TOUCH_RECORD_". Only
+     *            {@link HdmiControlManager#ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE},
+     *            {@link HdmiControlManager#ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE},
+     *            {@link HdmiControlManager#ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE}, and
+     *            {@link HdmiControlManager#ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT} mean normal
+     *            start of recording; otherwise, describes failure.
+     */
+    public void onOneTouchRecordResult(int result) {
+    }
+
+    /**
+     * Called when timer recording is started or failed during initialization.
+     *
+     * @param result The most significant three bytes may contain result of &lt;Timer Status&gt;
+     *        while the least significant byte may have error message like
+     *        {@link HdmiControlManager#TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION}
+     *        or
+     *        {@link HdmiControlManager #TIME_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE}
+     *        . If the least significant byte has non zero value the most significant three bytes
+     *        may have 0 value.
+     */
+    // TODO: implement result parser.
+    public void onTimerRecordingResult(int result) {
+    }
+}
diff --git a/core/java/android/hardware/hdmi/HdmiRecordSources.java b/core/java/android/hardware/hdmi/HdmiRecordSources.java
index 296cae6..917d1d9 100644
--- a/core/java/android/hardware/hdmi/HdmiRecordSources.java
+++ b/core/java/android/hardware/hdmi/HdmiRecordSources.java
@@ -492,7 +492,7 @@
         /** Indicates that a service is identified by a logical or virtual channel number. */
         private static final int DIGITAL_SERVICE_IDENTIFIED_BY_CHANNEL = 1;
 
-        private static final int EXTRA_DATA_SIZE = 7;
+        static final int EXTRA_DATA_SIZE = 7;
 
         /**
          * Type of identification. It should be one of DIGITAL_SERVICE_IDENTIFIED_BY_DIGITAL_ID and
@@ -609,7 +609,7 @@
      */
     @SystemApi
     public static final class AnalogueServiceSource extends RecordSource {
-        private static final int EXTRA_DATA_SIZE = 4;
+        static final int EXTRA_DATA_SIZE = 4;
 
         /** Indicates the Analogue broadcast type. */
         private final int mBroadcastType;
@@ -668,7 +668,7 @@
      */
     @SystemApi
     public static final class ExternalPlugData extends RecordSource {
-        private static final int EXTRA_DATA_SIZE = 1;
+        static final int EXTRA_DATA_SIZE = 1;
 
         /** External Plug number on the Recording Device. */
         private final int mPlugNumber;
@@ -713,7 +713,7 @@
      */
     @SystemApi
     public static final class ExternalPhysicalAddress extends RecordSource {
-        private static final int EXTRA_DATA_SIZE = 2;
+        static final int EXTRA_DATA_SIZE = 2;
 
         private final int mPhysicalAddress;
 
@@ -751,6 +751,7 @@
      * Check the byte array of record source.
      * @hide
      */
+    @SystemApi
     public static boolean checkRecordSource(byte[] recordSource) {
         int recordSourceType = recordSource[0];
         int extraDataSize = recordSource.length - 1;
diff --git a/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java b/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java
index 3e5e49b..01b4dd3 100644
--- a/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java
+++ b/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java
@@ -16,6 +16,10 @@
 
 package android.hardware.hdmi;
 
+import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE;
+import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
+import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
+
 import android.annotation.SystemApi;
 import android.hardware.hdmi.HdmiRecordSources.AnalogueServiceSource;
 import android.hardware.hdmi.HdmiRecordSources.DigitalServiceSource;
@@ -420,4 +424,35 @@
             return getDataSize(false);
         }
     }
+
+    /**
+     * Check the byte array of timer record source.
+     * @param sourcetype
+     * @param recordSource
+     * @hide
+     */
+    @SystemApi
+    public static boolean checkTimerRecordSource(int sourcetype, byte[] recordSource) {
+        int recordSourceSize = recordSource.length - TimerInfo.BASIC_INFO_SIZE;
+        switch (sourcetype) {
+            case TIMER_RECORDING_TYPE_DIGITAL:
+                return DigitalServiceSource.EXTRA_DATA_SIZE == recordSourceSize;
+            case TIMER_RECORDING_TYPE_ANALOGUE:
+                return AnalogueServiceSource.EXTRA_DATA_SIZE == recordSourceSize;
+            case TIMER_RECORDING_TYPE_EXTERNAL:
+                int specifier = recordSource[TimerInfo.BASIC_INFO_SIZE];
+                if (specifier == EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PLUG) {
+                    // One byte for specifier.
+                    return ExternalPlugData.EXTRA_DATA_SIZE + 1 == recordSourceSize;
+                } else if (specifier == EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PHYSICAL_ADDRESS) {
+                    // One byte for specifier.
+                    return ExternalPhysicalAddress.EXTRA_DATA_SIZE + 1 == recordSourceSize;
+                } else {
+                    // Invalid specifier.
+                    return false;
+                }
+            default:
+                return false;
+        }
+    }
 }
diff --git a/core/java/android/hardware/hdmi/HdmiTvClient.java b/core/java/android/hardware/hdmi/HdmiTvClient.java
index 6080914..3436287 100644
--- a/core/java/android/hardware/hdmi/HdmiTvClient.java
+++ b/core/java/android/hardware/hdmi/HdmiTvClient.java
@@ -15,10 +15,9 @@
  */
 package android.hardware.hdmi;
 
-import static android.hardware.hdmi.HdmiRecordSources.RecordSource;
-import static android.hardware.hdmi.HdmiTimerRecordSources.TimerRecordSource;
-
 import android.annotation.SystemApi;
+import android.hardware.hdmi.HdmiRecordSources.RecordSource;
+import android.hardware.hdmi.HdmiTimerRecordSources.TimerRecordSource;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -155,27 +154,15 @@
     }
 
     /**
-     * Callback interface to used to get notified when a record request from recorder device.
+     * Set record listener
+     *
+     * @param listener
      */
-    public interface RecordRequestListener {
-        /**
-         * Called when tv receives request request from recorder device. When it's called,
-         * it should return record source in byte array so that hdmi control service
-         * can start recording with the given source info.
-         *
-         * @return {@link HdmiRecordSources} to be used to set recording info
-         */
-        RecordSource onRecordRequestReceived(int recorderAddress);
-    }
-
-    /**
-     * Set {@link RecordRequestListener} to hdmi control service.
-     */
-    public void setOneTouchRecordRequestListener(RecordRequestListener listener) {
+    public void setRecordListener(HdmiRecordListener listener) {
         try {
-            mService.setOneTouchRecordRequestListener(getCallbackWrapper(listener));
+            mService.setHdmiRecordListener(getListenerWrapper(listener));
         } catch (RemoteException e) {
-            Log.e(TAG, "failed to set record request listener: ", e);
+            Log.e(TAG, "failed to set record listener.", e);
         }
     }
 
@@ -282,13 +269,12 @@
         };
     }
 
-    private static IHdmiRecordRequestListener getCallbackWrapper(
-            final RecordRequestListener listener) {
-        return new IHdmiRecordRequestListener.Stub() {
+    private static IHdmiRecordListener getListenerWrapper(final HdmiRecordListener callback) {
+        return new IHdmiRecordListener.Stub() {
             @Override
-            public byte[] onRecordRequestReceived(int recorderAddress) throws RemoteException {
+            public byte[] getOneTouchRecordSource(int recorderAddress) {
                 HdmiRecordSources.RecordSource source =
-                        listener.onRecordRequestReceived(recorderAddress);
+                        callback.getOneTouchRecordSource(recorderAddress);
                 if (source == null) {
                     return EmptyArray.BYTE;
                 }
@@ -296,6 +282,16 @@
                 source.toByteArray(true, data, 0);
                 return data;
             }
+
+            @Override
+            public void onOneTouchRecordResult(int result) {
+                callback.onOneTouchRecordResult(result);
+            }
+
+            @Override
+            public void onTimerRecordingResult(int result) {
+                callback.onTimerRecordingResult(result);
+            }
         };
     }
 }
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 95e0ee0..808e0c9 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -22,7 +22,7 @@
 import android.hardware.hdmi.IHdmiDeviceEventListener;
 import android.hardware.hdmi.IHdmiHotplugEventListener;
 import android.hardware.hdmi.IHdmiInputChangeListener;
-import android.hardware.hdmi.IHdmiRecordRequestListener;
+import android.hardware.hdmi.IHdmiRecordListener;
 import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
 import android.hardware.hdmi.IHdmiVendorCommandListener;
 
@@ -62,7 +62,7 @@
     void sendVendorCommand(int deviceType, int targetAddress, in byte[] params,
             boolean hasVendorId);
     void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType);
-    void setOneTouchRecordRequestListener(IHdmiRecordRequestListener listener);
+    void setHdmiRecordListener(IHdmiRecordListener callback);
     void startOneTouchRecord(int recorderAddress, in byte[] recordSource);
     void stopOneTouchRecord(int recorderAddress);
     void startTimerRecording(int recorderAddress, int sourceType, in byte[] recordSource);
diff --git a/core/java/android/hardware/hdmi/IHdmiRecordListener.aidl b/core/java/android/hardware/hdmi/IHdmiRecordListener.aidl
new file mode 100644
index 0000000..fba4b05
--- /dev/null
+++ b/core/java/android/hardware/hdmi/IHdmiRecordListener.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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 android.hardware.hdmi;
+
+/**
+ * @hide
+ */
+ interface IHdmiRecordListener {
+     /**
+      * Called when TV received one touch record request from record device.
+      *
+      * @param recorderAddress
+      * @return record source in byte array.
+      */
+     byte[] getOneTouchRecordSource(int recorderAddress);
+
+     /**
+      * Called when one touch record is started or failed during initialization.
+      *
+      * @param result result code for one touch record
+      */
+     void onOneTouchRecordResult(int result);
+     /**
+      * Called when timer recording is started or failed during initialization.
+      * @param result result code for timer recording
+      */
+     void onTimerRecordingResult(int result);
+ }
\ No newline at end of file
diff --git a/core/java/android/hardware/hdmi/IHdmiRecordRequestListener.aidl b/core/java/android/hardware/hdmi/IHdmiRecordRequestListener.aidl
deleted file mode 100644
index f8f9e5f..0000000
--- a/core/java/android/hardware/hdmi/IHdmiRecordRequestListener.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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 android.hardware.hdmi;
-
-/**
- * Callback interface definition for HDMI client to fill record source info
- * when it gets record start request.
- *
- * @hide
- */
-interface IHdmiRecordRequestListener {
-    byte[] onRecordRequestReceived(int recorderAddress);
-}
\ No newline at end of file
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 30831cd..52618cc 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -222,7 +222,6 @@
  * @attr ref android.R.styleable#TextView_imeActionId
  * @attr ref android.R.styleable#TextView_editorExtras
  * @attr ref android.R.styleable#TextView_elegantTextHeight
- * @attr ref android.R.styleable#TextView_letterSpacing
  */
 @RemoteView
 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 0e87893..fedea4e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3755,7 +3755,7 @@
         <attr name="shadowRadius" format="float" />
         <!-- Elegant text height, especially for less compacted complex script text. -->
         <attr name="elegantTextHeight" format="boolean" />
-        <!-- @hide Text letter-spacing. -->
+        <!-- Text letter-spacing. -->
         <attr name="letterSpacing" format="float" />
     </declare-styleable>
     <declare-styleable name="TextClock">
@@ -4050,7 +4050,7 @@
         <attr name="textAllCaps" />
         <!-- Elegant text height, especially for less compacted complex script text. -->
         <attr name="elegantTextHeight" />
-        <!-- @hide Text letter-spacing. -->
+        <!-- Text letter-spacing. -->
         <attr name="letterSpacing" />
     </declare-styleable>
     <declare-styleable name="TextViewAppearance">
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index b1902dd..a6e85e9 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2268,7 +2268,6 @@
   <public type="attr" name="checkMarkTintMode" />
   <public type="attr" name="popupTheme" />
   <public type="attr" name="toolbarStyle" />
-  <public type="attr" name="letterSpacing" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index a75b485..86e14e1 100644
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -69,6 +69,7 @@
         private final int mLogicalAddress;
 
         private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
+        private int mPortId = Constants.INVALID_PORT_ID;
         private int mVendorId = Constants.UNKNOWN_VENDOR_ID;
         private String mDisplayName = "";
         private int mDeviceType = HdmiCecDeviceInfo.DEVICE_INACTIVE;
@@ -78,8 +79,8 @@
         }
 
         private HdmiCecDeviceInfo toHdmiCecDeviceInfo() {
-            return new HdmiCecDeviceInfo(mLogicalAddress, mPhysicalAddress, mDeviceType, mVendorId,
-                    mDisplayName);
+            return new HdmiCecDeviceInfo(mLogicalAddress, mPhysicalAddress, mPortId, mDeviceType,
+                    mVendorId, mDisplayName);
         }
     }
 
@@ -252,12 +253,17 @@
 
         byte params[] = cmd.getParams();
         current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
+        current.mPortId = getPortId(current.mPhysicalAddress);
         current.mDeviceType = params[2] & 0xFF;
 
         increaseProcessedDeviceCount();
         checkAndProceedStage();
     }
 
+    private int getPortId(int physicalAddress) {
+        return tv().getPortId(physicalAddress);
+    }
+
     private void handleSetOsdName(HdmiCecMessage cmd) {
         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index bbecafa..0d457e3 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -16,10 +16,18 @@
 
 package com.android.server.hdmi;
 
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CEC_DISABLED;
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION;
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN;
+import static android.hardware.hdmi.HdmiControlManager.TIME_RECORDING_RESULT_EXTRA_CEC_DISABLED;
+import static android.hardware.hdmi.HdmiControlManager.TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION;
+import static android.hardware.hdmi.HdmiControlManager.TIME_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE;
+
 import android.content.Intent;
 import android.hardware.hdmi.HdmiCecDeviceInfo;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiRecordSources;
+import android.hardware.hdmi.HdmiTimerRecordSources;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.media.AudioManager;
 import android.media.AudioSystem;
@@ -47,7 +55,7 @@
 final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     private static final String TAG = "HdmiCecLocalDeviceTv";
 
-    // Whether ARC is available or not. "true" means that ARC is estabilished between TV and
+    // Whether ARC is available or not. "true" means that ARC is established between TV and
     // AVR as audio receiver.
     @ServiceThreadOnly
     private boolean mArcEstablished = false;
@@ -190,6 +198,10 @@
         }
     }
 
+    int getPortId(int physicalAddress) {
+        return mService.pathToPortId(physicalAddress);
+    }
+
     /**
      * Returns the previous port id kept to handle input switching on <Inactive Source>.
      */
@@ -512,8 +524,8 @@
         }
 
         addCecDevice(new HdmiCecDeviceInfo(deviceInfo.getLogicalAddress(),
-                deviceInfo.getPhysicalAddress(), deviceInfo.getDeviceType(),
-                deviceInfo.getVendorId(), osdName));
+                deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
+                deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
         return true;
     }
 
@@ -819,7 +831,8 @@
             // Assumes only one OneTouchRecordAction.
             OneTouchRecordAction action = actions.get(0);
             if (action.getRecorderAddress() != message.getSource()) {
-                displayOsd(HdmiControlManager.MESSAGE_NO_RECORDING_PREVIOUS_RECORDING_IN_PROGRESS);
+                announceOneTouchRecordResult(
+                        HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
             }
             return super.handleRecordTvScreen(message);
         }
@@ -830,6 +843,14 @@
         return true;
     }
 
+    void announceOneTouchRecordResult(int result) {
+        mService.invokeOneTouchRecordResult(result);
+    }
+
+    void announceTimerRecordingResult(int result) {
+        mService.invokeTimerRecordingResult(result);
+    }
+
     private boolean isMessageForSystemAudio(HdmiCecMessage message) {
         if (message.getSource() != Constants.ADDR_AUDIO_SYSTEM
                 || message.getDestination() != Constants.ADDR_TV
@@ -1241,16 +1262,19 @@
         assertRunOnServiceThread();
         if (!mService.isControlEnabled()) {
             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
+            announceOneTouchRecordResult(ONE_TOUCH_RECORD_CEC_DISABLED);
             return;
         }
 
         if (!checkRecorder(recorderAddress)) {
             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
+            announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
             return;
         }
 
         if (!checkRecordSource(recordSource)) {
             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
+            announceOneTouchRecordResult(ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN);
             return;
         }
 
@@ -1264,11 +1288,13 @@
         assertRunOnServiceThread();
         if (!mService.isControlEnabled()) {
             Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
+            announceOneTouchRecordResult(ONE_TOUCH_RECORD_CEC_DISABLED);
             return;
         }
 
         if (!checkRecorder(recorderAddress)) {
             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
+            announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
             return;
         }
 
@@ -1292,8 +1318,35 @@
     @ServiceThreadOnly
     void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
         assertRunOnServiceThread();
+        if (!mService.isControlEnabled()) {
+            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
+            announceTimerRecordingResult(TIME_RECORDING_RESULT_EXTRA_CEC_DISABLED);
+            return;
+        }
 
-        // TODO: implement this.
+        if (!checkRecorder(recorderAddress)) {
+            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
+            announceTimerRecordingResult(
+                    TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
+            return;
+        }
+
+        if (!checkTimerRecordingSource(sourceType, recordSource)) {
+            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
+            announceTimerRecordingResult(
+                    TIME_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE);
+            return;
+        }
+
+        addAndStartAction(
+                new TimerRecordingAction(this, recorderAddress, sourceType, recordSource));
+        Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:"
+                + sourceType + ", RecordSource:" + Arrays.toString(recordSource));
+    }
+
+    private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) {
+        return (recordSource != null)
+                && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource);
     }
 
     @ServiceThreadOnly
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 79f1964..0855bfa 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -478,6 +478,78 @@
         return buildCommand(src, dest, Constants.MESSAGE_RECORD_OFF);
     }
 
+    /**
+     * Build &lt;Set Digital Timer&gt; command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param params byte array of timing information and digital service information to be recorded
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildSetDigitalTimer(int src, int dest, byte[] params) {
+        return buildCommand(src, dest, Constants.MESSAGE_SET_DIGITAL_TIMER, params);
+    }
+
+    /**
+     * Build &lt;Set Analogue Timer&gt; command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param params byte array of timing information and analog service information to be recorded
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildSetAnalogueTimer(int src, int dest, byte[] params) {
+        return buildCommand(src, dest, Constants.MESSAGE_SET_ANALOG_TIMER, params);
+    }
+
+    /**
+     * Build &lt;Set External Timer&gt; command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param params byte array of timing information and external source information to be recorded
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildSetExternalTimer(int src, int dest, byte[] params) {
+        return buildCommand(src, dest, Constants.MESSAGE_SET_EXTERNAL_TIMER, params);
+    }
+
+    /**
+     * Build &lt;Clear Digital Timer&gt; command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param params byte array of timing information and digital service information to be cleared
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildClearDigitalTimer(int src, int dest, byte[] params) {
+        return buildCommand(src, dest, Constants.MESSAGE_CLEAR_DIGITAL_TIMER, params);
+    }
+
+    /**
+     * Build &lt;Clear Analog Timer&gt; command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param params byte array of timing information and analog service information to be cleared
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildClearAnalogueTimer(int src, int dest, byte[] params) {
+        return buildCommand(src, dest, Constants.MESSAGE_CLEAR_ANALOG_TIMER, params);
+    }
+
+    /**
+     * Build &lt;Clear Digital Timer&gt; command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param params byte array of timing information and external source information to be cleared
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildClearExternalTimer(int src, int dest, byte[] params) {
+        return buildCommand(src, dest, Constants.MESSAGE_CLEAR_EXTERNAL_TIMER, params);
+    }
+
     /***** Please ADD new buildXXX() methods above. ******/
 
     /**
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index eafcd7c..3532213 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -32,7 +32,7 @@
 import android.hardware.hdmi.IHdmiDeviceEventListener;
 import android.hardware.hdmi.IHdmiHotplugEventListener;
 import android.hardware.hdmi.IHdmiInputChangeListener;
-import android.hardware.hdmi.IHdmiRecordRequestListener;
+import android.hardware.hdmi.IHdmiRecordListener;
 import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
 import android.hardware.hdmi.IHdmiVendorCommandListener;
 import android.media.AudioManager;
@@ -45,6 +45,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.provider.Settings.Global;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -160,10 +161,10 @@
     private InputChangeListenerRecord mInputChangeListenerRecord;
 
     @GuardedBy("mLock")
-    private IHdmiRecordRequestListener mRecordRequestListener;
+    private IHdmiRecordListener mRecordListener;
 
     @GuardedBy("mLock")
-    private HdmiRecordRequestListenerRecord mRecordRequestListenerRecord;
+    private HdmiRecordListenerRecord mRecordListenerRecord;
 
     // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
     // handling will be disabled and no request will be handled.
@@ -197,6 +198,12 @@
     // from being modified.
     private List<HdmiPortInfo> mPortInfo;
 
+    // Map from path(physical address) to port ID.
+    private SparseIntArray mPortIdMap = new SparseIntArray();
+
+    // Map from port ID to HdmiPortInfo.
+    private SparseArray<HdmiPortInfo> mPortInfoMap = new SparseArray<>();
+
     private HdmiCecMessageValidator mMessageValidator;
 
     private final PowerStateReceiver mPowerStateReceiver = new PowerStateReceiver();
@@ -238,7 +245,7 @@
         if (mMhlController == null) {
             Slog.i(TAG, "Device does not support MHL-control.");
         }
-        mPortInfo = initPortInfo();
+        initPortInfo();
         mMessageValidator = new HdmiCecMessageValidator(this);
         publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
 
@@ -318,7 +325,7 @@
     // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
     // keep them in one place.
     @ServiceThreadOnly
-    private List<HdmiPortInfo> initPortInfo() {
+    private void initPortInfo() {
         assertRunOnServiceThread();
         HdmiPortInfo[] cecPortInfo = null;
 
@@ -328,7 +335,12 @@
             cecPortInfo = mCecController.getPortInfos();
         }
         if (cecPortInfo == null) {
-            return Collections.emptyList();
+            return;
+        }
+
+        for (HdmiPortInfo info : cecPortInfo) {
+            mPortIdMap.put(info.getAddress(), info.getId());
+            mPortInfoMap.put(info.getId(), info);
         }
 
         HdmiPortInfo[] mhlPortInfo = new HdmiPortInfo[0];
@@ -337,27 +349,24 @@
             // mhlPortInfo = mMhlController.getPortInfos();
         }
 
-        // Use the id (port number) to find the matched info between CEC and MHL to combine them
-        // into one. Leave the field `mhlSupported` to false if matched MHL entry is not found.
-        ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
-        for (int i = 0; i < cecPortInfo.length; ++i) {
-            HdmiPortInfo cec = cecPortInfo[i];
-            int id = cec.getId();
-            boolean mhlInfoFound = false;
-            for (HdmiPortInfo mhl : mhlPortInfo) {
-                if (id == mhl.getId()) {
-                    result.add(new HdmiPortInfo(id, cec.getType(), cec.getAddress(),
-                            cec.isCecSupported(), mhl.isMhlSupported(), cec.isArcSupported()));
-                    mhlInfoFound = true;
-                    break;
-                }
-            }
-            if (!mhlInfoFound) {
-                result.add(cec);
+        ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
+        for (HdmiPortInfo info : mhlPortInfo) {
+            if (info.isMhlSupported()) {
+                mhlSupportedPorts.add(info.getId());
             }
         }
 
-        return Collections.unmodifiableList(result);
+        // Build HDMI port info list with CEC port info plus MHL supported flag.
+        ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
+        for (HdmiPortInfo info : cecPortInfo) {
+            if (mhlSupportedPorts.contains(info.getId())) {
+                result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
+                        info.isCecSupported(), true, info.isArcSupported()));
+            } else {
+                result.add(info);
+            }
+        }
+        mPortInfo = Collections.unmodifiableList(result);
     }
 
     /**
@@ -366,22 +375,19 @@
      * @param portId HDMI port id
      * @return {@link HdmiPortInfo} for the given port
      */
+    @ServiceThreadOnly
     HdmiPortInfo getPortInfo(int portId) {
-        // mPortInfo is an unmodifiable list and the only reference to its inner list.
-        // No lock is necessary.
-        for (HdmiPortInfo info : mPortInfo) {
-            if (portId == info.getId()) {
-                return info;
-            }
-        }
-        return null;
+        assertRunOnServiceThread();
+        return mPortInfoMap.get(portId, null);
     }
 
     /**
      * Returns the routing path (physical address) of the HDMI port for the given
      * port id.
      */
+    @ServiceThreadOnly
     int portIdToPath(int portId) {
+        assertRunOnServiceThread();
         HdmiPortInfo portInfo = getPortInfo(portId);
         if (portInfo == null) {
             Slog.e(TAG, "Cannot find the port info: " + portId);
@@ -396,23 +402,17 @@
      * the port id to be returned is the ID associated with the port address
      * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
      */
+    @ServiceThreadOnly
     int pathToPortId(int path) {
+        assertRunOnServiceThread();
         int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
-        for (HdmiPortInfo info : mPortInfo) {
-            if (portAddress == info.getAddress()) {
-                return info.getId();
-            }
-        }
-        return Constants.INVALID_PORT_ID;
+        return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
     }
 
+    @ServiceThreadOnly
     boolean isValidPortId(int portId) {
-        for (HdmiPortInfo info : mPortInfo) {
-            if (portId == info.getId()) {
-                return true;
-            }
-        }
-        return false;
+        assertRunOnServiceThread();
+        return getPortInfo(portId) != null;
     }
 
     /**
@@ -468,12 +468,12 @@
     /**
      * Whether a device of the specified physical address is connected to ARC enabled port.
      */
+    @ServiceThreadOnly
     boolean isConnectedToArcPort(int physicalAddress) {
-        for (HdmiPortInfo portInfo : mPortInfo) {
-            if (hasSameTopPort(portInfo.getAddress(), physicalAddress)
-                    && portInfo.isArcSupported()) {
-                return true;
-            }
+        assertRunOnServiceThread();
+        int portId = mPortIdMap.get(physicalAddress);
+        if (portId != Constants.INVALID_PORT_ID) {
+            return mPortInfoMap.get(portId).isArcSupported();
         }
         return false;
     }
@@ -622,7 +622,8 @@
         // TODO: find better name instead of model name.
         String displayName = Build.MODEL;
         return new HdmiCecDeviceInfo(logicalAddress,
-                getPhysicalAddress(), deviceType, getVendorId(), displayName);
+                getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
+                getVendorId(), displayName);
     }
 
     // Record class that monitors the event of the caller of being killed. Used to clean up
@@ -692,11 +693,11 @@
         }
     }
 
-    private class HdmiRecordRequestListenerRecord implements IBinder.DeathRecipient {
+    private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
         @Override
         public void binderDied() {
             synchronized (mLock) {
-                mRecordRequestListener = null;
+                mRecordListener = null;
             }
         }
     }
@@ -1023,11 +1024,11 @@
                     }
                 }
             });
-         }
+        }
 
         @Override
-        public void setOneTouchRecordRequestListener(IHdmiRecordRequestListener listener) {
-            HdmiControlService.this.setOneTouchRecordRequestListener(listener);
+        public void setHdmiRecordListener(IHdmiRecordListener listener) {
+            HdmiControlService.this.setHdmiRecordListener(listener);
         }
 
         @Override
@@ -1230,32 +1231,55 @@
         }
     }
 
-    private void setOneTouchRecordRequestListener(IHdmiRecordRequestListener listener) {
+    private void setHdmiRecordListener(IHdmiRecordListener listener) {
         synchronized (mLock) {
-            mRecordRequestListenerRecord = new HdmiRecordRequestListenerRecord();
+            mRecordListenerRecord = new HdmiRecordListenerRecord();
             try {
-                listener.asBinder().linkToDeath(mRecordRequestListenerRecord, 0);
+                listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
             } catch (RemoteException e) {
-                Slog.w(TAG, "Listener already died", e);
-                return;
+                Slog.w(TAG, "Listener already died.", e);
             }
-            mRecordRequestListener = listener;
+            mRecordListener = listener;
         }
     }
 
     byte[] invokeRecordRequestListener(int recorderAddress) {
         synchronized (mLock) {
-            try {
-                if (mRecordRequestListener != null) {
-                    return mRecordRequestListener.onRecordRequestReceived(recorderAddress);
+            if (mRecordListener != null) {
+                try {
+                    return mRecordListener.getOneTouchRecordSource(recorderAddress);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to start record.", e);
                 }
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to start record.", e);
             }
             return EmptyArray.BYTE;
         }
     }
 
+    void invokeOneTouchRecordResult(int result) {
+        synchronized (mLock) {
+            if (mRecordListener != null) {
+                try {
+                    mRecordListener.onOneTouchRecordResult(result);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
+                }
+            }
+        }
+    }
+
+    void invokeTimerRecordingResult(int result) {
+        synchronized (mLock) {
+            if (mRecordListener != null) {
+                try {
+                    mRecordListener.onTimerRecordingResult(result);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
+                }
+            }
+        }
+    }
+
     private void invokeCallback(IHdmiControlCallback callback, int result) {
         try {
             callback.onComplete(result);
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index 7d7a95b..4ef7c96 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -153,6 +153,7 @@
         }
         tv().addCecDevice(new HdmiCecDeviceInfo(
                 mDeviceLogicalAddress, mDevicePhysicalAddress,
+                tv().getPortId(mDevicePhysicalAddress),
                 HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress),
                 mVendorId, mDisplayName));
 
diff --git a/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java b/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java
index 9ecbc5e..befc640 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java
@@ -16,26 +16,24 @@
 
 package com.android.server.hdmi;
 
-import static android.hardware.hdmi.HdmiControlManager.MESSAGE_NO_RECORDING_CHECK_RECORDER_CONNECTION;
-import static android.hardware.hdmi.HdmiControlManager.MESSAGE_RECORDING_ANALOGUE_SERVICE;
-import static android.hardware.hdmi.HdmiControlManager.MESSAGE_RECORDING_CURRENTLY_SELECTED_SOURCE;
-import static android.hardware.hdmi.HdmiControlManager.MESSAGE_RECORDING_DIGITAL_SERVICE;
-import static android.hardware.hdmi.HdmiControlManager.MESSAGE_RECORDING_EXTERNAL_INPUT;
-import static android.hardware.hdmi.HdmiControlManager.MESSAGE_RECORDING_STATUS_MESSAGE_START;
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION;
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE;
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE;
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE;
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT;
 
 import android.util.Slog;
 
 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
 
 /**
- * Feature action that performs one touch record. This class only provides a skeleton of one touch
- * play and has no detail implementation.
+ * Feature action that performs one touch record.
  */
 public class OneTouchRecordAction extends FeatureAction {
     private static final String TAG = "OneTouchRecordAction";
 
-    // Timer out for waiting <Record Status>
-    private static final int RECORD_STATUS_TIMEOUT = 120000;
+    // Timer out for waiting <Record Status> 120s
+    private static final int RECORD_STATUS_TIMEOUT_MS = 120000;
 
     // State that waits for <Record Status> once sending <Record On>
     private static final int STATE_WAITING_FOR_RECORD_STATUS = 1;
@@ -65,13 +63,14 @@
                     public void onSendCompleted(int error) {
                         // if failed to send <Record On>, display error message and finish action.
                         if (error != Constants.SEND_RESULT_SUCCESS) {
-                            tv().displayOsd(MESSAGE_NO_RECORDING_CHECK_RECORDER_CONNECTION);
+                            tv().announceOneTouchRecordResult(
+                                    ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
                             finish();
                             return;
                         }
 
                         mState = STATE_WAITING_FOR_RECORD_STATUS;
-                        addTimer(mState, RECORD_STATUS_TIMEOUT);
+                        addTimer(mState, RECORD_STATUS_TIMEOUT_MS);
                     }
                 });
     }
@@ -97,18 +96,16 @@
         }
 
         int recordStatus = cmd.getParams()[0];
+        tv().announceOneTouchRecordResult(recordStatus);
         Slog.i(TAG, "Got record status:" + recordStatus + " from " + cmd.getSource());
 
-        int recordStatusMessageCode = recordStatus + MESSAGE_RECORDING_STATUS_MESSAGE_START;
-        tv().displayOsd(recordStatusMessageCode);
-
         // If recording started successfully, change state and keep this action until <Record Off>
         // received. Otherwise, finish action.
-        switch (recordStatusMessageCode) {
-            case MESSAGE_RECORDING_CURRENTLY_SELECTED_SOURCE:
-            case MESSAGE_RECORDING_DIGITAL_SERVICE:
-            case MESSAGE_RECORDING_ANALOGUE_SERVICE:
-            case MESSAGE_RECORDING_EXTERNAL_INPUT:
+        switch (recordStatus) {
+            case ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE:
+            case ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE:
+            case ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE:
+            case ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT:
                 mState = STATE_RECORDING_IN_PROGRESS;
                 mActionTimer.clearTimerMessage();
                 break;
@@ -126,7 +123,7 @@
             return;
         }
 
-        tv().displayOsd(MESSAGE_NO_RECORDING_CHECK_RECORDER_CONNECTION);
+        tv().announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
         finish();
     }
 
diff --git a/services/core/java/com/android/server/hdmi/TimerRecordingAction.java b/services/core/java/com/android/server/hdmi/TimerRecordingAction.java
new file mode 100644
index 0000000..1dc26f1
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/TimerRecordingAction.java
@@ -0,0 +1,169 @@
+/*
+ * 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 static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE;
+import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
+import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
+import static android.hardware.hdmi.HdmiControlManager.TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION;
+
+import android.util.Slog;
+
+import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+
+import java.util.Arrays;
+
+/**
+ * Feature action that performs timer recording.
+ */
+public class TimerRecordingAction extends FeatureAction {
+    private static final String TAG = "TimerRecordingAction";
+
+    // Timer out for waiting <Timer Status> 120s.
+    private static final int TIMER_STATUS_TIMEOUT_MS = 120000;
+
+    // State that waits for <Timer Status> once sending <Set XXX Timer>
+    private static final int STATE_WAITING_FOR_TIMER_STATUS = 1;
+
+    private final int mRecorderAddress;
+    private final int mSourceType;
+    private final byte[] mRecordSource;
+
+    TimerRecordingAction(HdmiCecLocalDevice source, int recorderAddress, int sourceType,
+            byte[] recordSource) {
+        super(source);
+        mRecorderAddress = recorderAddress;
+        mSourceType = sourceType;
+        mRecordSource = recordSource;
+    }
+
+    @Override
+    boolean start() {
+        sendTimerMessage();
+        return true;
+    }
+
+    private void sendTimerMessage() {
+        HdmiCecMessage message = null;
+        switch (mSourceType) {
+            case TIMER_RECORDING_TYPE_DIGITAL:
+                message = HdmiCecMessageBuilder.buildSetDigitalTimer(getSourceAddress(),
+                        mRecorderAddress, mRecordSource);
+                break;
+            case TIMER_RECORDING_TYPE_ANALOGUE:
+                message = HdmiCecMessageBuilder.buildSetAnalogueTimer(getSourceAddress(),
+                        mRecorderAddress, mRecordSource);
+                break;
+            case TIMER_RECORDING_TYPE_EXTERNAL:
+                message = HdmiCecMessageBuilder.buildSetExternalTimer(getSourceAddress(),
+                        mRecorderAddress, mRecordSource);
+                break;
+            default:
+                tv().announceTimerRecordingResult(
+                        TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
+                finish();
+                return;
+        }
+        sendCommand(message, new SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                if (error != Constants.SEND_RESULT_SUCCESS) {
+                    mState = STATE_WAITING_FOR_TIMER_STATUS;
+                    addTimer(mState, TIMER_STATUS_TIMEOUT_MS);
+                    finish();
+                    return;
+                }
+            }
+        });
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        if (mState != STATE_WAITING_FOR_TIMER_STATUS) {
+            return false;
+        }
+
+        if (cmd.getSource() != mRecorderAddress) {
+            return false;
+        }
+
+        switch (cmd.getOpcode()) {
+            case Constants.MESSAGE_TIMER_STATUS:
+                return handleTimerStatus(cmd);
+            case Constants.MESSAGE_FEATURE_ABORT:
+                return handleFeatureAbort(cmd);
+        }
+        return false;
+    }
+
+    private boolean handleTimerStatus(HdmiCecMessage cmd) {
+        byte[] timerStatusData = cmd.getParams();
+        // [Timer Status Data] should be one or three bytes.
+        if (timerStatusData.length == 1 || timerStatusData.length == 3) {
+            tv().announceTimerRecordingResult(bytesToInt(timerStatusData));
+            Slog.i(TAG, "Received [Timer Status Data]:" + Arrays.toString(timerStatusData));
+        } else {
+            Slog.w(TAG, "Invalid [Timer Status Data]:" + Arrays.toString(timerStatusData));
+        }
+
+        // Unlike one touch record, finish timer record when <Timer Status> is received.
+        finish();
+        return true;
+    }
+
+    private boolean handleFeatureAbort(HdmiCecMessage cmd) {
+        byte[] params = cmd.getParams();
+        int messageType = params[0];
+        switch (messageType) {
+            case Constants.MESSAGE_SET_DIGITAL_TIMER: // fall through
+            case Constants.MESSAGE_SET_ANALOG_TIMER: // fall through
+            case Constants.MESSAGE_SET_EXTERNAL_TIMER: // fall through
+                break;
+            default:
+                return false;
+        }
+        int reason = params[1];
+        Slog.i(TAG, "[Feature Abort] for " + messageType + " reason:" + reason);
+        tv().announceTimerRecordingResult(TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
+        finish();
+        return true;
+    }
+
+    // Convert byte array to int.
+    private static int bytesToInt(byte[] data) {
+        if (data.length > 4) {
+            throw new IllegalArgumentException("Invalid data size:" + Arrays.toString(data));
+        }
+        int result = 0;
+        for (int i = 0; i < data.length; ++i) {
+            int shift = (3 - i) * 8;
+            result |= ((data[i] & 0xFF) << shift);
+        }
+        return result;
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        if (mState != state) {
+            Slog.w(TAG, "Timeout in invalid state:[Expected:" + mState + ", Actual:" + state + "]");
+            return;
+        }
+
+        tv().announceTimerRecordingResult(TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
+        finish();
+    }
+}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 5808e2f..d0fdc8b 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -693,45 +693,6 @@
         updateServiceConnectionLocked(sessionState.mInfo.getComponent(), userId);
     }
 
-    private void unregisterClientInternalLocked(IBinder clientToken, String inputId,
-            int userId) {
-        UserState userState = getUserStateLocked(userId);
-        ClientState clientState = userState.clientStateMap.get(clientToken);
-        if (clientState != null) {
-            clientState.mInputIds.remove(inputId);
-            if (clientState.isEmpty()) {
-                userState.clientStateMap.remove(clientToken);
-            }
-        }
-
-        TvInputInfo info = userState.inputMap.get(inputId).mInfo;
-        if (info == null) {
-            return;
-        }
-        ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
-        if (serviceState == null) {
-            return;
-        }
-
-        // Remove this client from the client list and unregister the callback.
-        serviceState.mClientTokens.remove(clientToken);
-        if (!serviceState.mClientTokens.isEmpty()) {
-            // We have other clients who want to keep the callback. Do this later.
-            return;
-        }
-        if (serviceState.mService == null || serviceState.mCallback == null) {
-            return;
-        }
-        try {
-            serviceState.mService.unregisterCallback(serviceState.mCallback);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "error in unregisterCallback", e);
-        } finally {
-            serviceState.mCallback = null;
-            updateServiceConnectionLocked(info.getComponent(), userId);
-        }
-    }
-
     private void notifyInputAddedLocked(UserState userState, String inputId) {
         if (DEBUG) {
             Slog.d(TAG, "notifyInputAdded: inputId = " + inputId);
@@ -1415,13 +1376,6 @@
 
                         pw.increaseIndent();
 
-                        pw.println("mInputIds:");
-                        pw.increaseIndent();
-                        for (String inputId : client.mInputIds) {
-                            pw.println(inputId);
-                        }
-                        pw.decreaseIndent();
-
                         pw.println("mSessionTokens:");
                         pw.increaseIndent();
                         for (IBinder token : client.mSessionTokens) {
@@ -1545,7 +1499,6 @@
     }
 
     private final class ClientState implements IBinder.DeathRecipient {
-        private final List<String> mInputIds = new ArrayList<String>();
         private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
 
         private IBinder mClientToken;
@@ -1557,7 +1510,7 @@
         }
 
         public boolean isEmpty() {
-            return mInputIds.isEmpty() && mSessionTokens.isEmpty();
+            return mSessionTokens.isEmpty();
         }
 
         @Override
@@ -1565,17 +1518,13 @@
             synchronized (mLock) {
                 UserState userState = getUserStateLocked(mUserId);
                 // DO NOT remove the client state of clientStateMap in this method. It will be
-                // removed in releaseSessionLocked() or unregisterClientInternalLocked().
+                // removed in releaseSessionLocked().
                 ClientState clientState = userState.clientStateMap.get(mClientToken);
                 if (clientState != null) {
                     while (clientState.mSessionTokens.size() > 0) {
                         releaseSessionLocked(
                                 clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId);
                     }
-                    while (clientState.mInputIds.size() > 0) {
-                        unregisterClientInternalLocked(
-                                mClientToken, clientState.mInputIds.get(0), mUserId);
-                    }
                 }
                 mClientToken = null;
             }
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index d2044be..aa6c47c 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -149,10 +149,21 @@
      */
     public static final int EXITED_ECM                     = 42;
 
+    /**
+     * The outgoing call failed with an unknown cause.
+     */
+    public static final int OUTGOING_FAILURE = 43;
+
+    /**
+     * The outgoing call was canceled by the {@link android.telecomm.ConnectionService}.
+     */
+    public static final int OUTGOING_CANCELED = 44;
+
     /** Smallest valid value for call disconnect codes. */
     public static final int MINIMUM_VALID_VALUE = NOT_DISCONNECTED;
+
     /** Largest valid value for call disconnect codes. */
-    public static final int MAXIMUM_VALID_VALUE = EXITED_ECM;
+    public static final int MAXIMUM_VALID_VALUE = OUTGOING_CANCELED;
 
     /** Private constructor to avoid class instantiation. */
     private DisconnectCause() {
@@ -246,6 +257,10 @@
             return "EXITED_ECM";
         case ERROR_UNSPECIFIED:
             return "ERROR_UNSPECIFIED";
+        case OUTGOING_FAILURE:
+            return "OUTGOING_FAILURE";
+        case OUTGOING_CANCELED:
+            return "OUTGOING_CANCELED";
         default:
             return "INVALID";
         }