blob: 98e3e8691c6fac107cf6a86c2101b74bfd20fc4f [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.appinspection
import android.app.Activity
import android.app.ActivityThread
import android.app.PendingIntent
import android.content.Intent
import com.android.tools.appinspection.PendingIntentHandlerImpl.IntentWrapper.Companion.wrap
import java.util.concurrent.ConcurrentHashMap
/**
* Method name for [PendingIntent.getActivity(Context, int, Intent, int, Bundle)] to capture
* the [Intent] used to create a [PendingIntent] that starts an [android.app.Activity].
*/
const val GET_ACTIVITY_METHOD_NAME = "getActivity" +
"(Landroid/content/Context;ILandroid/content/Intent;I" +
"Landroid/os/Bundle;)Landroid/app/PendingIntent;"
/**
* Method name for [PendingIntent#getService(Context, int, Intent, int)] to capture the
* [Intent] used to create a [PendingIntent] that starts an [android.app.Service].
*/
const val GET_SERVICES_METHOD_NAME = "getService" +
"(Landroid/content/Context;ILandroid/content/Intent;I)" +
"Landroid/app/PendingIntent;"
/**
* Method name for [PendingIntent.getBroadcast(Context, int, Intent, int)] to capture the
* [Intent] used to create a [PendingIntent] that starts an
* [android.content.BroadcastReceiver].
*/
const val GET_BROADCAST_METHOD_NAME = "getBroadcast" +
"(Landroid/content/Context;ILandroid/content/Intent;I)" +
"Landroid/app/PendingIntent;"
/**
* Method name for [Instrumentation.callActivityOnCreate(Activity, Bundle)] to capture
* Activity Intent.
*
* Due to b/77549390, instrumenting [Activity] causes Transport to crash. So we add the
* hook to [Instrumentation#callActivityOnCreate(Activity, Bundle)], which calls
* [Activity#onCreate(Bundle)].
*/
const val CALL_ACTIVITY_ON_CREATE_METHOD_NAME = "callActivityOnCreate" +
"(Landroid/app/Activity;Landroid/os/Bundle;)V"
/**
* Method name for [Instrumentation#callActivityOnCreate(Activity, Bundle,
* PersistableBundle)] to capture Activity Intent.
*
* Due to b/77549390, instrumenting [Activity] causes Profiler to crash. So we add the
* hook to [Instrumentation#callActivityOnCreate(Activity, Bundle, PersistableBundle)],
* which calls [Activity#onCreate(Bundle, PersistableBundle)].
*/
const val CALL_ACTIVITY_ON_CREATE_PERSISTABLE_BUNDLE_METHOD_NAME = "callActivityOnCreate" +
"(Landroid/app/Activity;Landroid/os/Bundle;Landroid/" +
"os/PersistableBundle;)V"
/**
* Method name for [IntentService.onStartCommand(Intent, int, int)] to capture Service
* Intent.
*/
const val ON_START_COMMAND_METHOD_NAME = "onStartCommand(Landroid/content/Intent;II)I"
/**
* Method name for [ActivityThread.handleReceiver(ReceiverData)] to capture
* a ReceiverData containing the needed [Intent].
* The [Intent] field in ReceiverData is not properly set at the beginning of the method,
* so we need to wait until [SET_PENDING_RESULT_METHOD_NAME] being called to capture the [Intent].
*/
const val HANDLE_RECEIVER_METHOD_NAME = "handleReceiver" +
"(Landroid/app/ActivityThread\$ReceiverData;)V"
/**
* Method name for [android.content.BroadcastReceiver.setPendingResult(PendingResult)].
* If the [PendingResult] is same with the ReceiverData captured from method
* [HANDLE_RECEIVER_METHOD_NAME], it is safe to say that the method is called from
* [ActivityThread] and we can extract the [Intent] properly.
*/
const val SET_PENDING_RESULT_METHOD_NAME = "setPendingResult" +
"(Landroid/content/BroadcastReceiver\$PendingResult;)V"
/**
* A handler class that adds necessary hooks to track [Intent] and its related
* [PendingIntent].
*/
interface PendingIntentHandler {
fun onIntentCapturedEntry(intent: Intent)
fun onIntentCapturedExit(pendingIntent: PendingIntent): PendingIntent
fun onIntentReceived(intent: Intent)
fun onReceiverDataCreated(data: Any)
fun onReceiverDataResult(data: Any)
}
class PendingIntentHandlerImpl(private val alarmHandler: AlarmHandler) : PendingIntentHandler {
/**
* Wraps an [Intent] and overrides its `equals` and `hashCode` methods so we
* can use it as a HashMap key. Two intents are considered equal iff [Intent.filterEquals]
* returns true.
*/
private class IntentWrapper private constructor(private val mIntent: Intent) {
override fun equals(other: Any?): Boolean {
return (other is IntentWrapper
&& mIntent.filterEquals(other.mIntent))
}
override fun hashCode() = mIntent.filterHashCode()
companion object {
fun Intent.wrap() = IntentWrapper(this)
}
}
/**
* A mapping from [Intent] to [PendingIntent] so when an Intent is sent we
* can trace back to its PendingIntent (if any) for event tracking.
*/
private val intentMap = ConcurrentHashMap<IntentWrapper, PendingIntent>()
/* Intent shared between an entry hook and an exit hook for the same method. */
private val intentData = ThreadLocal<Intent>()
/**
* ReceiverData captured from handleReceiver to find the correct calling of
* method setPendingResult.
*
* @see HANDLE_RECEIVER_METHOD_NAME
* @see SET_PENDING_RESULT_METHOD_NAME
*/
private val receiverData = ThreadLocal<Any>()
override fun onIntentCapturedEntry(intent: Intent) {
intentData.set(intent)
}
override fun onIntentCapturedExit(pendingIntent: PendingIntent): PendingIntent {
intentMap[intentData.get().wrap()] = pendingIntent
return pendingIntent
}
override fun onIntentReceived(intent: Intent) {
val pendingIntent = intentMap[intent.wrap()] ?: return
if (intent.getIntExtra(Intent.EXTRA_ALARM_COUNT, 0) != 0) {
alarmHandler.onAlarmFired(pendingIntent)
}
}
override fun onReceiverDataCreated(data: Any) {
receiverData.set(data)
}
override fun onReceiverDataResult(data: Any) {
val clazz = Class.forName("android.app.ActivityThread\$ReceiverData")
if (data == receiverData.get()) {
val field = clazz.getDeclaredField("intent")
field.isAccessible = true
val intent = field.get(data) as? Intent ?: return
onIntentReceived(intent)
}
}
}