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