1 /* 2 * Copyright (C) 2022 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.settingslib.spa.slice 18 19 import android.app.Activity 20 import android.app.PendingIntent 21 import android.content.BroadcastReceiver 22 import android.content.ComponentName 23 import android.content.Context 24 import android.content.Intent 25 import android.net.Uri 26 import android.os.Bundle 27 import com.android.settingslib.spa.framework.common.SettingsEntry 28 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory 29 import com.android.settingslib.spa.framework.util.KEY_DESTINATION 30 import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY 31 import com.android.settingslib.spa.framework.util.SESSION_SLICE 32 import com.android.settingslib.spa.framework.util.SPA_INTENT_RESERVED_KEYS 33 import com.android.settingslib.spa.framework.util.appendSpaParams 34 import com.android.settingslib.spa.framework.util.getDestination 35 import com.android.settingslib.spa.framework.util.getEntryId 36 37 // Defines SliceUri, which contains special query parameters: 38 // -- KEY_DESTINATION: The route that this slice is navigated to. 39 // -- KEY_HIGHLIGHT_ENTRY: The entry id of this slice 40 // Other parameters can considered as runtime parameters. 41 // Use {entryId, runtimeParams} as the unique Id of this Slice. 42 typealias SliceUri = Uri 43 44 fun SliceUri.getEntryId(): String? { 45 return getQueryParameter(KEY_HIGHLIGHT_ENTRY) 46 } 47 48 fun SliceUri.getDestination(): String? { 49 return getQueryParameter(KEY_DESTINATION) 50 } 51 52 fun SliceUri.getRuntimeArguments(): Bundle { 53 val params = Bundle() 54 for (queryName in queryParameterNames) { 55 if (SPA_INTENT_RESERVED_KEYS.contains(queryName)) continue 56 params.putString(queryName, getQueryParameter(queryName)) 57 } 58 return params 59 } 60 61 fun SliceUri.getSliceId(): String? { 62 val entryId = getEntryId() ?: return null 63 val params = getRuntimeArguments() 64 return "${entryId}_$params" 65 } 66 67 fun Uri.Builder.appendSpaParams( 68 destination: String? = null, 69 entryId: String? = null, 70 runtimeArguments: Bundle? = null 71 ): Uri.Builder { 72 if (destination != null) appendQueryParameter(KEY_DESTINATION, destination) 73 if (entryId != null) appendQueryParameter(KEY_HIGHLIGHT_ENTRY, entryId) 74 if (runtimeArguments != null) { 75 for (key in runtimeArguments.keySet()) { 76 appendQueryParameter(key, runtimeArguments.getString(key, "")) 77 } 78 } 79 return this 80 } 81 82 fun Uri.Builder.fromEntry( 83 entry: SettingsEntry, 84 authority: String?, 85 runtimeArguments: Bundle? = null 86 ): Uri.Builder { 87 if (authority == null) return this 88 val sp = entry.containerPage() 89 return scheme("content").authority(authority).appendSpaParams( 90 destination = sp.buildRoute(), 91 entryId = entry.id, 92 runtimeArguments = runtimeArguments 93 ) 94 } 95 96 fun SliceUri.createBroadcastPendingIntent(): PendingIntent? { 97 val context = SpaEnvironmentFactory.instance.appContext 98 val sliceBroadcastClass = 99 SpaEnvironmentFactory.instance.sliceBroadcastReceiverClass ?: return null 100 val entryId = getEntryId() ?: return null 101 return createBroadcastPendingIntent(context, sliceBroadcastClass, entryId) 102 } 103 104 fun SliceUri.createBrowsePendingIntent(): PendingIntent? { 105 val context = SpaEnvironmentFactory.instance.appContext 106 val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null 107 val destination = getDestination() ?: return null 108 val entryId = getEntryId() 109 return createBrowsePendingIntent(context, browseActivityClass, destination, entryId) 110 } 111 112 fun Intent.createBrowsePendingIntent(): PendingIntent? { 113 val context = SpaEnvironmentFactory.instance.appContext 114 val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null 115 val destination = getDestination() ?: return null 116 val entryId = getEntryId() 117 return createBrowsePendingIntent(context, browseActivityClass, destination, entryId) 118 } 119 120 private fun createBrowsePendingIntent( 121 context: Context, 122 browseActivityClass: Class<out Activity>, 123 destination: String, 124 entryId: String? 125 ): PendingIntent { 126 val intent = Intent().setComponent(ComponentName(context, browseActivityClass)) 127 .appendSpaParams(destination, entryId, SESSION_SLICE) 128 .apply { 129 // Set both extra and data (which is a Uri) in Slice Intent: 130 // 1) extra is used in SPA navigation framework 131 // 2) data is used in Slice framework 132 data = Uri.Builder().appendSpaParams(destination, entryId).build() 133 flags = Intent.FLAG_ACTIVITY_NEW_TASK 134 } 135 136 return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) 137 } 138 139 private fun createBroadcastPendingIntent( 140 context: Context, 141 sliceBroadcastClass: Class<out BroadcastReceiver>, 142 entryId: String 143 ): PendingIntent { 144 val intent = Intent().setComponent(ComponentName(context, sliceBroadcastClass)) 145 .apply { data = Uri.Builder().appendSpaParams(entryId = entryId).build() } 146 return PendingIntent.getBroadcast( 147 context, 0 /* requestCode */, intent, 148 PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE 149 ) 150 } 151