Merge changes Ic8f92bd4,Ia130985e
* changes: TIAF: Add IApp setup & start flow TIAF: handle overlay media view
This commit is contained in:
19
media/java/android/media/tv/AitInfo.aidl
Normal file
19
media/java/android/media/tv/AitInfo.aidl
Normal file
@@ -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;
|
||||
87
media/java/android/media/tv/AitInfo.java
Normal file
87
media/java/android/media/tv/AitInfo.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -306,6 +311,12 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
|
||||
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_SELECT_TRACK, type, trackId));
|
||||
}
|
||||
|
||||
@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,
|
||||
|
||||
@@ -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) {
|
||||
@@ -2339,6 +2380,25 @@ public final class TvInputManager {
|
||||
throw new IllegalArgumentException("invalid type: " + type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
||||
@@ -827,6 +827,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.
|
||||
@@ -1020,6 +1041,14 @@ public abstract class TvInputService extends Service {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -1368,6 +1397,13 @@ public abstract class TvInputService extends Service {
|
||||
// TODO: Handle failure.
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #onSetIAppNotificationEnabled}.
|
||||
*/
|
||||
void setIAppNotificationEnabled(boolean enabled) {
|
||||
onSetIAppNotificationEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #onAppPrivateCommand}.
|
||||
*/
|
||||
|
||||
@@ -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()");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -482,6 +480,18 @@ public class TvView extends ViewGroup {
|
||||
return mSession.getSelectedTrack(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package android.media.tv.interactive;
|
||||
import android.media.tv.BroadcastInfoRequest;
|
||||
|
||||
import android.media.tv.BroadcastInfoRequest;
|
||||
import android.view.InputChannel;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -331,6 +334,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}.
|
||||
*
|
||||
@@ -442,6 +457,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);
|
||||
|
||||
|
||||
@@ -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,9 +184,40 @@ 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.
|
||||
*
|
||||
* @param response response received from TIS.
|
||||
* <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 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,6 +281,20 @@ public abstract class TvIAppService extends Service {
|
||||
public void onRelease() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,9 +683,15 @@ 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);
|
||||
|
||||
@@ -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;
|
||||
@@ -118,6 +123,18 @@ public class TvIAppView extends ViewGroup {
|
||||
mTvIAppManager = (TvIAppManager) getContext().getSystemService("tv_interactive_app");
|
||||
}
|
||||
|
||||
@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) {
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -1760,6 +1761,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) {
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -569,6 +566,11 @@ public class TvIAppManagerService extends SystemService {
|
||||
return sessionState;
|
||||
}
|
||||
|
||||
@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;
|
||||
@@ -600,6 +602,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) {
|
||||
@@ -682,18 +710,54 @@ 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")
|
||||
|
||||
Reference in New Issue
Block a user