Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9736c2eb authored by shubang's avatar shubang
Browse files

TIAF: handle TIAS XML and build TIAS info

Bug: 204949944
Test: mmm
Change-Id: I0aed45b9ff1b7f9a5a66d4f02277843c8efc8060
parent a54cbccd
Loading
Loading
Loading
Loading
+7 −0
Original line number Original line Diff line number Diff line
@@ -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 {
+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);
            }
        }
    }
}
+26 −8
Original line number Original line Diff line number Diff line
@@ -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>&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
    @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() {
@@ -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();
@@ -113,6 +128,7 @@ public abstract class TvIAppService extends Service {


        /**
        /**
         * Starts TvIAppService session.
         * Starts TvIAppService session.
         * @hide
         */
         */
        public void onStartIApp() {
        public void onStartIApp() {
        }
        }
@@ -144,6 +160,7 @@ public abstract class TvIAppService extends Service {


        /**
        /**
         * Releases TvIAppService session.
         * Releases TvIAppService session.
         * @hide
         */
         */
        public void onRelease() {
        public void onRelease() {
        }
        }
@@ -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;
+177 −7
Original line number Original line Diff line number Diff line
@@ -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;
@@ -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) {
@@ -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;
@@ -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 {