Installer: handle package parse errors, fixes #395

This commit is contained in:
Daniel Martí 2015-09-03 18:52:40 -07:00
parent 74c93907c5
commit 10171d9228
6 changed files with 102 additions and 68 deletions

View File

@ -295,6 +295,7 @@
<string name="update_all">Update all</string> <string name="update_all">Update all</string>
<string name="install_error_title">Install error</string> <string name="install_error_title">Install error</string>
<string name="install_error_unknown">Failed to install due to an unknown error</string> <string name="install_error_unknown">Failed to install due to an unknown error</string>
<string name="install_error_cannot_parse">An error occurred while parsing the package</string>
<string name="uninstall_error_title">Uninstall error</string> <string name="uninstall_error_title">Uninstall error</string>
<string name="uninstall_error_unknown">Failed to uninstall due to an unknown error</string> <string name="uninstall_error_unknown">Failed to uninstall due to an unknown error</string>
<string name="system_permission_denied_title">System permissions denied</string> <string name="system_permission_denied_title">System permissions denied</string>

View File

@ -909,15 +909,20 @@ public class AppDetails extends AppCompatActivity implements ProgressListener, A
final int title, body; final int title, body;
if (operation == InstallerCallback.OPERATION_INSTALL) { if (operation == InstallerCallback.OPERATION_INSTALL) {
title = R.string.install_error_title; title = R.string.install_error_title;
} else { switch (errorCode) {
title = R.string.uninstall_error_title; case ERROR_CODE_CANNOT_PARSE:
} body = R.string.install_error_cannot_parse;
switch (errorCode) { break;
default: default: // ERROR_CODE_OTHER
if (operation == InstallerCallback.OPERATION_INSTALL) {
body = R.string.install_error_unknown; body = R.string.install_error_unknown;
} else { break;
body = R.string.uninstall_error_unknown; }
} else { // InstallerCallback.OPERATION_DELETE
title = R.string.uninstall_error_title;
switch (errorCode) {
default: // ERROR_CODE_OTHER
body = R.string.install_error_unknown;
break;
} }
} }
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {

View File

@ -36,6 +36,15 @@ public class AppDiff {
final String pkgPath = mPackageURI.getPath(); final String pkgPath = mPackageURI.getPath();
mPkgInfo = mPm.getPackageArchiveInfo(pkgPath, PackageManager.GET_PERMISSIONS); mPkgInfo = mPm.getPackageArchiveInfo(pkgPath, PackageManager.GET_PERMISSIONS);
// We could not get the package info from the file. This means that we
// could not parse the file, which can happen if the file cannot be
// read or the minSdk is not satisfied.
// Since we can't return an error from a constructor, we refuse to
// continue. The caller must check if mPkgInfo is null to see if the
// AppDiff was initialised correctly.
if (mPkgInfo == null) {
return;
}
mPkgInfo.applicationInfo.sourceDir = pkgPath; mPkgInfo.applicationInfo.sourceDir = pkgPath;
mPkgInfo.applicationInfo.publicSourceDir = pkgPath; mPkgInfo.applicationInfo.publicSourceDir = pkgPath;

View File

@ -43,6 +43,8 @@ import org.fdroid.fdroid.R;
public class InstallConfirmActivity extends Activity implements OnCancelListener, OnClickListener { public class InstallConfirmActivity extends Activity implements OnCancelListener, OnClickListener {
public static final int RESULT_CANNOT_PARSE = RESULT_FIRST_USER + 1;
private Intent intent; private Intent intent;
PackageManager mPm; PackageManager mPm;
@ -83,55 +85,54 @@ public class InstallConfirmActivity extends Activity implements OnCancelListener
mScrollView = null; mScrollView = null;
mOkCanInstall = false; mOkCanInstall = false;
int msg = 0; int msg = 0;
if (mAppDiff.mPkgInfo != null) { AppSecurityPermissions perms = new AppSecurityPermissions(this, mAppDiff.mPkgInfo);
AppSecurityPermissions perms = new AppSecurityPermissions(this, mAppDiff.mPkgInfo); final int NP = perms.getPermissionCount(AppSecurityPermissions.WHICH_PERSONAL);
final int NP = perms.getPermissionCount(AppSecurityPermissions.WHICH_PERSONAL); final int ND = perms.getPermissionCount(AppSecurityPermissions.WHICH_DEVICE);
final int ND = perms.getPermissionCount(AppSecurityPermissions.WHICH_DEVICE); if (mAppDiff.mInstalledAppInfo != null) {
if (mAppDiff.mInstalledAppInfo != null) { msg = (mAppDiff.mInstalledAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
msg = (mAppDiff.mInstalledAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? R.string.install_confirm_update_system
? R.string.install_confirm_update_system : R.string.install_confirm_update;
: R.string.install_confirm_update; mScrollView = new CaffeinatedScrollView(this);
mScrollView = new CaffeinatedScrollView(this); mScrollView.setFillViewport(true);
mScrollView.setFillViewport(true); final boolean newPermissionsFound =
final boolean newPermissionsFound = (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0);
(perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0); if (newPermissionsFound) {
if (newPermissionsFound) {
permVisible = true;
mScrollView.addView(perms.getPermissionsView(
AppSecurityPermissions.WHICH_NEW));
} else {
throw new RuntimeException("This should not happen. No new permissions were found but InstallConfirmActivity has been started!");
}
adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator(
getText(R.string.newPerms)), mScrollView);
} else {
findViewById(R.id.tabscontainer).setVisibility(View.GONE);
findViewById(R.id.divider).setVisibility(View.VISIBLE);
}
if (NP > 0 || ND > 0) {
permVisible = true; permVisible = true;
LayoutInflater inflater = (LayoutInflater) getSystemService( mScrollView.addView(perms.getPermissionsView(
Context.LAYOUT_INFLATER_SERVICE); AppSecurityPermissions.WHICH_NEW));
View root = inflater.inflate(R.layout.permissions_list, null); } else {
if (mScrollView == null) { throw new RuntimeException("This should not happen. No new permissions were found but InstallConfirmActivity has been started!");
mScrollView = (CaffeinatedScrollView) root.findViewById(R.id.scrollview);
}
final ViewGroup privacyList = (ViewGroup) root.findViewById(R.id.privacylist);
if (NP > 0) {
privacyList.addView(perms.getPermissionsView(AppSecurityPermissions.WHICH_PERSONAL));
} else {
privacyList.setVisibility(View.GONE);
}
final ViewGroup deviceList = (ViewGroup) root.findViewById(R.id.devicelist);
if (ND > 0) {
deviceList.addView(perms.getPermissionsView(AppSecurityPermissions.WHICH_DEVICE));
} else {
root.findViewById(R.id.devicelist).setVisibility(View.GONE);
}
adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator(
getText(R.string.allPerms)), root);
} }
adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator(
getText(R.string.newPerms)), mScrollView);
} else {
findViewById(R.id.tabscontainer).setVisibility(View.GONE);
findViewById(R.id.divider).setVisibility(View.VISIBLE);
} }
if (NP > 0 || ND > 0) {
permVisible = true;
LayoutInflater inflater = (LayoutInflater) getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View root = inflater.inflate(R.layout.permissions_list, null);
if (mScrollView == null) {
mScrollView = (CaffeinatedScrollView) root.findViewById(R.id.scrollview);
}
final ViewGroup privacyList = (ViewGroup) root.findViewById(R.id.privacylist);
if (NP > 0) {
privacyList.addView(perms.getPermissionsView(AppSecurityPermissions.WHICH_PERSONAL));
} else {
privacyList.setVisibility(View.GONE);
}
final ViewGroup deviceList = (ViewGroup) root.findViewById(R.id.devicelist);
if (ND > 0) {
deviceList.addView(perms.getPermissionsView(AppSecurityPermissions.WHICH_DEVICE));
} else {
root.findViewById(R.id.devicelist).setVisibility(View.GONE);
}
adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator(
getText(R.string.allPerms)), root);
}
if (!permVisible) { if (!permVisible) {
if (mAppDiff.mInstalledAppInfo != null) { if (mAppDiff.mInstalledAppInfo != null) {
// This is an update to an application, but there are no // This is an update to an application, but there are no
@ -184,6 +185,10 @@ public class InstallConfirmActivity extends Activity implements OnCancelListener
Uri packageURI = intent.getData(); Uri packageURI = intent.getData();
mAppDiff = new AppDiff(mPm, packageURI); mAppDiff = new AppDiff(mPm, packageURI);
if (mAppDiff.mPkgInfo == null) {
setResult(RESULT_CANNOT_PARSE, intent);
finish();
}
setContentView(R.layout.install_start); setContentView(R.layout.install_start);
mInstallConfirm = findViewById(R.id.install_confirm_panel); mInstallConfirm = findViewById(R.id.install_confirm_panel);

View File

@ -76,10 +76,12 @@ abstract public class Installer {
public interface InstallerCallback { public interface InstallerCallback {
int OPERATION_INSTALL = 1; int OPERATION_INSTALL = 1;
int OPERATION_DELETE = 2; int OPERATION_DELETE = 2;
int ERROR_CODE_CANCELED = 1; // Avoid using [-1,1] as they may conflict with Activity.RESULT_*
int ERROR_CODE_OTHER = 2; int ERROR_CODE_CANCELED = 2;
int ERROR_CODE_OTHER = 3;
int ERROR_CODE_CANNOT_PARSE = 4;
void onSuccess(int operation); void onSuccess(int operation);

View File

@ -140,7 +140,13 @@ public class SystemInstaller extends Installer {
@Override @Override
protected void installPackageInternal(File apkFile) throws AndroidNotCompatibleException { protected void installPackageInternal(File apkFile) throws AndroidNotCompatibleException {
Uri packageUri = Uri.fromFile(apkFile); Uri packageUri = Uri.fromFile(apkFile);
if (hasNewPermissions(packageUri)) { int count = newPermissionCount(packageUri);
if (count < 0) {
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
InstallerCallback.ERROR_CODE_CANNOT_PARSE);
return;
}
if (count > 0) {
Intent intent = new Intent(mContext, InstallConfirmActivity.class); Intent intent = new Intent(mContext, InstallConfirmActivity.class);
intent.setData(packageUri); intent.setData(packageUri);
mActivity.startActivityForResult(intent, REQUEST_CONFIRM_PERMS); mActivity.startActivityForResult(intent, REQUEST_CONFIRM_PERMS);
@ -154,17 +160,20 @@ public class SystemInstaller extends Installer {
} }
} }
private boolean hasNewPermissions(Uri packageUri) { private int newPermissionCount(Uri packageUri) {
AppDiff appDiff = new AppDiff(mContext.getPackageManager(), packageUri); AppDiff appDiff = new AppDiff(mContext.getPackageManager(), packageUri);
if (appDiff.mPkgInfo != null) { if (appDiff.mPkgInfo == null) {
AppSecurityPermissions perms = new AppSecurityPermissions(mContext, appDiff.mPkgInfo); // could not get diff because we couldn't parse the package
if (appDiff.mInstalledAppInfo != null) { // it is an update to an existing app return -1;
// return false if there are no new permissions
return (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0);
}
} }
// default: show install confirm activity AppSecurityPermissions perms = new AppSecurityPermissions(mContext, appDiff.mPkgInfo);
return true; if (appDiff.mInstalledAppInfo != null) {
// update to an existing app
return perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW);
}
// default: even if there aren't any permissions, we want to make the
// user always confirm installing new apps
return 1;
} }
private void doInstallPackageInternal(Uri packageURI) throws AndroidNotCompatibleException { private void doInstallPackageInternal(Uri packageURI) throws AndroidNotCompatibleException {
@ -260,7 +269,10 @@ public class SystemInstaller extends Installer {
mCallback.onError(InstallerCallback.OPERATION_INSTALL, mCallback.onError(InstallerCallback.OPERATION_INSTALL,
InstallerCallback.ERROR_CODE_OTHER); InstallerCallback.ERROR_CODE_OTHER);
} }
} else { } else if (resultCode == InstallConfirmActivity.RESULT_CANNOT_PARSE) {
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
InstallerCallback.ERROR_CODE_CANNOT_PARSE);
} else { // Activity.RESULT_CANCELED
mCallback.onError(InstallerCallback.OPERATION_INSTALL, mCallback.onError(InstallerCallback.OPERATION_INSTALL,
InstallerCallback.ERROR_CODE_CANCELED); InstallerCallback.ERROR_CODE_CANCELED);
} }