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 
getEntryIdnull44 fun SliceUri.getEntryId(): String? {
45     return getQueryParameter(KEY_HIGHLIGHT_ENTRY)
46 }
47 
getDestinationnull48 fun SliceUri.getDestination(): String? {
49     return getQueryParameter(KEY_DESTINATION)
50 }
51 
getRuntimeArgumentsnull52 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 
SliceUrinull61 fun SliceUri.getSliceId(): String? {
62     val entryId = getEntryId() ?: return null
63     val params = getRuntimeArguments()
64     return "${entryId}_$params"
65 }
66 
appendSpaParamsnull67 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 
fromEntrynull82 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 
createBroadcastPendingIntentnull96 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 
createBrowsePendingIntentnull104 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 
createBrowsePendingIntentnull112 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 
createBrowsePendingIntentnull120 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 
createBroadcastPendingIntentnull139 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