diff options
6 files changed, 272 insertions, 89 deletions
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index bb1f393b99bc..345917220b6b 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -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. @@ -191,6 +204,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; @@ -1081,6 +1103,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. * * Must run on mHandler. @@ -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 <dream>}. + * + * @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; + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index d179b828d4ab..0102a97c9b93 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -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)); diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index d9f73d9aa54e..53cab9ed80cf 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -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"> diff --git a/services/tests/servicestests/res/xml/test_dream_metadata.xml b/services/tests/servicestests/res/xml/test_dream_metadata.xml new file mode 100644 index 000000000000..aa054f1e9fa4 --- /dev/null +++ b/services/tests/servicestests/res/xml/test_dream_metadata.xml @@ -0,0 +1,19 @@ +<!-- + ~ 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" /> diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamServiceTest.java new file mode 100644 index 000000000000..74d2e0f231bb --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/dreams/DreamServiceTest.java @@ -0,0 +1,55 @@ +/* + * 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); + } +} diff --git a/services/tests/servicestests/src/com/android/server/dreams/TestDreamService.java b/services/tests/servicestests/src/com/android/server/dreams/TestDreamService.java new file mode 100644 index 000000000000..3c99a9829275 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/dreams/TestDreamService.java @@ -0,0 +1,25 @@ +/* + * 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 android.service.dreams.DreamService; + +/** + * Dream service implementation for unit testing. + */ +public class TestDreamService extends DreamService { +} |