• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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 android.platform.helpers.notesrole
18 
19 import android.content.BroadcastReceiver
20 import android.content.ClipData
21 import android.content.ClipDescription.MIMETYPE_TEXT_INTENT
22 import android.content.ClipDescription.MIMETYPE_TEXT_URILIST
23 import android.content.Context
24 import android.content.Intent
25 import android.content.Intent.ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE
26 import android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED
27 import android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS
28 import android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE
29 import android.content.IntentFilter
30 import android.net.Uri
31 import android.os.Bundle
32 import android.view.View
33 import android.widget.Button
34 import android.widget.ImageView
35 import android.widget.LinearLayout
36 import android.widget.TextView
37 import androidx.activity.ComponentActivity
38 import androidx.activity.result.ActivityResult
39 import androidx.activity.result.ActivityResultLauncher
40 import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
41 import androidx.core.view.setPadding
42 
43 /** Sample notes app activity for app clips verification in end to end tests. */
44 class NotesAppActivity : ComponentActivity() {
45 
46     private val finishBroadcastReceiver: BroadcastReceiver =
47         object : BroadcastReceiver() {
48             override fun onReceive(context: Context?, intent: Intent?) {
49                 finish()
50             }
51         }
52 
53     private lateinit var appClipsLauncher: ActivityResultLauncher<Intent>
54     private lateinit var rootLinearLayout: LinearLayout
55     private var screenshotUri: Uri? = null
56 
57     override fun onCreate(savedInstanceState: Bundle?) {
58         super.onCreate(savedInstanceState)
59 
60         registerReceiver(
61             finishBroadcastReceiver,
62             IntentFilter(FINISH_NOTES_APP_ACTIVITY_ACTION),
63             RECEIVER_EXPORTED
64         )
65 
66         appClipsLauncher =
67             registerForActivityResult(StartActivityForResult(), this::onAppClipsActivityResult)
68 
69         actionBar?.hide()
70         setContentView(createContentView())
71     }
72 
73     override fun onDestroy() {
74         super.onDestroy()
75         unregisterReceiver(finishBroadcastReceiver)
76         screenshotUri?.let { contentResolver.delete(it, /* extras= */ null) }
77     }
78 
79     private fun createContentView(): View =
80         LinearLayout(this)
81             .apply {
82                 orientation = LinearLayout.VERTICAL
83                 setPadding(PADDING)
84                 addView(createTriggerAppClipsButton())
85             }
86             .also { rootLinearLayout = it }
87 
88     private fun createTriggerAppClipsButton(): Button =
89         Button(this).apply {
90             text = APP_CLIPS_BUTTON_TEXT
91             setOnClickListener {
92                 appClipsLauncher.launch(Intent(ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE))
93             }
94         }
95 
96     private fun onAppClipsActivityResult(activityResult: ActivityResult) {
97         // Activity/app is killed after each test, so de-clutter views for easier verification.
98         rootLinearLayout.removeAllViews()
99 
100         // Add a text view with specific text to make it easier for verification in tests.
101         TextView(this).apply { text = RESPONSE_TEXT }.also { rootLinearLayout.addView(it) }
102 
103         val result = activityResult.data
104         val statusCode =
105             result?.getIntExtra(
106                 EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE,
107                 CAPTURE_CONTENT_FOR_NOTE_FAILED
108             )
109         // In case app clips returns error, show it and return early.
110         if (statusCode != CAPTURE_CONTENT_FOR_NOTE_SUCCESS) {
111             showErrorViewWithText("Error status code: $statusCode")
112             return
113         }
114 
115         // Show backlinks data, if available.
116         rootLinearLayout.addView(
117             result.clipData.let { clipData ->
118                 TextView(this).apply {
119                     text =
120                         clipData?.let { getStringFormattedBacklinksData(it) } ?: NO_BACKLINKS_TEXT
121                 }
122             }
123         )
124 
125         // Show app clips screenshot, if available.
126         result.data
127             ?.also { screenshotUri = it }
128             ?.let { uri ->
129                 val imageView = ImageView(this).apply { setImageURI(uri) }
130                 rootLinearLayout.addView(imageView)
131             }
132     }
133 
134     private fun showErrorViewWithText(error: String) {
135         TextView(this).apply { text = error }.also { rootLinearLayout.addView(it) }
136     }
137 
138     companion object {
139         private const val PADDING = 50
140 
141         const val FINISH_NOTES_APP_ACTIVITY_ACTION: String = "FINISH_NOTES_APP_ACTIVITY_ACTION"
142         const val APP_CLIPS_BUTTON_TEXT: String = "TRIGGER APP CLIPS"
143         const val RESPONSE_TEXT: String = "RESPONSE"
144         const val NO_BACKLINKS_TEXT: String = "NO BACKLINKS AVAILABLE"
145 
146         /** Formats Backlinks [ClipData] in readable format for verification. */
147         fun getStringFormattedBacklinksData(clipData: ClipData): String =
148             clipData.let { data ->
149                 data
150                     .getItemAt(0)
151                     .let { dataItem ->
152                         when (data.description.getMimeType(0)) {
153                             MIMETYPE_TEXT_URILIST -> dataItem.uri.toString()
154                             MIMETYPE_TEXT_INTENT -> dataItem.intent.toString()
155                             else -> dataItem.toString()
156                         }
157                     }
158                     .let { backlinksData -> "${data.description.label}\n$backlinksData" }
159             }
160     }
161 }
162