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.debug
18 
19 import android.content.ContentProvider
20 import android.content.ContentValues
21 import android.content.Context
22 import android.content.Intent
23 import android.content.Intent.URI_INTENT_SCHEME
24 import android.content.UriMatcher
25 import android.content.pm.ProviderInfo
26 import android.database.Cursor
27 import android.database.MatrixCursor
28 import android.net.Uri
29 import android.util.Log
30 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
31 import com.android.settingslib.spa.framework.util.KEY_DESTINATION
32 import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
33 import com.android.settingslib.spa.framework.util.KEY_SESSION_SOURCE_NAME
34 import com.android.settingslib.spa.framework.util.SESSION_BROWSE
35 import com.android.settingslib.spa.framework.util.SESSION_SEARCH
36 import com.android.settingslib.spa.framework.util.createIntent
37 
38 private const val TAG = "DebugProvider"
39 
40 /**
41  * The content provider to return debug data.
42  * One can query the provider result by:
43  *   $ adb shell content query --uri content://<AuthorityPath>/<QueryPath>
44  * For gallery, AuthorityPath = com.android.spa.gallery.debug
45  * Some examples:
46  *   $ adb shell content query --uri content://<AuthorityPath>/page_debug
47  *   $ adb shell content query --uri content://<AuthorityPath>/entry_debug
48  *   $ adb shell content query --uri content://<AuthorityPath>/page_info
49  *   $ adb shell content query --uri content://<AuthorityPath>/entry_info
50  */
51 class DebugProvider : ContentProvider() {
52     private val spaEnvironment get() = SpaEnvironmentFactory.instance
53     private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
54 
deletenull55     override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
56         TODO("Implement this to handle requests to delete one or more rows")
57     }
58 
getTypenull59     override fun getType(uri: Uri): String? {
60         TODO(
61             "Implement this to handle requests for the MIME type of the data" +
62                 "at the given URI"
63         )
64     }
65 
insertnull66     override fun insert(uri: Uri, values: ContentValues?): Uri? {
67         TODO("Implement this to handle requests to insert a new row.")
68     }
69 
updatenull70     override fun update(
71         uri: Uri,
72         values: ContentValues?,
73         selection: String?,
74         selectionArgs: Array<String>?
75     ): Int {
76         TODO("Implement this to handle requests to update one or more rows.")
77     }
78 
onCreatenull79     override fun onCreate(): Boolean {
80         Log.d(TAG, "onCreate")
81         return true
82     }
83 
attachInfonull84     override fun attachInfo(context: Context?, info: ProviderInfo?) {
85         if (info != null) {
86             QueryEnum.PAGE_DEBUG_QUERY.addUri(uriMatcher, info.authority)
87             QueryEnum.ENTRY_DEBUG_QUERY.addUri(uriMatcher, info.authority)
88             QueryEnum.PAGE_INFO_QUERY.addUri(uriMatcher, info.authority)
89             QueryEnum.ENTRY_INFO_QUERY.addUri(uriMatcher, info.authority)
90         }
91         super.attachInfo(context, info)
92     }
93 
querynull94     override fun query(
95         uri: Uri,
96         projection: Array<String>?,
97         selection: String?,
98         selectionArgs: Array<String>?,
99         sortOrder: String?
100     ): Cursor? {
101         return try {
102             when (uriMatcher.match(uri)) {
103                 QueryEnum.PAGE_DEBUG_QUERY.queryMatchCode -> queryPageDebug()
104                 QueryEnum.ENTRY_DEBUG_QUERY.queryMatchCode -> queryEntryDebug()
105                 QueryEnum.PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo()
106                 QueryEnum.ENTRY_INFO_QUERY.queryMatchCode -> queryEntryInfo()
107                 else -> throw UnsupportedOperationException("Unknown Uri $uri")
108             }
109         } catch (e: UnsupportedOperationException) {
110             throw e
111         } catch (e: Exception) {
112             Log.e(TAG, "Provider querying exception:", e)
113             null
114         }
115     }
116 
queryPageDebugnull117     private fun queryPageDebug(): Cursor {
118         val entryRepository by spaEnvironment.entryRepository
119         val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns())
120         for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
121             val page = pageWithEntry.page
122             if (!page.isBrowsable()) continue
123             val command = createBrowseAdbCommand(
124                 destination = page.buildRoute(),
125                 sessionName = SESSION_BROWSE
126             )
127             if (command != null) {
128                 cursor.newRow().add(ColumnEnum.PAGE_START_ADB.id, command)
129             }
130         }
131         return cursor
132     }
133 
queryEntryDebugnull134     private fun queryEntryDebug(): Cursor {
135         val entryRepository by spaEnvironment.entryRepository
136         val cursor = MatrixCursor(QueryEnum.ENTRY_DEBUG_QUERY.getColumns())
137         for (entry in entryRepository.getAllEntries()) {
138             val page = entry.containerPage()
139             if (!page.isBrowsable()) continue
140             val command = createBrowseAdbCommand(
141                 destination = page.buildRoute(),
142                 entryId = entry.id,
143                 sessionName = SESSION_SEARCH
144             )
145             if (command != null) {
146                 cursor.newRow().add(ColumnEnum.ENTRY_START_ADB.id, command)
147             }
148         }
149         return cursor
150     }
151 
queryPageInfonull152     private fun queryPageInfo(): Cursor {
153         val entryRepository by spaEnvironment.entryRepository
154         val cursor = MatrixCursor(QueryEnum.PAGE_INFO_QUERY.getColumns())
155         for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
156             val page = pageWithEntry.page
157             val intent = page.createIntent(SESSION_BROWSE) ?: Intent()
158             cursor.newRow()
159                 .add(ColumnEnum.PAGE_ID.id, page.id)
160                 .add(ColumnEnum.PAGE_NAME.id, page.displayName)
161                 .add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute())
162                 .add(ColumnEnum.PAGE_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
163                 .add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size)
164                 .add(ColumnEnum.PAGE_BROWSABLE.id, if (page.isBrowsable()) 1 else 0)
165         }
166         return cursor
167     }
168 
queryEntryInfonull169     private fun queryEntryInfo(): Cursor {
170         val entryRepository by spaEnvironment.entryRepository
171         val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns())
172         for (entry in entryRepository.getAllEntries()) {
173             val intent = entry.createIntent(SESSION_SEARCH) ?: Intent()
174             cursor.newRow()
175                 .add(ColumnEnum.ENTRY_ID.id, entry.id)
176                 .add(ColumnEnum.ENTRY_LABEL.id, entry.label)
177                 .add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute())
178                 .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
179                 .add(
180                     ColumnEnum.ENTRY_HIERARCHY_PATH.id,
181                     entryRepository.getEntryPathWithLabel(entry.id)
182                 )
183         }
184         return cursor
185     }
186 }
187 
createBrowseAdbCommandnull188 private fun createBrowseAdbCommand(
189     destination: String? = null,
190     entryId: String? = null,
191     sessionName: String? = null,
192 ): String? {
193     val context = SpaEnvironmentFactory.instance.appContext
194     val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
195     val packageName = context.packageName
196     val activityName = browseActivityClass.name.replace(packageName, "")
197     val destinationParam =
198         if (destination != null) " -e $KEY_DESTINATION $destination" else ""
199     val highlightParam =
200         if (entryId != null) " -e $KEY_HIGHLIGHT_ENTRY $entryId" else ""
201     val sessionParam =
202         if (sessionName != null) " -e $KEY_SESSION_SOURCE_NAME $sessionName" else ""
203     return "adb shell am start -n $packageName/$activityName" +
204         "$destinationParam$highlightParam$sessionParam"
205 }
206