diff options
| author | 2021-12-03 00:20:04 +0000 | |
|---|---|---|
| committer | 2021-12-03 00:20:04 +0000 | |
| commit | d2fe5a39f0a6783dbebd55ff26e490f11619e29e (patch) | |
| tree | f13df67549649f450e01f22a7f803578a0c66ca2 | |
| parent | 45450c2bf209d15b9dd4897e77cd73dffaeaf00a (diff) | |
| parent | 938ec458a0d56608ac249e9d3a0f5e6225a6248a (diff) | |
Merge changes Ic8f92bd4,Ia130985e
* changes:
TIAF: Add IApp setup & start flow
TIAF: handle overlay media view
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") |