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