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.role.controller.model;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ResolveInfo;
25 import android.os.UserHandle;
26 
27 import androidx.annotation.NonNull;
28 
29 import com.android.role.controller.util.UserUtils;
30 
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Objects;
34 
35 /**
36  * Specifies a preferred {@code Activity} configuration to be configured by a {@link Role}.
37  */
38 public class PreferredActivity {
39 
40     /**
41      * The specification of the {@code Activity} to be preferred.
42      */
43     @NonNull
44     private final RequiredActivity mActivity;
45 
46     /**
47      * The list of {@code IntentFilter} specifications to be configured to prefer this
48      * {@code Activity}.
49      */
50     @NonNull
51     private final List<IntentFilterData> mIntentFilterDatas;
52 
PreferredActivity(@onNull RequiredActivity activity, @NonNull List<IntentFilterData> intentFilterDatas)53     public PreferredActivity(@NonNull RequiredActivity activity,
54             @NonNull List<IntentFilterData> intentFilterDatas) {
55         mActivity = activity;
56         mIntentFilterDatas = intentFilterDatas;
57     }
58 
59     @NonNull
getActivity()60     public RequiredActivity getActivity() {
61         return mActivity;
62     }
63 
64     @NonNull
getIntentFilterDatas()65     public List<IntentFilterData> getIntentFilterDatas() {
66         return mIntentFilterDatas;
67     }
68 
69     /**
70      * Configure this preferred activity specification for an application.
71      *
72      * @param packageName the package name of the application
73      * @param user the user of the application
74      * @param context the {@code Context} to retrieve system services
75      */
configureAsUser(@onNull String packageName, @NonNull UserHandle user, @NonNull Context context)76     public void configureAsUser(@NonNull String packageName, @NonNull UserHandle user,
77             @NonNull Context context) {
78         ComponentName packageActivity = mActivity.getQualifyingComponentForPackageAsUser(
79                 packageName, user, context);
80         if (packageActivity == null) {
81             // We might be running into some race condition here, but we can't do anything about it.
82             // This should be handled by a future reconciliation started by the package change.
83             return;
84         }
85 
86         Context userContext = UserUtils.getUserContext(context, user);
87         PackageManager userPackageManager = userContext.getPackageManager();
88         int intentFilterDatasSize = mIntentFilterDatas.size();
89         for (int i = 0; i < intentFilterDatasSize; i++) {
90             IntentFilterData intentFilterData = mIntentFilterDatas.get(i);
91 
92             IntentFilter intentFilter = intentFilterData.createIntentFilter();
93             intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
94 
95             // PackageManager.replacePreferredActivity() expects filter to have no data authorities,
96             // paths, or types; and at most one scheme.
97             int match = intentFilterData.getDataScheme() != null
98                     ? IntentFilter.MATCH_CATEGORY_SCHEME : IntentFilter.MATCH_CATEGORY_EMPTY;
99 
100             Intent intent = intentFilterData.createIntent();
101             List<ResolveInfo> resolveInfos = userPackageManager.queryIntentActivities(intent,
102                     PackageManager.MATCH_DIRECT_BOOT_AWARE
103                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
104                             | PackageManager.MATCH_DEFAULT_ONLY);
105             List<ComponentName> set = new ArrayList<>();
106             int resolveInfosSize = resolveInfos.size();
107             for (int resolveInfosIndex = 0; resolveInfosIndex < resolveInfosSize;
108                     resolveInfosIndex++) {
109                 ResolveInfo resolveInfo = resolveInfos.get(resolveInfosIndex);
110 
111                 ComponentName componentName = new ComponentName(
112                         resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
113                 set.add(componentName);
114             }
115 
116             userPackageManager.replacePreferredActivity(intentFilter, match, set, packageActivity);
117         }
118     }
119 
120     @Override
toString()121     public String toString() {
122         return "PreferredActivity{"
123                 + "mActivity=" + mActivity
124                 + ", mIntentFilterDatas=" + mIntentFilterDatas
125                 + '}';
126     }
127 
128     @Override
equals(Object object)129     public boolean equals(Object object) {
130         if (this == object) {
131             return true;
132         }
133         if (object == null || getClass() != object.getClass()) {
134             return false;
135         }
136         PreferredActivity that = (PreferredActivity) object;
137         return Objects.equals(mActivity, that.mActivity)
138                 && Objects.equals(mIntentFilterDatas, that.mIntentFilterDatas);
139     }
140 
141     @Override
hashCode()142     public int hashCode() {
143         return Objects.hash(mActivity, mIntentFilterDatas);
144     }
145 }
146