Merge changes from topic "TIAF_attrs"

* changes:
  TIAF: handle TIAS XML and build TIAS info
  TIAF: Add attr for TV interactive app service
This commit is contained in:
Shubang Lu
2021-11-16 18:21:12 +00:00
committed by Android (Google) Code Review
6 changed files with 414 additions and 15 deletions

View File

@@ -1409,6 +1409,7 @@ package android {
field public static final int summaryColumn = 16843426; // 0x10102a2
field public static final int summaryOff = 16843248; // 0x10101f0
field public static final int summaryOn = 16843247; // 0x10101ef
field public static final int supportedTypes;
field public static final int supportsAssist = 16844016; // 0x10104f0
field public static final int supportsInlineSuggestions = 16844301; // 0x101060d
field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
@@ -26306,6 +26307,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 {

View File

@@ -9316,6 +9316,18 @@
<attr name="canPauseRecording" format="boolean" />
</declare-styleable>
<!-- Use <code>tv-iapp</code> as the root tag of the XML resource that describes a
{@link android.media.tv.interactive.TvIAppService}, which is referenced from its
{@link android.media.tv.interactive.TvIAppService#SERVICE_META_DATA} meta-data entry.
Described here are the attributes that can be included in that tag. -->
<declare-styleable name="TvIAppService">
<!-- The interactive app types that the TV interactive app service supports.
Reference to a string array resource that describes the supported types,
e.g. HbbTv, Ginga. -->
<attr name="supportedTypes" format="reference" />
</declare-styleable>
<!-- Attributes that can be used with <code>rating-system-definition</code> tags inside of the
XML resource that describes TV content rating of a {@link android.media.tv.TvInputService},
which is referenced from its

View File

@@ -3304,6 +3304,7 @@
<public name="requiredSplitTypes" />
<public name="splitTypes" />
<public name="canDisplayOnRemoteDevices" />
<public name="supportedTypes" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01de0000">

View 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);
}
}
}
}

View File

@@ -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>&lt;{@link android.R.styleable#TvIAppService tv-iapp}&gt;</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;

View File

@@ -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 {