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.server.pm.pkg.component;
18 
19 import static com.android.server.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.os.Build;
33 
34 import com.android.internal.R;
35 import com.android.server.pm.pkg.parsing.ParsingPackage;
36 import com.android.server.pm.pkg.parsing.ParsingUtils;
37 
38 import org.xmlpull.v1.XmlPullParser;
39 import org.xmlpull.v1.XmlPullParserException;
40 
41 import java.io.IOException;
42 import java.util.Objects;
43 
44 /** @hide */
45 public class ParsedServiceUtils {
46 
47     @NonNull
parseService(String[] separateProcesses, ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags, boolean useRoundIcon, @Nullable String defaultSplitName, @NonNull ParseInput input)48     public static ParseResult<ParsedService> parseService(String[] separateProcesses,
49             ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
50             boolean useRoundIcon, @Nullable String defaultSplitName, @NonNull ParseInput input)
51             throws XmlPullParserException, IOException {
52         boolean visibleToEphemeral;
53         boolean setExported;
54 
55         final String packageName = pkg.getPackageName();
56         final ParsedServiceImpl service = new ParsedServiceImpl();
57         String tag = parser.getName();
58 
59         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestService);
60         try {
61             ParseResult<ParsedServiceImpl> result = ParsedMainComponentUtils.parseMainComponent(
62                     service, tag, separateProcesses, pkg, sa, flags, useRoundIcon, defaultSplitName,
63                     input,
64                     R.styleable.AndroidManifestService_banner,
65                     R.styleable.AndroidManifestService_description,
66                     R.styleable.AndroidManifestService_directBootAware,
67                     R.styleable.AndroidManifestService_enabled,
68                     R.styleable.AndroidManifestService_icon,
69                     R.styleable.AndroidManifestService_label,
70                     R.styleable.AndroidManifestService_logo,
71                     R.styleable.AndroidManifestService_name,
72                     R.styleable.AndroidManifestService_process,
73                     R.styleable.AndroidManifestService_roundIcon,
74                     R.styleable.AndroidManifestService_splitName,
75                     R.styleable.AndroidManifestService_attributionTags
76             );
77 
78             if (result.isError()) {
79                 return input.error(result);
80             }
81 
82             setExported = sa.hasValue(R.styleable.AndroidManifestService_exported);
83             if (setExported) {
84                 service.setExported(sa.getBoolean(R.styleable.AndroidManifestService_exported,
85                         false));
86             }
87 
88             String permission = sa.getNonConfigurationString(
89                     R.styleable.AndroidManifestService_permission, 0);
90             service.setPermission(permission != null ? permission : pkg.getPermission());
91 
92             service.setForegroundServiceType(sa.getInt(
93                     R.styleable.AndroidManifestService_foregroundServiceType,
94                     ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE))
95                     .setFlags(service.getFlags() | (flag(ServiceInfo.FLAG_STOP_WITH_TASK,
96                             R.styleable.AndroidManifestService_stopWithTask, sa)
97                             | flag(ServiceInfo.FLAG_ISOLATED_PROCESS,
98                             R.styleable.AndroidManifestService_isolatedProcess, sa)
99                             | flag(ServiceInfo.FLAG_EXTERNAL_SERVICE,
100                             R.styleable.AndroidManifestService_externalService, sa)
101                             | flag(ServiceInfo.FLAG_USE_APP_ZYGOTE,
102                             R.styleable.AndroidManifestService_useAppZygote, sa)
103                             | flag(ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS,
104                             R.styleable.AndroidManifestService_allowSharedIsolatedProcess, sa)
105                             | flag(ServiceInfo.FLAG_SINGLE_USER,
106                             R.styleable.AndroidManifestService_singleUser, sa)));
107 
108             visibleToEphemeral = sa.getBoolean(
109                     R.styleable.AndroidManifestService_visibleToInstantApps, false);
110             if (visibleToEphemeral) {
111                 service.setFlags(service.getFlags() | ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP);
112                 pkg.setVisibleToInstantApps(true);
113             }
114         } finally {
115             sa.recycle();
116         }
117 
118         if (pkg.isSaveStateDisallowed()) {
119             // A heavy-weight application can not have services in its main process
120             // We can do direct compare because we intern all strings.
121             if (Objects.equals(service.getProcessName(), packageName)) {
122                 return input.error("Heavy-weight applications can not have services "
123                         + "in main process");
124             }
125         }
126         final int depth = parser.getDepth();
127         int type;
128         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
129                 && (type != XmlPullParser.END_TAG
130                 || parser.getDepth() > depth)) {
131             if (type != XmlPullParser.START_TAG) {
132                 continue;
133             }
134 
135             final ParseResult parseResult;
136             switch (parser.getName()) {
137                 case "intent-filter":
138                     ParseResult<ParsedIntentInfoImpl> intentResult = ParsedMainComponentUtils
139                             .parseIntentFilter(service, pkg, res, parser, visibleToEphemeral,
140                                     true /*allowGlobs*/, false /*allowAutoVerify*/,
141                                     false /*allowImplicitEphemeralVisibility*/,
142                                     false /*failOnNoActions*/, input);
143                     parseResult = intentResult;
144                     if (intentResult.isSuccess()) {
145                         ParsedIntentInfoImpl intent = intentResult.getResult();
146                         IntentFilter intentFilter = intent.getIntentFilter();
147                         service.setOrder(Math.max(intentFilter.getOrder(), service.getOrder()));
148                         service.addIntent(intent);
149                     }
150                     break;
151                 case "meta-data":
152                     parseResult = ParsedComponentUtils.addMetaData(service, pkg, res, parser, input);
153                     break;
154                 case "property":
155                     parseResult =
156                             ParsedComponentUtils.addProperty(service, pkg, res, parser, input);
157                     break;
158                 default:
159                     parseResult = ParsingUtils.unknownTag(tag, pkg, parser, input);
160                     break;
161             }
162 
163             if (parseResult.isError()) {
164                 return input.error(parseResult);
165             }
166         }
167 
168         if (!setExported) {
169             boolean hasIntentFilters = service.getIntents().size() > 0;
170             if (hasIntentFilters) {
171                 final ParseResult exportedCheckResult = input.deferError(
172                         service.getName() + ": Targeting S+ (version " + Build.VERSION_CODES.S
173                         + " and above) requires that an explicit value for android:exported be"
174                         + " defined when intent filters are present",
175                         DeferredError.MISSING_EXPORTED_FLAG);
176                 if (exportedCheckResult.isError()) {
177                     return input.error(exportedCheckResult);
178                 }
179             }
180             service.setExported(hasIntentFilters);
181         }
182 
183         return input.success(service);
184     }
185 }
186