handle .obf.zip by unzipping the map file then installing it
This commit is contained in:
parent
021d5cc1ff
commit
c0344c1eed
@ -263,6 +263,9 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".installer.InstallHistoryService"
|
android:name=".installer.InstallHistoryService"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
<service
|
||||||
|
android:name=".installer.ObfInstallerService"
|
||||||
|
android:exported="false"/>
|
||||||
<service
|
<service
|
||||||
android:name=".data.InstalledAppProviderService"
|
android:name=".data.InstalledAppProviderService"
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||||
|
@ -592,7 +592,13 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
|
|||||||
try {
|
try {
|
||||||
File cachedFile = ApkCache.getApkDownloadPath(context, this.getCanonicalUrl());
|
File cachedFile = ApkCache.getApkDownloadPath(context, this.getCanonicalUrl());
|
||||||
ZipFile zipFile = new ZipFile(cachedFile);
|
ZipFile zipFile = new ZipFile(cachedFile);
|
||||||
if (zipFile.getEntry("META-INF/com/google/android/update-binary") != null) {
|
if (zipFile.size() == 1) {
|
||||||
|
String name = zipFile.entries().nextElement().getName();
|
||||||
|
if (name != null && name.endsWith(".obf")) {
|
||||||
|
// temporarily cache this, it will be deleted after unzipping
|
||||||
|
return context.getCacheDir();
|
||||||
|
}
|
||||||
|
} else if (zipFile.getEntry("META-INF/com/google/android/update-binary") != null) {
|
||||||
// Over-The-Air update ZIP files
|
// Over-The-Air update ZIP files
|
||||||
return new File(context.getApplicationInfo().dataDir + "/ota");
|
return new File(context.getApplicationInfo().dataDir + "/ota");
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,8 @@ import android.content.DialogInterface;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.StrictMode;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.ContextThemeWrapper;
|
import android.view.ContextThemeWrapper;
|
||||||
import android.webkit.MimeTypeMap;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
@ -25,7 +21,6 @@ import org.fdroid.fdroid.data.Apk;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
public class FileInstallerActivity extends FragmentActivity {
|
public class FileInstallerActivity extends FragmentActivity {
|
||||||
|
|
||||||
@ -164,10 +159,11 @@ public class FileInstallerActivity extends FragmentActivity {
|
|||||||
}
|
}
|
||||||
if (apk.isMediaInstalled(activity.getApplicationContext())) { // Copying worked
|
if (apk.isMediaInstalled(activity.getApplicationContext())) { // Copying worked
|
||||||
Utils.debugLog(TAG, "Copying worked: " + localApkUri.getPath());
|
Utils.debugLog(TAG, "Copying worked: " + localApkUri.getPath());
|
||||||
Toast.makeText(this, String.format(this.getString(R.string.app_installed_media), path.toString()),
|
if (!postInstall(canonicalUri, apk, path)) {
|
||||||
Toast.LENGTH_LONG).show();
|
Toast.makeText(this, String.format(this.getString(R.string.app_installed_media), path.toString()),
|
||||||
installer.sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_COMPLETE);
|
Toast.LENGTH_LONG).show();
|
||||||
postInstall(path);
|
installer.sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_COMPLETE);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
installer.sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_INTERRUPTED);
|
installer.sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_INTERRUPTED);
|
||||||
}
|
}
|
||||||
@ -176,33 +172,16 @@ public class FileInstallerActivity extends FragmentActivity {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Run any file-type-specific processes after the file has been copied into place.
|
* Run any file-type-specific processes after the file has been copied into place.
|
||||||
* <p>
|
*
|
||||||
* When this was written, OsmAnd only supported importing OBF files via a
|
* @return whether this handles sending the {@link Installer#ACTION_INSTALL_COMPLETE}
|
||||||
* {@code file:///} URL, so this disables {@link android.os.FileUriExposedException}.
|
* broadcast.
|
||||||
*/
|
*/
|
||||||
private void postInstall(File path) {
|
private boolean postInstall(Uri canonicalUri, Apk apk, File path) {
|
||||||
if (path.getName().endsWith(".obf")) {
|
if (path.getName().endsWith(".obf") || path.getName().endsWith(".obf.zip")) {
|
||||||
if (Build.VERSION.SDK_INT >= 24) {
|
ObfInstallerService.install(this, canonicalUri, apk, path);
|
||||||
try {
|
return true;
|
||||||
Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
|
|
||||||
m.invoke(null);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("obf");
|
|
||||||
intent.setDataAndType(Uri.fromFile(path), mimeType);
|
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
}
|
|
||||||
if (intent.resolveActivity(getPackageManager()) != null) {
|
|
||||||
startActivity(intent);
|
|
||||||
} else {
|
|
||||||
Log.i(TAG, "No Activity available to handle " + intent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void uninstallPackage(Apk apk) {
|
private void uninstallPackage(Apk apk) {
|
||||||
|
@ -0,0 +1,122 @@
|
|||||||
|
package org.fdroid.fdroid.installer;
|
||||||
|
|
||||||
|
import android.app.IntentService;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.os.StrictMode;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.fdroid.fdroid.data.Apk;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link IntentService} subclass for installing {@code .obf} and {@code .obf.zip}
|
||||||
|
* map files into OsmAnd. This will unzip the {@code .obf}
|
||||||
|
*/
|
||||||
|
public class ObfInstallerService extends IntentService {
|
||||||
|
private static final String TAG = "ObfInstallerService";
|
||||||
|
|
||||||
|
private static final String ACTION_INSTALL_OBF = "org.fdroid.fdroid.installer.action.INSTALL_OBF";
|
||||||
|
|
||||||
|
private static final String EXTRA_OBF_PATH = "org.fdroid.fdroid.installer.extra.OBF_PATH";
|
||||||
|
|
||||||
|
public ObfInstallerService() {
|
||||||
|
super("ObfInstallerService");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void install(Context context, Uri canonicalUri, Apk apk, File path) {
|
||||||
|
Intent intent = new Intent(context, ObfInstallerService.class);
|
||||||
|
intent.setAction(ACTION_INSTALL_OBF);
|
||||||
|
intent.putExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL, canonicalUri.toString());
|
||||||
|
intent.putExtra(Installer.EXTRA_APK, apk);
|
||||||
|
intent.putExtra(EXTRA_OBF_PATH, path.getAbsolutePath());
|
||||||
|
context.startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onHandleIntent(Intent intent) {
|
||||||
|
if (intent == null || !ACTION_INSTALL_OBF.equals(intent.getAction())) {
|
||||||
|
Log.e(TAG, "received invalid intent: " + intent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Uri canonicalUri = Uri.parse(intent.getStringExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL));
|
||||||
|
final Apk apk = intent.getParcelableExtra(Installer.EXTRA_APK);
|
||||||
|
final String path = intent.getStringExtra(EXTRA_OBF_PATH);
|
||||||
|
final String extension = MimeTypeMap.getFileExtensionFromUrl(path);
|
||||||
|
if ("obf".equals(extension)) {
|
||||||
|
sendPostInstallAndCompleteIntents(canonicalUri, apk, new File(path));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!"zip".equals(extension)) {
|
||||||
|
sendBroadcastInstall(Installer.ACTION_INSTALL_INTERRUPTED, canonicalUri, apk,
|
||||||
|
"Only .obf and .zip files are supported: " + path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
File zip = new File(path);
|
||||||
|
ZipFile zipFile = new ZipFile(zip);
|
||||||
|
if (zipFile.size() < 1) {
|
||||||
|
sendBroadcastInstall(Installer.ACTION_INSTALL_INTERRUPTED, canonicalUri, apk,
|
||||||
|
"Corrupt or empty ZIP file!");
|
||||||
|
}
|
||||||
|
ZipEntry zipEntry = zipFile.entries().nextElement();
|
||||||
|
File extracted = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
zipEntry.getName());
|
||||||
|
FileUtils.copyInputStreamToFile(zipFile.getInputStream(zipEntry), extracted);
|
||||||
|
zip.delete();
|
||||||
|
sendPostInstallAndCompleteIntents(canonicalUri, apk, extracted);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
sendBroadcastInstall(Installer.ACTION_INSTALL_INTERRUPTED, canonicalUri, apk, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendBroadcastInstall(String action, Uri canonicalUri, Apk apk, String msg) {
|
||||||
|
Installer.sendBroadcastInstall(this, canonicalUri, action, apk, null, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Once the file is downloaded and installed, send an {@link Intent} to
|
||||||
|
* let map apps know that the file is available for install.
|
||||||
|
* <p>
|
||||||
|
* When this was written, OsmAnd only supported importing OBF files via a
|
||||||
|
* {@code file:///} URL, so this disables {@link android.os.FileUriExposedException}.
|
||||||
|
*/
|
||||||
|
void sendPostInstallAndCompleteIntents(Uri canonicalUri, Apk apk, File file) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 24) {
|
||||||
|
try {
|
||||||
|
Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
|
||||||
|
m.invoke(null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("obf");
|
||||||
|
if (TextUtils.isEmpty(mimeType)) {
|
||||||
|
mimeType = "application/octet-stream";
|
||||||
|
}
|
||||||
|
intent.setDataAndType(Uri.fromFile(file), mimeType);
|
||||||
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
}
|
||||||
|
if (intent != null && intent.resolveActivity(getPackageManager()) != null) {
|
||||||
|
startActivity(intent);
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "No Activity available to handle " + intent);
|
||||||
|
}
|
||||||
|
sendBroadcastInstall(Installer.ACTION_INSTALL_COMPLETE, canonicalUri, apk, null);
|
||||||
|
}
|
||||||
|
}
|
@ -75,7 +75,7 @@ public class ApkTest {
|
|||||||
assertFalse(apk.isApk());
|
assertFalse(apk.isApk());
|
||||||
copyResourceFileToCache(apk);
|
copyResourceFileToCache(apk);
|
||||||
File path = apk.getMediaInstallPath(context);
|
File path = apk.getMediaInstallPath(context);
|
||||||
assertEquals(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), path);
|
assertEquals(context.getCacheDir(), path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyResourceFileToCache(Apk apk) throws IOException {
|
private void copyResourceFileToCache(Apk apk) throws IOException {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user