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

Commit d965b9f5 authored by Darrell Shi's avatar Darrell Shi Committed by Android (Google) Code Review
Browse files

Merge "Move dream metadata parsing to framework."

parents f1be3163 b0a0bfdf
Loading
Loading
Loading
Loading
+114 −2
Original line number Diff line number Diff line
@@ -31,6 +31,12 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
@@ -39,9 +45,11 @@ import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
import android.util.Xml;
import android.view.ActionMode;
import android.view.Display;
import android.view.KeyEvent;
@@ -59,7 +67,11 @@ import android.view.accessibility.AccessibilityEvent;

import com.android.internal.util.DumpUtils;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.function.Consumer;
@@ -159,8 +171,9 @@ import java.util.function.Consumer;
 * </pre>
 */
public class DreamService extends Service implements Window.Callback {
    private final String mTag =
            DreamService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]";
    private static final String TAG = DreamService.class.getSimpleName();
    private final String mTag = TAG + "[" + getClass().getSimpleName() + "]";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    /**
     * The name of the dream manager service.
@@ -190,6 +203,11 @@ public class DreamService extends Service implements Window.Callback {
     */
    public static final String DREAM_META_DATA = "android.service.dream";

    /**
     * Name of the root tag under which a Dream defines its metadata in an XML file.
     */
    private static final String DREAM_META_DATA_ROOT_TAG = "dream";

    /**
     * Extra containing a boolean for whether to show complications on the overlay.
     * @hide
@@ -1080,6 +1098,82 @@ public class DreamService extends Service implements Window.Callback {

    // end public api

    /**
     * Parses and returns metadata of the dream service indicated by the service info. Returns null
     * if metadata cannot be found.
     *
     * Note that {@link ServiceInfo} must be fetched with {@link PackageManager#GET_META_DATA} flag.
     *
     * @hide
     */
    @Nullable
    public static DreamMetadata getDreamMetadata(Context context, ServiceInfo serviceInfo) {
        final PackageManager pm = context.getPackageManager();

        final TypedArray rawMetadata = readMetadata(pm, serviceInfo);
        if (rawMetadata == null) return null;

        final DreamMetadata metadata = new DreamMetadata(
                convertToComponentName(rawMetadata.getString(
                        com.android.internal.R.styleable.Dream_settingsActivity), serviceInfo),
                rawMetadata.getDrawable(
                        com.android.internal.R.styleable.Dream_previewImage));
        rawMetadata.recycle();
        return metadata;
    }

    /**
     * Returns the raw XML metadata fetched from the ${@link ServiceInfo}.
     *
     * Returns <code>null</code> if the ${@link ServiceInfo} doesn't contain valid dream metadata.
     */
    @Nullable
    private static TypedArray readMetadata(PackageManager pm, ServiceInfo serviceInfo) {
        if (serviceInfo == null || serviceInfo.metaData == null) {
            return null;
        }

        try (XmlResourceParser parser =
                     serviceInfo.loadXmlMetaData(pm, DreamService.DREAM_META_DATA)) {
            if (parser == null) {
                if (DEBUG) Log.w(TAG, "No " + DreamService.DREAM_META_DATA + " metadata");
                return null;
            }

            final Resources res = pm.getResourcesForApplication(serviceInfo.applicationInfo);
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            while (true) {
                final int type = parser.next();
                if (type == XmlPullParser.END_DOCUMENT || type == XmlPullParser.START_TAG) {
                    break;
                }
            }

            if (!parser.getName().equals(DREAM_META_DATA_ROOT_TAG)) {
                if (DEBUG) {
                    Log.w(TAG, "Metadata does not start with " + DREAM_META_DATA_ROOT_TAG + " tag");
                }
                return null;
            }

            return res.obtainAttributes(attrs, com.android.internal.R.styleable.Dream);
        } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) {
            if (DEBUG) Log.e(TAG, "Error parsing: " + serviceInfo.packageName, e);
            return null;
        }
    }

    private static ComponentName convertToComponentName(String flattenedString,
            ServiceInfo serviceInfo) {
        if (flattenedString == null) return null;

        if (!flattenedString.contains("/")) {
            return new ComponentName(serviceInfo.packageName, flattenedString);
        }

        return ComponentName.unflattenFromString(flattenedString);
    }

    /**
     * Called by DreamController.stopDream() when the Dream is about to be unbound and destroyed.
     *
@@ -1302,4 +1396,22 @@ public class DreamService extends Service implements Window.Callback {
            onWindowCreated(a.getWindow());
        }
    }

    /**
     * Represents metadata defined in {@link android.R.styleable#Dream &lt;dream&gt;}.
     *
     * @hide
     */
    public static final class DreamMetadata {
        @Nullable
        public final ComponentName settingsActivity;

        @Nullable
        public final Drawable previewImage;

        DreamMetadata(ComponentName settingsActivity, Drawable previewImage) {
            this.settingsActivity = settingsActivity;
            this.previewImage = previewImage;
        }
    }
}
+8 −75
Original line number Diff line number Diff line
@@ -26,8 +26,6 @@ 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.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -35,21 +33,14 @@ import android.provider.Settings;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;

import com.android.settingslib.R;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
@@ -170,7 +161,7 @@ public class DreamBackend {
        PackageManager pm = mContext.getPackageManager();
        Intent dreamIntent = new Intent(DreamService.SERVICE_INTERFACE);
        List<ResolveInfo> resolveInfos = pm.queryIntentServices(dreamIntent,
                PackageManager.GET_META_DATA);
                PackageManager.ResolveInfoFlags.of(PackageManager.GET_META_DATA));
        List<DreamInfo> dreamInfos = new ArrayList<>(resolveInfos.size());
        for (ResolveInfo resolveInfo : resolveInfos) {
            final ComponentName componentName = getDreamComponentName(resolveInfo);
@@ -185,15 +176,18 @@ public class DreamBackend {
            dreamInfo.componentName = componentName;
            dreamInfo.isActive = dreamInfo.componentName.equals(activeDream);

            final DreamMetadata dreamMetadata = getDreamMetadata(pm, resolveInfo);
            dreamInfo.settingsComponentName = dreamMetadata.mSettingsActivity;
            dreamInfo.previewImage = dreamMetadata.mPreviewImage;
            final DreamService.DreamMetadata dreamMetadata = DreamService.getDreamMetadata(mContext,
                    resolveInfo.serviceInfo);
            if (dreamMetadata != null) {
                dreamInfo.settingsComponentName = dreamMetadata.settingsActivity;
                dreamInfo.previewImage = dreamMetadata.previewImage;
            }
            if (dreamInfo.previewImage == null) {
                dreamInfo.previewImage = mDreamPreviewDefault;
            }
            dreamInfos.add(dreamInfo);
        }
        Collections.sort(dreamInfos, mComparator);
        dreamInfos.sort(mComparator);
        return dreamInfos;
    }

@@ -483,67 +477,6 @@ public class DreamBackend {
        return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
    }

    private static final class DreamMetadata {
        @Nullable
        Drawable mPreviewImage;
        @Nullable
        ComponentName mSettingsActivity;
    }

    @Nullable
    private static TypedArray readMetadata(PackageManager pm, ServiceInfo serviceInfo) {
        if (serviceInfo == null || serviceInfo.metaData == null) {
            return null;
        }
        try (XmlResourceParser parser =
                     serviceInfo.loadXmlMetaData(pm, DreamService.DREAM_META_DATA)) {
            if (parser == null) {
                Log.w(TAG, "No " + DreamService.DREAM_META_DATA + " meta-data");
                return null;
            }
            Resources res = pm.getResourcesForApplication(serviceInfo.applicationInfo);
            AttributeSet attrs = Xml.asAttributeSet(parser);
            while (true) {
                final int type = parser.next();
                if (type == XmlPullParser.END_DOCUMENT || type == XmlPullParser.START_TAG) {
                    break;
                }
            }
            String nodeName = parser.getName();
            if (!"dream".equals(nodeName)) {
                Log.w(TAG, "Meta-data does not start with dream tag");
                return null;
            }
            return res.obtainAttributes(attrs, com.android.internal.R.styleable.Dream);
        } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) {
            Log.w(TAG, "Error parsing : " + serviceInfo.packageName, e);
            return null;
        }
    }

    private static ComponentName convertToComponentName(String flattenedString,
            ServiceInfo serviceInfo) {
        if (flattenedString == null) return null;

        if (flattenedString.indexOf('/') < 0) {
            flattenedString = serviceInfo.packageName + "/" + flattenedString;
        }
        return ComponentName.unflattenFromString(flattenedString);
    }

    private static DreamMetadata getDreamMetadata(PackageManager pm, ResolveInfo resolveInfo) {
        DreamMetadata result = new DreamMetadata();
        if (resolveInfo == null) return result;
        TypedArray rawMetadata = readMetadata(pm, resolveInfo.serviceInfo);
        if (rawMetadata == null) return result;
        result.mSettingsActivity = convertToComponentName(rawMetadata.getString(
                com.android.internal.R.styleable.Dream_settingsActivity), resolveInfo.serviceInfo);
        result.mPreviewImage = rawMetadata.getDrawable(
                com.android.internal.R.styleable.Dream_previewImage);
        rawMetadata.recycle();
        return result;
    }

    private static void logd(String msg, Object... args) {
        if (DEBUG) {
            Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args));
+13 −0
Original line number Diff line number Diff line
@@ -130,6 +130,19 @@
               android:resource="@xml/test_account_type2_authenticator"/>
        </service>

        <service
            android:name="com.android.server.dreams.TestDreamService"
            android:exported="false"
            android:label="Test Dream" >
            <intent-filter>
                <action android:name="android.service.dreams.DreamService" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <meta-data
                android:name="android.service.dream"
                android:resource="@xml/test_dream_metadata" />
        </service>

        <receiver android:name="com.android.server.devicepolicy.ApplicationRestrictionsTest$AdminReceiver"
             android:permission="android.permission.BIND_DEVICE_ADMIN"
             android:exported="true">
+18 −0
Original line number Diff line number Diff line
<!--
  ~ Copyright (C) 2022 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.
  -->

<dream xmlns:android="http://schemas.android.com/apk/res/android"
       android:settingsActivity="com.android.server.dreams/.TestDreamSettingsActivity" />
+56 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 com.android.server.dreams;

import static org.junit.Assert.assertEquals;

import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.service.dreams.DreamService;

import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class DreamServiceTest {
    @Test
    public void testMetadataParsing() {
        final String testDreamServiceComponent = "com.android.server.dreams/.TestDreamService";
        final String testSettingsActivity = "com.android.server.dreams/.TestDreamSettingsActivity";

        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

        try {
            final ServiceInfo si = context.getPackageManager().getServiceInfo(
                    ComponentName.unflattenFromString(testDreamServiceComponent),
                    PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
            final DreamService.DreamMetadata metadata = DreamService.getDreamMetadata(context, si);

            assertEquals(0, metadata.settingsActivity.compareTo(
                    ComponentName.unflattenFromString(testSettingsActivity)));
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}
Loading