211 lines
8.5 KiB
Java
211 lines
8.5 KiB
Java
/*
|
|
* Copyright (C) 2014-2016 Dominik Schürmann <dominik@dominikschuermann.de>
|
|
* Copyright (C) 2016 Blue Jay Wireless
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 3
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301, USA.
|
|
*/
|
|
|
|
package org.fdroid.fdroid.installer;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.ContentResolver;
|
|
import android.content.Intent;
|
|
import android.content.pm.PackageManager;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.util.Log;
|
|
|
|
import org.fdroid.fdroid.R;
|
|
import org.fdroid.fdroid.data.Apk;
|
|
import org.fdroid.fdroid.net.DownloaderService;
|
|
|
|
import androidx.appcompat.app.AppCompatActivity;
|
|
import androidx.fragment.app.FragmentActivity;
|
|
|
|
/**
|
|
* A transparent activity as a wrapper around Android's PackageInstaller Intents
|
|
*/
|
|
public class DefaultInstallerActivity extends FragmentActivity {
|
|
private static final String TAG = "DefaultInstallerActivit";
|
|
|
|
static final String ACTION_INSTALL_PACKAGE
|
|
= "org.fdroid.fdroid.installer.DefaultInstaller.action.INSTALL_PACKAGE";
|
|
static final String ACTION_UNINSTALL_PACKAGE
|
|
= "org.fdroid.fdroid.installer.DefaultInstaller.action.UNINSTALL_PACKAGE";
|
|
|
|
private static final int REQUEST_CODE_INSTALL = 0;
|
|
private static final int REQUEST_CODE_UNINSTALL = 1;
|
|
|
|
/**
|
|
* @see InstallManagerService
|
|
*/
|
|
private Uri canonicalUri;
|
|
|
|
// for the broadcasts
|
|
private DefaultInstaller installer;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
Intent intent = getIntent();
|
|
String action = intent.getAction();
|
|
Apk apk = intent.getParcelableExtra(Installer.EXTRA_APK);
|
|
installer = new DefaultInstaller(this, apk);
|
|
if (ACTION_INSTALL_PACKAGE.equals(action)) {
|
|
Uri localApkUri = intent.getData();
|
|
canonicalUri = Uri.parse(intent.getStringExtra(DownloaderService.EXTRA_CANONICAL_URL));
|
|
installPackage(localApkUri);
|
|
} else if (ACTION_UNINSTALL_PACKAGE.equals(action)) {
|
|
uninstallPackage(apk.packageName);
|
|
} else {
|
|
throw new IllegalStateException("Intent action not specified!");
|
|
}
|
|
}
|
|
|
|
@SuppressLint("InlinedApi")
|
|
private void installPackage(Uri uri) {
|
|
if (uri == null) {
|
|
throw new RuntimeException("Set the data uri to point to an apk location!");
|
|
}
|
|
// https://code.google.com/p/android/issues/detail?id=205827
|
|
if ((Build.VERSION.SDK_INT < 24)
|
|
&& (!ContentResolver.SCHEME_FILE.equals(uri.getScheme()))) {
|
|
throw new RuntimeException("PackageInstaller < Android N only supports file scheme!");
|
|
}
|
|
if ((Build.VERSION.SDK_INT >= 24)
|
|
&& (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()))) {
|
|
throw new RuntimeException("PackageInstaller >= Android N only supports content scheme!");
|
|
}
|
|
|
|
Intent intent = new Intent();
|
|
|
|
// Note regarding EXTRA_NOT_UNKNOWN_SOURCE:
|
|
// works only when being installed as system-app
|
|
// https://code.google.com/p/android/issues/detail?id=42253
|
|
|
|
if (Build.VERSION.SDK_INT < 16) {
|
|
intent.setAction(Intent.ACTION_INSTALL_PACKAGE);
|
|
intent.setData(uri);
|
|
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
|
|
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
|
|
intent.putExtra(Intent.EXTRA_ALLOW_REPLACE, true);
|
|
} else if (Build.VERSION.SDK_INT < 24) {
|
|
intent.setAction(Intent.ACTION_INSTALL_PACKAGE);
|
|
intent.setData(uri);
|
|
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
|
|
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
|
|
} else { // Android N
|
|
intent.setAction(Intent.ACTION_INSTALL_PACKAGE);
|
|
intent.setData(uri);
|
|
// grant READ permission for this content Uri
|
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
|
|
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
|
|
}
|
|
|
|
try {
|
|
startActivityForResult(intent, REQUEST_CODE_INSTALL);
|
|
} catch (ActivityNotFoundException e) {
|
|
Log.e(TAG, "ActivityNotFoundException", e);
|
|
installer.sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_INTERRUPTED,
|
|
"This Android rom does not support ACTION_INSTALL_PACKAGE!");
|
|
finish();
|
|
}
|
|
}
|
|
|
|
private void uninstallPackage(String packageName) {
|
|
// check that the package is installed
|
|
try {
|
|
getPackageManager().getPackageInfo(packageName, 0);
|
|
} catch (PackageManager.NameNotFoundException e) {
|
|
Log.e(TAG, "NameNotFoundException", e);
|
|
installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED,
|
|
"Package that is scheduled for uninstall is not installed!");
|
|
finish();
|
|
return;
|
|
}
|
|
|
|
Uri uri = Uri.fromParts("package", packageName, null);
|
|
Intent intent = new Intent();
|
|
intent.setData(uri);
|
|
|
|
intent.setAction(Intent.ACTION_UNINSTALL_PACKAGE);
|
|
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
|
|
|
|
try {
|
|
startActivityForResult(intent, REQUEST_CODE_UNINSTALL);
|
|
} catch (ActivityNotFoundException e) {
|
|
Log.e(TAG, "ActivityNotFoundException", e);
|
|
installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED,
|
|
"This Android rom does not support ACTION_UNINSTALL_PACKAGE!");
|
|
finish();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
super.onActivityResult(requestCode, resultCode, data);
|
|
switch (requestCode) {
|
|
case REQUEST_CODE_INSTALL:
|
|
switch (resultCode) {
|
|
case AppCompatActivity.RESULT_OK:
|
|
installer.sendBroadcastInstall(canonicalUri,
|
|
Installer.ACTION_INSTALL_COMPLETE);
|
|
break;
|
|
case AppCompatActivity.RESULT_CANCELED:
|
|
installer.sendBroadcastInstall(canonicalUri,
|
|
Installer.ACTION_INSTALL_INTERRUPTED);
|
|
break;
|
|
case AppCompatActivity.RESULT_FIRST_USER:
|
|
default:
|
|
// AOSP returns AppCompatActivity.RESULT_FIRST_USER on error
|
|
installer.sendBroadcastInstall(canonicalUri,
|
|
Installer.ACTION_INSTALL_INTERRUPTED,
|
|
getString(R.string.install_error_unknown));
|
|
break;
|
|
}
|
|
|
|
break;
|
|
case REQUEST_CODE_UNINSTALL:
|
|
switch (resultCode) {
|
|
case AppCompatActivity.RESULT_OK:
|
|
installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_COMPLETE);
|
|
break;
|
|
case AppCompatActivity.RESULT_CANCELED:
|
|
installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED);
|
|
break;
|
|
case AppCompatActivity.RESULT_FIRST_USER:
|
|
default:
|
|
// AOSP UninstallAppProgress returns RESULT_FIRST_USER on error
|
|
installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED,
|
|
getString(R.string.uninstall_error_unknown));
|
|
break;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
throw new RuntimeException("Invalid request code!");
|
|
}
|
|
|
|
// after doing the broadcasts, finish this transparent wrapper activity
|
|
finish();
|
|
}
|
|
|
|
}
|