1 /*
2  * Copyright (C) 2010 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.nfc;
18 
19 import static android.Manifest.permission.BIND_NFC_SERVICE;
20 import static android.Manifest.permission.NFC;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.app.PendingIntent;
25 import android.app.PendingIntentProto;
26 import android.content.AuthorityEntryProto;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.IntentFilterProto;
31 import android.content.pm.PackageInfo;
32 import android.content.pm.PackageManager;
33 import android.net.Uri;
34 import android.os.PatternMatcher;
35 import android.os.PatternMatcherProto;
36 import android.os.UserHandle;
37 import android.text.TextUtils;
38 import android.util.Log;
39 import android.util.proto.ProtoOutputStream;
40 
41 import androidx.annotation.VisibleForTesting;
42 
43 import java.util.Arrays;
44 import java.util.Objects;
45 
46 public final class Utils {
47     private static final String TAG = "Utils";
Utils()48     private Utils() {
49     }
50 
51     /**
52      * Returns true if the given {@code array} contains the given element.
53      *
54      * @param array {@code array} to check for {@code elem}
55      * @param elem {@code elem} to test for
56      * @return {@code true} if the given element is contained
57      */
arrayContains(@ullable T[] array, T elem)58     public static <T> boolean arrayContains(@Nullable T[] array, T elem) {
59         if (array == null) {
60             return false;
61         }
62         for (int i = 0; i < array.length; i++) {
63             if (Objects.equals(array[i], elem)) {
64                 return true;
65             }
66         }
67         return false;
68     }
69 
70     /** Ported (this uses public API's as opposed to private fields used in the original impl from
71      * {@link IntentFilter#dumpDebug(ProtoOutputStream, long)} */
dumpDebugIntentFilter( @onNull IntentFilter intentFilter, @NonNull ProtoOutputStream proto, long fieldId)72     public static void dumpDebugIntentFilter(
73             @NonNull IntentFilter intentFilter, @NonNull ProtoOutputStream proto, long fieldId) {
74         long token = proto.start(fieldId);
75         for (int i = 0; i < intentFilter.countActions(); i++) {
76             proto.write(IntentFilterProto.ACTIONS, intentFilter.getAction(i));
77         }
78         for (int i = 0; i < intentFilter.countCategories(); i++) {
79             proto.write(IntentFilterProto.CATEGORIES, intentFilter.getCategory(i));
80         }
81         for (int i = 0; i < intentFilter.countDataSchemes(); i++) {
82             proto.write(IntentFilterProto.DATA_SCHEMES, intentFilter.getDataScheme(i));
83         }
84         for (int i = 0; i < intentFilter.countDataSchemeSpecificParts(); i++) {
85             dumpDebugPatternMatcher(
86                     intentFilter.getDataSchemeSpecificPart(i), proto,
87                     IntentFilterProto.DATA_SCHEME_SPECS);
88         }
89         for (int i = 0; i < intentFilter.countDataAuthorities(); i++) {
90             dumpDebugAuthorityEntry(
91                     intentFilter.getDataAuthority(i), proto,
92                     IntentFilterProto.DATA_AUTHORITIES);
93         }
94         for (int i = 0; i < intentFilter.countDataPaths(); i++) {
95             dumpDebugPatternMatcher(
96                     intentFilter.getDataPath(i), proto,
97                     IntentFilterProto.DATA_PATHS);
98         }
99         for (int i = 0; i < intentFilter.countDataTypes(); i++) {
100             proto.write(IntentFilterProto.DATA_TYPES, intentFilter.getDataType(i));
101         }
102         /*
103         for (int i = 0; i < intentFilter.countMimeGroups(); i++) {
104             proto.write(IntentFilterProto.MIME_GROUPS, intentFilter.getMimeGroup(i));
105         }*/
106 
107         if (intentFilter.getPriority() != 0
108                 /* || TODO(b/271463752): Get this info - hasPartialTypes() */) {
109             proto.write(IntentFilterProto.PRIORITY, intentFilter.getPriority());
110             proto.write(IntentFilterProto.HAS_PARTIAL_TYPES, false /* hasPartialTypes() */);
111         }
112         proto.write(IntentFilterProto.GET_AUTO_VERIFY, false /* intentFilter.getAutoVerify() */);
113         proto.end(token);
114     }
115 
116     /** Ported (this uses public API's as opposed to private fields used in the original impl from
117      * {@link PatternMatcher#dumpDebug(ProtoOutputStream, long)} */
dumpDebugPatternMatcher(@onNull PatternMatcher patternMatcher, @NonNull ProtoOutputStream proto, long fieldId)118     private static void dumpDebugPatternMatcher(@NonNull PatternMatcher patternMatcher,
119             @NonNull ProtoOutputStream proto, long fieldId) {
120         long token = proto.start(fieldId);
121         proto.write(PatternMatcherProto.PATTERN, patternMatcher.getPath());
122         proto.write(PatternMatcherProto.TYPE, patternMatcher.getType());
123         // PatternMatcherProto.PARSED_PATTERN is too much to dump, but the field is reserved to
124         // match the current data structure.
125         proto.end(token);
126     }
127 
128     /** Ported (this uses public API's as opposed to private fields used in the original impl from
129      * {@link IntentFilter.AuthorityEntry#dumpDebug(ProtoOutputStream, long)} */
dumpDebugAuthorityEntry( @onNull IntentFilter.AuthorityEntry authorityEntry, ProtoOutputStream proto, long fieldId)130     private static void dumpDebugAuthorityEntry(
131             @NonNull IntentFilter.AuthorityEntry authorityEntry, ProtoOutputStream proto,
132             long fieldId) {
133         long token = proto.start(fieldId);
134         // The public API's only give the orig host name. The fields in the proto are derived from
135         // this host info. {@see AuthorityEntry{String, String}
136         String origHost = authorityEntry.getHost();
137         boolean wild = origHost.length() > 0 && origHost.charAt(0) == '*';
138         String host = wild ? origHost.substring(1).intern() : origHost;
139         proto.write(AuthorityEntryProto.HOST, host);
140         proto.write(AuthorityEntryProto.WILD, wild);
141         proto.write(AuthorityEntryProto.PORT, authorityEntry.getPort());
142         proto.end(token);
143     }
144 
145     /** Ported (this uses public API's as opposed to private fields used in the original impl from
146      * {@link PendingIntent#dumpDebug(ProtoOutputStream, long)} */
dumpDebugPendingIntent( @onNull PendingIntent pendingIntent, @NonNull ProtoOutputStream proto, long fieldId)147     public static void dumpDebugPendingIntent(
148             @NonNull PendingIntent pendingIntent, @NonNull ProtoOutputStream proto, long fieldId) {
149         final long token = proto.start(fieldId);
150         // TODO: This is not exactly the same format as the original impl. But, it still prints
151         // the target info.
152         proto.write(PendingIntentProto.TARGET, pendingIntent.toString());
153         proto.end(token);
154     }
155 
156     @Nullable
157     @VisibleForTesting
getPackageNameFromIntent(Intent intent)158     public static String getPackageNameFromIntent(Intent intent) {
159         Uri uri = intent.getData();
160         if (!Objects.equals(uri.getScheme(), "package")) return null;
161         return uri.getSchemeSpecificPart();
162     }
163 
hasCeServicesWithValidPermissions( Context context, Intent pkgChangeIntent, int userId)164     public static boolean hasCeServicesWithValidPermissions(
165             Context context, Intent pkgChangeIntent, int userId) {
166         String pkgName = Utils.getPackageNameFromIntent(pkgChangeIntent);
167         if (pkgName == null) return true; // Cannot figure out pkg name, assume it's valid.
168         PackageInfo packageInfo = null;
169         try {
170             PackageManager pm =
171                     context.createPackageContextAsUser("android", 0, UserHandle.of(userId))
172                     .getPackageManager();
173             if (pm.checkPermission(NFC, pkgName) != PackageManager.PERMISSION_GRANTED) {
174                 return false;
175             }
176             packageInfo = pm.getPackageInfo(pkgName,
177                     (PackageManager.GET_PERMISSIONS
178                             | PackageManager.GET_SERVICES
179                             | PackageManager.MATCH_DISABLED_COMPONENTS));
180             if (packageInfo == null) return false;
181             return Arrays.stream(packageInfo.services).map(serviceInfo->serviceInfo.permission)
182                     .filter(permission-> TextUtils.equals(permission, BIND_NFC_SERVICE ))
183                     .findFirst().isPresent();
184         } catch (PackageManager.NameNotFoundException | NullPointerException e) {
185             Log.e(TAG, "Could not create user package context" + packageInfo);
186             return false;
187         }
188     }
189 
maskSubstring(String original, int start)190     public static String maskSubstring(String original, int start) {
191         if (original == null) {
192             return "";
193         }
194         if (original.length() <= start) {
195             return original;
196         }
197         StringBuilder masked = new StringBuilder(original);
198         for (int i = start; i < original.length(); i++) {
199             masked.setCharAt(i, '*');
200         }
201         return masked.toString();
202     }
203 }
204