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 android.Manifest;
20 import android.annotation.NonNull;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.PackageInfo;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.health.connect.Constants;
27 import android.health.connect.HealthConnectManager;
28 import android.util.Slog;
29 
30 import libcore.util.HexEncoding;
31 
32 import java.security.MessageDigest;
33 import java.security.NoSuchAlgorithmException;
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.stream.Collectors;
37 
38 /** @hide */
39 public class MigrationUtils {
40     private static final String TAG = "HealthConnectMigrationUtils";
41 
42     /**
43      * Filters and returns the package names of applications which hold permission {@link
44      * android.Manifest.permission#MIGRATE_HEALTH_CONNECT_DATA}.
45      *
46      * @return List of filtered app package names which hold the specified permission
47      */
48     @NonNull
filterPermissions(@onNull Context context)49     public static List<String> filterPermissions(@NonNull Context context) {
50         if (android.health.connect.Constants.DEBUG) {
51             Slog.d(TAG, "Calling filterPermissions()");
52         }
53 
54         String[] permissions = new String[] {Manifest.permission.MIGRATE_HEALTH_CONNECT_DATA};
55 
56         List<PackageInfo> packageInfos =
57                 context.getPackageManager()
58                         .getPackagesHoldingPermissions(
59                                 permissions, PackageManager.PackageInfoFlags.of(0));
60 
61         List<String> permissionFilteredPackages =
62                 packageInfos.stream().map(info -> info.packageName).collect(Collectors.toList());
63 
64         if (android.health.connect.Constants.DEBUG) {
65             Slog.d(TAG, "permissionFilteredPackages : " + permissionFilteredPackages);
66         }
67         return permissionFilteredPackages;
68     }
69 
70     /**
71      * Filters and returns the package names of applications which handle intent {@link
72      * android.health.connect.HealthConnectManager#ACTION_SHOW_MIGRATION_INFO}.
73      *
74      * @param permissionFilteredPackages List of app package names holding permission {@link
75      *     android.Manifest.permission#MIGRATE_HEALTH_CONNECT_DATA}
76      * @return List of filtered app package names which handle the specified intent action
77      */
78     @NonNull
filterIntent( @onNull Context context, @NonNull List<String> permissionFilteredPackages)79     public static List<String> filterIntent(
80             @NonNull Context context, @NonNull List<String> permissionFilteredPackages) {
81         return filterIntent(context, permissionFilteredPackages, PackageManager.MATCH_ALL);
82     }
83 
84     /**
85      * Filters and returns the package names of applications which handle intent {@link
86      * android.health.connect.HealthConnectManager#ACTION_SHOW_MIGRATION_INFO}.
87      *
88      * @param permissionFilteredPackages List of app package names holding permission {@link
89      *     android.Manifest.permission#MIGRATE_HEALTH_CONNECT_DATA}
90      * @param flags Additional option flags to modify the data returned.
91      * @return List of filtered app package names which handle the specified intent action
92      */
93     @NonNull
filterIntent( @onNull Context context, @NonNull List<String> permissionFilteredPackages, int flags)94     public static List<String> filterIntent(
95             @NonNull Context context, @NonNull List<String> permissionFilteredPackages, int flags) {
96         if (android.health.connect.Constants.DEBUG) {
97             Slog.d(TAG, "Calling filterIntents()");
98         }
99 
100         List<String> filteredPackages = new ArrayList<String>(permissionFilteredPackages.size());
101 
102         for (String packageName : permissionFilteredPackages) {
103 
104             if (android.health.connect.Constants.DEBUG) {
105                 Slog.d(TAG, "Checking intent for package : " + packageName);
106             }
107 
108             Intent intentToCheck =
109                     new Intent(HealthConnectManager.ACTION_SHOW_MIGRATION_INFO)
110                             .setPackage(packageName);
111 
112             ResolveInfo resolveResult =
113                     context.getPackageManager()
114                             .resolveActivity(
115                                     intentToCheck, PackageManager.ResolveInfoFlags.of(flags));
116 
117             if (resolveResult != null) {
118                 filteredPackages.add(packageName);
119             }
120         }
121         if (Constants.DEBUG) {
122             Slog.d(TAG, "filteredPackages : " + filteredPackages);
123         }
124         return filteredPackages;
125     }
126 
127     /** Computes the SHA256 digest of the input data. */
128     @SuppressWarnings("NullAway") // TODO(b/317029272): fix this suppression
computeSha256DigestBytes(@onNull byte[] data)129     public static String computeSha256DigestBytes(@NonNull byte[] data) {
130         MessageDigest messageDigest;
131         try {
132             messageDigest = MessageDigest.getInstance("SHA256");
133         } catch (NoSuchAlgorithmException e) {
134             return null;
135         }
136         messageDigest.update(data);
137 
138         return HexEncoding.encodeToString(messageDigest.digest(), /* uppercase= */ true);
139     }
140 
141     /** Checks if the package is stub by checking if its installer source is not set. */
isPackageStub(Context context, String packageName)142     public static boolean isPackageStub(Context context, String packageName) {
143         try {
144             return context.getPackageManager()
145                             .getInstallSourceInfo(packageName)
146                             .getInstallingPackageName()
147                     == null;
148         } catch (PackageManager.NameNotFoundException e) {
149             Slog.w(TAG, "Package not found " + packageName);
150         }
151         return false;
152     }
153 }
154