Introduce initOrder for apex-system-services.

This allows to have a relative order between apex system services if
needed due to dependencies.

Bug: 192880996
Test: atest ApexSystemServicesTestCases PackageParserTest ApexManagerTest
Change-Id: Ib9700be48390f8a78135c76e79ad2bc4cb6e8577
This commit is contained in:
satayev
2022-01-12 11:55:21 +00:00
parent 18f92370b5
commit 8308d9e981
14 changed files with 222 additions and 37 deletions

View File

@@ -35,4 +35,6 @@ public interface ParsedApexSystemService extends Parcelable {
@Nullable
String getMaxSdkVersion();
int getInitOrder();
}

View File

@@ -45,10 +45,11 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService {
@Nullable
private String maxSdkVersion;
private int initOrder;
public ParsedApexSystemServiceImpl() {
}
// Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
@@ -67,13 +68,15 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService {
@NonNull String name,
@Nullable String jarPath,
@Nullable String minSdkVersion,
@Nullable String maxSdkVersion) {
@Nullable String maxSdkVersion,
int initOrder) {
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
this.jarPath = jarPath;
this.minSdkVersion = minSdkVersion;
this.maxSdkVersion = maxSdkVersion;
this.initOrder = initOrder;
// onConstructed(); // You can define this method to get a callback
}
@@ -98,6 +101,11 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService {
return maxSdkVersion;
}
@DataClass.Generated.Member
public int getInitOrder() {
return initOrder;
}
@DataClass.Generated.Member
public @NonNull ParsedApexSystemServiceImpl setName(@NonNull String value) {
name = value;
@@ -124,6 +132,12 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService {
return this;
}
@DataClass.Generated.Member
public @NonNull ParsedApexSystemServiceImpl setInitOrder( int value) {
initOrder = value;
return this;
}
@DataClass.Generated.Member
static Parcelling<String> sParcellingForName =
Parcelling.Cache.get(
@@ -183,6 +197,7 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService {
sParcellingForJarPath.parcel(jarPath, dest, flags);
sParcellingForMinSdkVersion.parcel(minSdkVersion, dest, flags);
sParcellingForMaxSdkVersion.parcel(maxSdkVersion, dest, flags);
dest.writeInt(initOrder);
}
@Override
@@ -201,6 +216,7 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService {
String _jarPath = sParcellingForJarPath.unparcel(in);
String _minSdkVersion = sParcellingForMinSdkVersion.unparcel(in);
String _maxSdkVersion = sParcellingForMaxSdkVersion.unparcel(in);
int _initOrder = in.readInt();
this.name = _name;
com.android.internal.util.AnnotationValidations.validate(
@@ -208,6 +224,7 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService {
this.jarPath = _jarPath;
this.minSdkVersion = _minSdkVersion;
this.maxSdkVersion = _maxSdkVersion;
this.initOrder = _initOrder;
// onConstructed(); // You can define this method to get a callback
}
@@ -227,10 +244,10 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService {
};
@DataClass.Generated(
time = 1638903241144L,
time = 1641307133386L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java",
inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
@Deprecated
private void __metadata() {}

View File

@@ -53,10 +53,13 @@ public class ParsedApexSystemServiceUtils {
R.styleable.AndroidManifestApexSystemService_minSdkVersion);
String maxSdkVersion = sa.getString(
R.styleable.AndroidManifestApexSystemService_maxSdkVersion);
int initOrder = sa.getInt(R.styleable.AndroidManifestApexSystemService_initOrder, 0);
systemService.setName(className)
.setMinSdkVersion(minSdkVersion)
.setMaxSdkVersion(maxSdkVersion);
.setMaxSdkVersion(maxSdkVersion)
.setInitOrder(initOrder);
if (!TextUtils.isEmpty(jarPath)) {
systemService.setJarPath(jarPath);
}

View File

@@ -2825,6 +2825,14 @@
<attr name="path" />
<attr name="minSdkVersion" />
<attr name="maxSdkVersion" />
<!-- The order in which the apex system services are initiated. When there are dependencies
among apex system services, setting this attribute for each of them ensures that they are
created in the order required by those dependencies. The apex-system-services that are
started manually within SystemServer ignore the initOrder and are not considered for
automatic starting of the other services.
The value is a simple integer, with higher number being initialized first. If not specified,
the default order is 0. -->
<attr name="initOrder" format="integer" />
</declare-styleable>
<!-- The <code>receiver</code> tag declares an

View File

@@ -413,9 +413,11 @@ public abstract class ApexManager {
throws PackageManagerException;
/**
* Get a map of system services defined in an apex mapped to the jar files they reside in.
* Get a list of apex system services implemented in an apex.
*
* <p>The list is sorted by initOrder for consistency.
*/
public abstract Map<String, String> getApexSystemServices();
public abstract List<ApexSystemServiceInfo> getApexSystemServices();
/**
* Dumps various state information to the provided {@link PrintWriter} object.
@@ -448,7 +450,7 @@ public abstract class ApexManager {
* Map of all apex system services to the jar files they are contained in.
*/
@GuardedBy("mLock")
private Map<String, String> mApexSystemServices = new ArrayMap<>();
private List<ApexSystemServiceInfo> mApexSystemServices = new ArrayList<>();
/**
* Contains the list of {@code packageName}s of apks-in-apex for given
@@ -604,14 +606,19 @@ public abstract class ApexManager {
}
String name = service.getName();
if (mApexSystemServices.containsKey(name)) {
throw new IllegalStateException(String.format(
"Duplicate apex-system-service %s from %s, %s",
name, mApexSystemServices.get(name), service.getJarPath()));
for (ApexSystemServiceInfo info : mApexSystemServices) {
if (info.getName().equals(name)) {
throw new IllegalStateException(String.format(
"Duplicate apex-system-service %s from %s, %s",
name, info.mJarPath, service.getJarPath()));
}
}
mApexSystemServices.put(name, service.getJarPath());
ApexSystemServiceInfo info = new ApexSystemServiceInfo(
service.getName(), service.getJarPath(), service.getInitOrder());
mApexSystemServices.add(info);
}
Collections.sort(mApexSystemServices);
mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName);
if (ai.isActive) {
if (activePackagesSet.contains(packageInfo.packageName)) {
@@ -1132,7 +1139,7 @@ public abstract class ApexManager {
}
@Override
public Map<String, String> getApexSystemServices() {
public List<ApexSystemServiceInfo> getApexSystemServices() {
synchronized (mLock) {
Preconditions.checkState(mApexSystemServices != null,
"APEX packages have not been scanned");
@@ -1418,10 +1425,10 @@ public abstract class ApexManager {
}
@Override
public Map<String, String> getApexSystemServices() {
public List<ApexSystemServiceInfo> getApexSystemServices() {
// TODO(satayev): we can't really support flattened apex use case, and need to migrate
// the manifest entries into system's manifest asap.
return Collections.emptyMap();
return Collections.emptyList();
}
@Override

View File

@@ -0,0 +1,58 @@
/*
* 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.pm;
import android.annotation.Nullable;
/**
* A helper class that contains information about apex-system-service to be used within system
* server process.
*/
public final class ApexSystemServiceInfo implements Comparable<ApexSystemServiceInfo> {
final String mName;
@Nullable
final String mJarPath;
final int mInitOrder;
public ApexSystemServiceInfo(String name, String jarPath, int initOrder) {
this.mName = name;
this.mJarPath = jarPath;
this.mInitOrder = initOrder;
}
public String getName() {
return mName;
}
public String getJarPath() {
return mJarPath;
}
public int getInitOrder() {
return mInitOrder;
}
@Override
public int compareTo(ApexSystemServiceInfo other) {
if (mInitOrder == other.mInitOrder) {
return mName.compareTo(other.mName);
}
// higher initOrder values take precedence
return -Integer.compare(mInitOrder, other.mInitOrder);
}
}

View File

@@ -152,6 +152,7 @@ import com.android.server.os.NativeTombstoneManagerService;
import com.android.server.os.SchedulingPolicyService;
import com.android.server.people.PeopleService;
import com.android.server.pm.ApexManager;
import com.android.server.pm.ApexSystemServiceInfo;
import com.android.server.pm.CrossProfileAppsService;
import com.android.server.pm.DataLoaderManagerService;
import com.android.server.pm.DynamicCodeLoggingService;
@@ -220,8 +221,8 @@ import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Timer;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
@@ -1457,7 +1458,7 @@ public final class SystemServer implements Dumpable {
// TelecomLoader hooks into classes with defined HFP logic,
// so check for either telephony or microphone.
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) ||
mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
t.traceBegin("StartTelecomLoaderService");
mSystemServiceManager.startService(TelecomLoaderService.class);
t.traceEnd();
@@ -1465,7 +1466,7 @@ public final class SystemServer implements Dumpable {
t.traceBegin("StartTelephonyRegistry");
telephonyRegistry = new TelephonyRegistry(
context, new TelephonyRegistry.ConfigurationProvider());
context, new TelephonyRegistry.ConfigurationProvider());
ServiceManager.addService("telephony.registry", telephonyRegistry);
t.traceEnd();
@@ -2992,7 +2993,9 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
t.traceBegin("MakeTelephonyRegistryReady");
try {
if (telephonyRegistryF != null) telephonyRegistryF.systemRunning();
if (telephonyRegistryF != null) {
telephonyRegistryF.systemRunning();
}
} catch (Throwable e) {
reportWtf("Notifying TelephonyRegistry running", e);
}
@@ -3057,10 +3060,12 @@ public final class SystemServer implements Dumpable {
*/
private void startApexServices(@NonNull TimingsTraceAndSlog t) {
t.traceBegin("startApexServices");
Map<String, String> services = ApexManager.getInstance().getApexSystemServices();
// TODO(satayev): introduce android:order for services coming the same apexes
for (String name : new TreeSet<>(services.keySet())) {
String jarPath = services.get(name);
// TODO(b/192880996): get the list from "android" package, once the manifest entries
// are migrated to system manifest.
List<ApexSystemServiceInfo> services = ApexManager.getInstance().getApexSystemServices();
for (ApexSystemServiceInfo info : services) {
String name = info.getName();
String jarPath = info.getJarPath();
t.traceBegin("starting " + name);
if (TextUtils.isEmpty(jarPath)) {
mSystemServiceManager.startService(name);

View File

@@ -32,7 +32,7 @@ apex_test {
name: "test_com.android.server",
manifest: "manifest.json",
androidManifest: "AndroidManifest.xml",
java_libs: ["FakeApexSystemService"],
java_libs: ["FakeApexSystemServices"],
file_contexts: ":apex.test-file_contexts",
key: "test_com.android.server.key",
updatable: false,

View File

@@ -21,21 +21,29 @@
<application android:hasCode="false" android:testOnly="true">
<apex-system-service
android:name="com.android.server.testing.FakeApexSystemService"
android:path="/apex/test_com.android.server/javalib/FakeApexSystemService.jar"
android:minSdkVersion="30"/>
android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar"
android:minSdkVersion="30"
/>
<apex-system-service
android:name="com.android.server.testing.FakeApexSystemService2"
android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar"
android:minSdkVersion="30"
android:initOrder="1"
/>
<!-- Always inactive system service, since maxSdkVersion is low -->
<apex-system-service
android:name="com.android.apex.test.OldApexSystemService"
android:path="/apex/com.android.apex.test/javalib/fake.jar"
android:name="com.android.server.testing.OldApexSystemService"
android:path="/apex/test_com.android.server/javalib/fake.jar"
android:minSdkVersion="1"
android:maxSdkVersion="1"
/>
<!-- Always inactive system service, since minSdkVersion is high -->
<apex-system-service
android:name="com.android.apex.test.NewApexSystemService"
android:path="/apex/com.android.apex.test/javalib/fake.jar"
android:name="com.android.server.testing.NewApexSystemService"
android:path="/apex/test_com.android.server/javalib/fake.jar"
android:minSdkVersion="999999"
/>
</application>

View File

@@ -8,7 +8,7 @@ package {
}
java_library {
name: "FakeApexSystemService",
name: "FakeApexSystemServices",
srcs: ["**/*.java"],
sdk_version: "system_server_current",
libs: [

View File

@@ -0,0 +1,41 @@
/*
* 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 com.android.server.testing;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import com.android.server.SystemService;
/**
* A fake system service that just logs when it is started.
*/
public class FakeApexSystemService2 extends SystemService {
private static final String TAG = "FakeApexSystemService";
public FakeApexSystemService2(@NonNull Context context) {
super(context);
}
@Override
public void onStart() {
Log.d(TAG, "FakeApexSystemService2 onStart");
}
}

View File

@@ -37,6 +37,10 @@ import org.junit.rules.RuleChain;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@RunWith(DeviceJUnit4ClassRunner.class)
public class ApexSystemServicesTestCases extends BaseHostJUnit4Test {
@@ -67,7 +71,7 @@ public class ApexSystemServicesTestCases extends BaseHostJUnit4Test {
}
@Test
public void noApexSystemServerStartsWithoutApex() throws Exception {
public void testNoApexSystemServiceStartsWithoutApex() throws Exception {
mPreparer.reboot();
assertThat(getFakeApexSystemServiceLogcat())
@@ -75,7 +79,7 @@ public class ApexSystemServicesTestCases extends BaseHostJUnit4Test {
}
@Test
public void apexSystemServerStarts() throws Exception {
public void testApexSystemServiceStarts() throws Exception {
// Pre-install the apex
String apex = "test_com.android.server.apex";
mPreparer.pushResourceFile(apex, "/system/apex/" + apex);
@@ -86,9 +90,40 @@ public class ApexSystemServicesTestCases extends BaseHostJUnit4Test {
.contains("FakeApexSystemService onStart");
}
@Test
public void testInitOrder() throws Exception {
// Pre-install the apex
String apex = "test_com.android.server.apex";
mPreparer.pushResourceFile(apex, "/system/apex/" + apex);
// Reboot activates the apex
mPreparer.reboot();
assertThat(getFakeApexSystemServiceLogcat().lines()
.map(ApexSystemServicesTestCases::getDebugMessage)
.filter(Objects::nonNull)
.collect(Collectors.toList()))
.containsExactly(
// Second service has a higher initOrder and must be started first
"FakeApexSystemService2 onStart",
"FakeApexSystemService onStart"
)
.inOrder();
}
private String getFakeApexSystemServiceLogcat() throws DeviceNotAvailableException {
return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", "FakeApexSystemService:D",
"*:S");
}
private static final Pattern DEBUG_MESSAGE =
Pattern.compile("(FakeApexSystemService[0-9]* onStart)");
private static String getDebugMessage(String logcatLine) {
return DEBUG_MESSAGE.matcher(logcatLine)
.results()
.map(m -> m.group(1))
.findFirst()
.orElse(null);
}
}

View File

@@ -61,7 +61,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import java.util.List;
@SmallTest
@Presubmit
@@ -136,9 +136,10 @@ public class ApexManagerTest {
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
Map<String, String> services = mApexManager.getApexSystemServices();
List<ApexSystemServiceInfo> services = mApexManager.getApexSystemServices();
assertThat(services).hasSize(1);
assertThat(services).containsKey("com.android.apex.test.ApexSystemService");
assertThat(services.stream().map(ApexSystemServiceInfo::getName).findFirst().orElse(null))
.matches("com.android.apex.test.ApexSystemService");
}
@Test