Loading core/api/current.txt +7 −0 Original line number Original line Diff line number Diff line Loading @@ -26305,6 +26305,13 @@ package android.media.tv.interactive { public final class TvIAppManager { public final class TvIAppManager { } } public abstract class TvIAppService extends android.app.Service { ctor public TvIAppService(); method public final android.os.IBinder onBind(android.content.Intent); field public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvIAppService"; field public static final String SERVICE_META_DATA = "android.media.tv.interactive.app"; } } } package android.mtp { package android.mtp { media/java/android/media/tv/interactive/TvIAppInfo.java 0 → 100644 +190 −0 Original line number Original line Diff line number Diff line /* * 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.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * This class is used to specify meta information of a TV interactive app. * @hide */ public final class TvIAppInfo implements Parcelable { private static final boolean DEBUG = false; private static final String TAG = "TvIAppInfo"; private final ResolveInfo mService; private final String mId; private List<String> mTypes = new ArrayList<>(); private TvIAppInfo(ResolveInfo service, String id, List<String> types) { mService = service; mId = id; mTypes = types; } protected TvIAppInfo(Parcel in) { mService = ResolveInfo.CREATOR.createFromParcel(in); mId = in.readString(); in.readStringList(mTypes); } public static final Creator<TvIAppInfo> CREATOR = new Creator<TvIAppInfo>() { @Override public TvIAppInfo createFromParcel(Parcel in) { return new TvIAppInfo(in); } @Override public TvIAppInfo[] newArray(int size) { return new TvIAppInfo[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { mService.writeToParcel(dest, flags); dest.writeString(mId); dest.writeStringList(mTypes); } public String getId() { return mId; } /** * Returns the information of the service that implements this TV IApp service. */ public ServiceInfo getServiceInfo() { return mService.serviceInfo; } /** * A convenience builder for creating {@link TvIAppInfo} objects. */ public static final class Builder { private static final String XML_START_TAG_NAME = "tv-iapp"; private final Context mContext; private final ResolveInfo mResolveInfo; private final List<String> mTypes = new ArrayList<>(); /** * Constructs a new builder for {@link TvIAppInfo}. * * @param context A Context of the application package implementing this class. * @param component The name of the application component to be used for the * {@link TvIAppService}. */ public Builder(Context context, ComponentName component) { if (context == null) { throw new IllegalArgumentException("context cannot be null."); } Intent intent = new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component); mResolveInfo = context.getPackageManager().resolveService(intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); if (mResolveInfo == null) { throw new IllegalArgumentException("Invalid component. Can't find the service."); } mContext = context; } /** * Creates a {@link TvIAppInfo} instance with the specified fields. Most of the information * is obtained by parsing the AndroidManifest and {@link TvIAppService#SERVICE_META_DATA} * for the {@link TvIAppService} this TV IApp implements. * * @return TvIAppInfo containing information about this TV IApp service. */ public TvIAppInfo build() { ComponentName componentName = new ComponentName(mResolveInfo.serviceInfo.packageName, mResolveInfo.serviceInfo.name); String id; id = generateIAppServiceId(componentName); parseServiceMetadata(); return new TvIAppInfo(mResolveInfo, id, mTypes); } private static String generateIAppServiceId(ComponentName name) { return name.flattenToShortString(); } private void parseServiceMetadata() { ServiceInfo si = mResolveInfo.serviceInfo; PackageManager pm = mContext.getPackageManager(); try (XmlResourceParser parser = si.loadXmlMetaData(pm, TvIAppService.SERVICE_META_DATA)) { if (parser == null) { throw new IllegalStateException("No " + TvIAppService.SERVICE_META_DATA + " meta-data found for " + si.name); } Resources res = pm.getResourcesForApplication(si.applicationInfo); AttributeSet attrs = Xml.asAttributeSet(parser); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { // move to the START_TAG } String nodeName = parser.getName(); if (!XML_START_TAG_NAME.equals(nodeName)) { throw new IllegalStateException("Meta-data does not start with " + XML_START_TAG_NAME + " tag for " + si.name); } TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.TvIAppService); CharSequence[] types = sa.getTextArray( com.android.internal.R.styleable.TvIAppService_supportedTypes); for (CharSequence cs : types) { mTypes.add(cs.toString()); } sa.recycle(); } catch (IOException | XmlPullParserException e) { throw new IllegalStateException( "Failed reading meta-data for " + si.packageName, e); } catch (PackageManager.NameNotFoundException e) { throw new IllegalStateException("No resources found for " + si.packageName, e); } } } } media/java/android/media/tv/interactive/TvIAppService.java +26 −8 Original line number Original line Diff line number Diff line Loading @@ -38,22 +38,32 @@ import java.util.List; /** /** * The TvIAppService class represents a TV interactive applications RTE. * The TvIAppService class represents a TV interactive applications RTE. * @hide */ */ public abstract class TvIAppService extends Service { public abstract class TvIAppService extends Service { private static final boolean DEBUG = false; private static final boolean DEBUG = false; private static final String TAG = "TvIAppService"; private static final String TAG = "TvIAppService"; private final Handler mServiceHandler = new ServiceHandler(); // TODO: cleanup and unhide APIs. /** /** * This is the interface name that a service implementing an environment to run Tv IApp should * This is the interface name that a service implementing a TV IApp service should say that it * say that it support -- that is, this is the action it uses for its intent filter. To be * supports -- that is, this is the action it uses for its intent filter. To be supported, the * supported, the service must also require the BIND_TV_IAPP permission so that other * service must also require the android.Manifest.permission#BIND_TV_IAPP permission so * applications cannot abuse it. * that other applications cannot abuse it. */ */ public static final String SERVICE_INTERFACE = "android.media.tv.TvIAppService"; public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvIAppService"; /** * Name under which a TvIAppService component publishes information about itself. This meta-data * must reference an XML resource containing an * <code><{@link android.R.styleable#TvIAppService tv-iapp}></code> * tag. */ public static final String SERVICE_META_DATA = "android.media.tv.interactive.app"; private final Handler mServiceHandler = new ServiceHandler(); /** @hide */ @Override @Override public final IBinder onBind(Intent intent) { public final IBinder onBind(Intent intent) { ITvIAppService.Stub tvIAppServiceBinder = new ITvIAppService.Stub() { ITvIAppService.Stub tvIAppServiceBinder = new ITvIAppService.Stub() { Loading Loading @@ -83,12 +93,17 @@ public abstract class TvIAppService extends Service { * * * @param iAppServiceId The ID of the TV IApp associated with the session. * @param iAppServiceId The ID of the TV IApp associated with the session. * @param type The type of the TV IApp associated with the session. * @param type The type of the TV IApp associated with the session. * @hide */ */ @Nullable @Nullable public abstract Session onCreateSession(@NonNull String iAppServiceId, int type); public Session onCreateSession(@NonNull String iAppServiceId, int type) { // TODO: make it abstract when unhide return null; } /** /** * Base class for derived classes to implement to provide a TV interactive app session. * Base class for derived classes to implement to provide a TV interactive app session. * @hide */ */ public abstract static class Session implements KeyEvent.Callback { public abstract static class Session implements KeyEvent.Callback { private final Object mLock = new Object(); private final Object mLock = new Object(); Loading @@ -113,6 +128,7 @@ public abstract class TvIAppService extends Service { /** /** * Starts TvIAppService session. * Starts TvIAppService session. * @hide */ */ public void onStartIApp() { public void onStartIApp() { } } Loading Loading @@ -144,6 +160,7 @@ public abstract class TvIAppService extends Service { /** /** * Releases TvIAppService session. * Releases TvIAppService session. * @hide */ */ public void onRelease() { public void onRelease() { } } Loading Loading @@ -245,6 +262,7 @@ public abstract class TvIAppService extends Service { /** /** * Implements the internal ITvIAppSession interface. * Implements the internal ITvIAppSession interface. * @hide */ */ public static class ITvIAppSessionWrapper extends ITvIAppSession.Stub { public static class ITvIAppSessionWrapper extends ITvIAppSession.Stub { private final Session mSessionImpl; private final Session mSessionImpl; Loading services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java +177 −7 Original line number Original line Diff line number Diff line Loading @@ -18,31 +18,41 @@ package com.android.server.tv.interactive; import android.annotation.Nullable; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ComponentName; import android.content.Context; import android.content.Context; import android.content.Intent; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.media.tv.interactive.ITvIAppClient; import android.media.tv.interactive.ITvIAppClient; import android.media.tv.interactive.ITvIAppManager; import android.media.tv.interactive.ITvIAppManager; import android.media.tv.interactive.ITvIAppService; import android.media.tv.interactive.ITvIAppService; import android.media.tv.interactive.ITvIAppServiceCallback; import android.media.tv.interactive.ITvIAppServiceCallback; import android.media.tv.interactive.ITvIAppSession; import android.media.tv.interactive.ITvIAppSession; import android.media.tv.interactive.ITvIAppSessionCallback; import android.media.tv.interactive.ITvIAppSessionCallback; import android.media.tv.interactive.TvIAppInfo; import android.media.tv.interactive.TvIAppService; import android.media.tv.interactive.TvIAppService; import android.os.Binder; import android.os.Binder; import android.os.IBinder; import android.os.IBinder; import android.os.Process; import android.os.Process; import android.os.RemoteException; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserHandle; import android.util.ArrayMap; import android.util.SparseArray; import android.util.SparseArray; import android.view.Surface; import android.view.Surface; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.server.SystemService; import com.android.server.SystemService; import com.android.server.utils.Slogf; import com.android.server.utils.Slogf; import java.util.ArrayList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashMap; import java.util.HashSet; import java.util.HashSet; import java.util.List; import java.util.List; Loading Loading @@ -82,6 +92,75 @@ public class TvIAppManagerService extends SystemService { mContext = context; mContext = context; } } @GuardedBy("mLock") private void buildTvIAppServiceListLocked(int userId, String[] updatedPackages) { UserState userState = getOrCreateUserStateLocked(userId); userState.mPackageSet.clear(); if (DEBUG) { Slogf.d(TAG, "buildTvIAppServiceListLocked"); } PackageManager pm = mContext.getPackageManager(); List<ResolveInfo> services = pm.queryIntentServicesAsUser( new Intent(TvIAppService.SERVICE_INTERFACE), PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, userId); List<TvIAppInfo> iAppList = new ArrayList<>(); for (ResolveInfo ri : services) { ServiceInfo si = ri.serviceInfo; // TODO: add BIND_TV_IAPP permission and check it here ComponentName component = new ComponentName(si.packageName, si.name); try { TvIAppInfo info = new TvIAppInfo.Builder(mContext, component).build(); iAppList.add(info); } catch (Exception e) { Slogf.e(TAG, "failed to load TV IApp service " + si.name, e); continue; } userState.mPackageSet.add(si.packageName); } // sort the iApp list by iApp service id Collections.sort(iAppList, Comparator.comparing(TvIAppInfo::getId)); Map<String, TvIAppState> iAppMap = new HashMap<>(); ArrayMap<String, Integer> tiasAppCount = new ArrayMap<>(iAppMap.size()); for (TvIAppInfo info : iAppList) { String iAppServiceId = info.getId(); if (DEBUG) { Slogf.d(TAG, "add " + iAppServiceId); } // Running count of IApp for each IApp service Integer count = tiasAppCount.get(iAppServiceId); count = count == null ? 1 : count + 1; tiasAppCount.put(iAppServiceId, count); TvIAppState iAppState = userState.mIAppMap.get(iAppServiceId); if (iAppState == null) { iAppState = new TvIAppState(); } iAppState.mInfo = info; iAppState.mUid = getIAppUid(info); iAppMap.put(iAppServiceId, iAppState); iAppState.mIAppNumber = count; } // TODO: notify iApp added / removed userState.mIAppMap.clear(); userState.mIAppMap = iAppMap; } private int getIAppUid(TvIAppInfo info) { try { return getContext().getPackageManager().getApplicationInfo( info.getServiceInfo().packageName, 0).uid; } catch (PackageManager.NameNotFoundException e) { Slogf.w(TAG, "Unable to get UID for " + info, e); return Process.INVALID_UID; } } @Override @Override public void onStart() { public void onStart() { if (DEBUG) { if (DEBUG) { Loading @@ -90,6 +169,96 @@ public class TvIAppManagerService extends SystemService { publishBinderService(Context.TV_IAPP_SERVICE, new BinderService()); publishBinderService(Context.TV_IAPP_SERVICE, new BinderService()); } } @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { registerBroadcastReceivers(); } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { synchronized (mLock) { buildTvIAppServiceListLocked(mCurrentUserId, null); } } } private void registerBroadcastReceivers() { PackageMonitor monitor = new PackageMonitor() { private void buildTvIAppServiceList(String[] packages) { int userId = getChangingUserId(); synchronized (mLock) { if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) { buildTvIAppServiceListLocked(userId, packages); } } } @Override public void onPackageUpdateFinished(String packageName, int uid) { if (DEBUG) Slogf.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")"); // This callback is invoked when the TV iApp service is reinstalled. // In this case, isReplacing() always returns true. buildTvIAppServiceList(new String[] { packageName }); } @Override public void onPackagesAvailable(String[] packages) { if (DEBUG) { Slogf.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")"); } // This callback is invoked when the media on which some packages exist become // available. if (isReplacing()) { buildTvIAppServiceList(packages); } } @Override public void onPackagesUnavailable(String[] packages) { // This callback is invoked when the media on which some packages exist become // unavailable. if (DEBUG) { Slogf.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages) + ")"); } if (isReplacing()) { buildTvIAppServiceList(packages); } } @Override public void onSomePackagesChanged() { if (DEBUG) Slogf.d(TAG, "onSomePackagesChanged()"); if (isReplacing()) { if (DEBUG) Slogf.d(TAG, "Skipped building TV iApp list due to replacing"); // When the package is updated, buildTvIAppServiceListLocked is called in other // methods instead. return; } buildTvIAppServiceList(null); } @Override public boolean onPackageChanged(String packageName, int uid, String[] components) { // The iApp list needs to be updated in any cases, regardless of whether // it happened to the whole package or a specific component. Returning true so that // the update can be handled in {@link #onSomePackagesChanged}. return true; } }; monitor.register(mContext, null, UserHandle.ALL, true); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_USER_SWITCHED); intentFilter.addAction(Intent.ACTION_USER_REMOVED); intentFilter.addAction(Intent.ACTION_USER_STARTED); intentFilter.addAction(Intent.ACTION_USER_STOPPED); mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // TODO: handle switch / start / stop user } }, UserHandle.ALL, intentFilter, null, null); } private SessionState getSessionState(IBinder sessionToken) { private SessionState getSessionState(IBinder sessionToken) { // TODO: implement user state and get session from it. // TODO: implement user state and get session from it. return null; return null; Loading Loading @@ -462,19 +631,20 @@ public class TvIAppManagerService extends SystemService { // A mapping from the token of a TV IApp session to its state. // A mapping from the token of a TV IApp session to its state. private final Map<IBinder, SessionState> mSessionStateMap = new HashMap<>(); private final Map<IBinder, SessionState> mSessionStateMap = new HashMap<>(); // A set of all TV IApp service packages. private final Set<String> mPackageSet = new HashSet<>(); private UserState(int userId) { private UserState(int userId) { mUserId = userId; mUserId = userId; } } } } private static final class TvIAppState { private static final class TvIAppState { private final String mIAppServiceId; private String mIAppServiceId; private final ComponentName mComponentName; private ComponentName mComponentName; private TvIAppInfo mInfo; TvIAppState(String id, ComponentName componentName) { private int mUid; mIAppServiceId = id; private int mIAppNumber; mComponentName = componentName; } } } private final class SessionState implements IBinder.DeathRecipient { private final class SessionState implements IBinder.DeathRecipient { Loading Loading
core/api/current.txt +7 −0 Original line number Original line Diff line number Diff line Loading @@ -26305,6 +26305,13 @@ package android.media.tv.interactive { public final class TvIAppManager { public final class TvIAppManager { } } public abstract class TvIAppService extends android.app.Service { ctor public TvIAppService(); method public final android.os.IBinder onBind(android.content.Intent); field public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvIAppService"; field public static final String SERVICE_META_DATA = "android.media.tv.interactive.app"; } } } package android.mtp { package android.mtp {
media/java/android/media/tv/interactive/TvIAppInfo.java 0 → 100644 +190 −0 Original line number Original line Diff line number Diff line /* * 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.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * This class is used to specify meta information of a TV interactive app. * @hide */ public final class TvIAppInfo implements Parcelable { private static final boolean DEBUG = false; private static final String TAG = "TvIAppInfo"; private final ResolveInfo mService; private final String mId; private List<String> mTypes = new ArrayList<>(); private TvIAppInfo(ResolveInfo service, String id, List<String> types) { mService = service; mId = id; mTypes = types; } protected TvIAppInfo(Parcel in) { mService = ResolveInfo.CREATOR.createFromParcel(in); mId = in.readString(); in.readStringList(mTypes); } public static final Creator<TvIAppInfo> CREATOR = new Creator<TvIAppInfo>() { @Override public TvIAppInfo createFromParcel(Parcel in) { return new TvIAppInfo(in); } @Override public TvIAppInfo[] newArray(int size) { return new TvIAppInfo[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { mService.writeToParcel(dest, flags); dest.writeString(mId); dest.writeStringList(mTypes); } public String getId() { return mId; } /** * Returns the information of the service that implements this TV IApp service. */ public ServiceInfo getServiceInfo() { return mService.serviceInfo; } /** * A convenience builder for creating {@link TvIAppInfo} objects. */ public static final class Builder { private static final String XML_START_TAG_NAME = "tv-iapp"; private final Context mContext; private final ResolveInfo mResolveInfo; private final List<String> mTypes = new ArrayList<>(); /** * Constructs a new builder for {@link TvIAppInfo}. * * @param context A Context of the application package implementing this class. * @param component The name of the application component to be used for the * {@link TvIAppService}. */ public Builder(Context context, ComponentName component) { if (context == null) { throw new IllegalArgumentException("context cannot be null."); } Intent intent = new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component); mResolveInfo = context.getPackageManager().resolveService(intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); if (mResolveInfo == null) { throw new IllegalArgumentException("Invalid component. Can't find the service."); } mContext = context; } /** * Creates a {@link TvIAppInfo} instance with the specified fields. Most of the information * is obtained by parsing the AndroidManifest and {@link TvIAppService#SERVICE_META_DATA} * for the {@link TvIAppService} this TV IApp implements. * * @return TvIAppInfo containing information about this TV IApp service. */ public TvIAppInfo build() { ComponentName componentName = new ComponentName(mResolveInfo.serviceInfo.packageName, mResolveInfo.serviceInfo.name); String id; id = generateIAppServiceId(componentName); parseServiceMetadata(); return new TvIAppInfo(mResolveInfo, id, mTypes); } private static String generateIAppServiceId(ComponentName name) { return name.flattenToShortString(); } private void parseServiceMetadata() { ServiceInfo si = mResolveInfo.serviceInfo; PackageManager pm = mContext.getPackageManager(); try (XmlResourceParser parser = si.loadXmlMetaData(pm, TvIAppService.SERVICE_META_DATA)) { if (parser == null) { throw new IllegalStateException("No " + TvIAppService.SERVICE_META_DATA + " meta-data found for " + si.name); } Resources res = pm.getResourcesForApplication(si.applicationInfo); AttributeSet attrs = Xml.asAttributeSet(parser); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { // move to the START_TAG } String nodeName = parser.getName(); if (!XML_START_TAG_NAME.equals(nodeName)) { throw new IllegalStateException("Meta-data does not start with " + XML_START_TAG_NAME + " tag for " + si.name); } TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.TvIAppService); CharSequence[] types = sa.getTextArray( com.android.internal.R.styleable.TvIAppService_supportedTypes); for (CharSequence cs : types) { mTypes.add(cs.toString()); } sa.recycle(); } catch (IOException | XmlPullParserException e) { throw new IllegalStateException( "Failed reading meta-data for " + si.packageName, e); } catch (PackageManager.NameNotFoundException e) { throw new IllegalStateException("No resources found for " + si.packageName, e); } } } }
media/java/android/media/tv/interactive/TvIAppService.java +26 −8 Original line number Original line Diff line number Diff line Loading @@ -38,22 +38,32 @@ import java.util.List; /** /** * The TvIAppService class represents a TV interactive applications RTE. * The TvIAppService class represents a TV interactive applications RTE. * @hide */ */ public abstract class TvIAppService extends Service { public abstract class TvIAppService extends Service { private static final boolean DEBUG = false; private static final boolean DEBUG = false; private static final String TAG = "TvIAppService"; private static final String TAG = "TvIAppService"; private final Handler mServiceHandler = new ServiceHandler(); // TODO: cleanup and unhide APIs. /** /** * This is the interface name that a service implementing an environment to run Tv IApp should * This is the interface name that a service implementing a TV IApp service should say that it * say that it support -- that is, this is the action it uses for its intent filter. To be * supports -- that is, this is the action it uses for its intent filter. To be supported, the * supported, the service must also require the BIND_TV_IAPP permission so that other * service must also require the android.Manifest.permission#BIND_TV_IAPP permission so * applications cannot abuse it. * that other applications cannot abuse it. */ */ public static final String SERVICE_INTERFACE = "android.media.tv.TvIAppService"; public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvIAppService"; /** * Name under which a TvIAppService component publishes information about itself. This meta-data * must reference an XML resource containing an * <code><{@link android.R.styleable#TvIAppService tv-iapp}></code> * tag. */ public static final String SERVICE_META_DATA = "android.media.tv.interactive.app"; private final Handler mServiceHandler = new ServiceHandler(); /** @hide */ @Override @Override public final IBinder onBind(Intent intent) { public final IBinder onBind(Intent intent) { ITvIAppService.Stub tvIAppServiceBinder = new ITvIAppService.Stub() { ITvIAppService.Stub tvIAppServiceBinder = new ITvIAppService.Stub() { Loading Loading @@ -83,12 +93,17 @@ public abstract class TvIAppService extends Service { * * * @param iAppServiceId The ID of the TV IApp associated with the session. * @param iAppServiceId The ID of the TV IApp associated with the session. * @param type The type of the TV IApp associated with the session. * @param type The type of the TV IApp associated with the session. * @hide */ */ @Nullable @Nullable public abstract Session onCreateSession(@NonNull String iAppServiceId, int type); public Session onCreateSession(@NonNull String iAppServiceId, int type) { // TODO: make it abstract when unhide return null; } /** /** * Base class for derived classes to implement to provide a TV interactive app session. * Base class for derived classes to implement to provide a TV interactive app session. * @hide */ */ public abstract static class Session implements KeyEvent.Callback { public abstract static class Session implements KeyEvent.Callback { private final Object mLock = new Object(); private final Object mLock = new Object(); Loading @@ -113,6 +128,7 @@ public abstract class TvIAppService extends Service { /** /** * Starts TvIAppService session. * Starts TvIAppService session. * @hide */ */ public void onStartIApp() { public void onStartIApp() { } } Loading Loading @@ -144,6 +160,7 @@ public abstract class TvIAppService extends Service { /** /** * Releases TvIAppService session. * Releases TvIAppService session. * @hide */ */ public void onRelease() { public void onRelease() { } } Loading Loading @@ -245,6 +262,7 @@ public abstract class TvIAppService extends Service { /** /** * Implements the internal ITvIAppSession interface. * Implements the internal ITvIAppSession interface. * @hide */ */ public static class ITvIAppSessionWrapper extends ITvIAppSession.Stub { public static class ITvIAppSessionWrapper extends ITvIAppSession.Stub { private final Session mSessionImpl; private final Session mSessionImpl; Loading
services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java +177 −7 Original line number Original line Diff line number Diff line Loading @@ -18,31 +18,41 @@ package com.android.server.tv.interactive; import android.annotation.Nullable; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ComponentName; import android.content.Context; import android.content.Context; import android.content.Intent; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.media.tv.interactive.ITvIAppClient; import android.media.tv.interactive.ITvIAppClient; import android.media.tv.interactive.ITvIAppManager; import android.media.tv.interactive.ITvIAppManager; import android.media.tv.interactive.ITvIAppService; import android.media.tv.interactive.ITvIAppService; import android.media.tv.interactive.ITvIAppServiceCallback; import android.media.tv.interactive.ITvIAppServiceCallback; import android.media.tv.interactive.ITvIAppSession; import android.media.tv.interactive.ITvIAppSession; import android.media.tv.interactive.ITvIAppSessionCallback; import android.media.tv.interactive.ITvIAppSessionCallback; import android.media.tv.interactive.TvIAppInfo; import android.media.tv.interactive.TvIAppService; import android.media.tv.interactive.TvIAppService; import android.os.Binder; import android.os.Binder; import android.os.IBinder; import android.os.IBinder; import android.os.Process; import android.os.Process; import android.os.RemoteException; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserHandle; import android.util.ArrayMap; import android.util.SparseArray; import android.util.SparseArray; import android.view.Surface; import android.view.Surface; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.server.SystemService; import com.android.server.SystemService; import com.android.server.utils.Slogf; import com.android.server.utils.Slogf; import java.util.ArrayList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashMap; import java.util.HashSet; import java.util.HashSet; import java.util.List; import java.util.List; Loading Loading @@ -82,6 +92,75 @@ public class TvIAppManagerService extends SystemService { mContext = context; mContext = context; } } @GuardedBy("mLock") private void buildTvIAppServiceListLocked(int userId, String[] updatedPackages) { UserState userState = getOrCreateUserStateLocked(userId); userState.mPackageSet.clear(); if (DEBUG) { Slogf.d(TAG, "buildTvIAppServiceListLocked"); } PackageManager pm = mContext.getPackageManager(); List<ResolveInfo> services = pm.queryIntentServicesAsUser( new Intent(TvIAppService.SERVICE_INTERFACE), PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, userId); List<TvIAppInfo> iAppList = new ArrayList<>(); for (ResolveInfo ri : services) { ServiceInfo si = ri.serviceInfo; // TODO: add BIND_TV_IAPP permission and check it here ComponentName component = new ComponentName(si.packageName, si.name); try { TvIAppInfo info = new TvIAppInfo.Builder(mContext, component).build(); iAppList.add(info); } catch (Exception e) { Slogf.e(TAG, "failed to load TV IApp service " + si.name, e); continue; } userState.mPackageSet.add(si.packageName); } // sort the iApp list by iApp service id Collections.sort(iAppList, Comparator.comparing(TvIAppInfo::getId)); Map<String, TvIAppState> iAppMap = new HashMap<>(); ArrayMap<String, Integer> tiasAppCount = new ArrayMap<>(iAppMap.size()); for (TvIAppInfo info : iAppList) { String iAppServiceId = info.getId(); if (DEBUG) { Slogf.d(TAG, "add " + iAppServiceId); } // Running count of IApp for each IApp service Integer count = tiasAppCount.get(iAppServiceId); count = count == null ? 1 : count + 1; tiasAppCount.put(iAppServiceId, count); TvIAppState iAppState = userState.mIAppMap.get(iAppServiceId); if (iAppState == null) { iAppState = new TvIAppState(); } iAppState.mInfo = info; iAppState.mUid = getIAppUid(info); iAppMap.put(iAppServiceId, iAppState); iAppState.mIAppNumber = count; } // TODO: notify iApp added / removed userState.mIAppMap.clear(); userState.mIAppMap = iAppMap; } private int getIAppUid(TvIAppInfo info) { try { return getContext().getPackageManager().getApplicationInfo( info.getServiceInfo().packageName, 0).uid; } catch (PackageManager.NameNotFoundException e) { Slogf.w(TAG, "Unable to get UID for " + info, e); return Process.INVALID_UID; } } @Override @Override public void onStart() { public void onStart() { if (DEBUG) { if (DEBUG) { Loading @@ -90,6 +169,96 @@ public class TvIAppManagerService extends SystemService { publishBinderService(Context.TV_IAPP_SERVICE, new BinderService()); publishBinderService(Context.TV_IAPP_SERVICE, new BinderService()); } } @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { registerBroadcastReceivers(); } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { synchronized (mLock) { buildTvIAppServiceListLocked(mCurrentUserId, null); } } } private void registerBroadcastReceivers() { PackageMonitor monitor = new PackageMonitor() { private void buildTvIAppServiceList(String[] packages) { int userId = getChangingUserId(); synchronized (mLock) { if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) { buildTvIAppServiceListLocked(userId, packages); } } } @Override public void onPackageUpdateFinished(String packageName, int uid) { if (DEBUG) Slogf.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")"); // This callback is invoked when the TV iApp service is reinstalled. // In this case, isReplacing() always returns true. buildTvIAppServiceList(new String[] { packageName }); } @Override public void onPackagesAvailable(String[] packages) { if (DEBUG) { Slogf.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")"); } // This callback is invoked when the media on which some packages exist become // available. if (isReplacing()) { buildTvIAppServiceList(packages); } } @Override public void onPackagesUnavailable(String[] packages) { // This callback is invoked when the media on which some packages exist become // unavailable. if (DEBUG) { Slogf.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages) + ")"); } if (isReplacing()) { buildTvIAppServiceList(packages); } } @Override public void onSomePackagesChanged() { if (DEBUG) Slogf.d(TAG, "onSomePackagesChanged()"); if (isReplacing()) { if (DEBUG) Slogf.d(TAG, "Skipped building TV iApp list due to replacing"); // When the package is updated, buildTvIAppServiceListLocked is called in other // methods instead. return; } buildTvIAppServiceList(null); } @Override public boolean onPackageChanged(String packageName, int uid, String[] components) { // The iApp list needs to be updated in any cases, regardless of whether // it happened to the whole package or a specific component. Returning true so that // the update can be handled in {@link #onSomePackagesChanged}. return true; } }; monitor.register(mContext, null, UserHandle.ALL, true); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_USER_SWITCHED); intentFilter.addAction(Intent.ACTION_USER_REMOVED); intentFilter.addAction(Intent.ACTION_USER_STARTED); intentFilter.addAction(Intent.ACTION_USER_STOPPED); mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // TODO: handle switch / start / stop user } }, UserHandle.ALL, intentFilter, null, null); } private SessionState getSessionState(IBinder sessionToken) { private SessionState getSessionState(IBinder sessionToken) { // TODO: implement user state and get session from it. // TODO: implement user state and get session from it. return null; return null; Loading Loading @@ -462,19 +631,20 @@ public class TvIAppManagerService extends SystemService { // A mapping from the token of a TV IApp session to its state. // A mapping from the token of a TV IApp session to its state. private final Map<IBinder, SessionState> mSessionStateMap = new HashMap<>(); private final Map<IBinder, SessionState> mSessionStateMap = new HashMap<>(); // A set of all TV IApp service packages. private final Set<String> mPackageSet = new HashSet<>(); private UserState(int userId) { private UserState(int userId) { mUserId = userId; mUserId = userId; } } } } private static final class TvIAppState { private static final class TvIAppState { private final String mIAppServiceId; private String mIAppServiceId; private final ComponentName mComponentName; private ComponentName mComponentName; private TvIAppInfo mInfo; TvIAppState(String id, ComponentName componentName) { private int mUid; mIAppServiceId = id; private int mIAppNumber; mComponentName = componentName; } } } private final class SessionState implements IBinder.DeathRecipient { private final class SessionState implements IBinder.DeathRecipient { Loading