Merge "Refactoring of the prototype data app"
diff --git a/tzdata/prototype_data/Android.mk b/tzdata/prototype_data/Android.mk
index 568bbaf..c618da3 100644
--- a/tzdata/prototype_data/Android.mk
+++ b/tzdata/prototype_data/Android.mk
@@ -16,19 +16,21 @@
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
-LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_PROGUARD_FLAG_FILES := proguard.cfg
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := PrototypeTimeZoneDataApp
LOCAL_CERTIFICATE := platform
LOCAL_PRIVILEGED_MODULE := true
+LOCAL_STATIC_JAVA_LIBRARIES := time_zone_distro
include $(BUILD_PACKAGE)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
-LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_PROGUARD_FLAG_FILES := proguard.cfg
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := PrototypeTimeZoneDataApp_data
LOCAL_CERTIFICATE := platform
# Needed to ensure the .apk can be installed. Without it the .apk is missing a .dex.
LOCAL_DEX_PREOPT := false
+LOCAL_STATIC_JAVA_LIBRARIES := time_zone_distro
include $(BUILD_PACKAGE)
diff --git a/tzdata/prototype_data/AndroidManifest.xml b/tzdata/prototype_data/AndroidManifest.xml
index ae0fe6b..13b60e5 100644
--- a/tzdata/prototype_data/AndroidManifest.xml
+++ b/tzdata/prototype_data/AndroidManifest.xml
@@ -17,7 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="libcore.tzdata.prototype_data"
- android:versionCode="1">
+ android:versionCode="4">
<application
android:allowBackup="false"
@@ -31,16 +31,6 @@
android:exported="true">
<meta-data android:name="android.timezoneprovider.OPERATION"
android:value="INSTALL"/>
- <meta-data android:name="android.timezoneprovider.DATA_ASSET"
- android:value="test_2030a.zip"/>
- <meta-data android:name="android.timezoneprovider.DISTRO_MAJOR_VERSION"
- android:value="1"/>
- <meta-data android:name="android.timezoneprovider.DISTRO_MINOR_VERSION"
- android:value="1"/>
- <meta-data android:name="android.timezoneprovider.RULES_VERSION"
- android:value="2030a"/>
- <meta-data android:name="android.timezoneprovider.REVISION"
- android:value="1"/>
</provider>
</application>
</manifest>
diff --git a/tzdata/prototype_data/assets/test_2030a.zip b/tzdata/prototype_data/assets/distro.zip
similarity index 99%
rename from tzdata/prototype_data/assets/test_2030a.zip
rename to tzdata/prototype_data/assets/distro.zip
index ae274cd..1287f1a 100644
--- a/tzdata/prototype_data/assets/test_2030a.zip
+++ b/tzdata/prototype_data/assets/distro.zip
Binary files differ
diff --git a/tzdata/prototype_data/proguard.cfg b/tzdata/prototype_data/proguard.cfg
new file mode 100644
index 0000000..49cccc4
--- /dev/null
+++ b/tzdata/prototype_data/proguard.cfg
@@ -0,0 +1,3 @@
+-keep class libcore.tzdata.prototypedata.** { *; }
+-dontobfuscate
+-verbose
diff --git a/tzdata/prototype_data/src/libcore/tzdata/prototypedata/TimeZoneRulesDataProvider.java b/tzdata/prototype_data/src/libcore/tzdata/prototypedata/TimeZoneRulesDataProvider.java
index c7be72a..49758ef 100644
--- a/tzdata/prototype_data/src/libcore/tzdata/prototypedata/TimeZoneRulesDataProvider.java
+++ b/tzdata/prototype_data/src/libcore/tzdata/prototypedata/TimeZoneRulesDataProvider.java
@@ -16,6 +16,10 @@
package libcore.tzdata.prototypedata;
+import com.android.timezone.distro.DistroException;
+import com.android.timezone.distro.DistroVersion;
+import com.android.timezone.distro.TimeZoneDistro;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentProvider;
@@ -23,21 +27,20 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
+import android.content.res.AssetManager;
import android.database.AbstractCursor;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.provider.TimeZoneRulesDataContract;
-import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -45,7 +48,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import libcore.io.Streams;
+import java.util.function.Supplier;
import static android.content.res.AssetManager.ACCESS_STREAMING;
import static android.provider.TimeZoneRulesDataContract.COLUMN_DISTRO_MAJOR_VERSION;
@@ -64,18 +67,10 @@
static final String TAG = "TimeZoneRulesDataProvider";
private static final String METADATA_KEY_OPERATION = "android.timezoneprovider.OPERATION";
- private static final String METADATA_KEY_ASSET = "android.timezoneprovider.DATA_ASSET";
- private static final String METADATA_KEY_DISTRO_MAJOR_VERSION
- = "android.timezoneprovider.DISTRO_MAJOR_VERSION";
- private static final String METADATA_KEY_DISTRO_MINOR_VERSION
- = "android.timezoneprovider.DISTRO_MINOR_VERSION";
- private static final String METADATA_KEY_RULES_VERSION
- = "android.timezoneprovider.RULES_VERSION";
- private static final String METADATA_KEY_REVISION
- = "android.timezoneprovider.REVISION";
private static final Set<String> KNOWN_COLUMN_NAMES;
private static final Map<String, Class<?>> KNOWN_COLUMN_TYPES;
+
static {
Set<String> columnNames = new HashSet<>();
columnNames.add(COLUMN_OPERATION);
@@ -94,8 +89,7 @@
KNOWN_COLUMN_TYPES = Collections.unmodifiableMap(columnTypes);
}
- private Map<String, Object> mColumnData = new HashMap<>();
- private String mAssetName;
+ private final Map<String, Object> mColumnData = new HashMap<>();
@Override
public boolean onCreate() {
@@ -138,6 +132,7 @@
throw new SecurityException("meta-data must be set");
}
+ // Work out what the operation is.
String operation;
try {
operation = getMandatoryMetaDataString(metaData, METADATA_KEY_OPERATION);
@@ -145,31 +140,29 @@
} catch (IllegalArgumentException e) {
throw new SecurityException(METADATA_KEY_OPERATION + " meta-data not set.");
}
- if (OPERATION_INSTALL.equals(operation)) {
- mColumnData.put(
- COLUMN_DISTRO_MAJOR_VERSION,
- getMandatoryMetaDataInt(metaData, METADATA_KEY_DISTRO_MAJOR_VERSION));
- mColumnData.put(
- COLUMN_DISTRO_MINOR_VERSION,
- getMandatoryMetaDataInt(metaData, METADATA_KEY_DISTRO_MINOR_VERSION));
- mColumnData.put(
- COLUMN_RULES_VERSION,
- getMandatoryMetaDataString(metaData, METADATA_KEY_RULES_VERSION));
- mColumnData.put(
- COLUMN_REVISION,
- getMandatoryMetaDataInt(metaData, METADATA_KEY_REVISION));
- // Make sure the asset containing the data to install exists.
- String assetName = getMandatoryMetaDataString(metaData, METADATA_KEY_ASSET);
+ // Fill in version information if this is an install operation.
+ if (OPERATION_INSTALL.equals(operation)) {
+ // Extract the version information from the distro.
+ Supplier<InputStream> distroBytesSupplier = () -> {
+ try {
+ return context.getAssets().open(TimeZoneDistro.FILE_NAME);
+ } catch (IOException e) {
+ throw new SecurityException(
+ "Unable to open asset: " + TimeZoneDistro.FILE_NAME, e);
+ }
+ };
+ TimeZoneDistro distro = new TimeZoneDistro(distroBytesSupplier);
try {
- InputStream is = context.getAssets().open(assetName);
- // An exception is thrown if the asset does not exist. list(assetName) appears not
- // to work with file paths.
- is.close();
- } catch (IOException e) {
- throw new SecurityException("Unable to open asset:" + assetName);
+ DistroVersion distroVersion = distro.getDistroVersion();
+ mColumnData.put(COLUMN_DISTRO_MAJOR_VERSION, distroVersion.formatMajorVersion);
+ mColumnData.put(COLUMN_DISTRO_MINOR_VERSION, distroVersion.formatMinorVersion);
+ mColumnData.put(COLUMN_RULES_VERSION, distroVersion.rulesVersion);
+ mColumnData.put(COLUMN_REVISION, distroVersion.revision);
+ } catch (IOException | DistroException e) {
+ throw new SecurityException("Invalid asset: " + TimeZoneDistro.FILE_NAME, e);
}
- mAssetName = assetName;
+
}
}
@@ -264,54 +257,37 @@
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
throws FileNotFoundException {
if (!TimeZoneRulesDataContract.DATA_URI.equals(uri)) {
- return null;
+ throw new FileNotFoundException("Unknown URI: " + uri);
}
- if (mAssetName == null) {
- throw new FileNotFoundException();
- }
- if (!mode.equals("r")) {
- throw new SecurityException("Only read-only access supported.");
+ if (!"r".equals(mode)) {
+ throw new FileNotFoundException("Only read-only access supported.");
}
- // Extract the asset to a local dir. We do it every time: we don't make assumptions that the
- // current copy (if any) is valid.
- File localFile = extractAssetToLocalFile();
-
- // Create a read-only ParcelFileDescriptor that can be passed to the caller process.
+ // We cannot return the asset ParcelFileDescriptor from
+ // assets.openFd(name).getParcelFileDescriptor() here as the receiver in the reading
+ // process gets a ParcelFileDescriptor pointing at the whole .apk. Instead, we extract
+ // the asset file we want to storage then wrap that in a ParcelFileDescriptor.
+ File distroFile = null;
try {
- return ParcelFileDescriptor.open(localFile, ParcelFileDescriptor.MODE_READ_ONLY,
- new Handler(Looper.getMainLooper()),
- e -> {
- if (e != null) {
- Log.w(TAG, "Error in OnCloseListener for " + localFile, e);
- }
- localFile.delete();
- });
- } catch (IOException e) {
- throw new RuntimeException("Unable to open asset file", e);
- }
- }
+ distroFile = File.createTempFile("distro", null, getContext().getFilesDir());
- private File extractAssetToLocalFile() throws FileNotFoundException {
- File extractedFile = new File(getContext().getFilesDir(), "timezone_data.zip");
- InputStream is;
- try {
- is = getContext().getAssets().open(mAssetName, ACCESS_STREAMING);
- } catch (FileNotFoundException e) {
- throw e;
- } catch (IOException e) {
- FileNotFoundException fnfe = new FileNotFoundException("Problem reading asset");
- fnfe.initCause(e);
- throw fnfe;
- }
+ AssetManager assets = getContext().getAssets();
+ try (InputStream is = assets.open(TimeZoneDistro.FILE_NAME, ACCESS_STREAMING);
+ FileOutputStream fos = new FileOutputStream(distroFile, false /* append */)) {
+ copy(is, fos);
+ }
- try (InputStream fis = is;
- FileOutputStream fos = new FileOutputStream(extractedFile, false /* append */)) {
- Streams.copy(fis, fos);
+ return ParcelFileDescriptor.open(distroFile, ParcelFileDescriptor.MODE_READ_ONLY);
} catch (IOException e) {
- throw new RuntimeException("Unable to create asset storage file: " + extractedFile, e);
+ throw new RuntimeException("Unable to copy distro asset file", e);
+ } finally {
+ if (distroFile != null) {
+ // Even if we have an open file descriptor pointing at the file it should be safe to
+ // delete because of normal Unix file behavior. Deleting here avoids leaking any
+ // storage.
+ distroFile.delete();
+ }
}
- return extractedFile;
}
@Override
@@ -343,10 +319,14 @@
return metaData.getString(key);
}
- private static int getMandatoryMetaDataInt(Bundle metaData, String key) {
- if (!metaData.containsKey(key)) {
- throw new SecurityException("No metadata with key " + key + " found.");
+ /**
+ * Copies all of the bytes from {@code in} to {@code out}. Neither stream is closed.
+ */
+ private static void copy(InputStream in, OutputStream out) throws IOException {
+ byte[] buffer = new byte[8192];
+ int c;
+ while ((c = in.read(buffer)) != -1) {
+ out.write(buffer, 0, c);
}
- return metaData.getInt(key, -1);
}
}