Merge "TIF: Add content ratings for Netherlands" into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index 425675b..b2a73fe 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -17057,22 +17057,6 @@
     method public void onInputStateChanged(java.lang.String, int);
   }
 
-  public abstract class TvInputPassthroughWrapperService extends android.media.tv.TvInputService {
-    ctor public TvInputPassthroughWrapperService();
-    method public abstract java.lang.String getPassthroughInputId(java.lang.String);
-    method public abstract android.media.tv.TvInputPassthroughWrapperService.PassthroughWrapperSession onCreatePassthroughWrapperSession();
-    method public final android.media.tv.TvInputService.Session onCreateSession(java.lang.String);
-  }
-
-  public abstract class TvInputPassthroughWrapperService.PassthroughWrapperSession extends android.media.tv.TvInputService.Session {
-    ctor public TvInputPassthroughWrapperService.PassthroughWrapperSession();
-    method public abstract void onPassthroughSessionCreationFailed();
-    method public abstract void onPassthroughSessionReleased();
-    method public abstract void onPassthroughVideoAvailable();
-    method public abstract void onPassthroughVideoUnavailable(int);
-    method public final boolean onSetSurface(android.view.Surface);
-  }
-
   public abstract class TvInputService extends android.app.Service {
     ctor public TvInputService();
     method public final android.os.IBinder onBind(android.content.Intent);
@@ -17081,6 +17065,14 @@
     field public static final java.lang.String SERVICE_META_DATA = "android.media.tv.input";
   }
 
+  public abstract class TvInputService.HardwareSession extends android.media.tv.TvInputService.Session {
+    ctor public TvInputService.HardwareSession();
+    method public abstract java.lang.String getHardwareInputId();
+    method public void onHardwareVideoAvailable();
+    method public void onHardwareVideoUnavailable(int);
+    method public final boolean onSetSurface(android.view.Surface);
+  }
+
   public abstract class TvInputService.Session implements android.view.KeyEvent.Callback {
     ctor public TvInputService.Session();
     method public void notifyChannelRetuned(android.net.Uri);
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 50c5566..2311e67 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4865,6 +4865,15 @@
     <!-- TV content rating system strings for CO TV -->
 
     <!-- TV content rating system strings for DE TV -->
+    <string name="display_name_detv" translatable="false">DE-TV</string>
+    <string name="display_name_detv_all" translatable="false">ab 0 Jahren</string>
+    <string name="display_name_detv_12" translatable="false">ab 12 Jahren</string>
+    <string name="display_name_detv_16" translatable="false">ab 16 Jahren</string>
+    <string name="display_name_detv_18" translatable="false">ab 18 Jahren</string>
+    <string name="description_detv_all">Die nachfolgende Sendung ist für alle alters geeignet.</string>
+    <string name="description_detv_12">Die nachfolgende Sendung ist für Zuschauer unter 12 Jahren nicht geeignet.</string>
+    <string name="description_detv_16">Die nachfolgende Sendung ist für Zuschauer unter 16 Jahren nicht geeignet.</string>
+    <string name="description_detv_18">Die nachfolgende Sendung ist für Zuschauer unter 18 Jahren nicht geeignet.</string>
 
     <!-- TV content rating system strings for DK TV -->
 
@@ -4873,6 +4882,17 @@
     <!-- TV content rating system strings for FI TV -->
 
     <!-- TV content rating system strings for FR TV -->
+    <string name="display_name_frtv" translatable="false">FR-TV</string>
+    <string name="display_name_frtv_all" translatable="false">Les programmes tous publics</string>
+    <string name="display_name_frtv_10" translatable="false">Déconseillé aux -10 ans</string>
+    <string name="display_name_frtv_12" translatable="false">Déconseillé aux -12 ans</string>
+    <string name="display_name_frtv_16" translatable="false">Déconseillé aux -16 ans</string>
+    <string name="display_name_frtv_18" translatable="false">Déconseillé aux -18 ans</string>
+    <string name="description_frtv_all">Les programmes tous publics</string>
+    <string name="description_frtv_10">Programmes comportant certaines scènes susceptibles de heurter les -10 ans.</string>
+    <string name="description_frtv_12">Programmes pouvant troubler les -12 ans, notamment lorsque le scénario recourt de façon systématique et répétée à la violence physique ou psychologique.</string>
+    <string name="description_frtv_16">Programmes à caractère érotique ou de grande violence, susceptibles de nuire à l’épanouissement physique, mental ou moral des -16 ans.</string>
+    <string name="description_frtv_18">Programmes pornographiques ou de très grande violence, réservés à un public adulte averti et susceptibles de nuire à l’épanouissement physique, mental ou moral des -18 ans.</string>
 
     <!-- TV content rating system strings for GR TV -->
 
diff --git a/core/res/res/xml/tv_content_rating_systems.xml b/core/res/res/xml/tv_content_rating_systems.xml
index 6dcd10cc..2df091d 100644
--- a/core/res/res/xml/tv_content_rating_systems.xml
+++ b/core/res/res/xml/tv_content_rating_systems.xml
@@ -36,6 +36,32 @@
     <!-- TV content rating system for CO TV -->
 
     <!-- TV content rating system for DE TV -->
+    <rating-system-definition id="DE_TV"
+        displayName="@string/display_name_detv"
+        country="DE">
+        <rating-definition id="DE_TV_ALL"
+            displayName="@string/display_name_detv_all"
+            description="@string/description_detv_all"
+            ageHint="0" />
+        <rating-definition id="DE_TV_12"
+            displayName="@string/display_name_detv_12"
+            description="@string/description_detv_12"
+            ageHint="12" />
+        <rating-definition id="DE_TV_16"
+            displayName="@string/display_name_detv_16"
+            description="@string/description_detv_16"
+            ageHint="16" />
+        <rating-definition id="DE_TV_18"
+            displayName="@string/display_name_detv_18"
+            description="@string/description_detv_18"
+            ageHint="18" />
+        <order>
+            <rating id="DE_TV_ALL" />
+            <rating id="DE_TV_12" />
+            <rating id="DE_TV_16" />
+            <rating id="DE_TV_18" />
+        </order>
+    </rating-system-definition>
 
     <!-- TV content rating system for DK TV -->
 
@@ -44,6 +70,41 @@
     <!-- TV content rating system for FI TV -->
 
     <!-- TV content rating system for FR TV -->
+    <rating-system-definition id="FR_TV"
+        displayName="@string/display_name_frtv"
+        country="FR">
+        <rating-definition id="FR_TV_ALL"
+            displayName="@string/display_name_frtv_all"
+            description="@string/description_frtv_all"
+            ageHint="0" />
+        <rating-definition id="FR_TV_10"
+            displayName="@string/display_name_frtv_10"
+            description="@string/description_frtv_10"
+            ageHint="10">
+        </rating-definition>
+        <rating-definition id="FR_TV_12"
+            displayName="@string/display_name_frtv_12"
+            description="@string/description_frtv_12"
+            ageHint="12">
+        </rating-definition>
+        <rating-definition id="FR_TV_16"
+            displayName="@string/display_name_frtv_16"
+            description="@string/description_frtv_16"
+            ageHint="16">
+        </rating-definition>
+        <rating-definition id="FR_TV_18"
+            displayName="@string/display_name_frtv_18"
+            description="@string/description_frtv_18"
+            ageHint="18">
+        </rating-definition>
+        <order>
+            <rating id="FR_TV_ALL" />
+            <rating id="FR_TV_10" />
+            <rating id="FR_TV_12" />
+            <rating id="FR_TV_16" />
+            <rating id="FR_TV_18" />
+        </order>
+    </rating-system-definition>
 
     <!-- TV content rating system for GR TV -->
 
diff --git a/media/java/android/media/tv/ITvInputServiceCallback.aidl b/media/java/android/media/tv/ITvInputServiceCallback.aidl
index 26a0d20..df648e7 100644
--- a/media/java/android/media/tv/ITvInputServiceCallback.aidl
+++ b/media/java/android/media/tv/ITvInputServiceCallback.aidl
@@ -27,5 +27,4 @@
     void addHardwareTvInput(in int deviceID, in TvInputInfo inputInfo);
     void addHdmiCecTvInput(in int logicalAddress, in TvInputInfo inputInfo);
     void removeTvInput(in String inputId);
-    void setWrappedInputId(in String inputId, in String wrappedInputId);
 }
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index f8c22fc..8a918e1 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -27,7 +27,7 @@
  * @hide
  */
 oneway interface ITvInputSessionCallback {
-    void onSessionCreated(ITvInputSession session);
+    void onSessionCreated(ITvInputSession session, in IBinder hardwareSessionToken);
     void onSessionEvent(in String name, in Bundle args);
     void onChannelRetuned(in Uri channelUri);
     void onTracksChanged(in List<TvTrackInfo> tracks);
diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java
index 2b3ac5d..fc3ff81 100644
--- a/media/java/android/media/tv/TvContentRating.java
+++ b/media/java/android/media/tv/TvContentRating.java
@@ -124,10 +124,10 @@
  *         <td>CO_TV</td>
  *         <td></td>
  *     </tr-->
- *     <!--tr>
+ *     <tr>
  *         <td>DE_TV</td>
- *         <td></td>
- *     </tr-->
+ *         <td>The Germany television rating system</td>
+ *     </tr>
  *     <!--tr>
  *         <td>DK_TV</td>
  *         <td></td>
@@ -140,10 +140,10 @@
  *         <td>FI_TV</td>
  *         <td></td>
  *     </tr-->
- *     <!--tr>
+ *     <tr>
  *         <td>FR_TV</td>
- *         <td></td>
- *     </tr-->
+ *         <td>The content rating system in French</td>
+ *     </tr>
  *     <!--tr>
  *         <td>GR_TV</td>
  *         <td></td>
@@ -316,10 +316,31 @@
  *         <td>CO_TV_ALL</td>
  *         <td></td>
  *     </tr-->
- *     <!--tr>
+ *     <tr>
  *         <td>DE_TV_ALL</td>
- *         <td></td>
- *     </tr-->
+ *         <td>Without restriction. There are time schedules and certain age groups which have to be
+ *         considered. {@code DE_TV_ALL} is scheduled in daytime (6:00AM – 8:00PM). However, cinema
+ *         films classified with "12" may be shown during the daytime, if they are not considered
+ *         harmful to younger children.</td>
+ *     </tr>
+ *     <tr>
+ *         <td>DE_TV_12</td>
+ *         <td>Suitable for 12 years and above. There are time schedules and certain age groups
+ *         which have to be considered. {@code DE_TV_12} is scheduled in primetime (from 8:00PM
+ *         – 10.00 p.m.).</td>
+ *     </tr>
+ *     <tr>
+ *         <td>DE_TV_16</td>
+ *         <td>Suitable for 16 years and above. There are time schedules and certain age groups
+ *         which have to be considered. {@code DE_TV_16} is scheduled in late evening (from 10:00PM
+ *         - 11:00PM). </td>
+ *     </tr>
+ *     <tr>
+ *         <td>DE_TV_18</td>
+ *         <td>Suitable for 18 years and above. There are time schedules and certain age groups
+ *         which have to be considered. {@code DE_TV_18} is scheduled in late night (from 11:00PM
+ *         - 6:00AM). </td>
+ *     </tr>
  *     <!--tr>
  *         <td>DK_TV_ALL</td>
  *         <td></td>
@@ -332,10 +353,36 @@
  *         <td>FI_TV_ALL</td>
  *         <td></td>
  *     </tr-->
- *     <!--tr>
+ *     <tr>
  *         <td>FR_TV_ALL</td>
- *         <td></td>
- *     </tr-->
+ *         <td>A rating string for {@code FR_TV}. According to CSA in France, if no rating appears,
+ *         the program is most likely appropriate for all ages. In Android TV, however,
+ *         {@code RATING_FR_ALL} is used for handling that case.</td>
+ *     </tr>
+ *     <tr>
+ *         <td>FR_TV_10</td>
+ *         <td>A rating string for {@code FR_TV}. This rating is for programs that are not
+ *         recommended for children under 10.</td>
+ *     </tr>
+ *     <tr>
+ *         <td>FR_TV_12</td>
+ *         <td>A rating string for {@code FR_TV}. This rating is for programs that are not
+ *         recommended for children under 12. Programs rated this are not allowed to air before
+ *         10:00 pm (Some channels and programs are subject to exception). </td>
+ *     </tr>
+ *     <tr>
+ *         <td>FR_TV_16</td>
+ *         <td>A rating string for {@code FR_TV}. This rating is for programs that are not
+ *         recommended for children under 16. Programs rated this are not allowed to air before
+ *         10:30 pm (Some channels and programs are subject to exception). </td>
+ *     </tr>
+ *     <tr>
+ *         <td>FR_TV_18</td>
+ *         <td>A rating string for {@code FR_TV}.  This rating is for programs that are not
+ *         recommended for persons under 18. Programs rated this are allowed between midnight and
+ *         5 am and only on some channels. The access to these programs is locked by a personal
+ *         password.</td>
+ *     </tr>
  *     <!--tr>
  *         <td>GR_TV_ALL</td>
  *         <td></td>
@@ -603,10 +650,6 @@
  *         <td></td>
  *     </tr-->
  *     <!--tr>
- *         <td>DE_TV_</td>
- *         <td></td>
- *     </tr-->
- *     <!--tr>
  *         <td>DK_TV_</td>
  *         <td></td>
  *     </tr-->
@@ -619,10 +662,6 @@
  *         <td></td>
  *     </tr-->
  *     <!--tr>
- *         <td>FR_TV_</td>
- *         <td></td>
- *     </tr-->
- *     <!--tr>
  *         <td>GR_TV_</td>
  *         <td></td>
  *     </tr-->
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 9c4f121..fdf0d9c 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -1454,6 +1454,10 @@
             mPendingEventPool.release(p);
         }
 
+        IBinder getToken() {
+            return mToken;
+        }
+
         private void releaseInternal() {
             mToken = null;
             synchronized (mHandler) {
diff --git a/media/java/android/media/tv/TvInputPassthroughWrapperService.java b/media/java/android/media/tv/TvInputPassthroughWrapperService.java
deleted file mode 100644
index 08c802f6..0000000
--- a/media/java/android/media/tv/TvInputPassthroughWrapperService.java
+++ /dev/null
@@ -1,259 +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.media.tv;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Handler;
-import android.util.Log;
-import android.view.Surface;
-
-/**
- * TvInputPassthroughWrapperService represents a TV input which controls an external device
- * connected to a pass-through TV input (e.g. HDMI 1).
- * <p>
- * This service wraps around a pass-through TV input and delegates the {@link Surface} to the
- * connected TV input so that the application can show the pass-through TV input while
- * TvInputPassthroughWrapperService controls the underlying external device via a separate
- * connection. In the setup activity, the TV input should get the pass-through TV input ID, around
- * which this service will wrap. The service implementation should pass the ID via
- * {@link TvInputPassthroughWrapperService#getPassthroughInputId(String)}. In addition,
- * it needs to implement {@link TvInputPassthroughWrapperService#onCreatePassthroughWrapperSession}
- * to handle requests from the application.
- * </p>
- */
-public abstract class TvInputPassthroughWrapperService extends TvInputService {
-    private static final String TAG = "TvInputPassthroughWrapperService";
-    // STOPSHIP: Turn debugging off.
-    private static final boolean DEBUG = true;
-    private TvInputManager mTvInputManager;
-    private Handler mHandler;
-
-    @Override
-    public void onCreate() {
-        if (DEBUG) Log.d(TAG, "onCreate()");
-        super.onCreate();
-        mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
-        mHandler = new Handler();
-    }
-
-    @Override
-    public final Session onCreateSession(String inputId) {
-        if (DEBUG) Log.d(TAG, "onCreateSession()");
-        // Checks the pass-through TV input is properly setup.
-        String passthroughInputId = getPassthroughInputId(inputId);
-        if (passthroughInputId == null) {
-            Log.w(TAG, "The passthrough TV input for input id(" + inputId + ") is not setup yet.");
-            return null;
-        }
-        // Checks if input id from the derived class is really pass-through type.
-        TvInputInfo info = mTvInputManager.getTvInputInfo(passthroughInputId);
-        if (info == null || !info.isPassthroughInputType()) {
-            Log.w(TAG, "Invalid TV input id from derived class: " + passthroughInputId);
-            return null;
-        }
-        // Creates a PassthroughWrapperSession.
-        PassthroughWrapperSession session = onCreatePassthroughWrapperSession();
-        if (session == null) {
-            return null;
-        }
-        // Connects to the pass-through input the external device is connected to.
-        if (!session.connect(passthroughInputId)) {
-            throw new IllegalStateException("WrapperSession cannot be reused.");
-        }
-        notifyWrappedInputId(inputId, passthroughInputId);
-        return session;
-    }
-
-    /**
-     * Returns an implementation of {@link PassthroughWrapperSession}.
-     * <p>
-     * May return {@code null} if {@link TvInputPassthroughWrapperService} fails to create a
-     * session.
-     * </p>
-     */
-    public abstract PassthroughWrapperSession onCreatePassthroughWrapperSession();
-
-    /**
-     * Returns the TV input id the external device is connected to.
-     * <p>
-     * {@link TvInputPassthroughWrapperService} is expected to identify the pass-though TV
-     * input the external device is connected to in the setup phase of this TV input.
-     * May return {@code null} if the pass-though TV input is not identified yet.
-     * </p>
-     * @param inputId The ID of the TV input which controls the external device.
-     */
-    public abstract String getPassthroughInputId(String inputId);
-
-    /**
-     * Base session class for derived classes to handle the request from the application. This
-     * creates additional session to the pass-through TV input internally and delegates the
-     * {@link Surface} given from the application.
-     */
-    public abstract class PassthroughWrapperSession extends Session {
-        private static final float VOLUME_ON = 1.0f;
-        private static final float VOLUME_OFF = 0f;
-        private TvInputManager.Session mSession;
-        private Surface mSurface;
-        private Float mVolume;
-        private boolean mReleased;
-        private int mSurfaceFormat;
-        private int mSurfaceWidth;
-        private int mSurfaceHeight;
-        private boolean mSurfaceChanged;
-        private boolean mConnectionRequested;
-
-        private final TvInputManager.SessionCallback mSessionCallback =
-                new TvInputManager.SessionCallback() {
-            @Override
-            public void onSessionCreated(TvInputManager.Session session) {
-                if (session == null) {
-                    Log.w(TAG, "Failed to create session.");
-                    onPassthroughSessionCreationFailed();
-                    return;
-                }
-                if (mReleased) {
-                    session.release();
-                    return;
-                }
-                if (mSurface != null) {
-                    session.setSurface(mSurface);
-                    mSurface = null;
-                }
-                if (mVolume != null) {
-                    session.setStreamVolume(mVolume);
-                    mVolume = null;
-                }
-                if (mSurfaceChanged) {
-                    session.dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
-                    mSurfaceChanged = false;
-                }
-                mSession = session;
-            }
-
-            @Override
-            public void onSessionReleased(TvInputManager.Session session) {
-                mReleased = true;
-                mSession = null;
-                onPassthroughSessionReleased();
-            }
-
-            @Override
-            public void onVideoAvailable(TvInputManager.Session session) {
-                if (mSession == session) {
-                    onPassthroughVideoAvailable();
-                }
-            }
-
-            @Override
-            public void onVideoUnavailable(TvInputManager.Session session, int reason) {
-                if (mSession == session) {
-                    onPassthroughVideoUnavailable(reason);
-                }
-            }
-
-            @Override
-            public void onSessionEvent(TvInputManager.Session session, String eventType,
-                    Bundle eventArgs) {
-                if (mSession == session) {
-                    notifySessionEvent(eventType, eventArgs);
-                }
-            }
-        };
-
-        /**
-         * Called when failed to create a session for pass-through TV input.
-         */
-        public abstract void onPassthroughSessionCreationFailed();
-
-        /**
-         * Called when the pass-through TV input session is released. This typically happens when
-         * the process hosting the pass-through TV input has crashed or been killed.
-         */
-        public abstract void onPassthroughSessionReleased();
-
-        /**
-         * Called when the underlying pass-through TV input session calls
-         * {@link #notifyVideoAvailable()}.
-         */
-        public abstract void onPassthroughVideoAvailable();
-
-        /**
-         * Called when the underlying pass-through TV input session calls
-         * {@link #notifyVideoUnavailable(int)}.
-         *
-         * @param reason The reason why the pass-through TV input stopped the playback.
-         */
-        public abstract void onPassthroughVideoUnavailable(int reason);
-
-        @Override
-        public final boolean onSetSurface(Surface surface) {
-            if (DEBUG) Log.d(TAG, "onSetSurface(" + surface + ")");
-            if (mSession == null) {
-                mSurface = surface;
-            } else {
-                mSession.setSurface(surface);
-            }
-            return true;
-        }
-
-        private boolean connect(String inputId) {
-            if (mConnectionRequested) {
-                return false;
-            }
-            mTvInputManager.createSession(inputId, mSessionCallback, mHandler);
-            mConnectionRequested = true;
-            return true;
-        }
-
-        @Override
-        void release() {
-            super.release();
-            mReleased = true;
-            if (mSession != null) {
-                mSession.release();
-            }
-        }
-
-        @Override
-        void dispatchSurfaceChanged(int format, int width, int height) {
-            super.dispatchSurfaceChanged(format, width, height);
-            if (mSession == null) {
-                mSurfaceFormat = format;
-                mSurfaceWidth = width;
-                mSurfaceHeight = height;
-                mSurfaceChanged = true;
-            } else {
-                mSession.dispatchSurfaceChanged(format, width, height);
-            }
-        }
-
-        @Override
-        void setStreamVolume(float volume) {
-            super.setStreamVolume(volume);
-            // Here, we let the pass-through TV input know only whether volume is on or off and
-            // make the fine control done in the derived class to prevent that the volume is
-            // controlled in the both side.
-            float volumeForPassthriughInput = (volume > 0.0f) ? VOLUME_ON : VOLUME_OFF;
-            if (mSession == null) {
-                mVolume = Float.valueOf(volumeForPassthriughInput);
-            } else {
-                mSession.setStreamVolume(volumeForPassthriughInput);
-            }
-        }
-    }
-}
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index d084cf7..0f90c2d 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -31,6 +31,7 @@
 import android.os.Message;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.InputChannel;
@@ -161,6 +162,8 @@
      * Returns a concrete implementation of {@link Session}.
      * <p>
      * May return {@code null} if this TV input service fails to create a session for some reason.
+     * If TV input represents an external device connected to a hardware TV input,
+     * {@link HardwareSession} should be returned.
      * </p>
      * @param inputId The ID of the TV input associated with the session.
      */
@@ -219,21 +222,6 @@
     }
 
     /**
-     * Notify wrapped TV input ID of current input to TV input framework manager
-     *
-     * @param inputId The TV input ID of {@link TvInputPassthroughWrapperService}
-     * @param wrappedInputId The ID of the wrapped TV input such as external pass-though TV input
-     * @hide
-     */
-    public final void notifyWrappedInputId(String inputId, String wrappedInputId) {
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = inputId;
-        args.arg2 = wrappedInputId;
-        mHandler.obtainMessage(TvInputService.ServiceHandler.DO_SET_WRAPPED_TV_INPUT_ID,
-                args).sendToTarget();
-    }
-
-    /**
      * Base class for derived classes to implement to provide a TV input session.
      */
     public abstract class Session implements KeyEvent.Callback {
@@ -398,7 +386,7 @@
          * Informs the application that video is not available, so the TV input cannot continue
          * playing the TV stream.
          *
-         * @param reason The reason why the TV input stopped the playback:
+         * @param reason The reason that the TV input stopped the playback:
          * <ul>
          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
@@ -987,6 +975,97 @@
         }
     }
 
+    /**
+     * Base class for a TV input session which represents an external device connected to a
+     * hardware TV input. Once TV input returns an implementation of this class on
+     * {@link #onCreateSession(String)}, the framework will create a hardware session and forward
+     * the application's surface to the hardware TV input.
+     * @see #onCreateSession(String)
+     */
+    public abstract class HardwareSession extends Session {
+
+        private TvInputManager.Session mHardwareSession;
+        private ITvInputSession mProxySession;
+        private ITvInputSessionCallback mProxySessionCallback;
+
+        /**
+         * Returns the hardware TV input ID the external device is connected to.
+         * <p>
+         * TV input is expected to provide {@link android.R.attr#setupActivity} so that
+         * the application can launch it before using this TV input. The setup activity may let
+         * the user select the hardware TV input to which the external device is connected. The ID
+         * of the selected one should be stored in the TV input so that it can be returned here.
+         * </p>
+         */
+        public abstract String getHardwareInputId();
+
+        private final TvInputManager.SessionCallback mHardwareSessionCallback =
+                new TvInputManager.SessionCallback() {
+            @Override
+            public void onSessionCreated(TvInputManager.Session session) {
+                mHardwareSession = session;
+                SomeArgs args = SomeArgs.obtain();
+                if (session != null) {
+                    args.arg1 = mProxySession;
+                    args.arg2 = mProxySessionCallback;
+                    args.arg3 = session.getToken();
+                } else {
+                    args.arg1 = null;
+                    args.arg2 = mProxySessionCallback;
+                    args.arg3 = null;
+                    onRelease();
+                }
+                mHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED, args)
+                        .sendToTarget();
+            }
+
+            @Override
+            public void onVideoAvailable(final TvInputManager.Session session) {
+                if (mHardwareSession == session) {
+                    onHardwareVideoAvailable();
+                }
+            }
+
+            @Override
+            public void onVideoUnavailable(final TvInputManager.Session session,
+                    final int reason) {
+                if (mHardwareSession == session) {
+                    onHardwareVideoUnavailable(reason);
+                }
+            }
+        };
+
+        /**
+         * This method will not be called in {@link HardwareSession}. Framework will
+         * forward the application's surface to the hardware TV input.
+         */
+        @Override
+        public final boolean onSetSurface(Surface surface) {
+            Log.e(TAG, "onSetSurface() should not be called in HardwareProxySession.");
+            return false;
+        }
+
+        /**
+         * Called when the underlying hardware TV input session calls
+         * {@link TvInputService.Session#notifyVideoAvailable()}.
+         */
+        public void onHardwareVideoAvailable() { }
+
+        /**
+         * Called when the underlying hardware TV input session calls
+         * {@link TvInputService.Session#notifyVideoUnavailable(int)}.
+         *
+         * @param reason The reason that the hardware TV input stopped the playback:
+         * <ul>
+         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
+         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
+         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
+         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
+         * </ul>
+         */
+        public void onHardwareVideoUnavailable(int reason) { }
+    }
+
     /** @hide */
     public static boolean isNavigationKey(int keyCode) {
         switch (keyCode) {
@@ -1010,11 +1089,11 @@
     @SuppressLint("HandlerLeak")
     private final class ServiceHandler extends Handler {
         private static final int DO_CREATE_SESSION = 1;
-        private static final int DO_ADD_HARDWARE_TV_INPUT = 2;
-        private static final int DO_REMOVE_HARDWARE_TV_INPUT = 3;
-        private static final int DO_ADD_HDMI_CEC_TV_INPUT = 4;
-        private static final int DO_REMOVE_HDMI_CEC_TV_INPUT = 5;
-        private static final int DO_SET_WRAPPED_TV_INPUT_ID = 6;
+        private static final int DO_NOTIFY_SESSION_CREATED = 2;
+        private static final int DO_ADD_HARDWARE_TV_INPUT = 3;
+        private static final int DO_REMOVE_HARDWARE_TV_INPUT = 4;
+        private static final int DO_ADD_HDMI_CEC_TV_INPUT = 5;
+        private static final int DO_REMOVE_HDMI_CEC_TV_INPUT = 6;
 
         private void broadcastAddHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
             int n = mCallbacks.beginBroadcast();
@@ -1053,18 +1132,6 @@
             mCallbacks.finishBroadcast();
         }
 
-        private void broadcastSetWrappedTvInputId(String inputId, String wrappedInputId) {
-            int n = mCallbacks.beginBroadcast();
-            for (int i = 0; i < n; ++i) {
-                try {
-                    mCallbacks.getBroadcastItem(i).setWrappedInputId(inputId, wrappedInputId);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error while broadcasting.", e);
-                }
-            }
-            mCallbacks.finishBroadcast();
-        }
-
         @Override
         public final void handleMessage(Message msg) {
             switch (msg.what) {
@@ -1073,17 +1140,58 @@
                     InputChannel channel = (InputChannel) args.arg1;
                     ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
                     String inputId = (String) args.arg3;
-                    try {
-                        Session sessionImpl = onCreateSession(inputId);
-                        if (sessionImpl == null) {
+                    Session sessionImpl = onCreateSession(inputId);
+                    args.recycle();
+                    if (sessionImpl == null) {
+                        try {
                             // Failed to create a session.
-                            cb.onSessionCreated(null);
-                        } else {
-                            sessionImpl.setSessionCallback(cb);
-                            ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
-                                    sessionImpl, channel);
-                            cb.onSessionCreated(stub);
+                            cb.onSessionCreated(null, null);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "error in onSessionCreated");
                         }
+                    } else {
+                        sessionImpl.setSessionCallback(cb);
+                        ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
+                                sessionImpl, channel);
+                        if (sessionImpl instanceof HardwareSession) {
+                            HardwareSession proxySession =
+                                    ((HardwareSession) sessionImpl);
+                            String harewareInputId = proxySession.getHardwareInputId();
+                            if (!TextUtils.isEmpty(harewareInputId)) {
+                                // TODO: check if the given ID is really hardware TV input.
+                                proxySession.mProxySession = stub;
+                                proxySession.mProxySessionCallback = cb;
+                                TvInputManager manager = (TvInputManager) getSystemService(
+                                        Context.TV_INPUT_SERVICE);
+                                manager.createSession(harewareInputId,
+                                        proxySession.mHardwareSessionCallback, mHandler);
+                            } else {
+                                sessionImpl.onRelease();
+                                Log.w(TAG, "Hardware input id is not setup yet.");
+                                try {
+                                    cb.onSessionCreated(null, null);
+                                } catch (RemoteException e) {
+                                    Log.e(TAG, "error in onSessionCreated");
+                                }
+                            }
+                        } else {
+                            SomeArgs someArgs = SomeArgs.obtain();
+                            someArgs.arg1 = stub;
+                            someArgs.arg2 = cb;
+                            someArgs.arg3 = null;
+                            mHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED,
+                                    someArgs).sendToTarget();
+                        }
+                    }
+                    return;
+                }
+                case DO_NOTIFY_SESSION_CREATED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    ITvInputSession stub = (ITvInputSession) args.arg1;
+                    ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
+                    IBinder hardwareSessionToken = (IBinder) args.arg3;
+                    try {
+                        cb.onSessionCreated(stub, hardwareSessionToken);
                     } catch (RemoteException e) {
                         Log.e(TAG, "error in onSessionCreated");
                     }
@@ -1122,13 +1230,6 @@
                     }
                     return;
                 }
-                case DO_SET_WRAPPED_TV_INPUT_ID: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    String inputId = (String) args.arg1;
-                    String wrappedInputId = (String) args.arg2;
-                    broadcastSetWrappedTvInputId(inputId, wrappedInputId);
-                    return;
-                }
                 default: {
                     Log.w(TAG, "Unhandled message code: " + msg.what);
                     return;
diff --git a/services/core/java/com/android/server/location/FlpHardwareProvider.java b/services/core/java/com/android/server/location/FlpHardwareProvider.java
index 495d3a9..530ad4b 100644
--- a/services/core/java/com/android/server/location/FlpHardwareProvider.java
+++ b/services/core/java/com/android/server/location/FlpHardwareProvider.java
@@ -272,8 +272,7 @@
             synchronized (mLocationSinkLock) {
                 // only one sink is allowed at the moment
                 if (mLocationSink != null) {
-                    throw new RuntimeException(
-                            "IFusedLocationHardware does not support multiple sinks");
+                    Log.e(TAG, "Replacing an existing IFusedLocationHardware sink");
                 }
 
                 mLocationSink = eventSink;
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 73b4a54..18446ae 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -479,12 +479,13 @@
         // Set up a callback to send the session token.
         ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() {
             @Override
-            public void onSessionCreated(ITvInputSession session) {
+            public void onSessionCreated(ITvInputSession session, IBinder harewareSessionToken) {
                 if (DEBUG) {
                     Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.mInfo.getId() + ")");
                 }
                 synchronized (mLock) {
                     sessionState.mSession = session;
+                    sessionState.mHardwareSessionToken = harewareSessionToken;
                     if (session == null) {
                         removeSessionStateLocked(sessionToken, userId);
                         sendSessionTokenToClientLocked(sessionState.mClient,
@@ -1107,8 +1108,14 @@
             try {
                 synchronized (mLock) {
                     try {
-                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
-                                surface);
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        if (sessionState.mHardwareSessionToken == null) {
+                            getSessionLocked(sessionState).setSurface(surface);
+                        } else {
+                            getSessionLocked(sessionState.mHardwareSessionToken,
+                                    Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
+                        }
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in setSurface", e);
                     }
@@ -1132,8 +1139,13 @@
             try {
                 synchronized (mLock) {
                     try {
-                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
-                                .dispatchSurfaceChanged(format, width, height);
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).dispatchSurfaceChanged(format, width, height);
+                        if (sessionState.mHardwareSessionToken != null) {
+                            getSessionLocked(sessionState.mHardwareSessionToken, Process.SYSTEM_UID,
+                                    resolvedUserId).dispatchSurfaceChanged(format, width, height);
+                        }
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in dispatchSurfaceChanged", e);
                     }
@@ -1145,6 +1157,8 @@
 
         @Override
         public void setVolume(IBinder sessionToken, float volume, int userId) {
+            final float REMOTE_VOLUME_ON = 1.0f;
+            final float REMOTE_VOLUME_OFF = 0f;
             final int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
                     userId, "setVolume");
@@ -1152,8 +1166,16 @@
             try {
                 synchronized (mLock) {
                     try {
-                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
-                                volume);
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).setVolume(volume);
+                        if (sessionState.mHardwareSessionToken != null) {
+                            // Here, we let the hardware session know only whether volume is on or
+                            // off to prevent that the volume is controlled in the both side.
+                            getSessionLocked(sessionState.mHardwareSessionToken,
+                                    Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
+                                            ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
+                        }
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in setVolume", e);
                     }
@@ -1457,13 +1479,24 @@
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
                     userId, "captureFrame");
             try {
-                final String wrappedInputId;
+                String hardwareInputId = null;
                 synchronized (mLock) {
                     UserState userState = getUserStateLocked(resolvedUserId);
-                    wrappedInputId = userState.wrappedInputMap.get(inputId);
+                    if (userState.inputMap.get(inputId) == null) {
+                        Slog.e(TAG, "Input not found for " + inputId);
+                        return false;
+                    }
+                    for (SessionState sessionState : userState.sessionStateMap.values()) {
+                        if (sessionState.mInfo.getId().equals(inputId)
+                                && sessionState.mHardwareSessionToken != null) {
+                            hardwareInputId = userState.sessionStateMap.get(
+                                    sessionState.mHardwareSessionToken).mInfo.getId();
+                            break;
+                        }
+                    }
                 }
                 return mTvInputHardwareManager.captureFrame(
-                        (wrappedInputId != null) ? wrappedInputId : inputId,
+                        (hardwareInputId != null) ? hardwareInputId : inputId,
                         surface, config, callingUid, resolvedUserId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -1581,6 +1614,7 @@
                         pw.println("mSessionToken: " + session.mSessionToken);
                         pw.println("mSession: " + session.mSession);
                         pw.println("mLogUri: " + session.mLogUri);
+                        pw.println("mHardwareSessionToken: " + session.mHardwareSessionToken);
                         pw.decreaseIndent();
                     }
                     pw.decreaseIndent();
@@ -1670,9 +1704,6 @@
         private final Set<ITvInputManagerCallback> callbackSet =
                 new HashSet<ITvInputManagerCallback>();
 
-        // A mapping from the TV input id to wrapped input id.
-        private final Map<String, String> wrappedInputMap = new HashMap<String, String>();
-
         // The token of a "main" TV input session.
         private IBinder mainSessionToken = null;
 
@@ -1747,9 +1778,11 @@
         private final IBinder mSessionToken;
         private ITvInputSession mSession;
         private Uri mLogUri;
+        // Not null if this session represents an external device connected to a hardware TV input.
+        private IBinder mHardwareSessionToken;
 
-        private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client, int seq,
-                int callingUid, int userId) {
+        private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client,
+                int seq, int callingUid, int userId) {
             mSessionToken = sessionToken;
             mInfo = info;
             mClient = client;
@@ -1769,6 +1802,22 @@
                         Slog.e(TAG, "error in onSessionReleased", e);
                     }
                 }
+                // If there are any other sessions based on this session, they should be released.
+                UserState userState = getUserStateLocked(mUserId);
+                for (SessionState sessionState : userState.sessionStateMap.values()) {
+                    if (mSession != null && mSession == sessionState.mHardwareSessionToken) {
+                        try {
+                            sessionState.mSession.release();
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "error in release", e);
+                        }
+                        try {
+                            sessionState.mClient.onSessionReleased(sessionState.mSeq);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "error in onSessionReleased", e);
+                        }
+                    }
+                }
                 removeSessionStateLocked(mSessionToken, mUserId);
             }
         }
@@ -1870,10 +1919,8 @@
 
                     for (TvInputState inputState : userState.inputMap.values()) {
                         if (inputState.mInfo.getComponent().equals(component)) {
-                            String inputId = inputState.mInfo.getId();
-                            notifyInputStateChangedLocked(userState, inputId,
+                            notifyInputStateChangedLocked(userState, inputState.mInfo.getId(),
                                     INPUT_STATE_DISCONNECTED, null);
-                            userState.wrappedInputMap.remove(inputId);
                         }
                     }
                     updateServiceConnectionLocked(mComponent, mUserId);
@@ -1952,27 +1999,6 @@
                 }
             }
         }
-
-        @Override
-        public void setWrappedInputId(String inputId, String wrappedInputId) {
-            synchronized (mLock) {
-                if (!hasInputIdLocked(inputId)) {
-                    return;
-                }
-                UserState userState = getUserStateLocked(mUserId);
-                userState.wrappedInputMap.put(inputId, wrappedInputId);
-            }
-        }
-
-        private boolean hasInputIdLocked(String inputId) {
-            ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
-            for (TvInputInfo inputInfo : serviceState.mInputList) {
-                if (inputInfo.getId().equals(inputId)) {
-                    return true;
-                }
-            }
-            return false;
-        }
     }
 
     private final class LogHandler extends Handler {