1 /* 2 * Copyright (C) 2024 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.car.carlauncher.repositories.appactions 18 19 import android.app.ActivityOptions 20 import android.car.Car 21 import android.car.media.CarMediaManager 22 import android.content.ComponentName 23 import android.content.Context 24 import android.content.Intent 25 import android.content.pm.PackageManager 26 import android.util.Log 27 import com.android.car.carlauncher.R 28 import java.net.URISyntaxException 29 30 /** 31 * A sealed class representing various ways to launch applications within the system. 32 * Subclasses define specialized launch behaviors for different app types (launcher 33 * activities, media services, disabled apps, etc.). 34 * 35 * @see LauncherActivityLaunchProvider 36 * @see MediaServiceLaunchProvider 37 * @see DisabledAppLaunchProvider 38 * @see TosDisabledAppLaunchProvider 39 * @see MirroringAppLaunchProvider 40 */ 41 sealed class AppLaunchProvider { 42 43 /** 44 * Launches the app associated with the given ComponentName. This is the core 45 * method that all subclasses must implement. 46 * 47 * @param context Application context used for launching the activity. 48 * @param componentName The ComponentName identifying the app to launch. 49 * @param launchIntent An optional Intent that might contain additional launch instructions. 50 */ launchnull51 abstract fun launch( 52 context: Context, 53 componentName: ComponentName, 54 launchIntent: Intent? = null 55 ) 56 57 /** 58 * Utility method to launch an Intent with consistent ActivityOptions 59 */ 60 protected fun launchApp(context: Context, intent: Intent) { 61 val options = ActivityOptions.makeBasic() 62 options.launchDisplayId = context.display?.displayId ?: 0 63 context.startActivity(intent, options.toBundle()) 64 } 65 66 /** 67 * Handles launching standard launcher activities. It constructs an Intent with 68 * the necessary flags for launching a new instance of the given app. 69 */ 70 internal object LauncherActivityLaunchProvider : AppLaunchProvider() { launchnull71 override fun launch(context: Context, componentName: ComponentName, launchIntent: Intent?) { 72 launchApp( 73 context, 74 Intent(Intent.ACTION_MAIN).setComponent(componentName) 75 .addCategory(Intent.CATEGORY_LAUNCHER).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 76 ) 77 } 78 } 79 80 /** 81 * Launches media services, either by directly starting a media center template or 82 * switching active media sources. This provider works in conjunction with the CarMediaManager. 83 * 84 * @param carMediaManager Interface for controlling car media settings. 85 * @param launchMediaCenter If true, launches the media center template directly. 86 * @param closeScreen A callback function, likely used to close a prior UI screen. 87 */ 88 internal class MediaServiceLaunchProvider( 89 private val carMediaManager: CarMediaManager, 90 private val launchMediaCenter: Boolean, 91 val closeScreen: () -> Unit 92 ) : AppLaunchProvider() { launchnull93 override fun launch(context: Context, componentName: ComponentName, launchIntent: Intent?) { 94 if (launchMediaCenter) { 95 launchApp( 96 context, 97 Intent(Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE).putExtra( 98 Car.CAR_EXTRA_MEDIA_COMPONENT, 99 componentName.flattenToString() 100 ) 101 ) 102 return 103 } 104 carMediaManager.setMediaSource(componentName, CarMediaManager.MEDIA_SOURCE_MODE_BROWSE) 105 closeScreen() 106 } 107 } 108 109 /** 110 * Responsible for enabling and then launching disabled applications. Requires access 111 * to the PackageManager to manage application state. 112 */ 113 internal class DisabledAppLaunchProvider( 114 private val packageManager: PackageManager, 115 ) : AppLaunchProvider() { launchnull116 override fun launch(context: Context, componentName: ComponentName, launchIntent: Intent?) { 117 packageManager.setApplicationEnabledSetting( 118 componentName.packageName, 119 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 120 0 121 ) 122 // Fetch the current enabled setting to make sure the setting is synced 123 // before launching the activity. Otherwise, the activity may not 124 // launch. 125 check( 126 packageManager.getApplicationEnabledSetting(componentName.packageName) 127 == PackageManager.COMPONENT_ENABLED_STATE_ENABLED 128 ) { 129 ("Failed to enable the disabled package [" + componentName.packageName + "]") 130 } 131 Log.i(TAG, "Successfully enabled package [${componentName.packageName}]") 132 launchApp( 133 context, 134 Intent(Intent.ACTION_MAIN).setComponent(componentName) 135 .addCategory(Intent.CATEGORY_LAUNCHER).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 136 ) 137 } 138 139 companion object { 140 const val TAG = "DisabledAppLaunchProvider" 141 } 142 } 143 144 /** 145 * Handles launching the system activity for Terms of Service (TOS) acceptance. This 146 * provider redirects the user to the TOS flow for apps restricted by TOS. 147 */ 148 internal object TosDisabledAppLaunchProvider : AppLaunchProvider() { launchnull149 override fun launch(context: Context, componentName: ComponentName, launchIntent: Intent?) { 150 val tosIntentName = context.resources.getString(R.string.user_tos_activity_intent) 151 try { 152 launchApp(context, Intent.parseUri(tosIntentName, Intent.URI_ANDROID_APP_SCHEME)) 153 } catch (se: URISyntaxException) { 154 Log.e(TAG, "Invalid intent URI in user_tos_activity_intent", se) 155 } 156 } 157 158 const val TAG = "TosDisabledAppLaunchProvider" 159 } 160 161 /** 162 * Handles app mirroring scenarios. Assumes that a launchIntent is provided with the 163 * necessary information to launch the mirrored app. 164 */ 165 internal object MirroringAppLaunchProvider : 166 AppLaunchProvider() { launchnull167 override fun launch(context: Context, componentName: ComponentName, launchIntent: Intent?) { 168 launchIntent?.let { 169 launchApp(context, it) 170 } 171 } 172 } 173 } 174