Merge "Local and remote isolated storage feature flags."
This commit is contained in:
@@ -282,14 +282,31 @@ public final class Sm {
|
||||
StorageManager.DEBUG_VIRTUAL_DISK);
|
||||
}
|
||||
|
||||
public void runIsolatedStorage() throws RemoteException {
|
||||
final boolean enableIsolatedStorage = Boolean.parseBoolean(nextArg());
|
||||
public void runIsolatedStorage() {
|
||||
final int value;
|
||||
final int mask = StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON
|
||||
| StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF;
|
||||
switch (nextArg()) {
|
||||
case "on":
|
||||
case "true":
|
||||
value = StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON;
|
||||
break;
|
||||
case "off":
|
||||
value = StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF;
|
||||
break;
|
||||
case "default":
|
||||
case "false":
|
||||
value = 0;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggling isolated-storage state will result in a device reboot. So to avoid this command
|
||||
// from erroring out (DeadSystemException), call setDebugFlags() in a separate thread.
|
||||
new Thread(() -> {
|
||||
try {
|
||||
mSm.setDebugFlags(enableIsolatedStorage ? StorageManager.DEBUG_ISOLATED_STORAGE : 0,
|
||||
StorageManager.DEBUG_ISOLATED_STORAGE);
|
||||
mSm.setDebugFlags(value, mask);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Encountered an error!", e);
|
||||
}
|
||||
@@ -334,7 +351,7 @@ public final class Sm {
|
||||
System.err.println("");
|
||||
System.err.println(" sm set-emulate-fbe [true|false]");
|
||||
System.err.println("");
|
||||
System.err.println(" sm set-isolated-storage [true|false]");
|
||||
System.err.println(" sm set-isolated-storage [on|off|default]");
|
||||
System.err.println("");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ import android.util.Slog;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.SizedInputStream;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
@@ -110,8 +111,6 @@ public class FileUtils {
|
||||
public static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+");
|
||||
}
|
||||
|
||||
private static final File[] EMPTY = new File[0];
|
||||
|
||||
// non-final so it can be toggled by Robolectric's ShadowFileUtils
|
||||
private static boolean sEnableCopyOptimizations = true;
|
||||
|
||||
@@ -1164,35 +1163,20 @@ public class FileUtils {
|
||||
|
||||
/** {@hide} */
|
||||
public static @NonNull String[] listOrEmpty(@Nullable File dir) {
|
||||
if (dir == null) return EmptyArray.STRING;
|
||||
final String[] res = dir.list();
|
||||
if (res != null) {
|
||||
return res;
|
||||
} else {
|
||||
return EmptyArray.STRING;
|
||||
}
|
||||
return (dir != null) ? ArrayUtils.defeatNullable(dir.list())
|
||||
: EmptyArray.STRING;
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
|
||||
if (dir == null) return EMPTY;
|
||||
final File[] res = dir.listFiles();
|
||||
if (res != null) {
|
||||
return res;
|
||||
} else {
|
||||
return EMPTY;
|
||||
}
|
||||
return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
|
||||
: ArrayUtils.EMPTY_FILE;
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public static @NonNull File[] listFilesOrEmpty(@Nullable File dir, FilenameFilter filter) {
|
||||
if (dir == null) return EMPTY;
|
||||
final File[] res = dir.listFiles(filter);
|
||||
if (res != null) {
|
||||
return res;
|
||||
} else {
|
||||
return EMPTY;
|
||||
}
|
||||
return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles(filter))
|
||||
: ArrayUtils.EMPTY_FILE;
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
|
||||
@@ -25,9 +25,14 @@ import android.util.MutableInt;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import libcore.util.HexEncoding;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Gives access to the system properties store. The system properties
|
||||
@@ -232,6 +237,27 @@ public class SystemProperties {
|
||||
native_report_sysprop_change();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@code SHA-1} digest of the given keys and their values as a
|
||||
* hex-encoded string. The ordering of the incoming keys doesn't change the
|
||||
* digest result.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static @NonNull String digestOf(@NonNull String... keys) {
|
||||
Arrays.sort(keys);
|
||||
try {
|
||||
final MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
for (String key : keys) {
|
||||
final String item = key + "=" + get(key) + "\n";
|
||||
digest.update(item.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
return HexEncoding.encodeToString(digest.digest()).toLowerCase();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private SystemProperties() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +226,9 @@ public class StorageManager {
|
||||
/** {@hide} */
|
||||
public static final int DEBUG_VIRTUAL_DISK = 1 << 5;
|
||||
/** {@hide} */
|
||||
public static final int DEBUG_ISOLATED_STORAGE = 1 << 6;
|
||||
public static final int DEBUG_ISOLATED_STORAGE_FORCE_ON = 1 << 6;
|
||||
/** {@hide} */
|
||||
public static final int DEBUG_ISOLATED_STORAGE_FORCE_OFF = 1 << 7;
|
||||
|
||||
/** {@hide} */
|
||||
public static final int FLAG_STORAGE_DE = IInstalld.FLAG_STORAGE_DE;
|
||||
|
||||
@@ -12964,6 +12964,11 @@ public final class Settings {
|
||||
public static final String CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED =
|
||||
"content_capture_service_explicitly_enabled";
|
||||
|
||||
/** {@hide} */
|
||||
public static final String ISOLATED_STORAGE_LOCAL = "isolated_storage_local";
|
||||
/** {@hide} */
|
||||
public static final String ISOLATED_STORAGE_REMOTE = "isolated_storage_remote";
|
||||
|
||||
/**
|
||||
* Settings to backup. This is here so that it's in the same place as the settings
|
||||
* keys and easy to update.
|
||||
|
||||
@@ -24,6 +24,7 @@ import dalvik.system.VMRuntime;
|
||||
|
||||
import libcore.util.EmptyArray;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -42,6 +43,8 @@ public class ArrayUtils {
|
||||
private static final int CACHE_SIZE = 73;
|
||||
private static Object[] sCache = new Object[CACHE_SIZE];
|
||||
|
||||
public static final File[] EMPTY_FILE = new File[0];
|
||||
|
||||
private ArrayUtils() { /* cannot be instantiated */ }
|
||||
|
||||
public static byte[] newUnpaddedByteArray(int minLen) {
|
||||
@@ -645,6 +648,10 @@ public class ArrayUtils {
|
||||
return (val != null) ? val : EmptyArray.STRING;
|
||||
}
|
||||
|
||||
public static @NonNull File[] defeatNullable(@Nullable File[] val) {
|
||||
return (val != null) ? val : EMPTY_FILE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws {@link ArrayIndexOutOfBoundsException} if the index is out of bounds.
|
||||
*
|
||||
|
||||
@@ -33,7 +33,6 @@ public class OsTests {
|
||||
suite.addTestSuite(MessageQueueTest.class);
|
||||
suite.addTestSuite(MessengerTest.class);
|
||||
suite.addTestSuite(PatternMatcherTest.class);
|
||||
suite.addTestSuite(SystemPropertiesTest.class);
|
||||
|
||||
return suite;
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2006 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.os;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import android.os.SystemProperties;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
public class SystemPropertiesTest extends TestCase {
|
||||
private static final String KEY = "com.android.frameworks.coretests";
|
||||
@SmallTest
|
||||
public void testProperties() throws Exception {
|
||||
if (false) {
|
||||
String value;
|
||||
|
||||
SystemProperties.set(KEY, "");
|
||||
value = SystemProperties.get(KEY, "default");
|
||||
assertEquals("default", value);
|
||||
|
||||
SystemProperties.set(KEY, "AAA");
|
||||
value = SystemProperties.get(KEY, "default");
|
||||
assertEquals("AAA", value);
|
||||
|
||||
value = SystemProperties.get(KEY);
|
||||
assertEquals("AAA", value);
|
||||
|
||||
SystemProperties.set(KEY, "");
|
||||
value = SystemProperties.get(KEY, "default");
|
||||
assertEquals("default", value);
|
||||
|
||||
value = SystemProperties.get(KEY);
|
||||
assertEquals("", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -543,7 +543,9 @@ public class SettingsBackupTest {
|
||||
Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
|
||||
Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS,
|
||||
Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS,
|
||||
Settings.Global.BACKUP_MULTI_USER_ENABLED);
|
||||
Settings.Global.BACKUP_MULTI_USER_ENABLED,
|
||||
Settings.Global.ISOLATED_STORAGE_LOCAL,
|
||||
Settings.Global.ISOLATED_STORAGE_REMOTE);
|
||||
private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
|
||||
newHashSet(
|
||||
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
package android.os;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import android.os.SystemProperties;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class SystemPropertiesTest extends TestCase {
|
||||
private static final String KEY = "sys.testkey";
|
||||
@@ -188,4 +188,25 @@ public class SystemPropertiesTest extends TestCase {
|
||||
fail("InterruptedException");
|
||||
}
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testDigestOf() {
|
||||
final String empty = SystemProperties.digestOf();
|
||||
final String finger = SystemProperties.digestOf("ro.build.fingerprint");
|
||||
final String fingerBrand = SystemProperties.digestOf(
|
||||
"ro.build.fingerprint", "ro.product.brand");
|
||||
final String brandFinger = SystemProperties.digestOf(
|
||||
"ro.product.brand", "ro.build.fingerprint");
|
||||
|
||||
// Shouldn't change over time
|
||||
assertTrue(Objects.equals(finger, SystemProperties.digestOf("ro.build.fingerprint")));
|
||||
|
||||
// Different properties means different results
|
||||
assertFalse(Objects.equals(empty, finger));
|
||||
assertFalse(Objects.equals(empty, fingerBrand));
|
||||
assertFalse(Objects.equals(finger, fingerBrand));
|
||||
|
||||
// Same properties means same result
|
||||
assertTrue(Objects.equals(fingerBrand, brandFinger));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ import android.app.KeyguardManager;
|
||||
import android.app.admin.SecurityLog;
|
||||
import android.app.usage.StorageStatsManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
@@ -777,6 +778,18 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
}
|
||||
});
|
||||
refreshZramSettings();
|
||||
|
||||
// Toggle isolated-enable system property in response to settings
|
||||
mContext.getContentResolver().registerContentObserver(
|
||||
Settings.Global.getUriFor(Settings.Global.ISOLATED_STORAGE_REMOTE),
|
||||
false /*notifyForDescendants*/,
|
||||
new ContentObserver(null /* current thread */) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
refreshIsolatedStorageSettings();
|
||||
}
|
||||
});
|
||||
refreshIsolatedStorageSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -802,6 +815,32 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshIsolatedStorageSettings() {
|
||||
final int local = Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.ISOLATED_STORAGE_LOCAL, 0);
|
||||
final int remote = Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.ISOLATED_STORAGE_REMOTE, 0);
|
||||
|
||||
// Walk down precedence chain; we prefer local settings first, then
|
||||
// remote settings, before finally falling back to hard-coded default.
|
||||
final boolean res;
|
||||
if (local == -1) {
|
||||
res = false;
|
||||
} else if (local == 1) {
|
||||
res = true;
|
||||
} else if (remote == -1) {
|
||||
res = false;
|
||||
} else if (remote == 1) {
|
||||
res = true;
|
||||
} else {
|
||||
res = false;
|
||||
}
|
||||
|
||||
Slog.d(TAG, "Isolated storage local flag " + local + " and remote flag "
|
||||
+ remote + " resolved to " + res);
|
||||
SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE, Boolean.toString(res));
|
||||
}
|
||||
|
||||
/**
|
||||
* MediaProvider has a ton of code that makes assumptions about storage
|
||||
* paths never changing, so we outright kill them to pick up new state.
|
||||
@@ -2208,18 +2247,22 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
if ((mask & StorageManager.DEBUG_ISOLATED_STORAGE) != 0) {
|
||||
final boolean enabled = (flags & StorageManager.DEBUG_ISOLATED_STORAGE) != 0;
|
||||
if ((mask & (StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON
|
||||
| StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF)) != 0) {
|
||||
final int value;
|
||||
if ((flags & StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON) != 0) {
|
||||
value = 1;
|
||||
} else if ((flags & StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF) != 0) {
|
||||
value = -1;
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE,
|
||||
Boolean.toString(enabled));
|
||||
|
||||
// Some of the storage related permissions get fiddled with during
|
||||
// package scanning. So, delete the package cache to force PackageManagerService
|
||||
// to do package scanning.
|
||||
FileUtils.deleteContents(Environment.getPackageCacheDirectory());
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.ISOLATED_STORAGE_LOCAL, value);
|
||||
refreshIsolatedStorageSettings();
|
||||
|
||||
// Perform hard reboot to kick policy into place
|
||||
mContext.getSystemService(PowerManager.class).reboot(null);
|
||||
@@ -3758,6 +3801,8 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
|
||||
pw.println();
|
||||
pw.println("Primary storage UUID: " + mPrimaryStorageUuid);
|
||||
|
||||
pw.println();
|
||||
final Pair<String, Long> pair = StorageManager.getPrimaryStoragePathAndSize();
|
||||
if (pair == null) {
|
||||
pw.println("Internal storage total size: N/A");
|
||||
@@ -3770,8 +3815,18 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
pw.print(DataUnit.MEBIBYTES.toBytes(pair.second));
|
||||
pw.println(" MiB)");
|
||||
}
|
||||
|
||||
pw.println();
|
||||
pw.println("Local unlocked users: " + Arrays.toString(mLocalUnlockedUsers));
|
||||
pw.println("System unlocked users: " + Arrays.toString(mSystemUnlockedUsers));
|
||||
|
||||
final ContentResolver cr = mContext.getContentResolver();
|
||||
pw.println();
|
||||
pw.println("Isolated storage, local feature flag: "
|
||||
+ Settings.Global.getInt(cr, Settings.Global.ISOLATED_STORAGE_LOCAL, 0));
|
||||
pw.println("Isolated storage, remote feature flag: "
|
||||
+ Settings.Global.getInt(cr, Settings.Global.ISOLATED_STORAGE_REMOTE, 0));
|
||||
pw.println("Isolated storage, resolved: " + StorageManager.hasIsolatedStorage());
|
||||
}
|
||||
|
||||
synchronized (mObbMounts) {
|
||||
|
||||
@@ -323,6 +323,7 @@ import dalvik.system.VMRuntime;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
import libcore.util.EmptyArray;
|
||||
import libcore.util.HexEncoding;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
@@ -590,12 +591,6 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
|
||||
public static final int REASON_LAST = REASON_SHARED;
|
||||
|
||||
/**
|
||||
* Version number for the package parser cache. Increment this whenever the format or
|
||||
* extent of cached data changes. See {@code PackageParser#setCacheDir}.
|
||||
*/
|
||||
private static final String PACKAGE_PARSER_CACHE_VERSION = "1";
|
||||
|
||||
/**
|
||||
* Whether the package parser cache is enabled.
|
||||
*/
|
||||
@@ -2329,7 +2324,7 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
mCacheDir = preparePackageParserCache(mIsUpgrade);
|
||||
mCacheDir = preparePackageParserCache();
|
||||
|
||||
// Set flag to monitor and not change apk file paths when
|
||||
// scanning install directories.
|
||||
@@ -3196,7 +3191,7 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
setUpInstantAppInstallerActivityLP(getInstantAppInstallerLPr());
|
||||
}
|
||||
|
||||
private static File preparePackageParserCache(boolean isUpgrade) {
|
||||
private static @Nullable File preparePackageParserCache() {
|
||||
if (!DEFAULT_PACKAGE_PARSER_CACHE_ENABLED) {
|
||||
return null;
|
||||
}
|
||||
@@ -3217,17 +3212,25 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
return null;
|
||||
}
|
||||
|
||||
// If this is a system upgrade scenario, delete the contents of the package cache dir.
|
||||
// This also serves to "GC" unused entries when the package cache version changes (which
|
||||
// can only happen during upgrades).
|
||||
if (isUpgrade) {
|
||||
FileUtils.deleteContents(cacheBaseDir);
|
||||
// There are several items that need to be combined together to safely
|
||||
// identify cached items. In particular, changing the value of certain
|
||||
// feature flags should cause us to invalidate any caches.
|
||||
final String cacheName = SystemProperties.digestOf(
|
||||
"ro.build.fingerprint",
|
||||
"persist.sys.isolated_storage");
|
||||
|
||||
// Reconcile cache directories, keeping only what we'd actually use.
|
||||
for (File cacheDir : FileUtils.listFilesOrEmpty(cacheBaseDir)) {
|
||||
if (Objects.equals(cacheName, cacheDir.getName())) {
|
||||
Slog.d(TAG, "Keeping known cache " + cacheDir.getName());
|
||||
} else {
|
||||
Slog.d(TAG, "Destroying unknown cache " + cacheDir.getName());
|
||||
FileUtils.deleteContentsAndDir(cacheDir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Return the versioned package cache directory. This is something like
|
||||
// "/data/system/package_cache/1"
|
||||
File cacheDir = FileUtils.createDir(cacheBaseDir, PACKAGE_PARSER_CACHE_VERSION);
|
||||
// Return the versioned package cache directory.
|
||||
File cacheDir = FileUtils.createDir(cacheBaseDir, cacheName);
|
||||
|
||||
if (cacheDir == null) {
|
||||
// Something went wrong. Attempt to delete everything and return.
|
||||
@@ -3253,7 +3256,7 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
File frameworkDir = new File(Environment.getRootDirectory(), "framework");
|
||||
if (cacheDir.lastModified() < frameworkDir.lastModified()) {
|
||||
FileUtils.deleteContents(cacheBaseDir);
|
||||
cacheDir = FileUtils.createDir(cacheBaseDir, PACKAGE_PARSER_CACHE_VERSION);
|
||||
cacheDir = FileUtils.createDir(cacheBaseDir, cacheName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user