TIAF: handle TIAS XML and build TIAS info
Bug: 204949944 Test: mmm Change-Id: I0aed45b9ff1b7f9a5a66d4f02277843c8efc8060
This commit is contained in:
@@ -26305,6 +26305,13 @@ package android.media.tv.interactive {
|
||||
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 {
|
||||
|
||||
190
media/java/android/media/tv/interactive/TvIAppInfo.java
Normal file
190
media/java/android/media/tv/interactive/TvIAppInfo.java
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,22 +38,32 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* The TvIAppService class represents a TV interactive applications RTE.
|
||||
* @hide
|
||||
*/
|
||||
public abstract class TvIAppService extends Service {
|
||||
private static final boolean DEBUG = false;
|
||||
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
|
||||
* say that it support -- that is, this is the action it uses for its intent filter. To be
|
||||
* supported, the service must also require the BIND_TV_IAPP permission so that other
|
||||
* applications cannot abuse it.
|
||||
* This is the interface name that a service implementing a TV IApp service should say that it
|
||||
* supports -- that is, this is the action it uses for its intent filter. To be supported, the
|
||||
* service must also require the android.Manifest.permission#BIND_TV_IAPP permission so
|
||||
* 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
|
||||
public final IBinder onBind(Intent intent) {
|
||||
ITvIAppService.Stub tvIAppServiceBinder = new ITvIAppService.Stub() {
|
||||
@@ -83,12 +93,17 @@ public abstract class TvIAppService extends Service {
|
||||
*
|
||||
* @param iAppServiceId The ID of the TV IApp associated with the session.
|
||||
* @param type The type of the TV IApp associated with the session.
|
||||
* @hide
|
||||
*/
|
||||
@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.
|
||||
* @hide
|
||||
*/
|
||||
public abstract static class Session implements KeyEvent.Callback {
|
||||
private final Object mLock = new Object();
|
||||
@@ -113,6 +128,7 @@ public abstract class TvIAppService extends Service {
|
||||
|
||||
/**
|
||||
* Starts TvIAppService session.
|
||||
* @hide
|
||||
*/
|
||||
public void onStartIApp() {
|
||||
}
|
||||
@@ -144,6 +160,7 @@ public abstract class TvIAppService extends Service {
|
||||
|
||||
/**
|
||||
* Releases TvIAppService session.
|
||||
* @hide
|
||||
*/
|
||||
public void onRelease() {
|
||||
}
|
||||
@@ -245,6 +262,7 @@ public abstract class TvIAppService extends Service {
|
||||
|
||||
/**
|
||||
* Implements the internal ITvIAppSession interface.
|
||||
* @hide
|
||||
*/
|
||||
public static class ITvIAppSessionWrapper extends ITvIAppSession.Stub {
|
||||
private final Session mSessionImpl;
|
||||
|
||||
@@ -18,31 +18,41 @@ package com.android.server.tv.interactive;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
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.ITvIAppManager;
|
||||
import android.media.tv.interactive.ITvIAppService;
|
||||
import android.media.tv.interactive.ITvIAppServiceCallback;
|
||||
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.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.SparseArray;
|
||||
import android.view.Surface;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.content.PackageMonitor;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.utils.Slogf;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -82,6 +92,75 @@ public class TvIAppManagerService extends SystemService {
|
||||
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
|
||||
public void onStart() {
|
||||
if (DEBUG) {
|
||||
@@ -90,6 +169,96 @@ public class TvIAppManagerService extends SystemService {
|
||||
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) {
|
||||
// TODO: implement user state and get session from it.
|
||||
return null;
|
||||
@@ -462,19 +631,20 @@ public class TvIAppManagerService extends SystemService {
|
||||
// A mapping from the token of a TV IApp session to its state.
|
||||
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) {
|
||||
mUserId = userId;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TvIAppState {
|
||||
private final String mIAppServiceId;
|
||||
private final ComponentName mComponentName;
|
||||
|
||||
TvIAppState(String id, ComponentName componentName) {
|
||||
mIAppServiceId = id;
|
||||
mComponentName = componentName;
|
||||
}
|
||||
private String mIAppServiceId;
|
||||
private ComponentName mComponentName;
|
||||
private TvIAppInfo mInfo;
|
||||
private int mUid;
|
||||
private int mIAppNumber;
|
||||
}
|
||||
|
||||
private final class SessionState implements IBinder.DeathRecipient {
|
||||
|
||||
Reference in New Issue
Block a user