am b1dca162: Merge "Maintain display power status for playback device" into klp-modular-dev

* commit 'b1dca1624dc8d853a52d1918c7672173e9b79836':
  Maintain display power status for playback device
diff --git a/core/java/android/hardware/hdmi/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java
index 38d9de4..8578a32 100644
--- a/core/java/android/hardware/hdmi/HdmiCec.java
+++ b/core/java/android/hardware/hdmi/HdmiCec.java
@@ -160,6 +160,12 @@
     public static final int MESSAGE_SET_EXTERNAL_TIMER = 0xA2;
     public static final int MESSAGE_ABORT = 0xFF;
 
+    public static final int POWER_STATUS_UNKNOWN = -1;
+    public static final int POWER_STATUS_ON = 0;
+    public static final int POWER_STATUS_STANDBY = 1;
+    public static final int POWER_TRANSIENT_TO_ON = 2;
+    public static final int POWER_TRANSIENT_TO_STANDBY = 3;
+
     private static final int[] ADDRESS_TO_TYPE = {
         DEVICE_TV,  // ADDR_TV
         DEVICE_RECORDER,  // ADDR_RECORDER_1
diff --git a/core/java/android/hardware/hdmi/HdmiCecClient.java b/core/java/android/hardware/hdmi/HdmiCecClient.java
index d7f4a72..1f382e6 100644
--- a/core/java/android/hardware/hdmi/HdmiCecClient.java
+++ b/core/java/android/hardware/hdmi/HdmiCecClient.java
@@ -110,16 +110,20 @@
     }
 
     /**
-     * Send <GiveDevicePowerStatus> message.
+     * Returns true if the TV or attached display is powered on.
+     * <p>
+     * The result of this method is only meaningful on playback devices (where the device
+     * type is {@link HdmiCec#DEVICE_PLAYBACK}).
+     * </p>
      *
-     * @param address logical address of the device to send the message to, such as
-     *        {@link HdmiCec#ADDR_TV}.
+     * @return true if TV is on; otherwise false.
      */
-    public void sendGiveDevicePowerStatus(int address) {
+    public boolean isTvOn() {
         try {
-            mService.sendGiveDevicePowerStatus(mBinder, address);
+            return mService.isTvOn(mBinder);
         } catch (RemoteException e) {
-            Log.e(TAG, "sendGiveDevicePowerStatus threw exception ", e);
+            Log.e(TAG, "isTvOn threw exception ", e);
         }
+        return false;
     }
 }
diff --git a/core/java/android/hardware/hdmi/HdmiCecManager.java b/core/java/android/hardware/hdmi/HdmiCecManager.java
index 575785d..10b058c 100644
--- a/core/java/android/hardware/hdmi/HdmiCecManager.java
+++ b/core/java/android/hardware/hdmi/HdmiCecManager.java
@@ -45,6 +45,9 @@
      * @return {@link HdmiCecClient} instance. {@code null} on failure.
      */
     public HdmiCecClient getClient(int type, HdmiCecClient.Listener listener) {
+        if (mService == null) {
+            return null;
+        }
         try {
             IBinder b = mService.allocateLogicalDevice(type, getListenerWrapper(listener));
             return HdmiCecClient.create(mService, b);
diff --git a/core/java/android/hardware/hdmi/IHdmiCecService.aidl b/core/java/android/hardware/hdmi/IHdmiCecService.aidl
index 6fefcf8..727f469 100644
--- a/core/java/android/hardware/hdmi/IHdmiCecService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiCecService.aidl
@@ -34,7 +34,7 @@
     void sendInactiveSource(IBinder b);
     void sendImageViewOn(IBinder b);
     void sendTextViewOn(IBinder b);
-    void sendGiveDevicePowerStatus(IBinder b, int address);
+    boolean isTvOn(IBinder b);
     void sendMessage(IBinder b, in HdmiCecMessage message);
 }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecDevice.java
index 9916435..ebb48dd 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecDevice.java
@@ -27,8 +27,12 @@
 import java.util.List;
 
 /**
- * CecDevice class represents a CEC logical device characterized
- * by its device type. A physical device can contain the functions of
+ * HdmiCecDevice class represents a CEC logical device characterized
+ * by its device type. It is a superclass of those serving concrete device type.
+ * Currently we're interested in playback(one of sources), display(sink) device type
+ * only. The support for the other types like recorder, audio system will come later.
+ *
+ * <p>A physical device can contain the functions of
  * more than one logical device, in which case it should create
  * as many logical devices as necessary.
  *
@@ -41,7 +45,7 @@
  *
  * <p>Declared as package-private, accessed by HdmiCecService only.
  */
-final class HdmiCecDevice {
+abstract class HdmiCecDevice {
     private static final String TAG = "HdmiCecDevice";
 
     private final int mType;
@@ -49,19 +53,39 @@
     // List of listeners to the message/event coming to the device.
     private final List<IHdmiCecListener> mListeners = new ArrayList<IHdmiCecListener>();
     private final Binder mBinder = new Binder();
+    private final HdmiCecService mService;
 
     private String mName;
     private boolean mIsActiveSource;
 
     /**
+     * Factory method that creates HdmiCecDevice instance to the device type.
+     */
+    public static HdmiCecDevice create(HdmiCecService service, int type) {
+        if (type == HdmiCec.DEVICE_PLAYBACK) {
+            return new HdmiCecDevicePlayback(service, type);
+        } else if (type == HdmiCec.DEVICE_TV) {
+            return new HdmiCecDeviceTv(service, type);
+        }
+        return null;
+    }
+
+    /**
      * Constructor.
      */
-    public HdmiCecDevice(int type) {
+    public HdmiCecDevice(HdmiCecService service, int type) {
+        mService = service;
         mType = type;
         mIsActiveSource = false;
     }
 
     /**
+     * Called right after the class is instantiated. This method can be used to
+     * implement any initialization tasks for the instance.
+     */
+    abstract public void initialize();
+
+    /**
      * Return the binder token that identifies this instance.
      */
     public Binder getToken() {
@@ -69,6 +93,13 @@
     }
 
     /**
+     * Return the service instance.
+     */
+    public HdmiCecService getService() {
+        return mService;
+    }
+
+    /**
      * Return the type of this device.
      */
     public int getType() {
@@ -128,6 +159,7 @@
         if (opcode == HdmiCec.MESSAGE_ACTIVE_SOURCE) {
             mIsActiveSource = false;
         }
+
         if (mListeners.size() == 0) {
             return;
         }
@@ -167,4 +199,13 @@
     public void setIsActiveSource(boolean state) {
         mIsActiveSource = state;
     }
+
+    /**
+     * Check if the connected sink device is in powered-on state. The default implementation
+     * simply returns false. Should be overriden by subclass to report the correct state.
+     */
+    public boolean isSinkDeviceOn() {
+        Log.w(TAG, "Not valid for the device type: " + mType);
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecDevicePlayback.java
new file mode 100644
index 0000000..0310264
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecDevicePlayback.java
@@ -0,0 +1,98 @@
+/*
+ * 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 android.hardware.hdmi.HdmiCec;
+
+/**
+ * Class for the logical device of playback type. Devices such as DVD/Blueray player
+ * that support 'playback' feature are classified as playback device. It is common
+ * that they don't have built-in display, therefore need to talk, stream their contents
+ * to TV/display device which is connected through HDMI cable.
+ *
+ * <p>It closely monitors the status of display device (other devices can be of interest
+ * too, but with much less priority), declares itself as 'active source' to have
+ * display show its output, switch the source state as ordered by display that may be
+ * talking to many other devices connected to it. It also receives commands from display
+ * such as remote control signal, standby, status report, playback mode.
+ *
+ * <p>Declared as package-private, accessed by HdmiCecService only.
+ */
+final class HdmiCecDevicePlayback extends HdmiCecDevice {
+    private static final String TAG = "HdmiCecDevicePlayback";
+
+    private int mSinkDevicePowerStatus;
+
+    /**
+     * Constructor.
+     */
+    public HdmiCecDevicePlayback(HdmiCecService service, int type) {
+        super(service, type);
+        mSinkDevicePowerStatus = HdmiCec.POWER_STATUS_UNKNOWN;
+    }
+
+    @Override
+    public void initialize() {
+        // Playback device tries to obtain the power status of TV/display when created,
+        // and maintains it all through its lifecycle. CEC spec says there is
+        // a maximum 1 second response time. Therefore it should be kept in mind
+        // that there can be as much amount of period of time the power status
+        // of the display remains unknown after the query is sent out.
+        queryTvPowerStatus();
+    }
+
+    private void queryTvPowerStatus() {
+        getService().sendMessage(getType(), HdmiCec.ADDR_TV,
+                HdmiCec.MESSAGE_GIVE_DEVICE_POWER_STATUS, HdmiCecService.EMPTY_PARAM);
+    }
+
+    @Override
+    public void handleMessage(int srcAddress, int dstAddress, int opcode, byte[] params) {
+        // Updates power status of display. The cases are:
+        // 1) Response for the queried power status request arrives. Update the status.
+        // 2) Broadcast or direct <Standby> command from TV, which is sent as TV itself is going
+        //    into standby mode too.
+        // 3) Broadcast <Report Physical Address> command from TV, which is sent while it boots up.
+        if (opcode == HdmiCec.MESSAGE_REPORT_POWER_STATUS) {
+            mSinkDevicePowerStatus = params[0];
+        } else if (srcAddress == HdmiCec.ADDR_TV) {
+            if (opcode == HdmiCec.MESSAGE_STANDBY) {
+                mSinkDevicePowerStatus = HdmiCec.POWER_STATUS_STANDBY;
+            } else if (opcode == HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS) {
+                mSinkDevicePowerStatus = HdmiCec.POWER_STATUS_ON;
+            }
+        }
+        super.handleMessage(srcAddress, dstAddress, opcode, params);
+    }
+
+    @Override
+    public void handleHotplug(boolean connected) {
+        // If cable get disconnected sink device becomes unreachable. Switch the status
+        // to unknown, and query the status once the cable gets connected back.
+        if (!connected) {
+            mSinkDevicePowerStatus = HdmiCec.POWER_STATUS_UNKNOWN;
+        } else {
+            queryTvPowerStatus();
+        }
+        super.handleHotplug(connected);
+    }
+
+    @Override
+    public boolean isSinkDeviceOn() {
+        return mSinkDevicePowerStatus == HdmiCec.POWER_STATUS_ON;
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecDeviceTv.java
new file mode 100644
index 0000000..09ff3ca
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecDeviceTv.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/**
+ * Class for logical device of TV type.
+ */
+final class HdmiCecDeviceTv extends HdmiCecDevice {
+    private static final String TAG = "HdmiCecDeviceTv";
+
+    /**
+     * Constructor.
+     */
+    public HdmiCecDeviceTv(HdmiCecService service, int type) {
+        super(service, type);
+    }
+
+    public void initialize() {
+        // TODO: Do the initialization task for TV device here.
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecService.java b/services/core/java/com/android/server/hdmi/HdmiCecService.java
index 0a7236c..1c9fed1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecService.java
@@ -30,6 +30,7 @@
 import android.util.SparseArray;
 
 import com.android.server.SystemService;
+import libcore.util.EmptyArray;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -37,8 +38,6 @@
 import java.util.ArrayList;
 import java.util.Locale;
 
-import libcore.util.EmptyArray;
-
 /**
  * Provides a service for sending and processing HDMI-CEC messages, and providing
  * the information on HDMI settings in general.
@@ -65,7 +64,7 @@
 
     private static final String PERMISSION = "android.permission.HDMI_CEC";
 
-    private static final byte[] EMPTY_PARAM = EmptyArray.BYTE;
+    static final byte[] EMPTY_PARAM = EmptyArray.BYTE;
 
     public HdmiCecService(Context context) {
         super(context);
@@ -76,7 +75,9 @@
     @Override
     public void onStart() {
         mNativePtr = nativeInit(this);
-        publishBinderService(Context.HDMI_CEC_SERVICE, new BinderService());
+        if (mNativePtr != 0) {
+            publishBinderService(Context.HDMI_CEC_SERVICE, new BinderService());
+        }
     }
 
     /**
@@ -88,7 +89,6 @@
         //       but better be handled in service by turning off the screen
         //       or putting the device into suspend mode. List up such messages
         //       and handle them here.
-        int type = HdmiCec.getTypeFromAddress(dstAddress);
         synchronized (mLock) {
             if (dstAddress == HdmiCec.ADDR_BROADCAST) {
                 for (int i = 0; i < mLogicalDevices.size(); ++i) {
@@ -96,6 +96,7 @@
                             params);
                 }
             } else {
+                int type = HdmiCec.getTypeFromAddress(dstAddress);
                 HdmiCecDevice device = mLogicalDevices.get(type);
                 if (device == null) {
                     Log.w(TAG, "logical device not found. type: " + type);
@@ -205,6 +206,11 @@
         throw new IllegalArgumentException("Device not found");
     }
 
+    // package-private. Used by HdmiCecDevice and its subclasses only.
+    void sendMessage(int type, int address, int opcode, byte[] params) {
+        nativeSendMessage(mNativePtr, type, address, opcode, params);
+    }
+
     private final class ListenerRecord implements IBinder.DeathRecipient {
         private final IHdmiCecListener mListener;
         private final int mType;
@@ -248,8 +254,13 @@
                         Log.e(TAG, "Logical address was not allocated");
                         return null;
                     } else {
-                        device = new HdmiCecDevice(type);
+                        device = HdmiCecDevice.create(HdmiCecService.this, type);
+                        if (device == null) {
+                            Log.e(TAG, "Device type not supported yet.");
+                            return null;
+                        }
                         device.setName(HdmiCec.getDefaultDeviceName(address));
+                        device.initialize();
                         mLogicalDevices.put(type, device);
                     }
                 }
@@ -331,12 +342,11 @@
         }
 
         @Override
-        public void sendGiveDevicePowerStatus(IBinder b, int address) {
+        public boolean isTvOn(IBinder b) {
             enforceAccessPermission();
             synchronized (mLock) {
                 HdmiCecDevice device = getLogicalDeviceLocked(b);
-                nativeSendMessage(mNativePtr, device.getType(), address,
-                        HdmiCec.MESSAGE_GIVE_DEVICE_POWER_STATUS, EMPTY_PARAM);
+                return device.isSinkDeviceOn();
             }
         }