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

Commit cf9d19e0 authored by Daniel Nishi's avatar Daniel Nishi
Browse files

First pass at adding the cache quota suggestions.

This currently integrates with installd, but not with
any framework API to expose this information to apps.

The first pass, as per the design doc, adds a service
which polls for large changes in the file system free space.
If enough spaces changes, it begins a recalculation of the
cache quotas and pipes the information down to installd.
This calculation is done in the updateable ExtServices.

Further enhancements in later patches include integrating this
to listen to package install and removal events, caching the
last computed quota values into an XML file on disk to load
on boot, and exposing the information to apps.

Bug: 33965858
Test: ExtServices unit test

Change-Id: Ie39f228b73532cb6ce2f98529f7c5df0839202ae
parent ceb25042
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -109,6 +109,7 @@ LOCAL_SRC_FILES += \
	core/java/android/app/backup/IRestoreObserver.aidl \
	core/java/android/app/backup/IRestoreObserver.aidl \
	core/java/android/app/backup/IRestoreSession.aidl \
	core/java/android/app/backup/IRestoreSession.aidl \
	core/java/android/app/backup/ISelectBackupTransportCallback.aidl \
	core/java/android/app/backup/ISelectBackupTransportCallback.aidl \
	core/java/android/app/usage/ICacheQuotaService.aidl \
	core/java/android/app/usage/IStorageStatsManager.aidl \
	core/java/android/app/usage/IStorageStatsManager.aidl \
	core/java/android/app/usage/IUsageStatsManager.aidl \
	core/java/android/app/usage/IUsageStatsManager.aidl \
	core/java/android/bluetooth/IBluetooth.aidl \
	core/java/android/bluetooth/IBluetooth.aidl \
@@ -706,6 +707,7 @@ aidl_files := \
	frameworks/base/core/java/android/service/notification/StatusBarNotification.aidl \
	frameworks/base/core/java/android/service/notification/StatusBarNotification.aidl \
	frameworks/base/core/java/android/service/chooser/ChooserTarget.aidl \
	frameworks/base/core/java/android/service/chooser/ChooserTarget.aidl \
	frameworks/base/core/java/android/speech/tts/Voice.aidl \
	frameworks/base/core/java/android/speech/tts/Voice.aidl \
	frameworks/base/core/java/android/app/usage/CacheQuotaHint.aidl \
	frameworks/base/core/java/android/app/usage/ExternalStorageStats.aidl \
	frameworks/base/core/java/android/app/usage/ExternalStorageStats.aidl \
	frameworks/base/core/java/android/app/usage/StorageStats.aidl \
	frameworks/base/core/java/android/app/usage/StorageStats.aidl \
	frameworks/base/core/java/android/app/usage/UsageEvents.aidl \
	frameworks/base/core/java/android/app/usage/UsageEvents.aidl \
+29 −0
Original line number Original line Diff line number Diff line
@@ -7178,6 +7178,35 @@ package android.app.job {
package android.app.usage {
package android.app.usage {
  public final class CacheQuotaHint implements android.os.Parcelable {
    ctor public CacheQuotaHint(android.app.usage.CacheQuotaHint.Builder);
    method public int describeContents();
    method public long getQuota();
    method public int getUid();
    method public android.app.usage.UsageStats getUsageStats();
    method public java.lang.String getVolumeUuid();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.app.usage.CacheQuotaHint> CREATOR;
    field public static final long QUOTA_NOT_SET = -1L; // 0xffffffffffffffffL
  }
  public static final class CacheQuotaHint.Builder {
    ctor public CacheQuotaHint.Builder();
    ctor public CacheQuotaHint.Builder(android.app.usage.CacheQuotaHint);
    method public android.app.usage.CacheQuotaHint build();
    method public android.app.usage.CacheQuotaHint.Builder setQuota(long);
    method public android.app.usage.CacheQuotaHint.Builder setUid(int);
    method public android.app.usage.CacheQuotaHint.Builder setUsageStats(android.app.usage.UsageStats);
    method public android.app.usage.CacheQuotaHint.Builder setVolumeUuid(java.lang.String);
  }
  public abstract class CacheQuotaService extends android.app.Service {
    ctor public CacheQuotaService();
    method public android.os.IBinder onBind(android.content.Intent);
    method public abstract java.util.List<android.app.usage.CacheQuotaHint> onComputeCacheQuotaHints(java.util.List<android.app.usage.CacheQuotaHint>);
    field public static final java.lang.String SERVICE_INTERFACE = "android.app.usage.CacheQuotaService";
  }
  public final class ConfigurationStats implements android.os.Parcelable {
  public final class ConfigurationStats implements android.os.Parcelable {
    ctor public ConfigurationStats(android.app.usage.ConfigurationStats);
    ctor public ConfigurationStats(android.app.usage.ConfigurationStats);
    method public int describeContents();
    method public int describeContents();
+20 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2017 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 android.app.usage;

/** {@hide} */
parcelable CacheQuotaHint;
 No newline at end of file
+142 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2017 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 android.app.usage;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;

import com.android.internal.util.Preconditions;

/**
 * CacheQuotaRequest represents a triplet of a uid, the volume UUID it is stored upon, and
 * its usage stats. When processed, it obtains a cache quota as defined by the system which
 * allows apps to understand how much cache to use.
 * {@hide}
 */
@SystemApi
public final class CacheQuotaHint implements Parcelable {
    public static final long QUOTA_NOT_SET = -1;
    private final String mUuid;
    private final int mUid;
    private final UsageStats mUsageStats;
    private final long mQuota;

    /**
     * Create a new request.
     * @param builder A builder for this object.
     */
    public CacheQuotaHint(Builder builder) {
        this.mUuid = builder.mUuid;
        this.mUid = builder.mUid;
        this.mUsageStats = builder.mUsageStats;
        this.mQuota = builder.mQuota;
    }

    public String getVolumeUuid() {
        return mUuid;
    }

    public int getUid() {
        return mUid;
    }

    public long getQuota() {
        return mQuota;
    }

    public UsageStats getUsageStats() {
        return mUsageStats;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mUuid);
        dest.writeInt(mUid);
        dest.writeLong(mQuota);
        dest.writeParcelable(mUsageStats, 0);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final class Builder {
        private String mUuid;
        private int mUid;
        private UsageStats mUsageStats;
        private long mQuota;

        public Builder() {
        }

        public Builder(CacheQuotaHint hint) {
            setVolumeUuid(hint.getVolumeUuid());
            setUid(hint.getUid());
            setUsageStats(hint.getUsageStats());
            setQuota(hint.getQuota());
        }

        public @NonNull Builder setVolumeUuid(@Nullable String uuid) {
            mUuid = uuid;
            return this;
        }

        public @NonNull Builder setUid(int uid) {
            Preconditions.checkArgumentPositive(uid, "Proposed uid was not positive.");
            mUid = uid;
            return this;
        }

        public @NonNull Builder setUsageStats(@Nullable UsageStats stats) {
            mUsageStats = stats;
            return this;
        }

        public @NonNull Builder setQuota(long quota) {
            Preconditions.checkArgument((quota >= QUOTA_NOT_SET));
            mQuota = quota;
            return this;
        }

        public @NonNull CacheQuotaHint build() {
            Preconditions.checkNotNull(mUsageStats);
            return new CacheQuotaHint(this);
        }
    }

    public static final Parcelable.Creator<CacheQuotaHint> CREATOR =
            new Creator<CacheQuotaHint>() {
                @Override
                public CacheQuotaHint createFromParcel(Parcel in) {
                    final Builder builder = new Builder();
                    return builder.setVolumeUuid(in.readString())
                            .setUid(in.readInt())
                            .setQuota(in.readLong())
                            .setUsageStats(in.readParcelable(UsageStats.class.getClassLoader()))
                            .build();
                }

                @Override
                public CacheQuotaHint[] newArray(int size) {
                    return new CacheQuotaHint[size];
                }
            };
}
+112 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2017 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 android.app.usage;

import android.annotation.SystemApi;
import android.app.Service;
import android.app.usage.ICacheQuotaService;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteCallback;
import android.util.Log;
import android.util.Pair;

import java.util.List;

/**
 * CacheQuoteService defines a service which accepts cache quota requests and processes them,
 * thereby filling out how much quota each request deserves.
 * {@hide}
 */
@SystemApi
public abstract class CacheQuotaService extends Service {
    private static final String TAG = "CacheQuotaService";

    /**
     * The {@link Intent} action that must be declared as handled by a service
     * in its manifest for the system to recognize it as a quota providing service.
     */
    public static final String SERVICE_INTERFACE = "android.app.usage.CacheQuotaService";

    /** {@hide} **/
    public static final String REQUEST_LIST_KEY = "requests";

    private CacheQuotaServiceWrapper mWrapper;
    private Handler mHandler;

    @Override
    public void onCreate() {
        super.onCreate();
        mWrapper = new CacheQuotaServiceWrapper();
        mHandler = new ServiceHandler(getMainLooper());
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mWrapper;
    }

    /**
     * Processes the cache quota list upon receiving a list of requests.
     * @param requests A list of cache quotas to fulfill.
     * @return A completed list of cache quota requests.
     */
    public abstract List<CacheQuotaHint> onComputeCacheQuotaHints(
            List<CacheQuotaHint> requests);

    private final class CacheQuotaServiceWrapper extends ICacheQuotaService.Stub {
        @Override
        public void computeCacheQuotaHints(
                RemoteCallback callback, List<CacheQuotaHint> requests) {
            final Pair<RemoteCallback, List<CacheQuotaHint>> pair =
                    Pair.create(callback, requests);
            Message msg = mHandler.obtainMessage(ServiceHandler.MSG_SEND_LIST, pair);
            mHandler.sendMessage(msg);
        }
    }

    private final class ServiceHandler extends Handler {
        public static final int MSG_SEND_LIST = 1;

        public ServiceHandler(Looper looper) {
            super(looper, null, true);
        }

        @Override
        public void handleMessage(Message msg) {
            final int action = msg.what;
            switch (action) {
                case MSG_SEND_LIST:
                    final Pair<RemoteCallback, List<CacheQuotaHint>> pair =
                            (Pair<RemoteCallback, List<CacheQuotaHint>>) msg.obj;
                    List<CacheQuotaHint> processed = onComputeCacheQuotaHints(pair.second);
                    final Bundle data = new Bundle();
                    data.putParcelableList(REQUEST_LIST_KEY, processed);

                    final RemoteCallback callback = pair.first;
                    callback.sendResult(data);
                    break;
                default:
                    Log.w(TAG, "Handling unknown message: " + action);
            }
        }
    }
}
Loading