• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.healthconnect.migration;
18 
19 import static com.android.server.healthconnect.migration.MigrationConstants.HC_PACKAGE_NAME_CONFIG_NAME;
20 import static com.android.server.healthconnect.migration.MigrationUtils.filterIntent;
21 import static com.android.server.healthconnect.migration.MigrationUtils.filterPermissions;
22 
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ResolveInfo;
28 import android.content.res.Resources;
29 import android.health.connect.Constants;
30 import android.health.connect.HealthConnectManager;
31 import android.os.UserHandle;
32 import android.os.UserManager;
33 import android.util.Slog;
34 
35 import java.util.List;
36 import java.util.Objects;
37 
38 /**
39  * This class contains methods to:
40  *
41  * <ul>
42  *   <li>Filter and get the package names of migration aware apps present on the device. Migration
43  *       aware apps are those that both hold {@link
44  *       android.Manifest.permission#MIGRATE_HEALTH_CONNECT_DATA} and handle {@link
45  *       android.health.connect.HealthConnectManager#ACTION_SHOW_MIGRATION_INFO}.
46  *   <li>Send an explicit broadcast with action {@link
47  *       android.health.connect.HealthConnectManager#ACTION_HEALTH_CONNECT_MIGRATION_READY} to
48  *       migration aware apps to prompt them to start/continue HC data migration.
49  * </ul>
50  *
51  * @hide
52  */
53 public class MigrationBroadcast {
54 
55     private static final String TAG = "HealthConnectMigrationBroadcast";
56     private final Context mContext;
57     private final UserHandle mUser;
58 
59     /**
60      * Constructs a {@link MigrationBroadcast} object.
61      *
62      * @param context the service context.
63      * @param user the user to send the broadcasts to.
64      */
MigrationBroadcast(Context context, UserHandle user)65     public MigrationBroadcast(Context context, UserHandle user) {
66         mContext = context;
67         mUser = user;
68     }
69 
70     /**
71      * Sends a broadcast with action {@link
72      * android.health.connect.HealthConnectManager#ACTION_HEALTH_CONNECT_MIGRATION_READY} to
73      * applications which hold {@link android.Manifest.permission#MIGRATE_HEALTH_CONNECT_DATA} and
74      * handle {@link android.health.connect.HealthConnectManager#ACTION_SHOW_MIGRATION_INFO}.
75      */
sendInvocationBroadcast()76     public void sendInvocationBroadcast() throws Exception {
77         Slog.i(TAG, "Calling sendInvocationBroadcast()");
78 
79         String hcMigratorPackage =
80                 mContext.getResources()
81                         .getString(
82                                 Resources.getSystem()
83                                         .getIdentifier(HC_PACKAGE_NAME_CONFIG_NAME, null, null));
84         String migrationAwarePackage;
85 
86         List<String> permissionFilteredPackages = filterPermissions(mContext);
87         List<String> filteredPackages = filterIntent(mContext, permissionFilteredPackages);
88 
89         int numPackages = filteredPackages.size();
90 
91         if (numPackages == 0) {
92             if (Constants.DEBUG) {
93                 Slog.d(TAG, "There are no migration aware apps");
94             }
95             return;
96         } else if (numPackages == 1) {
97             if (Objects.equals(hcMigratorPackage, filteredPackages.get(0))) {
98                 migrationAwarePackage = filteredPackages.get(0);
99             } else {
100                 throw new Exception("Migration aware app is not Health Connect");
101             }
102         } else {
103             if (filteredPackages.contains(hcMigratorPackage)) {
104                 migrationAwarePackage = hcMigratorPackage;
105             } else {
106                 throw new Exception("Multiple packages are migration aware");
107             }
108         }
109 
110         if (Constants.DEBUG) {
111             Slog.d(TAG, "Checking if migration aware package is installed on user");
112         }
113 
114         Context userContext = mContext.createContextAsUser(mUser, 0);
115         if (isPackageInstalled(migrationAwarePackage, userContext)) {
116             UserManager userManager =
117                     Objects.requireNonNull(userContext.getSystemService(UserManager.class));
118             if (userManager.isUserForeground()) {
119                 Intent intent =
120                         new Intent(HealthConnectManager.ACTION_HEALTH_CONNECT_MIGRATION_READY)
121                                 .setPackage(migrationAwarePackage);
122 
123                 queryAndSetComponentForIntent(intent);
124 
125                 mContext.sendBroadcastAsUser(intent, mUser);
126                 if (Constants.DEBUG) {
127                     Slog.d(TAG, "Sent broadcast to migration aware application.");
128                 }
129             } else if (Constants.DEBUG) {
130                 Slog.d(TAG, "User " + mUser + " is not currently active");
131             }
132         } else if (Constants.DEBUG) {
133             Slog.d(TAG, "Migration aware app is not installed on the current user");
134         }
135     }
136 
137     /** Checks if the package is installed on the given user. */
isPackageInstalled(String packageName, Context userContext)138     private boolean isPackageInstalled(String packageName, Context userContext) {
139         try {
140             PackageManager packageManager = userContext.getPackageManager();
141             packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0));
142             return true;
143         } catch (PackageManager.NameNotFoundException e) {
144             return false;
145         }
146     }
147 
148     /**
149      * Sets the component to send the migration ready intent to, if only one such receiver is found.
150      *
151      * <p>This is needed to send an explicit broadcast containing an explicit intent which has the
152      * target component specified.
153      *
154      * @param intent Intent which has the package set, for which the broadcast receiver is to be
155      *     queried.
156      * @throws Exception if multiple broadcast receivers are found for the migration ready intent.
157      */
queryAndSetComponentForIntent(Intent intent)158     private void queryAndSetComponentForIntent(Intent intent) throws Exception {
159         List<ResolveInfo> queryResults =
160                 mContext.getPackageManager()
161                         .queryBroadcastReceiversAsUser(
162                                 intent,
163                                 PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL),
164                                 mUser);
165 
166         int numReceivers = queryResults.size();
167 
168         if (numReceivers == 0 && Constants.DEBUG) {
169             Slog.d(TAG, "Found no broadcast receivers for the migration broadcast intent");
170         } else if (numReceivers == 1) {
171             ResolveInfo queryResult = queryResults.get(0);
172             if (queryResult.activityInfo != null) {
173                 ComponentName componentName =
174                         new ComponentName(
175                                 queryResult.activityInfo.packageName,
176                                 queryResult.activityInfo.name);
177                 intent.setComponent(componentName);
178             } else if (Constants.DEBUG) {
179                 Slog.d(TAG, "Found no corresponding broadcast receiver for intent resolution");
180             }
181         } else if (numReceivers > 1 && Constants.DEBUG) {
182             Slog.d(TAG, "Found multiple broadcast receivers for migration broadcast intent");
183         }
184     }
185 }
186