/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.healthconnect.migration;
import static com.android.server.healthconnect.migration.MigrationConstants.HC_PACKAGE_NAME_CONFIG_NAME;
import static com.android.server.healthconnect.migration.MigrationUtils.filterIntent;
import static com.android.server.healthconnect.migration.MigrationUtils.filterPermissions;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.health.connect.Constants;
import android.health.connect.HealthConnectManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
import java.util.List;
import java.util.Objects;
/**
* This class contains methods to:
*
*
* - Filter and get the package names of migration aware apps present on the device. Migration
* aware apps are those that both hold {@link
* android.Manifest.permission#MIGRATE_HEALTH_CONNECT_DATA} and handle {@link
* android.health.connect.HealthConnectManager#ACTION_SHOW_MIGRATION_INFO}.
*
- Send an explicit broadcast with action {@link
* android.health.connect.HealthConnectManager#ACTION_HEALTH_CONNECT_MIGRATION_READY} to
* migration aware apps to prompt them to start/continue HC data migration.
*
*
* @hide
*/
public class MigrationBroadcast {
private static final String TAG = "HealthConnectMigrationBroadcast";
private final Context mContext;
private final UserHandle mUser;
/**
* Constructs a {@link MigrationBroadcast} object.
*
* @param context the service context.
* @param user the user to send the broadcasts to.
*/
public MigrationBroadcast(Context context, UserHandle user) {
mContext = context;
mUser = user;
}
/**
* Sends a broadcast with action {@link
* android.health.connect.HealthConnectManager#ACTION_HEALTH_CONNECT_MIGRATION_READY} to
* applications which hold {@link android.Manifest.permission#MIGRATE_HEALTH_CONNECT_DATA} and
* handle {@link android.health.connect.HealthConnectManager#ACTION_SHOW_MIGRATION_INFO}.
*/
public void sendInvocationBroadcast() throws Exception {
Slog.i(TAG, "Calling sendInvocationBroadcast()");
String hcMigratorPackage =
mContext.getResources()
.getString(
Resources.getSystem()
.getIdentifier(HC_PACKAGE_NAME_CONFIG_NAME, null, null));
String migrationAwarePackage;
List permissionFilteredPackages = filterPermissions(mContext);
List filteredPackages = filterIntent(mContext, permissionFilteredPackages);
int numPackages = filteredPackages.size();
if (numPackages == 0) {
if (Constants.DEBUG) {
Slog.d(TAG, "There are no migration aware apps");
}
return;
} else if (numPackages == 1) {
if (Objects.equals(hcMigratorPackage, filteredPackages.get(0))) {
migrationAwarePackage = filteredPackages.get(0);
} else {
throw new Exception("Migration aware app is not Health Connect");
}
} else {
if (filteredPackages.contains(hcMigratorPackage)) {
migrationAwarePackage = hcMigratorPackage;
} else {
throw new Exception("Multiple packages are migration aware");
}
}
if (Constants.DEBUG) {
Slog.d(TAG, "Checking if migration aware package is installed on user");
}
Context userContext = mContext.createContextAsUser(mUser, 0);
if (isPackageInstalled(migrationAwarePackage, userContext)) {
UserManager userManager =
Objects.requireNonNull(userContext.getSystemService(UserManager.class));
if (userManager.isUserForeground()) {
Intent intent =
new Intent(HealthConnectManager.ACTION_HEALTH_CONNECT_MIGRATION_READY)
.setPackage(migrationAwarePackage);
queryAndSetComponentForIntent(intent);
mContext.sendBroadcastAsUser(intent, mUser);
if (Constants.DEBUG) {
Slog.d(TAG, "Sent broadcast to migration aware application.");
}
} else if (Constants.DEBUG) {
Slog.d(TAG, "User " + mUser + " is not currently active");
}
} else if (Constants.DEBUG) {
Slog.d(TAG, "Migration aware app is not installed on the current user");
}
}
/** Checks if the package is installed on the given user. */
private boolean isPackageInstalled(String packageName, Context userContext) {
try {
PackageManager packageManager = userContext.getPackageManager();
packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0));
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
/**
* Sets the component to send the migration ready intent to, if only one such receiver is found.
*
* This is needed to send an explicit broadcast containing an explicit intent which has the
* target component specified.
*
* @param intent Intent which has the package set, for which the broadcast receiver is to be
* queried.
* @throws Exception if multiple broadcast receivers are found for the migration ready intent.
*/
private void queryAndSetComponentForIntent(Intent intent) throws Exception {
List queryResults =
mContext.getPackageManager()
.queryBroadcastReceiversAsUser(
intent,
PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL),
mUser);
int numReceivers = queryResults.size();
if (numReceivers == 0 && Constants.DEBUG) {
Slog.d(TAG, "Found no broadcast receivers for the migration broadcast intent");
} else if (numReceivers == 1) {
ResolveInfo queryResult = queryResults.get(0);
if (queryResult.activityInfo != null) {
ComponentName componentName =
new ComponentName(
queryResult.activityInfo.packageName,
queryResult.activityInfo.name);
intent.setComponent(componentName);
} else if (Constants.DEBUG) {
Slog.d(TAG, "Found no corresponding broadcast receiver for intent resolution");
}
} else if (numReceivers > 1 && Constants.DEBUG) {
Slog.d(TAG, "Found multiple broadcast receivers for migration broadcast intent");
}
}
}