diff options
author | Justin Klaassen <justinklaassen@google.com> | 2017-09-15 17:58:39 -0400 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2017-09-15 17:58:39 -0400 |
commit | 10d07c88d69cc64f73a069163e7ea5ba2519a099 (patch) | |
tree | 8dbd149eb350320a29c3d10e7ad3201de1c5cbee /android/ext | |
parent | 677516fb6b6f207d373984757d3d9450474b6b00 (diff) | |
download | android-28-10d07c88d69cc64f73a069163e7ea5ba2519a099.tar.gz |
Import Android SDK Platform PI [4335822]
/google/data/ro/projects/android/fetch_artifact \
--bid 4335822 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4335822.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: Ic8f04be005a71c2b9abeaac754d8da8d6f9a2c32
Diffstat (limited to 'android/ext')
-rw-r--r-- | android/ext/services/Version.java | 36 | ||||
-rw-r--r-- | android/ext/services/resolver/LRResolverRankerService.java | 199 | ||||
-rw-r--r-- | android/ext/services/storage/CacheQuotaServiceImpl.java | 143 | ||||
-rw-r--r-- | android/ext/shared/Version.java | 36 |
4 files changed, 414 insertions, 0 deletions
diff --git a/android/ext/services/Version.java b/android/ext/services/Version.java new file mode 100644 index 00000000..026cccd3 --- /dev/null +++ b/android/ext/services/Version.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2016 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.ext.services; + +/** + * Class that provides the version of the library. + */ +public final class Version { + + private Version() { + /* do nothing - hide constructor */ + } + + /** + * Gets the version of the library. + * + * @return The version. + */ + public static int getVersionCode() { + return 1; + } +}
\ No newline at end of file diff --git a/android/ext/services/resolver/LRResolverRankerService.java b/android/ext/services/resolver/LRResolverRankerService.java new file mode 100644 index 00000000..9d7a5689 --- /dev/null +++ b/android/ext/services/resolver/LRResolverRankerService.java @@ -0,0 +1,199 @@ +/* + * 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.ext.services.resolver; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Environment; +import android.os.IBinder; +import android.os.storage.StorageManager; +import android.service.resolver.ResolverRankerService; +import android.service.resolver.ResolverTarget; +import android.util.ArrayMap; +import android.util.Log; + +import java.io.File; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * A Logistic Regression based {@link android.service.resolver.ResolverRankerService}, to be used + * in {@link ResolverComparator}. + */ +public final class LRResolverRankerService extends ResolverRankerService { + private static final String TAG = "LRResolverRankerService"; + + private static final boolean DEBUG = false; + + private static final String PARAM_SHARED_PREF_NAME = "resolver_ranker_params"; + private static final String BIAS_PREF_KEY = "bias"; + private static final String VERSION_PREF_KEY = "version"; + + private static final String LAUNCH_SCORE = "launch"; + private static final String TIME_SPENT_SCORE = "timeSpent"; + private static final String RECENCY_SCORE = "recency"; + private static final String CHOOSER_SCORE = "chooser"; + + // parameters for a pre-trained model, to initialize the app ranker. When updating the + // pre-trained model, please update these params, as well as initModel(). + private static final int CURRENT_VERSION = 1; + private static final float LEARNING_RATE = 0.0001f; + private static final float REGULARIZER_PARAM = 0.0001f; + + private SharedPreferences mParamSharedPref; + private ArrayMap<String, Float> mFeatureWeights; + private float mBias; + + @Override + public IBinder onBind(Intent intent) { + initModel(); + return super.onBind(intent); + } + + @Override + public void onPredictSharingProbabilities(List<ResolverTarget> targets) { + final int size = targets.size(); + for (int i = 0; i < size; ++i) { + ResolverTarget target = targets.get(i); + ArrayMap<String, Float> features = getFeatures(target); + target.setSelectProbability(predict(features)); + } + } + + @Override + public void onTrainRankingModel(List<ResolverTarget> targets, int selectedPosition) { + final int size = targets.size(); + if (selectedPosition < 0 || selectedPosition >= size) { + if (DEBUG) { + Log.d(TAG, "Invalid Position of Selected App " + selectedPosition); + } + return; + } + final ArrayMap<String, Float> positive = getFeatures(targets.get(selectedPosition)); + final float positiveProbability = targets.get(selectedPosition).getSelectProbability(); + final int targetSize = targets.size(); + for (int i = 0; i < targetSize; ++i) { + if (i == selectedPosition) { + continue; + } + final ArrayMap<String, Float> negative = getFeatures(targets.get(i)); + final float negativeProbability = targets.get(i).getSelectProbability(); + if (negativeProbability > positiveProbability) { + update(negative, negativeProbability, false); + update(positive, positiveProbability, true); + } + } + commitUpdate(); + } + + private void initModel() { + mParamSharedPref = getParamSharedPref(); + mFeatureWeights = new ArrayMap<>(4); + if (mParamSharedPref == null || + mParamSharedPref.getInt(VERSION_PREF_KEY, 0) < CURRENT_VERSION) { + // Initializing the app ranker to a pre-trained model. When updating the pre-trained + // model, please increment CURRENT_VERSION, and update LEARNING_RATE and + // REGULARIZER_PARAM. + mBias = -1.6568f; + mFeatureWeights.put(LAUNCH_SCORE, 2.5543f); + mFeatureWeights.put(TIME_SPENT_SCORE, 2.8412f); + mFeatureWeights.put(RECENCY_SCORE, 0.269f); + mFeatureWeights.put(CHOOSER_SCORE, 4.2222f); + } else { + mBias = mParamSharedPref.getFloat(BIAS_PREF_KEY, 0.0f); + mFeatureWeights.put(LAUNCH_SCORE, mParamSharedPref.getFloat(LAUNCH_SCORE, 0.0f)); + mFeatureWeights.put( + TIME_SPENT_SCORE, mParamSharedPref.getFloat(TIME_SPENT_SCORE, 0.0f)); + mFeatureWeights.put(RECENCY_SCORE, mParamSharedPref.getFloat(RECENCY_SCORE, 0.0f)); + mFeatureWeights.put(CHOOSER_SCORE, mParamSharedPref.getFloat(CHOOSER_SCORE, 0.0f)); + } + } + + private ArrayMap<String, Float> getFeatures(ResolverTarget target) { + ArrayMap<String, Float> features = new ArrayMap<>(4); + features.put(RECENCY_SCORE, target.getRecencyScore()); + features.put(TIME_SPENT_SCORE, target.getTimeSpentScore()); + features.put(LAUNCH_SCORE, target.getLaunchScore()); + features.put(CHOOSER_SCORE, target.getChooserScore()); + return features; + } + + private float predict(ArrayMap<String, Float> target) { + if (target == null) { + return 0.0f; + } + final int featureSize = target.size(); + float sum = 0.0f; + for (int i = 0; i < featureSize; i++) { + String featureName = target.keyAt(i); + float weight = mFeatureWeights.getOrDefault(featureName, 0.0f); + sum += weight * target.valueAt(i); + } + return (float) (1.0 / (1.0 + Math.exp(-mBias - sum))); + } + + private void update(ArrayMap<String, Float> target, float predict, boolean isSelected) { + if (target == null) { + return; + } + final int featureSize = target.size(); + float error = isSelected ? 1.0f - predict : -predict; + for (int i = 0; i < featureSize; i++) { + String featureName = target.keyAt(i); + float currentWeight = mFeatureWeights.getOrDefault(featureName, 0.0f); + mBias += LEARNING_RATE * error; + currentWeight = currentWeight - LEARNING_RATE * REGULARIZER_PARAM * currentWeight + + LEARNING_RATE * error * target.valueAt(i); + mFeatureWeights.put(featureName, currentWeight); + } + if (DEBUG) { + Log.d(TAG, "Weights: " + mFeatureWeights + " Bias: " + mBias); + } + } + + private void commitUpdate() { + try { + SharedPreferences.Editor editor = mParamSharedPref.edit(); + editor.putFloat(BIAS_PREF_KEY, mBias); + final int size = mFeatureWeights.size(); + for (int i = 0; i < size; i++) { + editor.putFloat(mFeatureWeights.keyAt(i), mFeatureWeights.valueAt(i)); + } + editor.putInt(VERSION_PREF_KEY, CURRENT_VERSION); + editor.apply(); + } catch (Exception e) { + Log.e(TAG, "Failed to commit update" + e); + } + } + + private SharedPreferences getParamSharedPref() { + // The package info in the context isn't initialized in the way it is for normal apps, + // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we + // build the path manually below using the same policy that appears in ContextImpl. + if (DEBUG) { + Log.d(TAG, "Context Package Name: " + getPackageName()); + } + final File prefsFile = new File(new File( + Environment.getDataUserCePackageDirectory( + StorageManager.UUID_PRIVATE_INTERNAL, getUserId(), getPackageName()), + "shared_prefs"), + PARAM_SHARED_PREF_NAME + ".xml"); + return getSharedPreferences(prefsFile, Context.MODE_PRIVATE); + } +}
\ No newline at end of file diff --git a/android/ext/services/storage/CacheQuotaServiceImpl.java b/android/ext/services/storage/CacheQuotaServiceImpl.java new file mode 100644 index 00000000..862f50b2 --- /dev/null +++ b/android/ext/services/storage/CacheQuotaServiceImpl.java @@ -0,0 +1,143 @@ +/* + * 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.ext.services.storage; + +import android.app.usage.CacheQuotaHint; +import android.app.usage.CacheQuotaService; +import android.os.Environment; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; +import android.util.ArrayMap; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * CacheQuotaServiceImpl implements the CacheQuotaService with a strategy for populating the quota + * of {@link CacheQuotaHint}. + */ +public class CacheQuotaServiceImpl extends CacheQuotaService { + private static final double CACHE_RESERVE_RATIO = 0.15; + + @Override + public List<CacheQuotaHint> onComputeCacheQuotaHints(List<CacheQuotaHint> requests) { + ArrayMap<String, List<CacheQuotaHint>> byUuid = new ArrayMap<>(); + final int requestCount = requests.size(); + for (int i = 0; i < requestCount; i++) { + CacheQuotaHint request = requests.get(i); + String uuid = request.getVolumeUuid(); + List<CacheQuotaHint> listForUuid = byUuid.get(uuid); + if (listForUuid == null) { + listForUuid = new ArrayList<>(); + byUuid.put(uuid, listForUuid); + } + listForUuid.add(request); + } + + List<CacheQuotaHint> processed = new ArrayList<>(); + byUuid.entrySet().forEach( + requestListEntry -> { + // Collapse all usage stats to the same uid. + Map<Integer, List<CacheQuotaHint>> byUid = requestListEntry.getValue() + .stream() + .collect(Collectors.groupingBy(CacheQuotaHint::getUid)); + byUid.values().forEach(uidGroupedList -> { + int size = uidGroupedList.size(); + if (size < 2) { + return; + } + CacheQuotaHint first = uidGroupedList.get(0); + for (int i = 1; i < size; i++) { + /* Note: We can't use the UsageStats built-in addition function because + UIDs may span multiple packages and usage stats adding has + matching package names as a precondition. */ + first.getUsageStats().mTotalTimeInForeground += + uidGroupedList.get(i).getUsageStats().mTotalTimeInForeground; + } + }); + + // Because the foreground stats have been added to the first element, we need + // a list of only the first values (which contain the merged foreground time). + List<CacheQuotaHint> flattenedRequests = + byUid.values() + .stream() + .map(entryList -> entryList.get(0)) + .filter(entry -> entry.getUsageStats().mTotalTimeInForeground != 0) + .sorted(sCacheQuotaRequestComparator) + .collect(Collectors.toList()); + + // Because the elements are sorted, we can use the index to also be the sorted + // index for cache quota calculation. + double sum = getSumOfFairShares(flattenedRequests.size()); + String uuid = requestListEntry.getKey(); + long reservedSize = getReservedCacheSize(uuid); + for (int count = 0; count < flattenedRequests.size(); count++) { + double share = getFairShareForPosition(count) / sum; + CacheQuotaHint entry = flattenedRequests.get(count); + CacheQuotaHint.Builder builder = new CacheQuotaHint.Builder(entry); + builder.setQuota(Math.round(share * reservedSize)); + processed.add(builder.build()); + } + } + ); + + return processed.stream() + .filter(request -> request.getQuota() > 0).collect(Collectors.toList()); + } + + private double getFairShareForPosition(int position) { + double value = 1.0 / Math.log(position + 3) - 0.285; + return (value > 0.01) ? value : 0.01; + } + + private double getSumOfFairShares(int size) { + double sum = 0; + for (int i = 0; i < size; i++) { + sum += getFairShareForPosition(i); + } + return sum; + } + + private long getReservedCacheSize(String uuid) { + // TODO: Revisit the cache size after running more storage tests. + // TODO: Figure out how to ensure ExtServices has the permissions to call + // StorageStatsManager, because this is ignoring the cache... + StorageManager storageManager = getSystemService(StorageManager.class); + long freeBytes = 0; + if (uuid == StorageManager.UUID_PRIVATE_INTERNAL) { // regular equals because of null + freeBytes = Environment.getDataDirectory().getUsableSpace(); + } else { + final VolumeInfo vol = storageManager.findVolumeByUuid(uuid); + freeBytes = vol.getPath().getUsableSpace(); + } + return Math.round(freeBytes * CACHE_RESERVE_RATIO); + } + + // Compares based upon foreground time. + private static Comparator<CacheQuotaHint> sCacheQuotaRequestComparator = + new Comparator<CacheQuotaHint>() { + @Override + public int compare(CacheQuotaHint o, CacheQuotaHint t1) { + long x = t1.getUsageStats().getTotalTimeInForeground(); + long y = o.getUsageStats().getTotalTimeInForeground(); + return (x < y) ? -1 : ((x == y) ? 0 : 1); + } + }; +} diff --git a/android/ext/shared/Version.java b/android/ext/shared/Version.java new file mode 100644 index 00000000..3ff2d415 --- /dev/null +++ b/android/ext/shared/Version.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2016 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.ext.shared; + +/** + * Class that provides the version of the library. + */ +public final class Version { + + private Version() { + /* do nothing - hide constructor */ + } + + /** + * Gets the version of the library. + * + * @return The version. + */ + public static int getVersionCode() { + return 1; + } +}
\ No newline at end of file |