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