aboutsummaryrefslogtreecommitdiff
path: root/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java
diff options
context:
space:
mode:
Diffstat (limited to 'backends/platform/android/org/inodes/gus/scummvm/Unpacker.java')
-rw-r--r--backends/platform/android/org/inodes/gus/scummvm/Unpacker.java370
1 files changed, 370 insertions, 0 deletions
diff --git a/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java b/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java
new file mode 100644
index 0000000000..7280aac7d4
--- /dev/null
+++ b/backends/platform/android/org/inodes/gus/scummvm/Unpacker.java
@@ -0,0 +1,370 @@
+package org.inodes.gus.scummvm;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.ProgressBar;
+
+import java.io.IOException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipEntry;
+
+public class Unpacker extends Activity {
+ private final static boolean PLUGINS_ENABLED = true;
+ private final static String META_NEXT_ACTIVITY =
+ "org.inodes.gus.unpacker.nextActivity";
+ private ProgressBar mProgress;
+ private File mUnpackDest; // location to unpack into
+ private AsyncTask<String, Integer, Void> mUnpacker;
+ private final static int REQUEST_MARKET = 1;
+
+ private static class UnpackJob {
+ public ZipFile zipfile;
+ public Set<String> paths;
+
+ public UnpackJob(ZipFile zipfile, Set<String> paths) {
+ this.zipfile = zipfile;
+ this.paths = paths;
+ }
+
+ public long UnpackSize() {
+ long size = 0;
+ for (String path: paths) {
+ ZipEntry entry = zipfile.getEntry(path);
+ if (entry != null) size += entry.getSize();
+ }
+ return size;
+ }
+ }
+
+ private class UnpackTask extends AsyncTask<String, Integer, Void> {
+ @Override
+ protected void onProgressUpdate(Integer... progress) {
+ mProgress.setIndeterminate(false);
+ mProgress.setMax(progress[1]);
+ mProgress.setProgress(progress[0]);
+ mProgress.postInvalidate();
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ Bundle md = getMetaData();
+ String nextActivity = md.getString(META_NEXT_ACTIVITY);
+ if (nextActivity != null) {
+ final ComponentName cn =
+ ComponentName.unflattenFromString(nextActivity);
+ if (cn != null) {
+ final Intent origIntent = getIntent();
+ Intent intent = new Intent();
+ intent.setComponent(cn);
+ if (origIntent.getExtras() != null)
+ intent.putExtras(origIntent.getExtras());
+ intent.putExtra(Intent.EXTRA_INTENT, origIntent);
+ intent.setDataAndType(origIntent.getData(),
+ origIntent.getType());
+ //intent.fillIn(getIntent(), 0);
+ intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+ Log.i(this.toString(),
+ "Starting next activity with intent " + intent);
+ startActivity(intent);
+ } else {
+ Log.w(this.toString(),
+ "Unable to extract a component name from " + nextActivity);
+ }
+ }
+
+ finish();
+ }
+
+ @Override
+ protected Void doInBackground(String... all_libs) {
+ // This will contain all unpack jobs
+ Map<String, UnpackJob> unpack_jobs =
+ new HashMap<String, UnpackJob>(all_libs.length);
+
+ // This will contain all unpack filenames (so we can
+ // detect stale files in the unpack directory)
+ Set<String> all_files = new HashSet<String>(all_libs.length);
+
+ for (String lib: all_libs) {
+ final Uri uri = Uri.parse(lib);
+ final String pkg = uri.getAuthority();
+ final String path = uri.getPath().substring(1); // skip first /
+
+ all_files.add(new File(path).getName());
+
+ UnpackJob job = unpack_jobs.get(pkg);
+ if (job == null) {
+ try {
+ // getPackageResourcePath is hidden in Context,
+ // but exposed in ContextWrapper...
+ ContextWrapper context =
+ new ContextWrapper(createPackageContext(pkg, 0));
+ ZipFile zipfile =
+ new ZipFile(context.getPackageResourcePath());
+ job = new UnpackJob(zipfile, new HashSet<String>(1));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(this.toString(), "Package " + pkg +
+ " not found", e);
+ continue;
+ } catch (IOException e) {
+ // FIXME: show some sort of GUI error dialog
+ Log.e(this.toString(),
+ "Error opening ZIP for package " + pkg, e);
+ continue;
+ }
+ unpack_jobs.put(pkg, job);
+ }
+ job.paths.add(path);
+ }
+
+ // Delete stale filenames from mUnpackDest
+ for (File file: mUnpackDest.listFiles()) {
+ if (!all_files.contains(file.getName())) {
+ Log.i(this.toString(),
+ "Deleting stale cached file " + file);
+ file.delete();
+ }
+ }
+
+ int total_size = 0;
+ for (UnpackJob job: unpack_jobs.values())
+ total_size += job.UnpackSize();
+
+ publishProgress(0, total_size);
+
+ mUnpackDest.mkdirs();
+
+ int progress = 0;
+
+ for (UnpackJob job: unpack_jobs.values()) {
+ try {
+ ZipFile zipfile = job.zipfile;
+ for (String path: job.paths) {
+ ZipEntry zipentry = zipfile.getEntry(path);
+ if (zipentry == null)
+ throw new FileNotFoundException(
+ "Couldn't find " + path + " in zip");
+ File dest = new File(mUnpackDest, new File(path).getName());
+ if (dest.exists() &&
+ dest.lastModified() == zipentry.getTime() &&
+ dest.length() == zipentry.getSize()) {
+ // Already unpacked
+ progress += zipentry.getSize();
+ } else {
+ if (dest.exists())
+ Log.d(this.toString(),
+ "Replacing " + dest.getPath() +
+ " old.mtime=" + dest.lastModified() +
+ " new.mtime=" + zipentry.getTime() +
+ " old.size=" + dest.length() +
+ " new.size=" + zipentry.getSize());
+ else
+ Log.i(this.toString(),
+ "Extracting " + zipentry.getName() +
+ " from " + zipfile.getName() +
+ " to " + dest.getPath());
+
+ long next_update = progress;
+
+ InputStream in = zipfile.getInputStream(zipentry);
+ OutputStream out = new FileOutputStream(dest);
+ int len;
+ byte[] buffer = new byte[4096];
+ while ((len = in.read(buffer)) != -1) {
+ out.write(buffer, 0, len);
+ progress += len;
+ if (progress >= next_update) {
+ publishProgress(progress, total_size);
+ // Arbitrary limit of 2% update steps
+ next_update += total_size / 50;
+ }
+ }
+
+ in.close();
+ out.close();
+ dest.setLastModified(zipentry.getTime());
+ }
+ publishProgress(progress, total_size);
+ }
+
+ zipfile.close();
+ } catch (IOException e) {
+ // FIXME: show some sort of GUI error dialog
+ Log.e(this.toString(), "Error unpacking plugin", e);
+ }
+ }
+
+ if (progress != total_size)
+ Log.d(this.toString(), "Ended with progress " + progress +
+ " != total size " + total_size);
+
+ setResult(RESULT_OK);
+
+ return null;
+ }
+ }
+
+ private class PluginBroadcastReciever extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!intent.getAction()
+ .equals(ScummVMApplication.ACTION_PLUGIN_QUERY)) {
+ Log.e(this.toString(),
+ "Received unexpected action " + intent.getAction());
+ return;
+ }
+
+ Bundle extras = getResultExtras(false);
+ if (extras == null) {
+ // Nothing for us to do.
+ Unpacker.this.setResult(RESULT_OK);
+ finish();
+ }
+
+ ArrayList<String> unpack_libs =
+ extras.getStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS);
+
+ if (unpack_libs != null && !unpack_libs.isEmpty()) {
+ final String[] libs =
+ unpack_libs.toArray(new String[unpack_libs.size()]);
+ mUnpacker = new UnpackTask().execute(libs);
+ }
+ }
+ }
+
+ private void initPlugins() {
+ Bundle extras = new Bundle(1);
+
+ ArrayList<String> unpack_libs = new ArrayList<String>(1);
+ // This is the common ScummVM code (not really a "plugin" as such)
+ unpack_libs.add(new Uri.Builder()
+ .scheme("plugin")
+ .authority(getPackageName())
+ .path("mylib/armeabi/libscummvm.so")
+ .toString());
+ extras.putStringArrayList(ScummVMApplication.EXTRA_UNPACK_LIBS,
+ unpack_libs);
+
+ Intent intent = new Intent(ScummVMApplication.ACTION_PLUGIN_QUERY);
+ sendOrderedBroadcast(intent, Manifest.permission.SCUMMVM_PLUGIN,
+ new PluginBroadcastReciever(),
+ null, RESULT_OK, null, extras);
+ }
+
+ @Override
+ public void onCreate(Bundle b) {
+ super.onCreate(b);
+
+ mUnpackDest = ScummVMApplication.getLastCacheDir();
+
+ setContentView(R.layout.splash);
+ mProgress = (ProgressBar)findViewById(R.id.progress);
+
+ setResult(RESULT_CANCELED);
+
+ tryUnpack();
+ }
+
+ private void tryUnpack() {
+ Intent intent = new Intent(ScummVMApplication.ACTION_PLUGIN_QUERY);
+ List<ResolveInfo> plugins = getPackageManager()
+ .queryBroadcastReceivers(intent, 0);
+ if (PLUGINS_ENABLED && plugins.isEmpty()) {
+ // No plugins installed
+ AlertDialog.Builder alert = new AlertDialog.Builder(this)
+ .setTitle(R.string.no_plugins_title)
+ .setMessage(R.string.no_plugins_found)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
+ })
+ .setNegativeButton(R.string.quit,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+ });
+
+ final Uri uri = Uri.parse("market://search?q=ScummVM plugin");
+ final Intent market_intent = new Intent(Intent.ACTION_VIEW, uri);
+ if (getPackageManager().resolveActivity(market_intent, 0) != null) {
+ alert.setPositiveButton(R.string.to_market,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ try {
+ startActivityForResult(market_intent,
+ REQUEST_MARKET);
+ } catch (ActivityNotFoundException e) {
+ Log.e(this.toString(),
+ "Error starting market", e);
+ }
+ }
+ });
+ }
+
+ alert.show();
+
+ } else {
+ // Already have at least one plugin installed
+ initPlugins();
+ }
+ }
+
+ @Override
+ public void onStop() {
+ if (mUnpacker != null)
+ mUnpacker.cancel(true);
+ super.onStop();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode,
+ Intent data) {
+ switch (requestCode) {
+ case REQUEST_MARKET:
+ if (resultCode != RESULT_OK)
+ Log.w(this.toString(), "Market returned " + resultCode);
+ tryUnpack();
+ break;
+ }
+ }
+
+ private Bundle getMetaData() {
+ try {
+ ActivityInfo ai = getPackageManager()
+ .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
+ return ai.metaData;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(this.toString(), "Unable to find my own meta-data", e);
+ return new Bundle();
+ }
+ }
+}