(Resubmit) Move dream metadata parsing to framework.
Bug: 214117287 Test: atest com.android.server.dreams.DreamServiceTest Test: manually on device by opening Screensaver settings because it calls into the metadata parsing from DreamBackend Change-Id: I252f634dd10297ba7dcd91fae6289a85a2904e3d
This commit is contained in:
@@ -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;
|
||||
@@ -59,7 +66,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 +170,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 +202,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 +1097,84 @@ 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 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.
|
||||
*
|
||||
@@ -1302,4 +1397,22 @@ 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;
|
||||
|
||||
DreamMetadata(ComponentName settingsActivity, Drawable previewImage) {
|
||||
this.settingsActivity = settingsActivity;
|
||||
this.previewImage = previewImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
services/tests/servicestests/res/xml/test_dream_metadata.xml
Normal file
18
services/tests/servicestests/res/xml/test_dream_metadata.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
~ 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" />
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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() 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)));
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
Reference in New Issue
Block a user