diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl index c7b08d50824d0..104efe6d18814 100644 --- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl +++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl @@ -17,6 +17,8 @@ package android.media.tv.interactive; import android.media.tv.interactive.ITvIAppClient; +import android.media.tv.interactive.ITvIAppManagerCallback; +import android.media.tv.interactive.TvIAppInfo; import android.view.Surface; /** @@ -24,6 +26,7 @@ import android.view.Surface; * @hide */ interface ITvIAppManager { + List getTvIAppServiceList(int userId); void startIApp(in IBinder sessionToken, int userId); void createSession( in ITvIAppClient client, in String iAppServiceId, int type, int seq, int userId); @@ -31,4 +34,7 @@ interface ITvIAppManager { void setSurface(in IBinder sessionToken, in Surface surface, int userId); void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height, 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/ITvIAppManagerCallback.aidl b/media/java/android/media/tv/interactive/ITvIAppManagerCallback.aidl new file mode 100644 index 0000000000000..77a09b72bc2e2 --- /dev/null +++ b/media/java/android/media/tv/interactive/ITvIAppManagerCallback.aidl @@ -0,0 +1,30 @@ +/* + * 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.interactive; + +import android.media.tv.interactive.TvIAppInfo; + +/** + * Interface to receive callbacks from ITvIAppManager regardless of sessions. + * @hide + */ +interface ITvIAppManagerCallback { + void onIAppServiceAdded(in String iAppServiceId); + void onIAppServiceRemoved(in String iAppServiceId); + void onIAppServiceUpdated(in String iAppServiceId); + void onTvIAppInfoUpdated(in TvIAppInfo tvIAppInfo); +} \ No newline at end of file diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.aidl b/media/java/android/media/tv/interactive/TvIAppInfo.aidl new file mode 100644 index 0000000000000..6041460092545 --- /dev/null +++ b/media/java/android/media/tv/interactive/TvIAppInfo.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.interactive; + +parcelable TvIAppInfo; \ No newline at end of file diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.java b/media/java/android/media/tv/interactive/TvIAppInfo.java index b3b5aeee86b6e..de3a47eb0756e 100644 --- a/media/java/android/media/tv/interactive/TvIAppInfo.java +++ b/media/java/android/media/tv/interactive/TvIAppInfo.java @@ -89,6 +89,14 @@ public final class TvIAppInfo implements Parcelable { return mId; } + /** + * Returns the component of the TV IApp service. + * @hide + */ + public ComponentName getComponent() { + return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name); + } + /** * Returns the information of the service that implements this TV IApp service. */ diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java index 16e19e7827616..a858b068e226f 100644 --- a/media/java/android/media/tv/interactive/TvIAppManager.java +++ b/media/java/android/media/tv/interactive/TvIAppManager.java @@ -29,6 +29,10 @@ import android.view.Surface; import com.android.internal.util.Preconditions; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + /** * Central system API to the overall TV interactive application framework (TIAF) architecture, which * arbitrates interaction between applications and interactive apps. @@ -45,10 +49,15 @@ public final class TvIAppManager { private final SparseArray mSessionCallbackRecordMap = new SparseArray<>(); + // @GuardedBy("mLock") + private final List mCallbackRecords = new LinkedList<>(); + // A sequence number for the next session to be created. Should be protected by a lock // {@code mSessionCallbackRecordMap}. private int mNextSeq; + private final Object mLock = new Object(); + private final ITvIAppClient mClient; /** @hide */ @@ -102,6 +111,154 @@ public final class TvIAppManager { } } }; + ITvIAppManagerCallback managerCallback = new ITvIAppManagerCallback.Stub() { + // TODO: handle IApp service state changes + @Override + public void onIAppServiceAdded(String iAppServiceId) { + synchronized (mLock) { + for (TvIAppCallbackRecord record : mCallbackRecords) { + record.postIAppServiceAdded(iAppServiceId); + } + } + } + + @Override + public void onIAppServiceRemoved(String iAppServiceId) { + synchronized (mLock) { + for (TvIAppCallbackRecord record : mCallbackRecords) { + record.postIAppServiceRemoved(iAppServiceId); + } + } + } + + @Override + public void onIAppServiceUpdated(String iAppServiceId) { + synchronized (mLock) { + for (TvIAppCallbackRecord record : mCallbackRecords) { + record.postIAppServiceUpdated(iAppServiceId); + } + } + } + + @Override + public void onTvIAppInfoUpdated(TvIAppInfo iAppInfo) { + // TODO: add public API updateIAppInfo() + synchronized (mLock) { + for (TvIAppCallbackRecord record : mCallbackRecords) { + record.postTvIAppInfoUpdated(iAppInfo); + } + } + } + }; + try { + if (mService != null) { + mService.registerCallback(managerCallback, mUserId); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Callback used to monitor status of the TV IApp. + * @hide + */ + public abstract static class TvIAppCallback { + /** + * This is called when a TV IApp service is added to the system. + * + *

Normally it happens when the user installs a new TV IApp service package that + * implements {@link TvIAppService} interface. + * + * @param iAppServiceId The ID of the TV IApp service. + */ + public void onIAppServiceAdded(String iAppServiceId) { + } + + /** + * This is called when a TV IApp service is removed from the system. + * + *

Normally it happens when the user uninstalls the previously installed TV IApp service + * package. + * + * @param iAppServiceId The ID of the TV IApp service. + */ + public void onIAppServiceRemoved(String iAppServiceId) { + } + + /** + * This is called when a TV IApp service is updated on the system. + * + *

Normally it happens when a previously installed TV IApp service package is + * re-installed or a newer version of the package exists becomes available/unavailable. + * + * @param iAppServiceId The ID of the TV IApp service. + */ + public void onIAppServiceUpdated(String iAppServiceId) { + } + + /** + * This is called when the information about an existing TV IApp service has been updated. + * + *

Because the system automatically creates a TvIAppInfo object for each TV + * IApp service based on the information collected from the + * AndroidManifest.xml, this method is only called back when such information + * has changed dynamically. + * + * @param iAppInfo The TvIAppInfo object that contains new information. + */ + public void onTvIAppInfoUpdated(TvIAppInfo iAppInfo) { + } + } + + private static final class TvIAppCallbackRecord { + private final TvIAppCallback mCallback; + private final Handler mHandler; + + TvIAppCallbackRecord(TvIAppCallback callback, Handler handler) { + mCallback = callback; + mHandler = handler; + } + + public TvIAppCallback getCallback() { + return mCallback; + } + + public void postIAppServiceAdded(final String iAppServiceId) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onIAppServiceAdded(iAppServiceId); + } + }); + } + + public void postIAppServiceRemoved(final String iAppServiceId) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onIAppServiceRemoved(iAppServiceId); + } + }); + } + + public void postIAppServiceUpdated(final String iAppServiceId) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onIAppServiceUpdated(iAppServiceId); + } + }); + } + + public void postTvIAppInfoUpdated(final TvIAppInfo iAppInfo) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onTvIAppInfoUpdated(iAppInfo); + } + }); + } } /** @@ -138,6 +295,56 @@ public final class TvIAppManager { } } + /** + * Returns the complete list of TV IApp service on the system. + * + * @return List of {@link TvIAppInfo} for each TV IApp service that describes its meta + * information. + * @hide + */ + public List getTvIAppServiceList() { + try { + return mService.getTvIAppServiceList(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. + * @param handler A {@link Handler} that the status change will be delivered to. + * @hide + */ + public void registerCallback(@NonNull TvIAppCallback callback, @NonNull Handler handler) { + Preconditions.checkNotNull(callback); + Preconditions.checkNotNull(handler); + synchronized (mLock) { + mCallbackRecords.add(new TvIAppCallbackRecord(callback, handler)); + } + } + + /** + * Unregisters the existing {@link TvIAppManager.TvIAppCallback}. + * + * @param callback The existing callback to remove. + * @hide + */ + public void unregisterCallback(@NonNull final TvIAppCallback callback) { + Preconditions.checkNotNull(callback); + synchronized (mLock) { + for (Iterator it = mCallbackRecords.iterator(); + it.hasNext(); ) { + TvIAppCallbackRecord record = it.next(); + if (record.getCallback() == callback) { + it.remove(); + break; + } + } + } + } + /** * The Session provides the per-session functionality of interactive app. * @hide 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 8bbac7a024aae..4d1ff9eb3f810 100644 --- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java @@ -29,6 +29,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.media.tv.interactive.ITvIAppClient; import android.media.tv.interactive.ITvIAppManager; +import android.media.tv.interactive.ITvIAppManagerCallback; import android.media.tv.interactive.ITvIAppService; import android.media.tv.interactive.ITvIAppServiceCallback; import android.media.tv.interactive.ITvIAppSession; @@ -38,9 +39,11 @@ import android.media.tv.interactive.TvIAppService; import android.os.Binder; import android.os.IBinder; import android.os.Process; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; +import android.util.Slog; import android.util.SparseArray; import android.view.Surface; @@ -145,12 +148,85 @@ public class TvIAppManagerService extends SystemService { iAppState.mIAppNumber = count; } - // TODO: notify iApp added / removed + for (String iAppServiceId : iAppMap.keySet()) { + if (!userState.mIAppMap.containsKey(iAppServiceId)) { + notifyIAppServiceAddedLocked(userState, iAppServiceId); + } else if (updatedPackages != null) { + // Notify the package updates + ComponentName component = iAppMap.get(iAppServiceId).mInfo.getComponent(); + for (String updatedPackage : updatedPackages) { + if (component.getPackageName().equals(updatedPackage)) { + updateServiceConnectionLocked(component, userId); + notifyIAppServiceUpdatedLocked(userState, iAppServiceId); + break; + } + } + } + } + + for (String iAppServiceId : userState.mIAppMap.keySet()) { + if (!iAppMap.containsKey(iAppServiceId)) { + TvIAppInfo info = userState.mIAppMap.get(iAppServiceId).mInfo; + ServiceState serviceState = userState.mServiceStateMap.get(info.getComponent()); + if (serviceState != null) { + abortPendingCreateSessionRequestsLocked(serviceState, iAppServiceId, userId); + } + notifyIAppServiceRemovedLocked(userState, iAppServiceId); + } + } userState.mIAppMap.clear(); userState.mIAppMap = iAppMap; } + @GuardedBy("mLock") + private void notifyIAppServiceAddedLocked(UserState userState, String iAppServiceId) { + if (DEBUG) { + Slog.d(TAG, "notifyIAppServiceAddedLocked(iAppServiceId=" + iAppServiceId + ")"); + } + int n = userState.mCallbacks.beginBroadcast(); + for (int i = 0; i < n; ++i) { + try { + userState.mCallbacks.getBroadcastItem(i).onIAppServiceAdded(iAppServiceId); + } catch (RemoteException e) { + Slog.e(TAG, "failed to report added IApp service to callback", e); + } + } + userState.mCallbacks.finishBroadcast(); + } + + @GuardedBy("mLock") + private void notifyIAppServiceRemovedLocked(UserState userState, String iAppServiceId) { + if (DEBUG) { + Slog.d(TAG, "notifyIAppServiceRemovedLocked(iAppServiceId=" + iAppServiceId + ")"); + } + int n = userState.mCallbacks.beginBroadcast(); + for (int i = 0; i < n; ++i) { + try { + userState.mCallbacks.getBroadcastItem(i).onIAppServiceRemoved(iAppServiceId); + } catch (RemoteException e) { + Slog.e(TAG, "failed to report removed IApp service to callback", e); + } + } + userState.mCallbacks.finishBroadcast(); + } + + @GuardedBy("mLock") + private void notifyIAppServiceUpdatedLocked(UserState userState, String iAppServiceId) { + if (DEBUG) { + Slog.d(TAG, "notifyIAppServiceUpdatedLocked(iAppServiceId=" + iAppServiceId + ")"); + } + int n = userState.mCallbacks.beginBroadcast(); + for (int i = 0; i < n; ++i) { + try { + userState.mCallbacks.getBroadcastItem(i).onIAppServiceUpdated(iAppServiceId); + } catch (RemoteException e) { + Slog.e(TAG, "failed to report updated IApp service to callback", e); + } + } + userState.mCallbacks.finishBroadcast(); + } + private int getIAppUid(TvIAppInfo info) { try { return getContext().getPackageManager().getApplicationInfo( @@ -318,6 +394,25 @@ public class TvIAppManagerService extends SystemService { private final class BinderService extends ITvIAppManager.Stub { + @Override + public List getTvIAppServiceList(int userId) { + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), + Binder.getCallingUid(), userId, "getTvIAppServiceList"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + UserState userState = getOrCreateUserStateLocked(resolvedUserId); + List iAppList = new ArrayList<>(); + for (TvIAppState state : userState.mIAppMap.values()) { + iAppList.add(state.mInfo); + } + return iAppList; + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + @Override public void createSession(final ITvIAppClient client, final String iAppServiceId, int type, int seq, int userId) { @@ -463,6 +558,40 @@ public class TvIAppManagerService extends SystemService { Binder.restoreCallingIdentity(identity); } } + + @Override + public void registerCallback(final ITvIAppManagerCallback callback, int userId) { + int callingPid = Binder.getCallingPid(); + int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, + "registerCallback"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + final UserState userState = getOrCreateUserStateLocked(resolvedUserId); + if (!userState.mCallbacks.register(callback)) { + Slog.e(TAG, "client process has already died"); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void unregisterCallback(ITvIAppManagerCallback callback, int userId) { + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), + Binder.getCallingUid(), userId, "unregisterCallback"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + UserState userState = getOrCreateUserStateLocked(resolvedUserId); + userState.mCallbacks.unregister(callback); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } } @GuardedBy("mLock") @@ -634,6 +763,10 @@ public class TvIAppManagerService extends SystemService { // A set of all TV IApp service packages. private final Set mPackageSet = new HashSet<>(); + // A list of callbacks. + private final RemoteCallbackList mCallbacks = + new RemoteCallbackList<>(); + private UserState(int userId) { mUserId = userId; }