From d225f0bfd5bc8e7767dc000ea5174bddd95bdea8 Mon Sep 17 00:00:00 2001 From: Fyodor Kupolov Date: Mon, 20 Mar 2017 16:04:57 -0700 Subject: Support expiration of preloaded file cache When device is provisioned, we delete all files from /data/preloads except file_cache. We should do best effort to keep file_cache during the first config_keepPreloadsMinDays. After that, persist.sys.preloads.file_cache_expired is set to 1, which indicates that cache can be deleted when additional storage space is requested. Bug: 34690396 Test: Manual + RetailDemoModeServiceTest Change-Id: Ie584a9dd6689bcc5e6b3cb448e95dfe5f73d2eeb --- core/res/AndroidManifest.xml | 4 + core/res/res/values/config.xml | 4 + core/res/res/values/symbols.xml | 1 + .../PreloadsFileCacheExpirationJobService.java | 85 ++++++++++++++++++++++ .../server/retaildemo/RetailDemoModeService.java | 7 ++ .../retaildemo/RetailDemoModeServiceTest.java | 11 +++ 6 files changed, 112 insertions(+) create mode 100644 services/core/java/com/android/server/PreloadsFileCacheExpirationJobService.java diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 58e4051b5111..248e66e21384 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3606,6 +3606,10 @@ android:permission="android.permission.BIND_JOB_SERVICE" > + + + diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 67a3aeec6547..68e766e46279 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2691,6 +2691,10 @@ + + 7 + false diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index f9fe333cae7f..4ef3922aa1fe 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -421,6 +421,7 @@ + diff --git a/services/core/java/com/android/server/PreloadsFileCacheExpirationJobService.java b/services/core/java/com/android/server/PreloadsFileCacheExpirationJobService.java new file mode 100644 index 000000000000..6fd0256fcbfd --- /dev/null +++ b/services/core/java/com/android/server/PreloadsFileCacheExpirationJobService.java @@ -0,0 +1,85 @@ +/* + * 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 com.android.server; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.content.res.Resources; +import android.os.Environment; +import android.os.SystemProperties; +import android.util.Slog; +import android.util.TimeUtils; + +import com.android.internal.R; + +import java.util.concurrent.TimeUnit; + +/** + * {@link JobService} that marks + * {@link Environment#getDataPreloadsFileCacheDirectory() preloaded file cache} as expired after a + * pre-configured timeout. + */ +public class PreloadsFileCacheExpirationJobService extends JobService { + private static final boolean DEBUG = false; // Do not submit with true + private static final String TAG = "PreloadsFileCacheExpirationJobService"; + + // TODO move all JOB_IDs into a single class to avoid collisions + private static final int JOB_ID = 100500; + + private static final String PERSIST_SYS_PRELOADS_FILE_CACHE_EXPIRED + = "persist.sys.preloads.file_cache_expired"; + + public static void schedule(Context context) { + int keepPreloadsMinDays = Resources.getSystem().getInteger( + R.integer.config_keepPreloadsMinDays); // Default is 1 week + long keepPreloadsMinTimeoutMs = DEBUG ? TimeUnit.MINUTES.toMillis(2) + : TimeUnit.DAYS.toMillis(keepPreloadsMinDays); + long keepPreloadsMaxTimeoutMs = DEBUG ? TimeUnit.MINUTES.toMillis(3) + : TimeUnit.DAYS.toMillis(keepPreloadsMinDays + 1); + + if (DEBUG) { + StringBuilder sb = new StringBuilder("Scheduling expiration job to run in "); + TimeUtils.formatDuration(keepPreloadsMinTimeoutMs, sb); + Slog.i(TAG, sb.toString()); + } + JobInfo expirationJob = new JobInfo.Builder(JOB_ID, + new ComponentName(context, PreloadsFileCacheExpirationJobService.class)) + .setPersisted(true) + .setMinimumLatency(keepPreloadsMinTimeoutMs) + .setOverrideDeadline(keepPreloadsMaxTimeoutMs) + .build(); + + JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); + jobScheduler.schedule(expirationJob); + } + + @Override + public boolean onStartJob(JobParameters params) { + SystemProperties.set(PERSIST_SYS_PRELOADS_FILE_CACHE_EXPIRED, "1"); + Slog.i(TAG, "Set " + PERSIST_SYS_PRELOADS_FILE_CACHE_EXPIRED + "=1"); + return false; + } + + @Override + public boolean onStopJob(JobParameters params) { + return false; + } +} diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java index 472f98455d6a..43c38a6422a4 100644 --- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java +++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java @@ -69,6 +69,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; +import com.android.server.PreloadsFileCacheExpirationJobService; import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.am.ActivityManagerService; @@ -259,6 +260,7 @@ public class RetailDemoModeService extends SystemService { if (!deletePreloadsFolderContents()) { Slog.w(TAG, "Failed to delete preloads folder contents"); } + PreloadsFileCacheExpirationJobService.schedule(mInjector.getContext()); }); stopDemoMode(); @@ -443,6 +445,11 @@ public class RetailDemoModeService extends SystemService { mInjector.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0; } + /** + * Deletes contents of {@link Environment#getDataPreloadsDirectory()}, + * but leave {@link Environment#getDataPreloadsFileCacheDirectory()} + * @return true if contents was sucessfully deleted + */ private boolean deletePreloadsFolderContents() { final File dir = mInjector.getDataPreloadsDirectory(); final File[] files = FileUtils.listFilesOrEmpty(dir); diff --git a/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java index d18457b79c55..ce5b8cbb9bed 100644 --- a/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java @@ -33,6 +33,8 @@ import android.app.ActivityManagerInternal; import android.app.Notification; import android.app.NotificationManager; import android.app.RetailDemoModeServiceInternal; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -97,6 +99,7 @@ public class RetailDemoModeServiceTest { private @Mock AudioManager mAudioManager; private @Mock WifiManager mWifiManager; private @Mock LockPatternUtils mLockPatternUtils; + private @Mock JobScheduler mJobScheduler; private MockPreloadAppsInstaller mPreloadAppsInstaller; private MockContentResolver mContentResolver; private MockContactsProvider mContactsProvider; @@ -110,6 +113,12 @@ public class RetailDemoModeServiceTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + Context originalContext = InstrumentationRegistry.getContext(); + when(mContext.getApplicationInfo()).thenReturn(originalContext.getApplicationInfo()); + when(mContext.getResources()).thenReturn(originalContext.getResources()); + when(mContext.getSystemServiceName(eq(JobScheduler.class))).thenReturn( + Context.JOB_SCHEDULER_SERVICE); + when(mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE)).thenReturn(mJobScheduler); mContentResolver = new MockContentResolver(mContext); mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); mContactsProvider = new MockContactsProvider(mContext); @@ -218,6 +227,8 @@ public class RetailDemoModeServiceTest { // verify that the preloaded directory is emptied. assertEquals("Preloads directory is not emptied", 0, mTestPreloadsDir.list().length); + // Verify that the expiration job was scheduled + verify(mJobScheduler).schedule(any(JobInfo.class)); } @Test -- cgit v1.2.3-59-g8ed1b