1 package com.android.healthconnect.controller.utils 2 3 import android.content.ActivityNotFoundException 4 import android.content.Context 5 import android.content.Intent 6 import android.content.pm.PackageManager 7 import android.net.Uri 8 import android.os.Build 9 import android.os.UserManager 10 import android.text.TextUtils 11 import android.util.Log 12 import androidx.fragment.app.FragmentActivity 13 import com.android.healthconnect.controller.R 14 import com.android.healthconnect.controller.permissions.shared.HelpAndFeedbackFragment.Companion.FEEDBACK_INTENT_RESULT_CODE 15 import com.android.healthconnect.controller.permissions.shared.HelpAndFeedbackFragment.Companion.USER_INITIATED_FEEDBACK_BUCKET_ID 16 import com.android.healthfitness.flags.Flags 17 import com.android.settingslib.HelpUtils 18 import dagger.Module 19 import dagger.Provides 20 import dagger.hilt.EntryPoint 21 import dagger.hilt.InstallIn 22 import dagger.hilt.components.SingletonComponent 23 import javax.inject.Inject 24 25 interface DeviceInfoUtils { 26 isHealthConnectAvailablenull27 fun isHealthConnectAvailable(context: Context): Boolean 28 29 fun isSendFeedbackAvailable(context: Context): Boolean 30 31 fun isPlayStoreAvailable(context: Context): Boolean 32 33 fun openHCGetStartedLink(activity: FragmentActivity) 34 35 fun openHealthFitnessPermissionsLearnMoreLink(activity: FragmentActivity) 36 37 fun openHCBackupAndRestoreLink(activity: FragmentActivity) 38 39 fun openSendFeedbackActivity(activity: FragmentActivity) 40 41 fun isIntentHandlerAvailable(context: Context, intent: Intent): Boolean 42 43 fun isOnWatch(context: Context): Boolean 44 } 45 46 class DeviceInfoUtilsImpl @Inject constructor() : DeviceInfoUtils { 47 48 companion object { 49 private val TAG = "DeviceInfoUtils" 50 } 51 52 override fun isSendFeedbackAvailable(context: Context): Boolean { 53 return isIntentHandlerAvailable(context, Intent(Intent.ACTION_BUG_REPORT)) 54 } 55 56 override fun isPlayStoreAvailable(context: Context): Boolean { 57 val playStorePackageName = context.resources?.getString(R.string.playstore_collection_url) 58 val vendingPackageName = context.resources?.getString(R.string.playstore_package_name) 59 if (TextUtils.isEmpty(playStorePackageName) || playStorePackageName == null) { 60 // Package name not configured. Return. 61 return false 62 } 63 return isIntentHandlerAvailable( 64 context, 65 Intent(Intent.ACTION_VIEW).apply { 66 data = Uri.parse(playStorePackageName) 67 setPackage(vendingPackageName) 68 }, 69 ) 70 } 71 72 override fun openHCGetStartedLink(activity: FragmentActivity) { 73 openHealthConnectHelpCenterLink(activity, R.string.hc_get_started_link) 74 } 75 76 override fun openHCBackupAndRestoreLink(activity: FragmentActivity) { 77 openHealthConnectHelpCenterLink(activity, R.string.hc_backup_and_restore_link) 78 } 79 80 override fun openHealthFitnessPermissionsLearnMoreLink(activity: FragmentActivity) { 81 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) { 82 openHealthConnectHelpCenterLink( 83 activity, 84 R.string.health_fitness_permissions_learn_more_link, 85 ) 86 } else { 87 Log.e( 88 TAG, 89 "attempting to open health fitness permissions URL on Build ${Build.VERSION.SDK_INT}", 90 ) 91 } 92 } 93 94 private fun openHealthConnectHelpCenterLink(activity: FragmentActivity, resourceId: Int) { 95 val helpUrlString = activity.getString(resourceId) 96 val fullUri = HelpUtils.uriWithAddedParameters(activity, Uri.parse(helpUrlString)) 97 val intent = 98 Intent(Intent.ACTION_VIEW, fullUri).apply { 99 flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 100 } 101 try { 102 activity.startActivity(intent) 103 } catch (e: ActivityNotFoundException) { 104 Log.e(TAG, "Unable to open help center URL.", e) 105 } 106 } 107 108 override fun openSendFeedbackActivity(activity: FragmentActivity) { 109 val intent = Intent(Intent.ACTION_BUG_REPORT) 110 intent.putExtra("category_tag", USER_INITIATED_FEEDBACK_BUCKET_ID) 111 activity.startActivityForResult(intent, FEEDBACK_INTENT_RESULT_CODE) 112 } 113 114 override fun isIntentHandlerAvailable(context: Context, intent: Intent): Boolean { 115 val packageManager = context.packageManager 116 if (intent.resolveActivity(packageManager) != null) { 117 return true 118 } 119 return false 120 } 121 122 override fun isHealthConnectAvailable(context: Context): Boolean { 123 return isHardwareSupported(context) && !isProfile(context) 124 } 125 126 override fun isOnWatch(context: Context): Boolean { 127 val pm: PackageManager = context.packageManager 128 return pm.hasSystemFeature(PackageManager.FEATURE_WATCH) 129 } 130 131 private fun isHardwareSupported(context: Context): Boolean { 132 val pm: PackageManager = context.packageManager 133 val disabledOnWatch = isOnWatch(context) && !Flags.replaceBodySensorPermissionEnabled() 134 return (!pm.hasSystemFeature(PackageManager.FEATURE_EMBEDDED) && 135 !disabledOnWatch && 136 !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK) && 137 !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) 138 } 139 140 private fun isProfile(context: Context): Boolean { 141 return (context.getSystemService(Context.USER_SERVICE) as UserManager).isProfile 142 } 143 } 144 145 @EntryPoint 146 @InstallIn(SingletonComponent::class) 147 interface DeviceInfoUtilsEntryPoint { deviceInfoUtilsnull148 fun deviceInfoUtils(): DeviceInfoUtils 149 } 150 151 @Module 152 @InstallIn(SingletonComponent::class) 153 class DeviceInfoUtilsModule { 154 @Provides 155 fun providesDeviceInfoUtils(): DeviceInfoUtils { 156 return DeviceInfoUtilsImpl() 157 } 158 } 159