1 /*
2  * Copyright (C) 2018 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.permissioncontroller.role.model;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.content.pm.ResolveInfo;
24 import android.os.Process;
25 import android.os.UserHandle;
26 import android.util.ArraySet;
27 
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Nullable;
30 
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Objects;
34 
35 /**
36  * Specifies a required component for an application to qualify for a {@link Role}.
37  */
38 public abstract class RequiredComponent {
39 
40     /**
41      * The {@code Intent} or {@code IntentFilter} data to match the components.
42      */
43     @NonNull
44     private final IntentFilterData mIntentFilterData;
45 
46     /**
47      * Optional permission required on a component for match to succeed.
48      *
49      * @see android.content.pm.ActivityInfo#permission
50      * @see android.content.pm.ServiceInfo#permission
51      */
52     @Nullable
53     private final String mPermission;
54 
RequiredComponent(@onNull IntentFilterData intentFilterData, @Nullable String permission)55     public RequiredComponent(@NonNull IntentFilterData intentFilterData,
56             @Nullable String permission) {
57         mIntentFilterData = intentFilterData;
58         mPermission = permission;
59     }
60 
61     @NonNull
getIntentFilterData()62     public IntentFilterData getIntentFilterData() {
63         return mIntentFilterData;
64     }
65 
66     @Nullable
getPermission()67     public String getPermission() {
68         return mPermission;
69     }
70 
71     /**
72      * Get the component that matches this required component within a package, if any.
73      *
74      * @param packageName the package name for this query
75      * @param context the {@code Context} to retrieve system services
76      *
77      * @return the matching component, or {@code null} if none.
78      */
79     @Nullable
getQualifyingComponentForPackage(@onNull String packageName, @NonNull Context context)80     public ComponentName getQualifyingComponentForPackage(@NonNull String packageName,
81             @NonNull Context context) {
82         List<ComponentName> componentNames = getQualifyingComponentsInternal(packageName,
83                 Process.myUserHandle(), context);
84         return !componentNames.isEmpty() ? componentNames.get(0) : null;
85     }
86 
87     /**
88      * Get the list of components that match this required component, <b>at most one component per
89      * package</b> and ordered from best to worst.
90      *
91      * @param user the user to get the qualifying components.
92      * @param context the {@code Context} to retrieve system services
93      *
94      * @return the list of matching components
95      *
96      * @see Role#getQualifyingPackagesAsUser(UserHandle, Context)
97      */
98     @NonNull
getQualifyingComponentsAsUser(@onNull UserHandle user, @NonNull Context context)99     public List<ComponentName> getQualifyingComponentsAsUser(@NonNull UserHandle user,
100             @NonNull Context context) {
101         return getQualifyingComponentsInternal(null, user, context);
102     }
103 
104     @NonNull
getQualifyingComponentsInternal(@ullable String packageName, @NonNull UserHandle user, @NonNull Context context)105     private List<ComponentName> getQualifyingComponentsInternal(@Nullable String packageName,
106             @NonNull UserHandle user, @NonNull Context context) {
107         Intent intent = mIntentFilterData.createIntent();
108         if (packageName != null) {
109             intent.setPackage(packageName);
110         }
111         List<ResolveInfo> resolveInfos = queryIntentComponentsAsUser(intent,
112                 PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
113                 user, context);
114 
115         ArraySet<String> componentPackageNames = new ArraySet<>();
116         List<ComponentName> componentNames = new ArrayList<>();
117         int resolveInfosSize = resolveInfos.size();
118         for (int resolveInfosIndex = 0; resolveInfosIndex < resolveInfosSize; resolveInfosIndex++) {
119             ResolveInfo resolveInfo = resolveInfos.get(resolveInfosIndex);
120 
121             if (mPermission != null) {
122                 String componentPermission = getComponentPermission(resolveInfo);
123                 if (!Objects.equals(componentPermission, mPermission)) {
124                     continue;
125                 }
126             }
127 
128             ComponentName componentName = getComponentComponentName(resolveInfo);
129             String componentPackageName = componentName.getPackageName();
130             if (componentPackageNames.contains(componentPackageName)) {
131                 continue;
132             }
133 
134             componentPackageNames.add(componentPackageName);
135             componentNames.add(componentName);
136         }
137         return componentNames;
138     }
139 
140     /**
141      * Query the {@code PackageManager} for components matching an {@code Intent}, ordered from best
142      * to worst.
143      *
144      * @param intent the {@code Intent} to match against
145      * @param flags the flags for this query
146      * @param user the user for this query
147      * @param context the {@code Context} to retrieve system services
148      *
149      * @return the list of matching components
150      */
151     @NonNull
queryIntentComponentsAsUser(@onNull Intent intent, int flags, @NonNull UserHandle user, @NonNull Context context)152     protected abstract List<ResolveInfo> queryIntentComponentsAsUser(@NonNull Intent intent,
153             int flags, @NonNull UserHandle user, @NonNull Context context);
154 
155     /**
156      * Get the {@code ComponentName} of a component.
157      *
158      * @param resolveInfo the {@code ResolveInfo} of the component
159      *
160      * @return the {@code ComponentName} of the component
161      */
162     @NonNull
getComponentComponentName(@onNull ResolveInfo resolveInfo)163     protected abstract ComponentName getComponentComponentName(@NonNull ResolveInfo resolveInfo);
164 
165     /**
166      * Get the permission required to access a component.
167      *
168      * @param resolveInfo the {@code ResolveInfo} of the component
169      *
170      * @return the permission required to access a component
171      */
172     @Nullable
getComponentPermission(@onNull ResolveInfo resolveInfo)173     protected abstract String getComponentPermission(@NonNull ResolveInfo resolveInfo);
174 
175     @Override
toString()176     public String toString() {
177         return "RequiredComponent{"
178                 + "mIntentFilterData=" + mIntentFilterData
179                 + ", mPermission='" + mPermission + '\''
180                 + '}';
181     }
182 
183     @Override
equals(Object object)184     public boolean equals(Object object) {
185         if (this == object) {
186             return true;
187         }
188         if (object == null || getClass() != object.getClass()) {
189             return false;
190         }
191         RequiredComponent that = (RequiredComponent) object;
192         return Objects.equals(mIntentFilterData, that.mIntentFilterData)
193                 && Objects.equals(mPermission, that.mPermission);
194     }
195 
196     @Override
hashCode()197     public int hashCode() {
198         return Objects.hash(mIntentFilterData, mPermission);
199     }
200 }
201