blob: 6ef140abb41e44251f0399576fdf0a7a5d157232 [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.AlarmManager
import android.app.Instrumentation
import android.app.IntentService
import android.app.JobSchedulerImpl
import android.app.PendingIntent
import android.app.job.JobInfo
import android.app.job.JobParameters
import android.app.job.JobService
import android.content.BroadcastReceiver
import android.content.Intent
import android.os.PowerManager
import android.os.PowerManager.WakeLock
import androidx.annotation.VisibleForTesting
import androidx.inspection.Connection
import androidx.inspection.Inspector
import androidx.inspection.InspectorEnvironment
import androidx.inspection.InspectorFactory
import backgroundtask.inspection.BackgroundTaskInspectorProtocol.Command
import backgroundtask.inspection.BackgroundTaskInspectorProtocol.Response
import backgroundtask.inspection.BackgroundTaskInspectorProtocol.TrackBackgroundTaskResponse
private const val BACKGROUND_INSPECTION_ID = "backgroundtask.inspection"
class BackgroundTaskInspectorFactory : InspectorFactory<BackgroundTaskInspector>(
BACKGROUND_INSPECTION_ID
) {
override fun createInspector(
connection: Connection,
environment: InspectorEnvironment
) = BackgroundTaskInspector(connection, environment)
}
class BackgroundTaskInspector(
connection: Connection,
private val environment: InspectorEnvironment
) :
Inspector(connection) {
@VisibleForTesting
lateinit var alarmHandler: AlarmHandler
@VisibleForTesting
lateinit var pendingIntentHandler: PendingIntentHandler
@VisibleForTesting
lateinit var wakeLockHandler: WakeLockHandler
@VisibleForTesting
lateinit var jobHandler: JobHandler
override fun onReceiveCommand(data: ByteArray, callback: CommandCallback) {
val command = Command.parseFrom(data)
when (command.specializedCase) {
Command.SpecializedCase.TRACK_BACKGROUND_TASK -> {
startBackgroundTaskHandlers()
callback.reply(
Response.newBuilder().setTrackBackgroundTask(
TrackBackgroundTaskResponse.getDefaultInstance()
).build().toByteArray()
)
}
else -> throw IllegalStateException(
"Unexpected view inspector command case: ${command.specializedCase}"
)
}
}
override fun onDispose() {
}
private fun startBackgroundTaskHandlers() {
registerAlarmHooks()
registerPendingIntentHooks()
registerWakeLockHooks()
registerJobHooks()
}
private fun registerAlarmHooks() {
alarmHandler = AlarmHandlerImpl(connection)
environment.artTooling().registerEntryHook(
AlarmManager::class.java,
"setImpl" +
"(IJJJILandroid/app/PendingIntent;" +
"Landroid/app/AlarmManager\$OnAlarmListener;Ljava/lang/String;" +
"Landroid/os/Handler;Landroid/os/WorkSource;" +
"Landroid/app/AlarmManager\$AlarmClockInfo;)V"
) { _, args ->
alarmHandler.onAlarmSet(
type = args[0] as Int,
triggerMs = args[1] as Long,
windowMs = args[2] as Long,
intervalMs = args[3] as Long,
operation = args[5] as PendingIntent?,
listener = args[6] as AlarmManager.OnAlarmListener?,
listenerTag = args[7] as String?
)
}
environment.artTooling().registerEntryHook(
AlarmManager::class.java,
"cancel(Landroid/app/PendingIntent;)V"
) { _, args ->
alarmHandler.onAlarmCancelled((args[0] as? PendingIntent) ?: return@registerEntryHook)
}
environment.artTooling().registerEntryHook(
AlarmManager::class.java,
"cancel(Landroid/app/AlarmManager\$OnAlarmListener;)V"
) { _, args ->
alarmHandler.onAlarmCancelled(
(args[0] as? AlarmManager.OnAlarmListener) ?: return@registerEntryHook
)
}
environment.artTooling().registerEntryHook(
AlarmManager.OnAlarmListener::class.java,
"onAlarm()V"
) { listener, _ ->
alarmHandler.onAlarmFired(listener as AlarmManager.OnAlarmListener)
}
}
private fun registerPendingIntentHooks() {
pendingIntentHandler = PendingIntentHandlerImpl(alarmHandler)
listOf(
GET_ACTIVITY_METHOD_NAME,
GET_SERVICES_METHOD_NAME,
GET_BROADCAST_METHOD_NAME
).forEach { methodName ->
environment.artTooling().registerEntryHook(
PendingIntent::class.java,
methodName
) { _, args ->
pendingIntentHandler.onIntentCapturedEntry(
(args[2] as? Intent) ?: return@registerEntryHook
)
}
environment.artTooling().registerExitHook(
PendingIntent::class.java,
methodName
) { pendingIntent: PendingIntent? ->
pendingIntent?.let {
pendingIntentHandler.onIntentCapturedExit(it)
}
}
}
listOf(
CALL_ACTIVITY_ON_CREATE_METHOD_NAME,
CALL_ACTIVITY_ON_CREATE_PERSISTABLE_BUNDLE_METHOD_NAME
).forEach { methodName ->
environment.artTooling().registerEntryHook(
Instrumentation::class.java,
methodName
) { _, args ->
pendingIntentHandler.onIntentReceived(
(args[0] as? Activity)?.intent ?: return@registerEntryHook
)
}
}
environment.artTooling().registerEntryHook(
IntentService::class.java,
ON_START_COMMAND_METHOD_NAME
) { _, args ->
pendingIntentHandler.onIntentReceived((args[0] as? Intent) ?: return@registerEntryHook)
}
environment.artTooling().registerEntryHook(
ActivityThread::class.java,
HANDLE_RECEIVER_METHOD_NAME
) { _, args ->
pendingIntentHandler.onReceiverDataCreated(args[0] ?: return@registerEntryHook)
}
environment.artTooling().registerEntryHook(
BroadcastReceiver::class.java,
SET_PENDING_RESULT_METHOD_NAME
) { _, args ->
pendingIntentHandler.onReceiverDataResult(args[0] ?: return@registerEntryHook)
}
}
private fun registerWakeLockHooks() {
wakeLockHandler = WakeLockHandlerImpl(connection)
environment.artTooling().registerEntryHook(
PowerManager::class.java,
"newWakeLock" +
"(ILjava/lang/String;)Landroid/os/PowerManager\$WakeLock;"
) { _, args ->
wakeLockHandler.onNewWakeLockEntry(args[0] as Int, (args[1] as String?) ?: "")
}
environment.artTooling().registerExitHook<WakeLock>(
PowerManager::class.java,
"newWakeLock" +
"(ILjava/lang/String;)Landroid/os/PowerManager\$WakeLock;"
) { wakeLock ->
wakeLockHandler.onNewWakeLockExit(wakeLock)
}
environment.artTooling().registerEntryHook(
WakeLock::class.java,
"acquire()V"
) { wakeLock, _ ->
wakeLockHandler.onWakeLockAcquired(wakeLock as WakeLock, 0)
}
environment.artTooling().registerEntryHook(
WakeLock::class.java,
"acquire(J)V"
) { wakeLock, args ->
wakeLockHandler.onWakeLockAcquired(wakeLock as WakeLock, args[0] as Long)
}
environment.artTooling().registerEntryHook(
WakeLock::class.java,
"release(I)V"
) { wakeLock, args ->
wakeLockHandler.onWakeLockReleasedEntry(wakeLock as WakeLock, args[0] as Int)
}
environment.artTooling().registerExitHook<Void>(
WakeLock::class.java,
"release(I)V",
) {
wakeLockHandler.onWakeLockReleasedExit()
it
}
}
private fun registerJobHooks() {
jobHandler = JobHandlerImpl(connection)
environment.artTooling().registerEntryHook(
JobSchedulerImpl::class.java,
"schedule(Landroid/app/job/JobInfo;)I"
) { _, args ->
jobHandler.onScheduleJobEntry((args[0] as JobInfo?) ?: return@registerEntryHook)
}
environment.artTooling().registerExitHook<Int>(
JobSchedulerImpl::class.java,
"schedule(Landroid/app/job/JobInfo;)I"
) { scheduleResult ->
jobHandler.onScheduleJobExit(scheduleResult)
}
val jobHandlerClass = Class.forName("android.app.job.JobServiceEngine\$JobHandler")
environment.artTooling().registerEntryHook(
jobHandlerClass,
"ackStartMessage(Landroid/app/job/JobParameters;Z)V"
) { _, args ->
jobHandler.wrapOnStartJob(
params = (args[0] as? JobParameters) ?: return@registerEntryHook,
workOngoing = args[1] as Boolean
)
}
environment.artTooling().registerEntryHook(
jobHandlerClass,
"ackStopMessage(Landroid/app/job/JobParameters;Z)V"
) { _, args ->
jobHandler.wrapOnStopJob(
params = (args[0] as? JobParameters) ?: return@registerEntryHook,
reschedule = args[1] as Boolean
)
}
environment.artTooling().registerEntryHook(
JobService::class.java,
"jobFinished(Landroid/app/job/JobParameters;Z)V"
) { _, args ->
jobHandler.wrapJobFinished(
params = (args[0] as? JobParameters) ?: return@registerEntryHook,
wantsReschedule = args[1] as Boolean
)
}
}
}