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

Commit 8eebdbe4 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Interface to dynamically generate DropBox data.

The existing DropBoxManager APIs require that data be entirely
pre-cooked before it can be added.  This adds significant overhead
to Log.wtf() style messages which collect logcat data, since we
need to copy that log data through a pipe to an in-memory buffer.

To avoid that overhead, this change introduces an EntrySource
interface which can either be pre-cooked data or dynamically
generated data written directly to an open FD on disk.  Future
changes will adjust the Log.wtf() logic to use this new interface
to have logcat write directly into an FD.

In addition, this interface paves the way for leveraging a newer
F2FS feature which transparently compresses and decompresses data in
the kernel, instead of forcing us to use DEFLATE in userspace.

This change drops periodic quota checking while recording new
entries, and instead adjusts to pre-flight the quota checks before
writing starts.  It also drops the expensive fsync(), since these
logs are collected on a best-effort basis.

Bug: 176843501
Test: atest CtsDropBoxManagerTestCases
Test: atest FrameworksServicesTests:com.android.server.DropBoxTest
Change-Id: Ic78e99a32cfaf4edac066a73a6864c9c9e9fdeef
parent 47e8e593
Loading
Loading
Loading
Loading
+11 −11
Original line number Diff line number Diff line
@@ -30298,9 +30298,9 @@ package android.os {
  public class DropBoxManager {
    ctor protected DropBoxManager();
    method public void addData(String, byte[], int);
    method public void addFile(String, java.io.File, int) throws java.io.IOException;
    method public void addText(String, String);
    method public void addData(@NonNull String, @Nullable byte[], int);
    method public void addFile(@NonNull String, @NonNull java.io.File, int) throws java.io.IOException;
    method public void addText(@NonNull String, @NonNull String);
    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_LOGS, android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.DropBoxManager.Entry getNextEntry(String, long);
    method public boolean isTagEnabled(String);
    field public static final String ACTION_DROPBOX_ENTRY_ADDED = "android.intent.action.DROPBOX_ENTRY_ADDED";
@@ -30313,17 +30313,17 @@ package android.os {
  }
  public static class DropBoxManager.Entry implements java.io.Closeable android.os.Parcelable {
    ctor public DropBoxManager.Entry(String, long);
    ctor public DropBoxManager.Entry(String, long, String);
    ctor public DropBoxManager.Entry(String, long, byte[], int);
    ctor public DropBoxManager.Entry(String, long, android.os.ParcelFileDescriptor, int);
    ctor public DropBoxManager.Entry(String, long, java.io.File, int) throws java.io.IOException;
    ctor public DropBoxManager.Entry(@NonNull String, long);
    ctor public DropBoxManager.Entry(@NonNull String, long, @NonNull String);
    ctor public DropBoxManager.Entry(@NonNull String, long, @Nullable byte[], int);
    ctor public DropBoxManager.Entry(@NonNull String, long, @Nullable android.os.ParcelFileDescriptor, int);
    ctor public DropBoxManager.Entry(@NonNull String, long, @NonNull java.io.File, int) throws java.io.IOException;
    method public void close();
    method public int describeContents();
    method public int getFlags();
    method public java.io.InputStream getInputStream() throws java.io.IOException;
    method public String getTag();
    method public String getText(int);
    method @Nullable public java.io.InputStream getInputStream() throws java.io.IOException;
    method @NonNull public String getTag();
    method @Nullable public String getText(int);
    method public long getTimeMillis();
    method public void writeToParcel(android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.os.DropBoxManager.Entry> CREATOR;
+48 −35
Original line number Diff line number Diff line
@@ -19,6 +19,10 @@ package android.os;
import static android.Manifest.permission.PACKAGE_USAGE_STATS;
import static android.Manifest.permission.READ_LOGS;

import android.annotation.BytesLong;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
@@ -35,6 +39,9 @@ import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.util.zip.GZIPInputStream;

/**
@@ -54,6 +61,11 @@ public class DropBoxManager {
    @UnsupportedAppUsage
    private final IDropBoxManagerService mService;

    /** @hide */
    @IntDef(flag = true, prefix = { "IS_" }, value = { IS_EMPTY, IS_TEXT, IS_GZIPPED })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Flags {}

    /** Flag value: Entry's content was deleted to save space. */
    public static final int IS_EMPTY = 1;

@@ -105,15 +117,15 @@ public class DropBoxManager {
     * {@link #close()} when you are done using it.
     */
    public static class Entry implements Parcelable, Closeable {
        private final String mTag;
        private final long mTimeMillis;
        private final @NonNull String mTag;
        private final @CurrentTimeMillisLong long mTimeMillis;

        private final byte[] mData;
        private final ParcelFileDescriptor mFileDescriptor;
        private final int mFlags;
        private final @Nullable byte[] mData;
        private final @Nullable ParcelFileDescriptor mFileDescriptor;
        private final @Flags int mFlags;

        /** Create a new empty Entry with no contents. */
        public Entry(String tag, long millis) {
        public Entry(@NonNull String tag, @CurrentTimeMillisLong long millis) {
            if (tag == null) throw new NullPointerException("tag == null");

            mTag = tag;
@@ -124,13 +136,14 @@ public class DropBoxManager {
        }

        /** Create a new Entry with plain text contents. */
        public Entry(String tag, long millis, String text) {
        public Entry(@NonNull String tag, @CurrentTimeMillisLong long millis,
                @NonNull String text) {
            if (tag == null) throw new NullPointerException("tag == null");
            if (text == null) throw new NullPointerException("text == null");

            mTag = tag;
            mTimeMillis = millis;
            mData = text.getBytes();
            mData = text.getBytes(StandardCharsets.UTF_8);
            mFileDescriptor = null;
            mFlags = IS_TEXT;
        }
@@ -139,7 +152,8 @@ public class DropBoxManager {
         * Create a new Entry with byte array contents.
         * The data array must not be modified after creating this entry.
         */
        public Entry(String tag, long millis, byte[] data, int flags) {
        public Entry(@NonNull String tag, @CurrentTimeMillisLong long millis,
                @Nullable byte[] data, @Flags int flags) {
            if (tag == null) throw new NullPointerException("tag == null");
            if (((flags & IS_EMPTY) != 0) != (data == null)) {
                throw new IllegalArgumentException("Bad flags: " + flags);
@@ -156,7 +170,8 @@ public class DropBoxManager {
         * Create a new Entry with streaming data contents.
         * Takes ownership of the ParcelFileDescriptor.
         */
        public Entry(String tag, long millis, ParcelFileDescriptor data, int flags) {
        public Entry(@NonNull String tag, @CurrentTimeMillisLong long millis,
                @Nullable ParcelFileDescriptor data, @Flags int flags) {
            if (tag == null) throw new NullPointerException("tag == null");
            if (((flags & IS_EMPTY) != 0) != (data == null)) {
                throw new IllegalArgumentException("Bad flags: " + flags);
@@ -173,7 +188,8 @@ public class DropBoxManager {
         * Create a new Entry with the contents read from a file.
         * The file will be read when the entry's contents are requested.
         */
        public Entry(String tag, long millis, File data, int flags) throws IOException {
        public Entry(@NonNull String tag, @CurrentTimeMillisLong long millis,
                @NonNull File data, @Flags int flags) throws IOException {
            if (tag == null) throw new NullPointerException("tag == null");
            if ((flags & IS_EMPTY) != 0) throw new IllegalArgumentException("Bad flags: " + flags);

@@ -190,19 +206,26 @@ public class DropBoxManager {
        }

        /** @return the tag originally attached to the entry. */
        public String getTag() { return mTag; }
        public @NonNull String getTag() {
            return mTag;
        }

        /** @return time when the entry was originally created. */
        public long getTimeMillis() { return mTimeMillis; }
        public @CurrentTimeMillisLong long getTimeMillis() {
            return mTimeMillis;
        }

        /** @return flags describing the content returned by {@link #getInputStream()}. */
        public int getFlags() { return mFlags & ~IS_GZIPPED; }  // getInputStream() decompresses.
        public @Flags int getFlags() {
            // getInputStream() decompresses.
            return mFlags & ~IS_GZIPPED;
        }

        /**
         * @param maxBytes of string to return (will truncate at this length).
         * @return the uncompressed text contents of the entry, null if the entry is not text.
         */
        public String getText(int maxBytes) {
        public @Nullable String getText(@BytesLong int maxBytes) {
            if ((mFlags & IS_TEXT) == 0) return null;
            if (mData != null) return new String(mData, 0, Math.min(maxBytes, mData.length));

@@ -225,7 +248,7 @@ public class DropBoxManager {
        }

        /** @return the uncompressed contents of the entry, or null if the contents were lost */
        public InputStream getInputStream() throws IOException {
        public @Nullable InputStream getInputStream() throws IOException {
            InputStream is;
            if (mData != null) {
                is = new ByteArrayInputStream(mData);
@@ -293,17 +316,8 @@ public class DropBoxManager {
     * @param tag describing the type of entry being stored
     * @param data value to store
     */
    public void addText(String tag, String data) {
        try {
            mService.add(new Entry(tag, 0, data));
        } catch (RemoteException e) {
            if (e instanceof TransactionTooLargeException
                    && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
                Log.e(TAG, "App sent too much data, so it was ignored", e);
                return;
            }
            throw e.rethrowFromSystemServer();
        }
    public void addText(@NonNull String tag, @NonNull String data) {
        addData(tag, data.getBytes(StandardCharsets.UTF_8), IS_TEXT);
    }

    /**
@@ -313,10 +327,10 @@ public class DropBoxManager {
     * @param data value to store
     * @param flags describing the data
     */
    public void addData(String tag, byte[] data, int flags) {
    public void addData(@NonNull String tag, @Nullable byte[] data, @Flags int flags) {
        if (data == null) throw new NullPointerException("data == null");
        try {
            mService.add(new Entry(tag, 0, data, flags));
            mService.addData(tag, data, flags);
        } catch (RemoteException e) {
            if (e instanceof TransactionTooLargeException
                    && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
@@ -336,15 +350,14 @@ public class DropBoxManager {
     * @param flags describing the data
     * @throws IOException if the file can't be opened
     */
    public void addFile(String tag, File file, int flags) throws IOException {
    public void addFile(@NonNull String tag, @NonNull File file, @Flags int flags)
            throws IOException {
        if (file == null) throw new NullPointerException("file == null");
        Entry entry = new Entry(tag, 0, file, flags);
        try {
            mService.add(entry);
        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
                ParcelFileDescriptor.MODE_READ_ONLY)) {
            mService.addFile(tag, pfd, flags);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        } finally {
            entry.close();
        }
    }

+3 −6
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.internal.os;

import android.os.DropBoxManager;
import android.os.ParcelFileDescriptor;

/**
 * "Backend" interface used by {@link android.os.DropBoxManager} to talk to the
@@ -26,12 +27,8 @@ import android.os.DropBoxManager;
 * @hide
 */
interface IDropBoxManagerService {
    /**
     * @see DropBoxManager#addText
     * @see DropBoxManager#addData
     * @see DropBoxManager#addFile
     */
    void add(in DropBoxManager.Entry entry);
    void addData(String tag, in byte[] data, int flags);
    void addFile(String tag, in ParcelFileDescriptor fd, int flags);

    /** @see DropBoxManager#getNextEntry */
    boolean isTagEnabled(String tag);
+0 −2
Original line number Diff line number Diff line
@@ -93,8 +93,6 @@ private:
    enum {
        HAS_BYTE_ARRAY = 8
    };

    Status add(const Entry& entry);
};

}} // namespace android::os
+17 −14
Original line number Diff line number Diff line
@@ -18,7 +18,9 @@

#include <android/os/DropBoxManager.h>

#include <android-base/unique_fd.h>
#include <binder/IServiceManager.h>
#include <binder/ParcelFileDescriptor.h>
#include <com/android/internal/os/IDropBoxManagerService.h>
#include <cutils/log.h>

@@ -178,18 +180,24 @@ DropBoxManager::~DropBoxManager()
Status
DropBoxManager::addText(const String16& tag, const string& text)
{
    Entry entry(tag, IS_TEXT);
    entry.mData.assign(text.c_str(), text.c_str() + text.size());
    return add(entry);
    return addData(tag, reinterpret_cast<uint8_t const*>(text.c_str()), text.size(), IS_TEXT);
}

Status
DropBoxManager::addData(const String16& tag, uint8_t const* data,
        size_t size, int flags)
{
    Entry entry(tag, flags);
    entry.mData.assign(data, data+size);
    return add(entry);
    sp<IDropBoxManagerService> service = interface_cast<IDropBoxManagerService>(
        defaultServiceManager()->getService(android::String16("dropbox")));
    if (service == NULL) {
        return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service");
    }
    ALOGD("About to call service->add()");
    vector<uint8_t> dataArg;
    dataArg.assign(data, data + size);
    Status status = service->addData(tag, dataArg, flags);
    ALOGD("service->add returned %s", status.toString8().string());
    return status;
}

Status
@@ -213,20 +221,15 @@ DropBoxManager::addFile(const String16& tag, int fd, int flags)
        ALOGW("DropboxManager: %s", message.c_str());
        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, message.c_str());
    }
    Entry entry(tag, flags, fd);
    return add(entry);
}

Status
DropBoxManager::add(const Entry& entry)
{
    sp<IDropBoxManagerService> service = interface_cast<IDropBoxManagerService>(
        defaultServiceManager()->getService(android::String16("dropbox")));
    if (service == NULL) {
        return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service");
    }
    ALOGD("About to call service->add()");
    Status status = service->add(entry);
    android::base::unique_fd uniqueFd(fd);
    android::os::ParcelFileDescriptor parcelFd(std::move(uniqueFd));
    Status status = service->addFile(tag, parcelFd, flags);
    ALOGD("service->add returned %s", status.toString8().string());
    return status;
}
Loading