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.settings.activityembedding 18 19 import android.app.Activity 20 import android.content.ActivityNotFoundException 21 import android.content.Context 22 import android.content.Intent 23 import android.content.pm.UserInfo 24 import android.provider.Settings 25 import android.util.Log 26 import com.android.settings.SettingsActivity 27 import com.android.settings.SettingsActivity.EXTRA_IS_DEEPLINK_HOME_STARTED_FROM_SEARCH 28 import com.android.settings.Utils 29 import com.android.settings.flags.Flags 30 import com.android.settings.homepage.DeepLinkHomepageActivityInternal 31 import com.android.settings.homepage.SettingsHomepageActivity 32 import com.android.settings.password.PasswordUtils 33 import com.android.settingslib.spaprivileged.framework.common.userManager 34 35 object EmbeddedDeepLinkUtils { 36 private const val TAG = "EmbeddedDeepLinkUtils" 37 38 @JvmStatic Contextnull39 fun Context.tryStartMultiPaneDeepLink( 40 intent: Intent, 41 highlightMenuKey: String? = null, 42 ): Boolean { 43 intent.putExtra( 44 SettingsActivity.EXTRA_INITIAL_CALLING_PACKAGE, 45 PasswordUtils.getCallingAppPackageName(activityToken), 46 ) 47 val trampolineIntent: Intent 48 if (intent.getBooleanExtra(SettingsActivity.EXTRA_IS_FROM_SLICE, false)) { 49 // Get menu key for slice deep link case. 50 var sliceHighlightMenuKey: String? = intent.getStringExtra( 51 Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY 52 ) 53 if (sliceHighlightMenuKey.isNullOrEmpty()) { 54 sliceHighlightMenuKey = highlightMenuKey 55 } 56 trampolineIntent = getTrampolineIntent(intent, sliceHighlightMenuKey) 57 trampolineIntent.setClass(this, DeepLinkHomepageActivityInternal::class.java) 58 } else { 59 trampolineIntent = getTrampolineIntent(intent, highlightMenuKey) 60 } 61 return startTrampolineIntent(trampolineIntent) 62 } 63 64 /** 65 * Returns the deep link trampoline intent for large screen devices. 66 */ 67 @JvmStatic getTrampolineIntentnull68 fun getTrampolineIntent(intent: Intent, highlightMenuKey: String?): Intent { 69 val detailIntent = Intent(intent) 70 // Guard against the arbitrary Intent injection. 71 if (detailIntent.selector != null) { 72 detailIntent.setSelector(null) 73 } 74 // It's a deep link intent, SettingsHomepageActivity will set SplitPairRule and start it. 75 return Intent(Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY).apply { 76 setPackage(Utils.SETTINGS_PACKAGE_NAME) 77 replaceExtras(detailIntent) 78 79 // Relay detail intent data to prevent failure of Intent#ParseUri. 80 // If Intent#getData() is not null, Intent#toUri will return an Uri which has the scheme 81 // of Intent#getData() and it may not be the scheme of an Intent. 82 putExtra( 83 SettingsHomepageActivity.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA, 84 detailIntent.data 85 ) 86 detailIntent.setData(null) 87 putExtra( 88 Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI, 89 detailIntent.toUri(Intent.URI_INTENT_SCHEME) 90 ) 91 putExtra( 92 Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY, 93 highlightMenuKey 94 ) 95 addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) 96 } 97 } 98 99 /** 100 * Returns the deep link trampoline intent for settings search results for large screen devices. 101 */ 102 @JvmStatic getTrampolineIntentForSearchResultnull103 fun getTrampolineIntentForSearchResult( 104 context: Context, 105 intent: Intent, 106 highlightMenuKey: String? 107 ): Intent { 108 return getTrampolineIntent(intent, highlightMenuKey).apply { 109 if (Flags.settingsSearchResultDeepLinkInSameTask()) { 110 // Ensure the deep link intent does not include FLAG_ACTIVITY_NEW_TASK which 111 // causes the search result deep link to open in a separate window. 112 removeFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 113 putExtra(EXTRA_IS_DEEPLINK_HOME_STARTED_FROM_SEARCH, true) 114 } else { 115 addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) 116 } 117 118 setClass(context, DeepLinkHomepageActivityInternal::class.java) 119 } 120 } 121 122 /** 123 * Returns whether the user is a sub profile. 124 */ 125 @JvmStatic isSubProfilenull126 fun isSubProfile(userInfo: UserInfo): Boolean = 127 userInfo.isManagedProfile || userInfo.isPrivateProfile 128 129 private fun Context.startTrampolineIntent(trampolineIntent: Intent): Boolean = try { 130 val userInfo = userManager.getUserInfo(user.identifier) 131 if (isSubProfile(userInfo)) { 132 trampolineIntent.setClass(this, DeepLinkHomepageActivityInternal::class.java) 133 .putExtra(SettingsActivity.EXTRA_USER_HANDLE, user) 134 startActivityAsUser( 135 trampolineIntent, 136 userManager.getProfileParent(userInfo.id).userHandle 137 ) 138 } else { 139 startActivity(trampolineIntent) 140 } 141 true 142 } catch (e: ActivityNotFoundException) { 143 Log.e(TAG, "Deep link homepage is not available to show 2-pane UI") 144 false 145 } 146 } 147