summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Shubang Lu <shubang@google.com> 2021-12-03 00:20:04 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2021-12-03 00:20:04 +0000
commitd2fe5a39f0a6783dbebd55ff26e490f11619e29e (patch)
treef13df67549649f450e01f22a7f803578a0c66ca2
parent45450c2bf209d15b9dd4897e77cd73dffaeaf00a (diff)
parent938ec458a0d56608ac249e9d3a0f5e6225a6248a (diff)
Merge changes Ic8f92bd4,Ia130985e
* changes: TIAF: Add IApp setup & start flow TIAF: handle overlay media view
-rw-r--r--media/java/android/media/tv/AitInfo.aidl19
-rw-r--r--media/java/android/media/tv/AitInfo.java87
-rw-r--r--media/java/android/media/tv/ITvInputClient.aidl6
-rw-r--r--media/java/android/media/tv/ITvInputManager.aidl4
-rw-r--r--media/java/android/media/tv/ITvInputSession.aidl4
-rw-r--r--media/java/android/media/tv/ITvInputSessionCallback.aidl4
-rw-r--r--media/java/android/media/tv/ITvInputSessionWrapper.java11
-rw-r--r--media/java/android/media/tv/TvInputManager.java84
-rwxr-xr-xmedia/java/android/media/tv/TvInputService.java36
-rw-r--r--media/java/android/media/tv/TvRecordingClient.java2
-rw-r--r--media/java/android/media/tv/TvView.java60
-rw-r--r--media/java/android/media/tv/interactive/ITvIAppClient.aidl1
-rw-r--r--media/java/android/media/tv/interactive/ITvIAppManager.aidl11
-rw-r--r--media/java/android/media/tv/interactive/ITvIAppService.aidl1
-rw-r--r--media/java/android/media/tv/interactive/ITvIAppSession.aidl8
-rw-r--r--media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl1
-rw-r--r--media/java/android/media/tv/interactive/TvIAppManager.java91
-rw-r--r--media/java/android/media/tv/interactive/TvIAppService.java296
-rw-r--r--media/java/android/media/tv/interactive/TvIAppView.java73
-rwxr-xr-xservices/core/java/com/android/server/tv/TvInputManagerService.java41
-rw-r--r--services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java145
21 files changed, 935 insertions, 50 deletions
diff --git a/media/java/android/media/tv/AitInfo.aidl b/media/java/android/media/tv/AitInfo.aidl
new file mode 100644
index 000000000000..b7d9fe8baacf
--- /dev/null
+++ b/media/java/android/media/tv/AitInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 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;
+
+parcelable AitInfo;
diff --git a/media/java/android/media/tv/AitInfo.java b/media/java/android/media/tv/AitInfo.java
new file mode 100644
index 000000000000..5f8487d0dc6b
--- /dev/null
+++ b/media/java/android/media/tv/AitInfo.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 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.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * AIT info.
+ * @hide
+ */
+public final class AitInfo implements Parcelable {
+ static final String TAG = "AitInfo";
+
+ public static final Creator<AitInfo> CREATOR = new Creator<AitInfo>() {
+ @Override
+ public AitInfo createFromParcel(Parcel in) {
+ return new AitInfo(in);
+ }
+
+ @Override
+ public AitInfo[] newArray(int size) {
+ return new AitInfo[size];
+ }
+ };
+
+ private final int mType;
+ private final int mVersion;
+
+ private AitInfo(Parcel in) {
+ mType = in.readInt();
+ mVersion = in.readInt();
+ }
+
+ /**
+ * Constructs AIT info.
+ */
+ public AitInfo(int type, int version) {
+ mType = type;
+ mVersion = version;
+ }
+
+ /**
+ * Gets type.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Gets version.
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeInt(mVersion);
+ }
+
+ @Override
+ public String toString() {
+ return "type=" + mType + ";version=" + mVersion;
+ }
+}
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index 5dad633a0236..6f7db4a7b192 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -17,12 +17,13 @@
package android.media.tv;
import android.content.ComponentName;
+import android.media.tv.AitInfo;
+import android.media.tv.BroadcastInfoResponse;
import android.media.tv.ITvInputSession;
import android.net.Uri;
import android.media.tv.TvTrackInfo;
import android.os.Bundle;
import android.view.InputChannel;
-import android.media.tv.BroadcastInfoResponse;
/**
* Interface a client of the ITvInputManager implements, to identify itself and receive information
@@ -44,9 +45,10 @@ oneway interface ITvInputClient {
void onTimeShiftStatusChanged(int status, int seq);
void onTimeShiftStartPositionChanged(long timeMs, int seq);
void onTimeShiftCurrentPositionChanged(long timeMs, int seq);
+ void onAitInfoUpdated(in AitInfo aitInfo, int seq);
+ void onTuned(in Uri channelUri, int seq);
// For the recording session
- void onTuned(int seq, in Uri channelUri);
void onRecordingStopped(in Uri recordedProgramUri, int seq);
void onError(int error, int seq);
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index eaf89ba61764..007089858481 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -20,6 +20,7 @@ import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Rect;
import android.media.PlaybackParams;
+import android.media.tv.BroadcastInfoRequest;
import android.media.tv.DvbDeviceInfo;
import android.media.tv.ITvInputClient;
import android.media.tv.ITvInputHardware;
@@ -35,7 +36,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.view.Surface;
-import android.media.tv.BroadcastInfoRequest;
/**
* Interface to the TV input manager service.
@@ -73,6 +73,8 @@ interface ITvInputManager {
void setCaptionEnabled(in IBinder sessionToken, boolean enabled, int userId);
void selectTrack(in IBinder sessionToken, int type, in String trackId, int userId);
+ void setIAppNotificationEnabled(in IBinder sessionToken, boolean enabled, int userId);
+
void sendAppPrivateCommand(in IBinder sessionToken, in String action, in Bundle data,
int userId);
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 1eab650ba953..984a551086ac 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -18,11 +18,11 @@ package android.media.tv;
import android.graphics.Rect;
import android.media.PlaybackParams;
+import android.media.tv.BroadcastInfoRequest;
import android.media.tv.TvTrackInfo;
import android.net.Uri;
import android.os.Bundle;
import android.view.Surface;
-import android.media.tv.BroadcastInfoRequest;
/**
* Sub-interface of ITvInputService which is created per session and has its own context.
@@ -41,6 +41,8 @@ oneway interface ITvInputSession {
void setCaptionEnabled(boolean enabled);
void selectTrack(int type, in String trackId);
+ void setIAppNotificationEnabled(boolean enable);
+
void appPrivateCommand(in String action, in Bundle data);
void createOverlayView(in IBinder windowToken, in Rect frame);
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index d857c00d8279..9a0aaa38f845 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -16,11 +16,12 @@
package android.media.tv;
+import android.media.tv.AitInfo;
+import android.media.tv.BroadcastInfoResponse;
import android.media.tv.ITvInputSession;
import android.net.Uri;
import android.media.tv.TvTrackInfo;
import android.os.Bundle;
-import android.media.tv.BroadcastInfoResponse;
/**
* Helper interface for ITvInputSession to allow the TV input to notify the system service when a
@@ -41,6 +42,7 @@ oneway interface ITvInputSessionCallback {
void onTimeShiftStatusChanged(int status);
void onTimeShiftStartPositionChanged(long timeMs);
void onTimeShiftCurrentPositionChanged(long timeMs);
+ void onAitInfoUpdated(in AitInfo aitInfo);
// For the recording session
void onTuned(in Uri channelUri);
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 425714582fd5..6539472d203c 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -71,6 +71,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
private static final int DO_PAUSE_RECORDING = 22;
private static final int DO_RESUME_RECORDING = 23;
private static final int DO_REQUEST_BROADCAST_INFO = 24;
+ private static final int DO_SET_IAPP_NOTIFICATION_ENABLED = 25;
private final boolean mIsRecordingSession;
private final HandlerCaller mCaller;
@@ -239,6 +240,10 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
mTvInputSessionImpl.requestBroadcastInfo((BroadcastInfoRequest) msg.obj);
break;
}
+ case DO_SET_IAPP_NOTIFICATION_ENABLED: {
+ mTvInputSessionImpl.setIAppNotificationEnabled((Boolean) msg.obj);
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -307,6 +312,12 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
}
@Override
+ public void setIAppNotificationEnabled(boolean enabled) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageO(DO_SET_IAPP_NOTIFICATION_ENABLED, enabled));
+ }
+
+ @Override
public void appPrivateCommand(String action, Bundle data) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action,
data));
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index bafb03bc53eb..b655a615794e 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -627,14 +627,20 @@ public final class TvInputManager {
public void onTimeShiftCurrentPositionChanged(Session session, long timeMs) {
}
- // For the recording session only
/**
- * This is called when the recording session has been tuned to the given channel and is
- * ready to start recording.
+ * This is called when AIT info is updated.
+ * @param session A {@link TvInputManager.Session} associated with this callback.
+ * @param aitInfo The current AIT info.
+ */
+ public void onAitInfoUpdated(Session session, AitInfo aitInfo) {
+ }
+
+ /**
+ * This is called when the session has been tuned to the given channel.
*
* @param channelUri The URI of a channel.
*/
- void onTuned(Session session, Uri channelUri) {
+ public void onTuned(Session session, Uri channelUri) {
}
// For the recording session only
@@ -807,12 +813,23 @@ public final class TvInputManager {
});
}
- // For the recording session only
+ void postAitInfoUpdated(final AitInfo aitInfo) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onAitInfoUpdated(mSession, aitInfo);
+ }
+ });
+ }
+
void postTuned(final Uri channelUri) {
mHandler.post(new Runnable() {
@Override
public void run() {
mSessionCallback.onTuned(mSession, channelUri);
+ if (mSession.mIAppNotificationEnabled && mSession.getIAppSession() != null) {
+ mSession.getIAppSession().notifyTuned(channelUri);
+ }
}
});
}
@@ -838,12 +855,16 @@ public final class TvInputManager {
}
void postBroadcastInfoResponse(final BroadcastInfoResponse response) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mSession.getIAppSession().notifyBroadcastInfoResponse(response);
- }
- });
+ if (mSession.mIAppNotificationEnabled) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mSession.getIAppSession() != null) {
+ mSession.getIAppSession().notifyBroadcastInfoResponse(response);
+ }
+ }
+ });
+ }
}
}
@@ -1209,7 +1230,19 @@ public final class TvInputManager {
}
@Override
- public void onTuned(int seq, Uri channelUri) {
+ public void onAitInfoUpdated(AitInfo aitInfo, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postAitInfoUpdated(aitInfo);
+ }
+ }
+
+ @Override
+ public void onTuned(Uri channelUri, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
if (record == null) {
@@ -1217,6 +1250,13 @@ public final class TvInputManager {
return;
}
record.postTuned(channelUri);
+ // TODO: synchronized and wrap the channelUri
+ if (record.mSession.mIAppNotificationEnabled) {
+ TvIAppManager.Session iappSession = record.mSession.mIAppSession;
+ if (iappSession != null) {
+ iappSession.notifyTuned(channelUri);
+ }
+ }
}
}
@@ -2068,6 +2108,7 @@ public final class TvInputManager {
private int mVideoHeight;
private TvIAppManager.Session mIAppSession;
+ private boolean mIAppNotificationEnabled = false;
private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
@@ -2340,6 +2381,25 @@ public final class TvInputManager {
}
/**
+ * Enables interactive app notification.
+ * @param enabled {@code true} if you want to enable interactive app notifications.
+ * {@code false} otherwise.
+ * @hide
+ */
+ public void setIAppNotificationEnabled(boolean enabled) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.setIAppNotificationEnabled(mToken, enabled, mUserId);
+ mIAppNotificationEnabled = enabled;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Responds to onTracksChanged() and updates the internal track information. Returns true if
* there is an update.
*/
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index d285b13e7762..6743dd6acad9 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -828,6 +828,27 @@ public abstract class TvInputService extends Service {
}
/**
+ * Notifies AIT info updated.
+ * @hide
+ */
+ public void notifyAitInfoUpdated(@NonNull final AitInfo aitInfo) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "notifyAitInfoUpdated");
+ if (mSessionCallback != null) {
+ mSessionCallback.onAitInfoUpdated(aitInfo);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in notifyAitInfoUpdated", e);
+ }
+ }
+ });
+ }
+
+ /**
* Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
* is relative to the overlay view that sits on top of this surface.
*
@@ -1021,6 +1042,14 @@ public abstract class TvInputService extends Service {
}
/**
+ * Enables or disables interactive app notification.
+ * @param enabled {@code true} to enable, {@code false} to disable.
+ * @hide
+ */
+ public void onSetIAppNotificationEnabled(boolean enabled) {
+ }
+
+ /**
* Processes a private command sent from the application to the TV input. This can be used
* to provide domain-specific features that are only known between certain TV inputs and
* their clients.
@@ -1369,6 +1398,13 @@ public abstract class TvInputService extends Service {
}
/**
+ * Calls {@link #onSetIAppNotificationEnabled}.
+ */
+ void setIAppNotificationEnabled(boolean enabled) {
+ onSetIAppNotificationEnabled(enabled);
+ }
+
+ /**
* Calls {@link #onAppPrivateCommand}.
*/
void appPrivateCommand(String action, Bundle data) {
diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java
index 180e2bd6845b..87d7a0b10d10 100644
--- a/media/java/android/media/tv/TvRecordingClient.java
+++ b/media/java/android/media/tv/TvRecordingClient.java
@@ -472,7 +472,7 @@ public class TvRecordingClient {
}
@Override
- void onTuned(TvInputManager.Session session, Uri channelUri) {
+ public void onTuned(TvInputManager.Session session, Uri channelUri) {
if (DEBUG) {
Log.d(TAG, "onTuned()");
}
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 6994d284ac27..4a12cd7eb534 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -34,8 +34,6 @@ import android.media.PlaybackParams;
import android.media.tv.TvInputManager.Session;
import android.media.tv.TvInputManager.Session.FinishedInputEventCallback;
import android.media.tv.TvInputManager.SessionCallback;
-import android.media.tv.interactive.TvIAppManager;
-import android.media.tv.interactive.TvIAppView;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -483,6 +481,18 @@ public class TvView extends ViewGroup {
}
/**
+ * Enables interactive app notification.
+ * @param enabled {@code true} if you want to enable interactive app notifications.
+ * {@code false} otherwise.
+ * @hide
+ */
+ public void setIAppNotificationEnabled(boolean enabled) {
+ if (mSession != null) {
+ mSession.setIAppNotificationEnabled(enabled);
+ }
+ }
+
+ /**
* Plays a given recorded TV program.
*
* @param inputId The ID of the TV input that created the given recorded program.
@@ -1050,6 +1060,24 @@ public class TvView extends ViewGroup {
public void onTimeShiftStatusChanged(
String inputId, @TvInputManager.TimeShiftStatus int status) {
}
+
+ /**
+ * This is called when the AIT info has been updated.
+ *
+ * @param aitInfo The current AIT info.
+ * @hide
+ */
+ public void onAitInfoUpdated(String inputId, AitInfo aitInfo) {
+ }
+
+ /**
+ * This is called when the session has been tuned to the given channel.
+ *
+ * @param channelUri The URI of a channel.
+ * @hide
+ */
+ public void onTuned(String inputId, Uri channelUri) {
+ }
}
/**
@@ -1346,5 +1374,33 @@ public class TvView extends ViewGroup {
mTimeShiftPositionCallback.onTimeShiftCurrentPositionChanged(mInputId, timeMs);
}
}
+
+ @Override
+ public void onAitInfoUpdated(Session session, AitInfo aitInfo) {
+ if (DEBUG) {
+ Log.d(TAG, "onAitInfoUpdated(aitInfo=" + aitInfo + ")");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onAitInfoUpdated - session not created");
+ return;
+ }
+ if (mCallback != null) {
+ mCallback.onAitInfoUpdated(mInputId, aitInfo);
+ }
+ }
+
+ @Override
+ public void onTuned(Session session, Uri channelUri) {
+ if (DEBUG) {
+ Log.d(TAG, "onTuned(channelUri=" + channelUri + ")");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onTuned - session not created");
+ return;
+ }
+ if (mCallback != null) {
+ mCallback.onTuned(mInputId, channelUri);
+ }
+ }
}
}
diff --git a/media/java/android/media/tv/interactive/ITvIAppClient.aidl b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
index 39c438a09311..9fc1fe71c318 100644
--- a/media/java/android/media/tv/interactive/ITvIAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
@@ -17,6 +17,7 @@
package android.media.tv.interactive;
import android.media.tv.BroadcastInfoRequest;
+import android.media.tv.BroadcastInfoRequest;
import android.view.InputChannel;
/**
diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
index 25e1acea226d..cd87a09b91e0 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
@@ -16,10 +16,12 @@
package android.media.tv.interactive;
+import android.graphics.Rect;
+import android.media.tv.BroadcastInfoResponse;
import android.media.tv.interactive.ITvIAppClient;
import android.media.tv.interactive.ITvIAppManagerCallback;
import android.media.tv.interactive.TvIAppInfo;
-import android.media.tv.BroadcastInfoResponse;
+import android.net.Uri;
import android.view.Surface;
/**
@@ -28,16 +30,23 @@ import android.view.Surface;
*/
interface ITvIAppManager {
List<TvIAppInfo> getTvIAppServiceList(int userId);
+ void prepare(String tiasId, int type, int userId);
void startIApp(in IBinder sessionToken, int userId);
void createSession(
in ITvIAppClient client, in String iAppServiceId, int type, int seq, int userId);
void releaseSession(in IBinder sessionToken, int userId);
+ void notifyTuned(in IBinder sessionToken, in Uri channelUri, int userId);
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
int userId);
void notifyBroadcastInfoResponse(in IBinder sessionToken, in BroadcastInfoResponse response,
int UserId);
+ void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
+ int userId);
+ void relayoutMediaView(in IBinder sessionToken, in Rect frame, int userId);
+ void removeMediaView(in IBinder sessionToken, int userId);
+
void registerCallback(in ITvIAppManagerCallback callback, int userId);
void unregisterCallback(in ITvIAppManagerCallback callback, int userId);
} \ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppService.aidl b/media/java/android/media/tv/interactive/ITvIAppService.aidl
index 1dee9cc4ed28..af15dd8f5a3f 100644
--- a/media/java/android/media/tv/interactive/ITvIAppService.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppService.aidl
@@ -30,4 +30,5 @@ oneway interface ITvIAppService {
void unregisterCallback(in ITvIAppServiceCallback callback);
void createSession(in InputChannel channel, in ITvIAppSessionCallback callback,
in String iAppServiceId, int type);
+ void prepare(int type);
} \ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppSession.aidl b/media/java/android/media/tv/interactive/ITvIAppSession.aidl
index 440b3d30c068..0d37a2baa49d 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppSession.aidl
@@ -16,6 +16,9 @@
package android.media.tv.interactive;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.media.tv.BroadcastInfoResponse;
import android.view.Surface;
import android.media.tv.BroadcastInfoResponse;
@@ -26,7 +29,12 @@ import android.media.tv.BroadcastInfoResponse;
oneway interface ITvIAppSession {
void startIApp();
void release();
+ void notifyTuned(in Uri channelUri);
void setSurface(in Surface surface);
void dispatchSurfaceChanged(int format, int width, int height);
void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
+
+ void createMediaView(in IBinder windowToken, in Rect frame);
+ void relayoutMediaView(in Rect frame);
+ void removeMediaView();
} \ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
index d308463cda4d..d2b966ed939f 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
@@ -16,6 +16,7 @@
package android.media.tv.interactive;
+import android.media.tv.BroadcastInfoRequest;
import android.media.tv.interactive.ITvIAppSession;
import android.media.tv.BroadcastInfoRequest;
diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java
index ae35edc7ef8a..fed976980cd7 100644
--- a/media/java/android/media/tv/interactive/TvIAppManager.java
+++ b/media/java/android/media/tv/interactive/TvIAppManager.java
@@ -20,9 +20,11 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
+import android.graphics.Rect;
import android.media.tv.BroadcastInfoRequest;
import android.media.tv.BroadcastInfoResponse;
import android.media.tv.TvInputManager;
+import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -35,6 +37,7 @@ import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventSender;
import android.view.Surface;
+import android.view.View;
import com.android.internal.util.Preconditions;
@@ -332,6 +335,18 @@ public final class TvIAppManager {
}
/**
+ * Prepares TV IApp service for the given type.
+ * @hide
+ */
+ public void prepare(String tvIAppServiceId, int type) {
+ try {
+ mService.prepare(tvIAppServiceId, type, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Registers a {@link TvIAppManager.TvIAppCallback}.
*
* @param callback A callback used to monitor status of the TV IApp services.
@@ -443,6 +458,67 @@ public final class TvIAppManager {
}
/**
+ * Creates a media view. Once the media view is created, {@link #relayoutMediaView}
+ * should be called whenever the layout of its containing view is changed.
+ * {@link #removeMediaView()} should be called to remove the media view.
+ * Since a session can have only one media view, this method should be called only once
+ * or it can be called again after calling {@link #removeMediaView()}.
+ *
+ * @param view A view for interactive app.
+ * @param frame A position of the media view.
+ * @throws IllegalStateException if {@code view} is not attached to a window.
+ */
+ void createMediaView(@NonNull View view, @NonNull Rect frame) {
+ Preconditions.checkNotNull(view);
+ Preconditions.checkNotNull(frame);
+ if (view.getWindowToken() == null) {
+ throw new IllegalStateException("view must be attached to a window");
+ }
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.createMediaView(mToken, view.getWindowToken(), frame, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Relayouts the current media view.
+ *
+ * @param frame A new position of the media view.
+ */
+ void relayoutMediaView(@NonNull Rect frame) {
+ Preconditions.checkNotNull(frame);
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.relayoutMediaView(mToken, frame, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes the current media view.
+ */
+ void removeMediaView() {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.removeMediaView(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Notifies of any structural changes (format or size) of the surface passed in
* {@link #setSurface}.
*
@@ -533,6 +609,21 @@ public final class TvIAppManager {
releaseInternal();
}
+ /**
+ * Notifies IAPP session when a channels is tuned.
+ */
+ public void notifyTuned(Uri channelUri) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyTuned(mToken, channelUri, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private void flushPendingEventsLocked() {
mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java
index fe087ca564d0..027b8909fe63 100644
--- a/media/java/android/media/tv/interactive/TvIAppService.java
+++ b/media/java/android/media/tv/interactive/TvIAppService.java
@@ -20,18 +20,25 @@ import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.media.tv.BroadcastInfoRequest;
import android.media.tv.BroadcastInfoResponse;
+import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
+import android.view.Gravity;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -39,6 +46,9 @@ import android.view.InputEventReceiver;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
import com.android.internal.os.SomeArgs;
@@ -52,6 +62,8 @@ public abstract class TvIAppService extends Service {
private static final boolean DEBUG = false;
private static final String TAG = "TvIAppService";
+ private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000;
+
// TODO: cleanup and unhide APIs.
/**
@@ -106,10 +118,23 @@ public abstract class TvIAppService extends Service {
mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args)
.sendToTarget();
}
+
+ @Override
+ public void prepare(int type) {
+ onPrepare(type);
+ }
};
return tvIAppServiceBinder;
}
+ /**
+ * Prepares TV IApp service for the given type.
+ * @hide
+ */
+ public void onPrepare(int type) {
+ // TODO: make it abstract when unhide
+ }
+
/**
* Returns a concrete implementation of {@link Session}.
@@ -141,8 +166,16 @@ public abstract class TvIAppService extends Service {
private final List<Runnable> mPendingActions = new ArrayList<>();
private final Context mContext;
- private final Handler mHandler;
+ final Handler mHandler;
+ private final WindowManager mWindowManager;
+ private WindowManager.LayoutParams mWindowParams;
private Surface mSurface;
+ private FrameLayout mMediaViewContainer;
+ private View mMediaView;
+ private MediaViewCleanUpTask mMediaViewCleanUpTask;
+ private boolean mMediaViewEnabled;
+ private IBinder mWindowToken;
+ private Rect mMediaFrame;
/**
* Creates a new Session.
@@ -151,10 +184,41 @@ public abstract class TvIAppService extends Service {
*/
public Session(Context context) {
mContext = context;
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mHandler = new Handler(context.getMainLooper());
}
/**
+ * Enables or disables the media view.
+ *
+ * <p>By default, the media view is disabled. Must be called explicitly after the
+ * session is created to enable the media view.
+ *
+ * <p>The TV IApp service can disable its media view when needed.
+ *
+ * @param enable {@code true} if you want to enable the media view. {@code false}
+ * otherwise.
+ */
+ public void setMediaViewEnabled(final boolean enable) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (enable == mMediaViewEnabled) {
+ return;
+ }
+ mMediaViewEnabled = enable;
+ if (enable) {
+ if (mWindowToken != null) {
+ createMediaView(mWindowToken, mMediaFrame);
+ }
+ } else {
+ removeMediaView(false);
+ }
+ }
+ });
+ }
+
+ /**
* Starts TvIAppService session.
* @hide
*/
@@ -187,11 +251,27 @@ public abstract class TvIAppService extends Service {
}
/**
- * Called when a broadcast info response is received from TIS.
+ * Called when the size of the media view is changed by the application.
+ *
+ * <p>This is always called at least once when the session is created regardless of whether
+ * the media view is enabled or not. The media view container size is the same as the
+ * containing {@link TvIAppView}. Note that the size of the underlying surface can be
+ * different if the surface was changed by calling {@link #layoutSurface}.
*
- * @param response response received from TIS.
+ * @param width The width of the media view.
+ * @param height The height of the media view.
*/
- public void onNotifyBroadcastInfoResponse(BroadcastInfoResponse response) {
+ public void onMediaViewSizeChanged(int width, int height) {
+ }
+
+ /**
+ * Called when the application requests to create an media view. Each session
+ * implementation can override this method and return its own view.
+ *
+ * @return a view attached to the media window
+ */
+ public View onCreateMediaView() {
+ return null;
}
/**
@@ -202,6 +282,20 @@ public abstract class TvIAppService extends Service {
}
/**
+ * Called when the corresponding TV input tuned to a channel.
+ * @hide
+ */
+ public void onTuned(Uri channelUri) {
+ }
+
+ /**
+ * Called when a broadcast info response is received.
+ * @hide
+ */
+ public void onBroadcastInfoResponse(BroadcastInfoResponse response) {
+ }
+
+ /**
* TODO: JavaDoc of APIs related to input events.
* @hide
*/
@@ -288,6 +382,10 @@ public abstract class TvIAppService extends Service {
});
}
+ /**
+ * Requests broadcast related information from the related TV input.
+ * @param request
+ */
public void requestBroadcastInfo(@NonNull final BroadcastInfoRequest request) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@@ -318,6 +416,32 @@ public abstract class TvIAppService extends Service {
mSurface.release();
mSurface = null;
}
+ synchronized (mLock) {
+ mSessionCallback = null;
+ mPendingActions.clear();
+ }
+ // Removes the media view lastly so that any hanging on the main thread can be handled
+ // in {@link #scheduleMediaViewCleanup}.
+ removeMediaView(true);
+ }
+
+ void notifyTuned(Uri channelUri) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyTuned (channelUri=" + channelUri + ")");
+ }
+ onTuned(channelUri);
+ }
+
+
+ /**
+ * Calls {@link #onBroadcastInfoResponse}.
+ */
+ void notifyBroadcastInfoResponse(BroadcastInfoResponse response) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyBroadcastInfoResponse (requestId="
+ + response.getRequestId() + ")");
+ }
+ onBroadcastInfoResponse(response);
}
/**
@@ -386,18 +510,6 @@ public abstract class TvIAppService extends Service {
onSurfaceChanged(format, width, height);
}
- /**
- *
- * Calls {@link #notifyBroadcastInfoResponse}.
- */
- void notifyBroadcastInfoResponse(BroadcastInfoResponse response) {
- if (DEBUG) {
- Log.d(TAG, "notifyBroadcastInfoResponse (requestId="
- + response.getRequestId() + ")");
- }
- onNotifyBroadcastInfoResponse(response);
- }
-
private void executeOrPostRunnableOnMainThread(Runnable action) {
synchronized (mLock) {
if (mSessionCallback == null) {
@@ -413,6 +525,137 @@ public abstract class TvIAppService extends Service {
}
}
}
+
+ /**
+ * Creates an media view. This calls {@link #onCreateMediaView} to get a view to attach
+ * to the media window.
+ *
+ * @param windowToken A window token of the application.
+ * @param frame A position of the media view.
+ */
+ void createMediaView(IBinder windowToken, Rect frame) {
+ if (mMediaViewContainer != null) {
+ removeMediaView(false);
+ }
+ if (DEBUG) Log.d(TAG, "create media view(" + frame + ")");
+ mWindowToken = windowToken;
+ mMediaFrame = frame;
+ onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+ if (!mMediaViewEnabled) {
+ return;
+ }
+ mMediaView = onCreateMediaView();
+ if (mMediaView == null) {
+ return;
+ }
+ if (mMediaViewCleanUpTask != null) {
+ mMediaViewCleanUpTask.cancel(true);
+ mMediaViewCleanUpTask = null;
+ }
+ // Creates a container view to check hanging on the media view detaching.
+ // Adding/removing the media view to/from the container make the view attach/detach
+ // logic run on the main thread.
+ mMediaViewContainer = new FrameLayout(mContext.getApplicationContext());
+ mMediaViewContainer.addView(mMediaView);
+
+ int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+ // We make the overlay view non-focusable and non-touchable so that
+ // the application that owns the window token can decide whether to consume or
+ // dispatch the input events.
+ int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ if (ActivityManager.isHighEndGfx()) {
+ flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ }
+ mWindowParams = new WindowManager.LayoutParams(
+ frame.right - frame.left, frame.bottom - frame.top,
+ frame.left, frame.top, type, flags, PixelFormat.TRANSPARENT);
+ mWindowParams.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ mWindowParams.gravity = Gravity.START | Gravity.TOP;
+ mWindowParams.token = windowToken;
+ mWindowManager.addView(mMediaViewContainer, mWindowParams);
+ }
+
+ /**
+ * Relayouts the current media view.
+ *
+ * @param frame A new position of the media view.
+ */
+ void relayoutMediaView(Rect frame) {
+ if (DEBUG) Log.d(TAG, "relayoutMediaView(" + frame + ")");
+ if (mMediaFrame == null || mMediaFrame.width() != frame.width()
+ || mMediaFrame.height() != frame.height()) {
+ // Note: relayoutMediaView is called whenever TvIAppView's layout is changed
+ // regardless of setMediaViewEnabled.
+ onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
+ }
+ mMediaFrame = frame;
+ if (!mMediaViewEnabled || mMediaViewContainer == null) {
+ return;
+ }
+ mWindowParams.x = frame.left;
+ mWindowParams.y = frame.top;
+ mWindowParams.width = frame.right - frame.left;
+ mWindowParams.height = frame.bottom - frame.top;
+ mWindowManager.updateViewLayout(mMediaViewContainer, mWindowParams);
+ }
+
+ /**
+ * Removes the current media view.
+ */
+ void removeMediaView(boolean clearWindowToken) {
+ if (DEBUG) Log.d(TAG, "removeMediaView(" + mMediaViewContainer + ")");
+ if (clearWindowToken) {
+ mWindowToken = null;
+ mMediaFrame = null;
+ }
+ if (mMediaViewContainer != null) {
+ // Removes the media view from the view hierarchy in advance so that it can be
+ // cleaned up in the {@link MediaViewCleanUpTask} if the remove process is
+ // hanging.
+ mMediaViewContainer.removeView(mMediaView);
+ mMediaView = null;
+ mWindowManager.removeView(mMediaViewContainer);
+ mMediaViewContainer = null;
+ mWindowParams = null;
+ }
+ }
+
+ /**
+ * Schedules a task which checks whether the media view is detached and kills the process
+ * if it is not. Note that this method is expected to be called in a non-main thread.
+ */
+ void scheduleMediaViewCleanup() {
+ View mediaViewParent = mMediaViewContainer;
+ if (mediaViewParent != null) {
+ mMediaViewCleanUpTask = new MediaViewCleanUpTask();
+ mMediaViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
+ mediaViewParent);
+ }
+ }
+ }
+
+ private static final class MediaViewCleanUpTask extends AsyncTask<View, Void, Void> {
+ @Override
+ protected Void doInBackground(View... views) {
+ View mediaViewParent = views[0];
+ try {
+ Thread.sleep(DETACH_MEDIA_VIEW_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ return null;
+ }
+ if (isCancelled()) {
+ return null;
+ }
+ if (mediaViewParent.isAttachedToWindow()) {
+ Log.e(TAG, "Time out on releasing media view. Killing "
+ + mediaViewParent.getContext().getPackageName());
+ android.os.Process.killProcess(Process.myPid());
+ }
+ return null;
+ }
}
/**
@@ -440,10 +683,16 @@ public abstract class TvIAppService extends Service {
@Override
public void release() {
+ mSessionImpl.scheduleMediaViewCleanup();
mSessionImpl.release();
}
@Override
+ public void notifyTuned(Uri channelUri) {
+ mSessionImpl.notifyTuned(channelUri);
+ }
+
+ @Override
public void setSurface(Surface surface) {
mSessionImpl.setSurface(surface);
}
@@ -458,6 +707,21 @@ public abstract class TvIAppService extends Service {
mSessionImpl.notifyBroadcastInfoResponse(response);
}
+ @Override
+ public void createMediaView(IBinder windowToken, Rect frame) {
+ mSessionImpl.createMediaView(windowToken, frame);
+ }
+
+ @Override
+ public void relayoutMediaView(Rect frame) {
+ mSessionImpl.relayoutMediaView(frame);
+ }
+
+ @Override
+ public void removeMediaView() {
+ mSessionImpl.removeMediaView(true);
+ }
+
private final class TvIAppEventReceiver extends InputEventReceiver {
TvIAppEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
diff --git a/media/java/android/media/tv/interactive/TvIAppView.java b/media/java/android/media/tv/interactive/TvIAppView.java
index 1b25c23deea0..803198162d30 100644
--- a/media/java/android/media/tv/interactive/TvIAppView.java
+++ b/media/java/android/media/tv/interactive/TvIAppView.java
@@ -20,6 +20,8 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
+import android.graphics.Rect;
+import android.graphics.RectF;
import android.media.tv.TvInputManager;
import android.media.tv.TvView;
import android.media.tv.interactive.TvIAppManager.Session;
@@ -65,6 +67,9 @@ public class TvIAppView extends ViewGroup {
private int mSurfaceViewTop;
private int mSurfaceViewBottom;
+ private boolean mMediaViewCreated;
+ private Rect mMediaViewFrame;
+
private final AttributeSet mAttrs;
private final int mDefStyleAttr;
private final XmlResourceParser mParser;
@@ -119,6 +124,18 @@ public class TvIAppView extends ViewGroup {
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ createSessionMediaView();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ removeSessionMediaView();
+ super.onDetachedFromWindow();
+ }
+
+ @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (DEBUG) {
Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right
@@ -147,6 +164,11 @@ public class TvIAppView extends ViewGroup {
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
mSurfaceView.setVisibility(visibility);
+ if (visibility == View.VISIBLE) {
+ createSessionMediaView();
+ } else {
+ removeSessionMediaView();
+ }
}
private void resetSurfaceView() {
@@ -155,7 +177,12 @@ public class TvIAppView extends ViewGroup {
removeView(mSurfaceView);
}
mSurface = null;
- mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr);
+ mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) {
+ @Override
+ protected void updateSurface() {
+ super.updateSurface();
+ relayoutSessionMediaView();
+ }};
// The surface view's content should be treated as secure all the time.
mSurfaceView.setSecure(true);
mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
@@ -170,6 +197,46 @@ public class TvIAppView extends ViewGroup {
resetInternal();
}
+ private void createSessionMediaView() {
+ // TODO: handle z-order
+ if (mSession == null || !isAttachedToWindow() || mMediaViewCreated) {
+ return;
+ }
+ mMediaViewFrame = getViewFrameOnScreen();
+ mSession.createMediaView(this, mMediaViewFrame);
+ mMediaViewCreated = true;
+ }
+
+ private void removeSessionMediaView() {
+ if (mSession == null || !mMediaViewCreated) {
+ return;
+ }
+ mSession.removeMediaView();
+ mMediaViewCreated = false;
+ mMediaViewFrame = null;
+ }
+
+ private void relayoutSessionMediaView() {
+ if (mSession == null || !isAttachedToWindow() || !mMediaViewCreated) {
+ return;
+ }
+ Rect viewFrame = getViewFrameOnScreen();
+ if (viewFrame.equals(mMediaViewFrame)) {
+ return;
+ }
+ mSession.relayoutMediaView(viewFrame);
+ mMediaViewFrame = viewFrame;
+ }
+
+ private Rect getViewFrameOnScreen() {
+ Rect frame = new Rect();
+ getGlobalVisibleRect(frame);
+ RectF frameF = new RectF(frame);
+ getMatrix().mapRect(frameF);
+ frameF.round(frame);
+ return frame;
+ }
+
private void setSessionSurface(Surface surface) {
if (mSession == null) {
return;
@@ -214,6 +281,7 @@ public class TvIAppView extends ViewGroup {
mSessionCallback = null;
if (mSession != null) {
setSessionSurface(null);
+ removeSessionMediaView();
mUseRequestedSurfaceLayout = false;
mSession.release();
mSession = null;
@@ -287,6 +355,7 @@ public class TvIAppView extends ViewGroup {
dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
}
}
+ createSessionMediaView();
} else {
// Failed to create
// Todo: forward error to Tv App
@@ -303,6 +372,8 @@ public class TvIAppView extends ViewGroup {
Log.w(TAG, "onSessionReleased - session not created");
return;
}
+ mMediaViewCreated = false;
+ mMediaViewFrame = null;
mSessionCallback = null;
mSession = null;
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 59b6a08ef150..7e36f8921678 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -44,6 +44,7 @@ import android.graphics.Rect;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.PlaybackParams;
+import android.media.tv.AitInfo;
import android.media.tv.BroadcastInfoRequest;
import android.media.tv.BroadcastInfoResponse;
import android.media.tv.DvbDeviceInfo;
@@ -1761,6 +1762,26 @@ public final class TvInputManagerService extends SystemService {
}
@Override
+ public void setIAppNotificationEnabled(IBinder sessionToken, boolean enabled, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "setIAppNotificationEnabled");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .setIAppNotificationEnabled(enabled);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in setIAppNotificationEnabled", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
int userId) {
final int callingUid = Binder.getCallingUid();
@@ -3341,7 +3362,23 @@ public final class TvInputManagerService extends SystemService {
}
}
- // For the recording session only
+ @Override
+ public void onAitInfoUpdated(AitInfo aitInfo) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "onAitInfoUpdated(" + aitInfo + ")");
+ }
+ if (mSessionState.session == null || mSessionState.client == null) {
+ return;
+ }
+ try {
+ mSessionState.client.onAitInfoUpdated(aitInfo, mSessionState.seq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onAitInfoUpdated", e);
+ }
+ }
+ }
+
@Override
public void onTuned(Uri channelUri) {
synchronized (mLock) {
@@ -3352,7 +3389,7 @@ public final class TvInputManagerService extends SystemService {
return;
}
try {
- mSessionState.client.onTuned(mSessionState.seq, channelUri);
+ mSessionState.client.onTuned(channelUri, mSessionState.seq);
} catch (RemoteException e) {
Slog.e(TAG, "error in onTuned", e);
}
diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
index c7b6421cd0c9..f12139d07f11 100644
--- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
@@ -28,6 +28,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.graphics.Rect;
import android.media.tv.BroadcastInfoRequest;
import android.media.tv.BroadcastInfoResponse;
import android.media.tv.interactive.ITvIAppClient;
@@ -39,6 +40,7 @@ import android.media.tv.interactive.ITvIAppSession;
import android.media.tv.interactive.ITvIAppSessionCallback;
import android.media.tv.interactive.TvIAppInfo;
import android.media.tv.interactive.TvIAppService;
+import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
@@ -522,11 +524,6 @@ public class TvIAppManagerService extends SystemService {
removeSessionStateLocked(state.mSessionToken, state.mUserId);
}
- private SessionState getSessionState(IBinder sessionToken) {
- // TODO: implement user state and get session from it.
- return null;
- }
-
private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
String methodName) {
return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
@@ -570,6 +567,11 @@ public class TvIAppManagerService extends SystemService {
}
@GuardedBy("mLock")
+ private ITvIAppSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
+ return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
+ }
+
+ @GuardedBy("mLock")
private ITvIAppSession getSessionLocked(SessionState sessionState) {
ITvIAppSession session = sessionState.mSession;
if (session == null) {
@@ -601,6 +603,32 @@ public class TvIAppManagerService extends SystemService {
}
@Override
+ public void prepare(String tiasId, int type, int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "prepare");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ TvIAppState iAppState = userState.mIAppMap.get(tiasId);
+ if (iAppState == null) {
+ Slogf.e(TAG, "failed to prepare TIAS - unknown TIAS id " + tiasId);
+ return;
+ }
+ ComponentName componentName = iAppState.mInfo.getComponent();
+ ServiceState serviceState = userState.mServiceStateMap.get(componentName);
+ if (serviceState != null) {
+ serviceState.mService.prepare(type);
+ }
+ }
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in prepare", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void createSession(final ITvIAppClient client, final String iAppServiceId, int type,
int seq, int userId) {
final int callingUid = Binder.getCallingUid();
@@ -683,17 +711,53 @@ public class TvIAppManagerService extends SystemService {
}
@Override
+ public void notifyTuned(IBinder sessionToken, Uri channelUri, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "notifyTuned(sessionToken=" + sessionToken
+ + ", Uri=" + channelUri + ")");
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "notifyTuned");
+ SessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).notifyTuned(channelUri);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in notifyTuned", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void startIApp(IBinder sessionToken, int userId) {
if (DEBUG) {
Slogf.d(TAG, "BinderService#start(userId=%d)", userId);
}
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "notifyTuned");
+ SessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
try {
- SessionState sessionState = getSessionState(sessionToken);
- if (sessionState != null && sessionState.mSession != null) {
- sessionState.mSession.startIApp();
+ synchronized (mLock) {
+ try {
+ sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).startIApp();
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in start", e);
+ }
}
- } catch (RemoteException e) {
- Slogf.e(TAG, "error in start", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
@@ -802,6 +866,67 @@ public class TvIAppManagerService extends SystemService {
Binder.restoreCallingIdentity(identity);
}
}
+
+ @Override
+ public void createMediaView(IBinder sessionToken, IBinder windowToken, Rect frame,
+ int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "createMediaView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .createMediaView(windowToken, frame);
+ } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) {
+ Slog.e(TAG, "error in createMediaView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void relayoutMediaView(IBinder sessionToken, Rect frame, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "relayoutMediaView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .relayoutMediaView(frame);
+ } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) {
+ Slog.e(TAG, "error in relayoutMediaView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void removeMediaView(IBinder sessionToken, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "removeMediaView");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .removeMediaView();
+ } catch (RemoteException | TvIAppManagerService.SessionNotFoundException e) {
+ Slog.e(TAG, "error in removeMediaView", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
@GuardedBy("mLock")