1 /*
2  * 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 com.android.systemui.recordissue
18 
19 import android.annotation.SuppressLint
20 import android.content.ComponentName
21 import android.content.Context
22 import android.content.Intent
23 import android.content.ServiceConnection
24 import android.content.pm.PackageManager
25 import android.net.Uri
26 import android.os.Bundle
27 import android.os.Handler
28 import android.os.IBinder
29 import android.os.Looper
30 import android.os.Message
31 import android.os.Messenger
32 import android.util.Log
33 import androidx.annotation.WorkerThread
34 import com.android.systemui.dagger.SysUISingleton
35 import com.android.systemui.dagger.qualifiers.Background
36 import com.android.traceur.FileSender
37 import com.android.traceur.MessageConstants
38 import com.android.traceur.TraceUtils.PresetTraceType
39 import javax.inject.Inject
40 
41 private const val TAG = "TraceurMessageSender"
42 
43 @SysUISingleton
44 class TraceurMessageSender @Inject constructor(@Background private val backgroundLooper: Looper) {
45     private var binder: Messenger? = null
46     private var isBound: Boolean = false
47 
48     private val traceurConnection =
49         object : ServiceConnection {
onServiceConnectednull50             override fun onServiceConnected(className: ComponentName, service: IBinder) {
51                 binder = Messenger(service)
52                 isBound = true
53             }
54 
onServiceDisconnectednull55             override fun onServiceDisconnected(className: ComponentName) {
56                 binder = null
57                 isBound = false
58             }
59         }
60 
61     @SuppressLint("WrongConstant")
62     @WorkerThread
bindToTraceurnull63     fun bindToTraceur(context: Context) {
64         if (isBound) {
65             // Binding needs to happen after the phone has been unlocked. The RecordIssueTile is
66             // initialized before this happens though, so binding is placed at a later time, during
67             // normal operations that can be repeated. This check avoids calling "bindService" 2x+
68             return
69         }
70         try {
71             val info =
72                 context.packageManager.getPackageInfo(
73                     MessageConstants.TRACING_APP_PACKAGE_NAME,
74                     PackageManager.MATCH_SYSTEM_ONLY
75                 )
76             val intent =
77                 Intent().setClassName(info.packageName, MessageConstants.TRACING_APP_ACTIVITY)
78             val flags =
79                 Context.BIND_AUTO_CREATE or
80                     Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE or
81                     Context.BIND_WAIVE_PRIORITY
82             context.bindService(intent, traceurConnection, flags)
83         } catch (e: Exception) {
84             Log.e(TAG, "failed to bind to Traceur's service", e)
85         }
86     }
87 
88     @WorkerThread
unbindFromTraceurnull89     fun unbindFromTraceur(context: Context) {
90         if (isBound) {
91             context.unbindService(traceurConnection)
92         }
93     }
94 
95     @WorkerThread
startTracingnull96     fun startTracing(traceType: PresetTraceType) {
97         val data =
98             Bundle().apply { putSerializable(MessageConstants.INTENT_EXTRA_TRACE_TYPE, traceType) }
99         notifyTraceur(MessageConstants.START_WHAT, data)
100     }
101 
stopTracingnull102     @WorkerThread fun stopTracing() = notifyTraceur(MessageConstants.STOP_WHAT)
103 
104     @WorkerThread
105     fun shareTraces(context: Context, screenRecord: Uri?) {
106         val replyHandler = Messenger(TraceurMessageHandler(context, screenRecord, backgroundLooper))
107         notifyTraceur(MessageConstants.SHARE_WHAT, replyTo = replyHandler)
108     }
109 
110     @WorkerThread
notifyTraceurnull111     private fun notifyTraceur(what: Int, data: Bundle = Bundle(), replyTo: Messenger? = null) {
112         try {
113             binder!!.send(
114                 Message.obtain().apply {
115                     this.what = what
116                     this.data = data
117                     this.replyTo = replyTo
118                 }
119             )
120         } catch (e: Exception) {
121             Log.e(TAG, "failed to notify Traceur", e)
122         }
123     }
124 
125     private class TraceurMessageHandler(
126         private val context: Context,
127         private val screenRecord: Uri?,
128         looper: Looper,
129     ) : Handler(looper) {
130 
handleMessagenull131         override fun handleMessage(msg: Message) {
132             if (MessageConstants.SHARE_WHAT == msg.what) {
133                 shareTraces(
134                     msg.data.getParcelable(MessageConstants.EXTRA_PERFETTO, Uri::class.java),
135                     msg.data.getParcelable(MessageConstants.EXTRA_WINSCOPE, Uri::class.java)
136                 )
137             } else {
138                 throw IllegalArgumentException("received unknown msg.what: " + msg.what)
139             }
140         }
141 
shareTracesnull142         private fun shareTraces(perfetto: Uri?, winscope: Uri?) {
143             val uris: List<Uri> =
144                 mutableListOf<Uri>().apply {
145                     perfetto?.let { add(it) }
146                     winscope?.let { add(it) }
147                     screenRecord?.let { add(it) }
148                 }
149             val fileSharingIntent =
150                 FileSender.buildSendIntent(context, uris)
151                     .addFlags(
152                         Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
153                     )
154             context.startActivity(fileSharingIntent)
155         }
156     }
157 }
158