Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bb049b14 authored by Philip P. Moltmann's avatar Philip P. Moltmann
Browse files

Dump Auto-revoke logs

- Introduce dumping from PermissionControllerServiceImpl
- Introduce a way to log and also store the logs for dumping later

Test: adb shell dumpsys permissionmgr
Bug: 155680199
Change-Id: I60128c9d027395888e22dc999051911a1fe6a034
parent b2121474
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -149,6 +149,11 @@ public class Constants {
     */
    public static final String REQUEST_ROLE_USER_DENIED_FILE = "request_role_user_denied";

    /**
     * Logs to dump
     */
    public static final String LOGS_TO_DUMP_FILE = "LogToDump.log";

    /**
     * Key in the user denied status for requesting roles shared preferences that stores a string
     * set for the names of the roles that an application has been denied for once.
+102 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.permissioncontroller

import android.util.Log
import com.android.permissioncontroller.Constants.LOGS_TO_DUMP_FILE
import java.io.File
import java.io.PrintWriter

/**
 * Like {@link Log} but stores the logs in a file which can later be dumped via {@link #dump}
 */
object DumpableLog {
    private const val MAX_FILE_SIZE = 32 * 1024

    private val lock = Any()
    private val file = File(PermissionControllerApplication.get().filesDir, LOGS_TO_DUMP_FILE)

    init {
        file.createNewFile()
    }

    /**
     * Equivalent to {@link Log.v}
     */
    fun v(tag: String, message: String, exception: Exception? = null) {
        Log.v(tag, message, exception)
        addLogToDump("v", tag, message, exception)
    }

    /**
     * Equivalent to {@link Log.d}
     */
    fun d(tag: String, message: String, exception: Exception? = null) {
        Log.d(tag, message, exception)
        addLogToDump("d", tag, message, exception)
    }

    /**
     * Equivalent to {@link Log.i}
     */
    fun i(tag: String, message: String, exception: Exception? = null) {
        Log.i(tag, message, exception)
        addLogToDump("i", tag, message, exception)
    }

    /**
     * Equivalent to {@link Log.w}
     */
    fun w(tag: String, message: String, exception: Exception? = null) {
        Log.w(tag, message, exception)
        addLogToDump("w", tag, message, exception)
    }

    /**
     * Equivalent to {@link Log.e}
     */
    fun e(tag: String, message: String, exception: Exception? = null) {
        Log.e(tag, message, exception)
        addLogToDump("e", tag, message, exception)
    }

    private fun addLogToDump(level: String, tag: String, message: String, exception: Exception?) {
        synchronized(lock) {
            // TODO: Needs to be replaced by proper log rotation
            if (file.length() > MAX_FILE_SIZE) {
                val dump = file.readLines()

                file.writeText("truncated at ${System.currentTimeMillis()}\n")
                dump.subList(dump.size / 2, dump.size).forEach { file.appendText(it + "\n") }
            }

            file.appendText("${System.currentTimeMillis()} $tag:$level $message " +
                    "${exception?.let { it.message + Log.getStackTraceString(it) } ?: ""}\n")
        }
    }

    /**
     * Write the previously logged entries to the print writer.
     *
     * @param pw the writer to dump to
     */
    fun dump(pw: PrintWriter) {
        synchronized(lock) {
            file.forEachLine { pw.println(it) }
        }
    }
}
 No newline at end of file
+16 −15
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ import com.android.permissioncontroller.Constants
import com.android.permissioncontroller.Constants.ACTION_MANAGE_AUTO_REVOKE
import com.android.permissioncontroller.Constants.AUTO_REVOKE_NOTIFICATION_ID
import com.android.permissioncontroller.Constants.PERMISSION_REMINDER_CHANNEL_ID
import com.android.permissioncontroller.DumpableLog
import com.android.permissioncontroller.PermissionControllerStatsLog
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_UNUSED_APP_PERMISSION_REVOKED
@@ -133,7 +134,7 @@ class AutoRevokeOnBootReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent?) {
        if (DEBUG) {
            Log.i(LOG_TAG, "scheduleAutoRevokePermissions " +
            DumpableLog.i(LOG_TAG, "scheduleAutoRevokePermissions " +
                "with frequency ${getCheckFrequencyMs(context)}ms " +
                "and threshold ${getUnusedThresholdMs(context)}ms")
        }
@@ -142,13 +143,13 @@ class AutoRevokeOnBootReceiver : BroadcastReceiver() {
        // If this user is a profile, then its auto revoke will be handled by the primary user
        if (userManager.isProfile) {
            if (DEBUG) {
                Log.i(LOG_TAG, "user ${myUserHandle().identifier} is a profile. Not running " +
                    "Auto Revoke.")
                DumpableLog.i(LOG_TAG, "user ${myUserHandle().identifier} is a profile. Not " +
                    "running Auto Revoke.")
            }
            return
        } else if (DEBUG) {
            Log.i(LOG_TAG, "user ${myUserHandle().identifier} is a profile owner. Running " +
                "Auto Revoke.")
            DumpableLog.i(LOG_TAG, "user ${myUserHandle().identifier} is a profile owner. " +
                "Running Auto Revoke.")
        }

        val jobInfo = JobInfo.Builder(
@@ -158,7 +159,7 @@ class AutoRevokeOnBootReceiver : BroadcastReceiver() {
            .build()
        val status = context.getSystemService(JobScheduler::class.java)!!.schedule(jobInfo)
        if (status != JobScheduler.RESULT_SUCCESS) {
            Log.e(LOG_TAG,
            DumpableLog.e(LOG_TAG,
                "Could not schedule ${AutoRevokeService::class.java.simpleName}: $status")
        }
    }
@@ -210,7 +211,7 @@ private suspend fun revokePermissionsOnUnusedApps(context: Context):

        unusedApps[user] = unusedUserApps
        if (DEBUG) {
            Log.i(LOG_TAG, "Unused apps for user ${user.identifier}: " +
            DumpableLog.i(LOG_TAG, "Unused apps for user ${user.identifier}: " +
                "${unusedUserApps.map { it.packageName }}")
        }
    }
@@ -235,7 +236,7 @@ private suspend fun revokePermissionsOnUnusedApps(context: Context):

            if (pkg.packageName in keyboardPackages) {
                if (DEBUG) {
                    Log.i(LOG_TAG, "Skipping IME: ${pkg.packageName}")
                    DumpableLog.i(LOG_TAG, "Skipping IME: ${pkg.packageName}")
                }
                return@forEachInParallel
            }
@@ -274,7 +275,7 @@ private suspend fun revokePermissionsOnUnusedApps(context: Context):
                    }

                    if (DEBUG) {
                        Log.i(LOG_TAG, "revokeUnused $packageName - $revocablePermissions")
                        DumpableLog.i(LOG_TAG, "revokeUnused $packageName - $revocablePermissions")
                    }

                    val uid = group.packageInfo.uid
@@ -289,7 +290,7 @@ private suspend fun revokePermissionsOnUnusedApps(context: Context):
                        .getPackageImportance(packageName)
                    if (packageImportance > IMPORTANCE_TOP_SLEEPING) {
                        if (DEBUG) {
                            Log.i(LOG_TAG, "revoking $packageName - $revocablePermissions")
                            DumpableLog.i(LOG_TAG, "revoking $packageName - $revocablePermissions")
                        }
                        anyPermsRevoked.compareAndSet(false, true)

@@ -309,7 +310,7 @@ private suspend fun revokePermissionsOnUnusedApps(context: Context):
                                FLAG_PERMISSION_USER_SET to false)
                        }
                    } else {
                        Log.i(LOG_TAG,
                        DumpableLog.i(LOG_TAG,
                            "Skipping auto-revoke - $packageName running with importance " +
                                "$packageImportance")
                    }
@@ -398,7 +399,7 @@ class AutoRevokeService : JobService() {

    override fun onStartJob(params: JobParameters?): Boolean {
        if (DEBUG) {
            Log.i(LOG_TAG, "onStartJob")
            DumpableLog.i(LOG_TAG, "onStartJob")
        }

        jobStartTime = System.currentTimeMillis()
@@ -409,7 +410,7 @@ class AutoRevokeService : JobService() {
                    showAutoRevokeNotification()
                }
            } catch (e: Exception) {
                Log.e(LOG_TAG, "Failed to auto-revoke permissions", e)
                DumpableLog.e(LOG_TAG, "Failed to auto-revoke permissions", e)
            }
            jobFinished(params, false)
        }
@@ -464,7 +465,7 @@ class AutoRevokeService : JobService() {
    }

    override fun onStopJob(params: JobParameters?): Boolean {
        Log.w(LOG_TAG, "onStopJob after ${System.currentTimeMillis() - jobStartTime}ms")
        DumpableLog.w(LOG_TAG, "onStopJob after ${System.currentTimeMillis() - jobStartTime}ms")
        job?.cancel()
        return true
    }
@@ -485,7 +486,7 @@ private data class TeamfoodSettings(
                "auto_revoke_parameters" /* Settings.Global.AUTO_REVOKE_PARAMETERS */)?.let { str ->

                if (DEBUG) {
                    Log.i(LOG_TAG, "Parsing teamfood setting value: $str")
                    DumpableLog.i(LOG_TAG, "Parsing teamfood setting value: $str")
                }
                str.split(",")
                    .mapNotNull {
+9 −0
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import android.util.Xml;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.permissioncontroller.DumpableLog;
import com.android.permissioncontroller.PermissionControllerStatsLog;
import com.android.permissioncontroller.permission.model.AppPermissionGroup;
import com.android.permissioncontroller.permission.model.AppPermissions;
@@ -61,8 +62,10 @@ import com.android.permissioncontroller.permission.utils.Utils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;

import java.io.FileDescriptor;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
@@ -97,6 +100,12 @@ public final class PermissionControllerServiceImpl extends PermissionControllerL
        return super.onUnbind(intent);
    }

    @Override
    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
        DumpableLog.INSTANCE.dump(writer);
        writer.flush();
    }

    /**
     * Expand {@code perms} by split permissions for an app with the given targetSDK.
     *