1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.pm.pkg.component;
18 
19 import static com.android.internal.pm.pkg.component.ComponentParseUtils.flag;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.IntentFilter;
24 import android.content.pm.ActivityInfo;
25 import android.content.pm.ServiceInfo;
26 import android.content.pm.parsing.result.ParseInput;
27 import android.content.pm.parsing.result.ParseInput.DeferredError;
28 import android.content.pm.parsing.result.ParseResult;
29 import android.content.res.Resources;
30 import android.content.res.TypedArray;
31 import android.content.res.XmlResourceParser;
32 import android.multiuser.Flags;
33 import android.os.Build;
34 
35 import com.android.internal.R;
36 import com.android.internal.pm.pkg.parsing.ParsingPackage;
37 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
38 import com.android.internal.pm.pkg.parsing.ParsingUtils;
39 
40 import org.xmlpull.v1.XmlPullParser;
41 import org.xmlpull.v1.XmlPullParserException;
42 
43 import java.io.IOException;
44 import java.util.Objects;
45 
46 /** @hide */
47 public class ParsedServiceUtils {
48 
49     @NonNull
parseService(String[] separateProcesses, ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags, boolean useRoundIcon, @Nullable String defaultSplitName, @NonNull ParseInput input)50     public static ParseResult<ParsedService> parseService(String[] separateProcesses,
51             ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
52             boolean useRoundIcon, @Nullable String defaultSplitName, @NonNull ParseInput input)
53             throws XmlPullParserException, IOException {
54         boolean visibleToEphemeral;
55         boolean setExported;
56 
57         final String packageName = pkg.getPackageName();
58         final ParsedServiceImpl service = new ParsedServiceImpl();
59         String tag = parser.getName();
60 
61         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestService);
62         try {
63             ParseResult<ParsedServiceImpl> result = ParsedMainComponentUtils.parseMainComponent(
64                     service, tag, separateProcesses, pkg, sa, flags, useRoundIcon, defaultSplitName,
65                     input,
66                     R.styleable.AndroidManifestService_banner,
67                     R.styleable.AndroidManifestService_description,
68                     R.styleable.AndroidManifestService_directBootAware,
69                     R.styleable.AndroidManifestService_enabled,
70                     R.styleable.AndroidManifestService_icon,
71                     R.styleable.AndroidManifestService_label,
72                     R.styleable.AndroidManifestService_logo,
73                     R.styleable.AndroidManifestService_name,
74                     R.styleable.AndroidManifestService_process,
75                     R.styleable.AndroidManifestService_roundIcon,
76                     R.styleable.AndroidManifestService_splitName,
77                     R.styleable.AndroidManifestService_attributionTags
78             );
79 
80             if (result.isError()) {
81                 return input.error(result);
82             }
83 
84             setExported = sa.hasValue(R.styleable.AndroidManifestService_exported);
85             if (setExported) {
86                 service.setExported(sa.getBoolean(R.styleable.AndroidManifestService_exported,
87                         false));
88             }
89 
90             String permission = sa.getNonConfigurationString(
91                     R.styleable.AndroidManifestService_permission, 0);
92             service.setPermission(permission != null ? permission : pkg.getPermission());
93 
94             service.setForegroundServiceType(sa.getInt(
95                     R.styleable.AndroidManifestService_foregroundServiceType,
96                     ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE))
97                     .setFlags(service.getFlags() | (flag(ServiceInfo.FLAG_STOP_WITH_TASK,
98                             R.styleable.AndroidManifestService_stopWithTask, sa)
99                             | flag(ServiceInfo.FLAG_ISOLATED_PROCESS,
100                             R.styleable.AndroidManifestService_isolatedProcess, sa)
101                             | flag(ServiceInfo.FLAG_EXTERNAL_SERVICE,
102                             R.styleable.AndroidManifestService_externalService, sa)
103                             | flag(ServiceInfo.FLAG_USE_APP_ZYGOTE,
104                             R.styleable.AndroidManifestService_useAppZygote, sa)
105                             | flag(ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS,
106                             R.styleable.AndroidManifestService_allowSharedIsolatedProcess, sa)
107                             | flag(ServiceInfo.FLAG_SINGLE_USER,
108                             R.styleable.AndroidManifestService_singleUser, sa)));
109 
110             if (Flags.enableSystemUserOnlyForServicesAndProviders()) {
111                 service.setFlags(service.getFlags() | flag(ServiceInfo.FLAG_SYSTEM_USER_ONLY,
112                         R.styleable.AndroidManifestService_systemUserOnly, sa));
113             }
114 
115             visibleToEphemeral = sa.getBoolean(
116                     R.styleable.AndroidManifestService_visibleToInstantApps, false);
117             if (visibleToEphemeral) {
118                 service.setFlags(service.getFlags() | ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP);
119                 pkg.setVisibleToInstantApps(true);
120             }
121         } finally {
122             sa.recycle();
123         }
124 
125         if (pkg.isSaveStateDisallowed()) {
126             // A heavy-weight application can not have services in its main process
127             // We can do direct compare because we intern all strings.
128             if (Objects.equals(service.getProcessName(), packageName)) {
129                 return input.error("Heavy-weight applications can not have services "
130                         + "in main process");
131             }
132         }
133         final int depth = parser.getDepth();
134         int type;
135         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
136                 && (type != XmlPullParser.END_TAG
137                 || parser.getDepth() > depth)) {
138             if (type != XmlPullParser.START_TAG) {
139                 continue;
140             }
141             if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
142                 continue;
143             }
144 
145             final ParseResult parseResult;
146             switch (parser.getName()) {
147                 case "intent-filter":
148                     ParseResult<ParsedIntentInfoImpl> intentResult = ParsedMainComponentUtils
149                             .parseIntentFilter(service, pkg, res, parser, visibleToEphemeral,
150                                     true /*allowGlobs*/, false /*allowAutoVerify*/,
151                                     false /*allowImplicitEphemeralVisibility*/,
152                                     false /*failOnNoActions*/, input);
153                     parseResult = intentResult;
154                     if (intentResult.isSuccess()) {
155                         ParsedIntentInfoImpl intent = intentResult.getResult();
156                         IntentFilter intentFilter = intent.getIntentFilter();
157                         service.setOrder(Math.max(intentFilter.getOrder(), service.getOrder()));
158                         service.addIntent(intent);
159                     }
160                     break;
161                 case "meta-data":
162                     parseResult = ParsedComponentUtils.addMetaData(service, pkg, res, parser, input);
163                     break;
164                 case "property":
165                     parseResult =
166                             ParsedComponentUtils.addProperty(service, pkg, res, parser, input);
167                     break;
168                 default:
169                     parseResult = ParsingUtils.unknownTag(tag, pkg, parser, input);
170                     break;
171             }
172 
173             if (parseResult.isError()) {
174                 return input.error(parseResult);
175             }
176         }
177 
178         if (!setExported) {
179             boolean hasIntentFilters = service.getIntents().size() > 0;
180             if (hasIntentFilters) {
181                 final ParseResult exportedCheckResult = input.deferError(
182                         service.getName() + ": Targeting S+ (version " + Build.VERSION_CODES.S
183                         + " and above) requires that an explicit value for android:exported be"
184                         + " defined when intent filters are present",
185                         DeferredError.MISSING_EXPORTED_FLAG);
186                 if (exportedCheckResult.isError()) {
187                     return input.error(exportedCheckResult);
188                 }
189             }
190             service.setExported(hasIntentFilters);
191         }
192 
193         return input.success(service);
194     }
195 }
196