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

Commit c8c26365 authored by Michael Wachenschwanz's avatar Michael Wachenschwanz
Browse files

Upgrade UsageStatsDatabase from XML to Protobuf

Add the relevant methods to read from ProtoInputStream to
various classes.

Also add some framework to handle version changes in
UsageStatsDatabase. There is some risk of users losing all their current
UsageStats data, if something goes horribly wrong. The debug flag and a
keep backup files flag are temporarily set in UsageStatsDatabase with
this change. They will both be unset in the future before the Q release.

Some rough number on the impact of this change:
Proto file size on disk reduces to ~47% of XML file size :)
Proto file read time reduces to ~55% of XML file read :)
Proto file write time increases ~17% over the XML file write :(

There will be a follow up CL to address the file write time regression

Bug: 111422946
Fixes: 111449927
Test: atest UsageStatsDatabaseTest
Change-Id: I084aea796ed2163c42947d52396a36cc7c5562a2
parent c210031d
Loading
Loading
Loading
Loading
+36 −0
Original line number Diff line number Diff line
@@ -28,9 +28,13 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import android.util.proto.WireTypeMismatchException;
import android.view.DisplayInfo;

import java.io.IOException;

/**
 * Class that contains windowing configuration/state for other objects that contain windows directly
 * or indirectly. E.g. Activities, Task, Displays, ...
@@ -510,6 +514,38 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
        protoOutputStream.end(token);
    }

    /**
     * Read from a protocol buffer input stream.
     * Protocol buffer message definition at {@link android.app.WindowConfigurationProto}
     *
     * @param proto   Stream to read the WindowConfiguration object from.
     * @param fieldId Field Id of the WindowConfiguration as defined in the parent message
     * @hide
     */
    public void readFromProto(ProtoInputStream proto, long fieldId)
            throws IOException, WireTypeMismatchException {
        final long token = proto.start(fieldId);
        try {
            while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                switch (proto.getFieldNumber()) {
                    case (int) APP_BOUNDS:
                        mAppBounds = new Rect();
                        mAppBounds.readFromProto(proto, APP_BOUNDS);
                        break;
                    case (int) WINDOWING_MODE:
                        mWindowingMode = proto.readInt(WINDOWING_MODE);
                        break;
                    case (int) ACTIVITY_TYPE:
                        mActivityType = proto.readInt(ACTIVITY_TYPE);
                        break;
                }
            }
        } finally {
            // Let caller handle any exceptions
            proto.end(token);
        }
    }

    /**
     * Returns true if the activities associated with this window configuration display a shadow
     * around their border.
+12 −0
Original line number Diff line number Diff line
@@ -165,6 +165,12 @@ public final class UsageEvents implements Parcelable {
         */
        public static final int KEYGUARD_HIDDEN = 18;

        /**
         * Keep in sync with the greatest event type value.
         * @hide
         */
        public static final int MAX_EVENT_TYPE = 18;

        /** @hide */
        public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0;

@@ -175,6 +181,12 @@ public final class UsageEvents implements Parcelable {
        @Retention(RetentionPolicy.SOURCE)
        public @interface EventFlags {}

        /**
         * Bitwise OR all valid flag constants to create this constant.
         * @hide
         */
        public static final int VALID_FLAG_BITS = FLAG_IS_PACKAGE_INSTANT_APP;

        /**
         * {@hide}
         */
+132 −2
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import android.annotation.Nullable;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.WindowConfiguration;
import android.content.LocaleProto;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
import android.os.Build;
@@ -54,7 +55,9 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import android.util.proto.WireTypeMismatchException;
import android.view.View;

import com.android.internal.util.XmlUtils;
@@ -67,6 +70,7 @@ import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
@@ -1086,12 +1090,14 @@ public final class Configuration implements Parcelable, Comparable<Configuration
    /**
     * Write to a protocol buffer output stream.
     * Protocol buffer message definition at {@link android.content.ConfigurationProto}
     * Has the option to ignore fields that don't need to be persisted to disk.
     *
     * @param protoOutputStream Stream to write the Configuration object to.
     * @param fieldId           Field Id of the Configuration as defined in the parent message
     * @param persisted         Note if this proto will be persisted to disk
     * @hide
     */
    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId, boolean persisted) {
        final long token = protoOutputStream.start(fieldId);
        protoOutputStream.write(FONT_SCALE, fontScale);
        protoOutputStream.write(MCC, mcc);
@@ -1113,12 +1119,136 @@ public final class Configuration implements Parcelable, Comparable<Configuration
        protoOutputStream.write(SCREEN_HEIGHT_DP, screenHeightDp);
        protoOutputStream.write(SMALLEST_SCREEN_WIDTH_DP, smallestScreenWidthDp);
        protoOutputStream.write(DENSITY_DPI, densityDpi);
        if (windowConfiguration != null) {
        // For persistence, we do not care about window configuration
        if (!persisted && windowConfiguration != null) {
            windowConfiguration.writeToProto(protoOutputStream, WINDOW_CONFIGURATION);
        }
        protoOutputStream.end(token);
    }

    /**
     * Write to a protocol buffer output stream.
     * Protocol buffer message definition at {@link android.content.ConfigurationProto}
     *
     * @param protoOutputStream Stream to write the Configuration object to.
     * @param fieldId           Field Id of the Configuration as defined in the parent message
     * @hide
     */
    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
        writeToProto(protoOutputStream, fieldId, false);
    }

    /**
     * Read from a protocol buffer output stream.
     * Protocol buffer message definition at {@link android.content.ConfigurationProto}
     *
     * @param protoInputStream Stream to read the Configuration object from.
     * @param fieldId          Field Id of the Configuration as defined in the parent message
     * @hide
     */
    public void readFromProto(ProtoInputStream protoInputStream, long fieldId) throws IOException {
        final long token = protoInputStream.start(fieldId);
        final List<Locale> list = new ArrayList();
        try {
            while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                switch (protoInputStream.getFieldNumber()) {
                    case (int) FONT_SCALE:
                        fontScale = protoInputStream.readFloat(FONT_SCALE);
                        break;
                    case (int) MCC:
                        mcc = protoInputStream.readInt(MCC);
                        break;
                    case (int) MNC:
                        mnc = protoInputStream.readInt(MNC);
                        break;
                    case (int) LOCALES:
                        // Parse the Locale here to handle all the repeated Locales
                        // The LocaleList will be created when the message is completed
                        final long localeToken = protoInputStream.start(LOCALES);
                        String language = "";
                        String country = "";
                        String variant = "";
                        try {
                            while (protoInputStream.nextField()
                                    != ProtoInputStream.NO_MORE_FIELDS) {
                                switch (protoInputStream.getFieldNumber()) {
                                    case (int) LocaleProto.LANGUAGE:
                                        language = protoInputStream.readString(
                                                LocaleProto.LANGUAGE);
                                        break;
                                    case (int) LocaleProto.COUNTRY:
                                        country = protoInputStream.readString(LocaleProto.COUNTRY);
                                        break;
                                    case (int) LocaleProto.VARIANT:
                                        variant = protoInputStream.readString(LocaleProto.VARIANT);
                                        break;
                                }
                            }
                        } catch (WireTypeMismatchException wtme) {
                            // rethrow for caller deal with
                            throw wtme;
                        } finally {
                            protoInputStream.end(localeToken);
                            list.add(new Locale(language, country, variant));
                        }
                        break;
                    case (int) SCREEN_LAYOUT:
                        screenLayout = protoInputStream.readInt(SCREEN_LAYOUT);
                        break;
                    case (int) COLOR_MODE:
                        colorMode = protoInputStream.readInt(COLOR_MODE);
                        break;
                    case (int) TOUCHSCREEN:
                        touchscreen = protoInputStream.readInt(TOUCHSCREEN);
                        break;
                    case (int) KEYBOARD:
                        keyboard = protoInputStream.readInt(KEYBOARD);
                        break;
                    case (int) KEYBOARD_HIDDEN:
                        keyboardHidden = protoInputStream.readInt(KEYBOARD_HIDDEN);
                        break;
                    case (int) HARD_KEYBOARD_HIDDEN:
                        hardKeyboardHidden = protoInputStream.readInt(HARD_KEYBOARD_HIDDEN);
                        break;
                    case (int) NAVIGATION:
                        navigation = protoInputStream.readInt(NAVIGATION);
                        break;
                    case (int) NAVIGATION_HIDDEN:
                        navigationHidden = protoInputStream.readInt(NAVIGATION_HIDDEN);
                        break;
                    case (int) ORIENTATION:
                        orientation = protoInputStream.readInt(ORIENTATION);
                        break;
                    case (int) UI_MODE:
                        uiMode = protoInputStream.readInt(UI_MODE);
                        break;
                    case (int) SCREEN_WIDTH_DP:
                        screenWidthDp = protoInputStream.readInt(SCREEN_WIDTH_DP);
                        break;
                    case (int) SCREEN_HEIGHT_DP:
                        screenHeightDp = protoInputStream.readInt(SCREEN_HEIGHT_DP);
                        break;
                    case (int) SMALLEST_SCREEN_WIDTH_DP:
                        smallestScreenWidthDp = protoInputStream.readInt(SMALLEST_SCREEN_WIDTH_DP);
                        break;
                    case (int) DENSITY_DPI:
                        densityDpi = protoInputStream.readInt(DENSITY_DPI);
                        break;
                    case (int) WINDOW_CONFIGURATION:
                        windowConfiguration.readFromProto(protoInputStream, WINDOW_CONFIGURATION);
                        break;
                }
            }
        } finally {
            // Let caller handle any exceptions
            if (list.size() > 0) {
                //Create the LocaleList from the collected Locales
                setLocales(new LocaleList(list.toArray(new Locale[list.size()])));
            }
            protoInputStream.end(token);
        }
    }

    /**
     * Write full {@link android.content.ResourcesConfigurationProto} to protocol buffer output
     * stream.
+98 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.
 */

syntax = "proto2";
package com.android.server.usage;
import "frameworks/base/core/proto/android/content/configuration.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";

option java_multiple_files = true;

message IntervalStatsProto {
  message StringPool {
    optional int32 size = 1;
    repeated string strings = 2;
  }

  message CountAndTime {
    optional int32 count = 1;
    optional int64 time_ms = 2;
  }

  // Stores the relevant information from a UsageStats
  message UsageStats {
    message ChooserAction {
      message CategoryCount {
        optional string name = 1;
        optional int32 count = 3;
      }
      optional string name = 1;
      repeated CategoryCount counts = 3;
    }
    optional string package = 1;
    // package_index contains the index + 1 of the package name in the string pool
    optional int32 package_index = 2;
    optional int64 last_time_active_ms = 3;
    optional int64 total_time_active_ms = 4;
    optional int32 last_event = 5;
    optional int32 app_launch_count = 6;
    repeated ChooserAction chooser_actions = 7;
  }

  // Stores the relevant information an IntervalStats will have about a Configuration
  message Configuration {
    optional .android.content.ConfigurationProto config = 1;
    optional int64 last_time_active_ms = 2;
    optional int64 total_time_active_ms = 3;
    optional int32 count = 4;
    optional bool active = 5;
  }

  // Stores the relevant information from a UsageEvents.Event
  message Event {
    optional string package = 1;
    // package_index contains the index + 1 of the package name in the string pool
    optional int32 package_index = 2;
    optional string class = 3;
    // class_index contains the index + 1 of the class name in the string pool
    optional int32 class_index = 4;
    optional int64 time_ms = 5;
    optional int32 flags = 6;
    optional int32 type = 7;
    optional .android.content.ConfigurationProto config = 8;
    optional string shortcut_id = 9;
    optional int32 standby_bucket = 11;
    optional string notification_channel = 12;
  }

  // The following fields contain supplemental data used to build IntervalStats, such as a string
  // pool.
  optional int64 end_time_ms = 1;
  // stringpool contains all the package and class names used by UsageStats and Event
  // They will hold a number that is equal to the index + 1 of their string in the pool
  optional StringPool stringpool = 2;

  // The following fields contain aggregated usage stats data
  optional CountAndTime interactive = 10;
  optional CountAndTime non_interactive = 11;
  optional CountAndTime keyguard_shown = 12;
  optional CountAndTime keyguard_hidden = 13;

  // The following fields contain listed usage stats data
  repeated UsageStats packages = 20;
  repeated Configuration configurations = 21;
  repeated Event event_log = 22;
}
+37 −0
Original line number Diff line number Diff line
@@ -23,8 +23,11 @@ import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import android.util.proto.WireTypeMismatchException;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -231,6 +234,40 @@ public final class Rect implements Parcelable {
        protoOutputStream.end(token);
    }

    /**
     * Read from a protocol buffer input stream.
     * Protocol buffer message definition at {@link android.graphics.RectProto}
     *
     * @param proto     Stream to read the Rect object from.
     * @param fieldId   Field Id of the Rect as defined in the parent message
     * @hide
     */
    public void readFromProto(@NonNull ProtoInputStream proto, long fieldId) throws IOException,
            WireTypeMismatchException {
        final long token = proto.start(fieldId);
        try {
            while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                switch (proto.getFieldNumber()) {
                    case (int) RectProto.LEFT:
                        left = proto.readInt(RectProto.LEFT);
                        break;
                    case (int) RectProto.TOP:
                        top = proto.readInt(RectProto.TOP);
                        break;
                    case (int) RectProto.RIGHT:
                        right = proto.readInt(RectProto.RIGHT);
                        break;
                    case (int) RectProto.BOTTOM:
                        bottom = proto.readInt(RectProto.BOTTOM);
                        break;
                }
            }
        } finally {
            // Let caller handle any exceptions
            proto.end(token);
        }
    }

    /**
     * Returns true if the rectangle is empty (left >= right or top >= bottom)
     */
Loading