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

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

Merge changes from topic "revert-16781464-YCPFURVHZR"

* changes:
  (Resubmit) Read "showClockAndComplications" metadata.
  (Resubmit) Move dream metadata parsing to framework.
parents 02d4f227 6550a928
Loading
Loading
Loading
Loading
+153 −4
Original line number Diff line number Diff line
@@ -31,6 +31,11 @@ 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.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 +44,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;
@@ -57,9 +64,14 @@ import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.accessibility.AccessibilityEvent;

import com.android.internal.R;
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
@@ -239,13 +257,16 @@ public class DreamService extends Service implements Window.Callback {
            mRequests = new ArrayDeque<>();
        }

        public void bind(Context context, @Nullable ComponentName overlayService) {
        public void bind(Context context, @Nullable ComponentName overlayService,
                ComponentName dreamService) {
            if (overlayService == null) {
                return;
            }

            final Intent overlayIntent = new Intent();
            overlayIntent.setComponent(overlayService);
            overlayIntent.putExtra(EXTRA_SHOW_COMPLICATIONS,
                    fetchShouldShowComplications(context, dreamService));

            context.bindService(overlayIntent,
                    this, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE);
@@ -967,7 +988,8 @@ public class DreamService extends Service implements Window.Callback {

        // Connect to the overlay service if present.
        if (!mWindowless) {
            mOverlayConnection.bind(this, intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT));
            mOverlayConnection.bind(this, intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT),
                    new ComponentName(this, getClass()));
        }

        return mDreamServiceWrapper;
@@ -1080,6 +1102,86 @@ 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.getBoolean(R.styleable.Dream_showClockAndComplications,
                        DEFAULT_SHOW_COMPLICATIONS));
        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 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 pm.getResourcesForApplication(serviceInfo.applicationInfo).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.
     *
@@ -1242,6 +1344,30 @@ public class DreamService extends Service implements Window.Callback {
        return (oldFlags&~mask) | (flags&mask);
    }

    /**
     * Fetches metadata of the dream indicated by the {@link ComponentName}, and returns whether
     * the dream should show complications on the overlay. If not defined, returns
     * {@link DreamService#DEFAULT_SHOW_COMPLICATIONS}.
     */
    private static boolean fetchShouldShowComplications(Context context,
            ComponentName componentName) {
        final PackageManager pm = context.getPackageManager();

        try {
            final ServiceInfo si = pm.getServiceInfo(componentName,
                    PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
            final DreamMetadata metadata = getDreamMetadata(context, si);

            if (metadata != null) {
                return metadata.showComplications;
            }
        } catch (PackageManager.NameNotFoundException e) {
            if (DEBUG) Log.w(TAG, "cannot find component " + componentName.flattenToShortString());
        }

        return DEFAULT_SHOW_COMPLICATIONS;
    }

    @Override
    protected void dump(final FileDescriptor fd, PrintWriter pw, final String[] args) {
        DumpUtils.dumpAsync(mHandler, (pw1, prefix) -> dumpOnHandler(fd, pw1, args), pw, "", 1000);
@@ -1302,4 +1428,27 @@ 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;

        @NonNull
        public final boolean showComplications;

        DreamMetadata(ComponentName settingsActivity, Drawable previewImage,
                boolean showComplications) {
            this.settingsActivity = settingsActivity;
            this.previewImage = previewImage;
            this.showComplications = showComplications;
        }
    }
}
+7 −85
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;
@@ -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,78 +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;
        }

        ComponentName cn = ComponentName.unflattenFromString(flattenedString);

        if (cn == null) return null;
        if (!cn.getPackageName().equals(serviceInfo.packageName)) {
            Log.w(TAG,
                    "Inconsistent package name in component: " + cn.getPackageName()
                            + ", should be: " + serviceInfo.packageName);
            return null;
        }

        return cn;
    }

    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">
+19 −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"
       android:showClockAndComplications="false" />
+55 −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 static org.junit.Assert.assertFalse;

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() throws PackageManager.NameNotFoundException {
        final String testPackageName = "com.android.frameworks.servicestests";
        final String testDreamClassName = "com.android.server.dreams.TestDreamService";
        final String testSettingsActivity = "com.android.server.dreams/.TestDreamSettingsActivity";

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

        final ServiceInfo si = context.getPackageManager().getServiceInfo(
                new ComponentName(testPackageName, testDreamClassName),
                PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
        final DreamService.DreamMetadata metadata = DreamService.getDreamMetadata(context, si);

        assertEquals(0, metadata.settingsActivity.compareTo(
                ComponentName.unflattenFromString(testSettingsActivity)));
        assertFalse(metadata.showComplications);
    }
}
Loading