Merge branch 'super-fdroid'
This commit is contained in:
commit
3c9d7b67cc
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -14,3 +14,7 @@
|
||||
path = extern/nanohttpd
|
||||
url = https://github.com/eighthave/nanohttpd
|
||||
ignore = dirty
|
||||
[submodule "extern/libsuperuser"]
|
||||
path = extern/libsuperuser
|
||||
url = https://github.com/dschuermann/libsuperuser.git
|
||||
ignore = dirty
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.fdroid.fdroid"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="670"
|
||||
@ -38,7 +39,16 @@
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
|
||||
|
||||
<!-- These permissions are only granted when F-Droid is installed as a system-app! -->
|
||||
<uses-permission android:name="android.permission.INSTALL_PACKAGES"
|
||||
tools:ignore="ProtectedPermissions"/>
|
||||
<uses-permission android:name="android.permission.DELETE_PACKAGES"
|
||||
tools:ignore="ProtectedPermissions"/>
|
||||
|
||||
<!-- Indicate that F-Droid may request root access (introduced by Koush's Superuser app) -->
|
||||
<uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>
|
||||
|
||||
<application
|
||||
android:name="FDroidApp"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
|
14
TODO.md
Normal file
14
TODO.md
Normal file
@ -0,0 +1,14 @@
|
||||
## TODO
|
||||
|
||||
* Provide a way to make F-Droid a system app on rooted phones
|
||||
|
||||
* Handle error codes returned by InstallSystemManager callback (like the
|
||||
internal code for PackageInstaller does)
|
||||
|
||||
* Show changed permissions when doing an unattended install
|
||||
|
||||
* Handle ROM updates in some way to avoid being removed from /system
|
||||
|
||||
## Links
|
||||
|
||||
* [Original MR](https://gitorious.org/f-droid/fdroidclient/merge_requests/37)
|
@ -4,6 +4,7 @@ android update lib-project --path extern/UniversalImageLoader/library
|
||||
android update lib-project --path extern/AndroidPinning
|
||||
android update lib-project --path extern/MemorizingTrustManager
|
||||
android update lib-project --path extern/nanohttpd
|
||||
android update lib-project --path extern/libsuperuser/libsuperuser
|
||||
android update project --path . --name F-Droid
|
||||
|
||||
{ echo -e "\nSuccessfully updated the main project.\n"; } 2>/dev/null
|
||||
|
@ -14,6 +14,7 @@ dependencies {
|
||||
compile project(':extern:AndroidPinning')
|
||||
compile project(':extern:UniversalImageLoader:library')
|
||||
compile project(':extern:MemorizingTrustManager')
|
||||
compile project(':extern:libsuperuser:libsuperuser')
|
||||
}
|
||||
|
||||
project(':extern:UniversalImageLoader:library') {
|
||||
|
1
extern/libsuperuser
vendored
Submodule
1
extern/libsuperuser
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit faffc41121b509b2b1b01d4ecac3f395e4adbee2
|
@ -6,3 +6,4 @@ android.library.reference.1=extern/UniversalImageLoader/library
|
||||
android.library.reference.2=extern/MemorizingTrustManager
|
||||
android.library.reference.3=extern/AndroidPinning
|
||||
android.library.reference.4=extern/nanohttpd
|
||||
android.library.reference.5=extern/libsuperuser/libsuperuser
|
||||
|
@ -28,7 +28,13 @@
|
||||
<string name="notify_off">Do not notify of any updates</string>
|
||||
<string name="update_history">Update history</string>
|
||||
<string name="update_history_summ">Days to consider apps new or recent: %s</string>
|
||||
|
||||
<string name="root_installer">Install using root access</string>
|
||||
<string name="root_installer_on">Request root access to install, update, and remove packages</string>
|
||||
<string name="root_installer_off">Do not request root access to install, update, and remove packages</string>
|
||||
<string name="system_installer">Install using system-permissions</string>
|
||||
<string name="system_installer_on">Use system permissions to install, update, and remove packages</string>
|
||||
<string name="system_installer_off">Do not use system permissions to install, update, and remove packages</string>
|
||||
|
||||
<string name="search_results">Search Results</string>
|
||||
<string name="app_details">App Details</string>
|
||||
<string name="no_such_app">No such app found</string>
|
||||
@ -261,5 +267,15 @@
|
||||
<string name="Security">Security</string>
|
||||
<string name="System">System</string>
|
||||
<string name="Wallpaper">Wallpaper</string>
|
||||
|
||||
|
||||
<string name="requesting_root_access_title">Root access</string>
|
||||
<string name="requesting_root_access_body">Requesting root access…</string>
|
||||
<string name="root_access_denied_title">Root access denied</string>
|
||||
<string name="root_access_denied_body">Either your Android device is not rooted or you have denied root access for F-Droid.</string>
|
||||
<string name="update_all">Update all</string>
|
||||
<string name="installer_error_title">(De-)Installation Error</string>
|
||||
<string name="installer_error_body">The (de-)installation failed. If you are using root access, try disabling this setting!</string>
|
||||
<string name="system_permission_denied_title">System permissions denied</string>
|
||||
<string name="system_permission_denied_body">This option is only available when F-Droid is installed as a system-app.</string>
|
||||
|
||||
</resources>
|
||||
|
@ -3,7 +3,8 @@
|
||||
<PreferenceCategory android:title="@string/updates">
|
||||
<ListPreference android:title="@string/update_interval"
|
||||
android:key="updateInterval"
|
||||
android:defaultValue="24" android:entries="@array/updateIntervalNames"
|
||||
android:defaultValue="24"
|
||||
android:entries="@array/updateIntervalNames"
|
||||
android:entryValues="@array/updateIntervalValues" />
|
||||
<CheckBoxPreference android:title="@string/automatic_scan_wifi"
|
||||
android:defaultValue="false"
|
||||
@ -49,5 +50,11 @@
|
||||
<CheckBoxPreference android:title="@string/expert"
|
||||
android:defaultValue="false"
|
||||
android:key="expert" />
|
||||
<CheckBoxPreference android:title="@string/root_installer"
|
||||
android:defaultValue="false"
|
||||
android:key="rootInstaller" />
|
||||
<CheckBoxPreference android:title="@string/system_installer"
|
||||
android:defaultValue="false"
|
||||
android:key="systemInstaller" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
@ -1 +1 @@
|
||||
include ':extern:AndroidPinning', ':extern:UniversalImageLoader:library', ':extern:MemorizingTrustManager'
|
||||
include ':extern:AndroidPinning', ':extern:UniversalImageLoader:library', ':extern:MemorizingTrustManager', ':extern:libsuperuser:libsuperuser'
|
||||
|
49
src/android/content/pm/IPackageDeleteObserver.java
Normal file
49
src/android/content/pm/IPackageDeleteObserver.java
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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 android.content.pm;
|
||||
|
||||
/**
|
||||
* Just a non-working implementation of this Stub to satisfy compiler!
|
||||
*/
|
||||
public interface IPackageDeleteObserver extends android.os.IInterface {
|
||||
|
||||
public abstract static class Stub extends android.os.Binder implements
|
||||
android.content.pm.IPackageDeleteObserver {
|
||||
public Stub() {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public static android.content.pm.IPackageDeleteObserver asInterface(android.os.IBinder obj) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public android.os.IBinder asBinder() {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply,
|
||||
int flags) throws android.os.RemoteException {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void packageDeleted(java.lang.String packageName, int returnCode)
|
||||
throws android.os.RemoteException;
|
||||
}
|
49
src/android/content/pm/IPackageInstallObserver.java
Normal file
49
src/android/content/pm/IPackageInstallObserver.java
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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 android.content.pm;
|
||||
|
||||
/**
|
||||
* Just a non-working implementation of this Stub to satisfy compiler!
|
||||
*/
|
||||
public interface IPackageInstallObserver extends android.os.IInterface {
|
||||
|
||||
public abstract static class Stub extends android.os.Binder implements
|
||||
android.content.pm.IPackageInstallObserver {
|
||||
public Stub() {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public static android.content.pm.IPackageInstallObserver asInterface(android.os.IBinder obj) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public android.os.IBinder asBinder() {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply,
|
||||
int flags) throws android.os.RemoteException {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void packageInstalled(java.lang.String packageName, int returnCode)
|
||||
throws android.os.RemoteException;
|
||||
}
|
@ -21,28 +21,29 @@ package org.fdroid.fdroid;
|
||||
|
||||
import android.content.*;
|
||||
import android.widget.*;
|
||||
|
||||
import org.fdroid.fdroid.data.*;
|
||||
import org.fdroid.fdroid.installer.Installer;
|
||||
import org.fdroid.fdroid.installer.Installer.AndroidNotCompatibleException;
|
||||
import org.fdroid.fdroid.installer.Installer.InstallerCallback;
|
||||
import org.xml.sax.XMLReader;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ListActivity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.net.Uri;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.Signature;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.database.ContentObserver;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.Html.TagHandler;
|
||||
@ -56,6 +57,7 @@ import android.view.MenuItem;
|
||||
import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
@ -75,8 +77,6 @@ import java.util.List;
|
||||
public class AppDetails extends ListActivity {
|
||||
private static final String TAG = "AppDetails";
|
||||
|
||||
private static final int REQUEST_INSTALL = 0;
|
||||
private static final int REQUEST_UNINSTALL = 1;
|
||||
public static final int REQUEST_ENABLE_BLUETOOTH = 2;
|
||||
|
||||
public static final String EXTRA_APPID = "appid";
|
||||
@ -95,6 +95,31 @@ public class AppDetails extends ListActivity {
|
||||
TextView added;
|
||||
TextView nativecode;
|
||||
}
|
||||
|
||||
// observer to update view when package has been installed/deleted
|
||||
AppObserver myAppObserver;
|
||||
class AppObserver extends ContentObserver {
|
||||
public AppObserver(Handler handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
this.onChange(selfChange, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
if (!reset()) {
|
||||
AppDetails.this.finish();
|
||||
return;
|
||||
}
|
||||
updateViews();
|
||||
|
||||
MenuManager.create(AppDetails.this).invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class ApkListAdapter extends ArrayAdapter<Apk> {
|
||||
|
||||
@ -255,10 +280,12 @@ public class AppDetails extends ListActivity {
|
||||
|
||||
private final Context mctx = this;
|
||||
private DisplayImageOptions displayImageOptions;
|
||||
private Installer installer;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
|
||||
|
||||
fdroidApp = ((FDroidApp) getApplication());
|
||||
fdroidApp.applyTheme(this);
|
||||
|
||||
@ -308,6 +335,9 @@ public class AppDetails extends ListActivity {
|
||||
}
|
||||
|
||||
mPm = getPackageManager();
|
||||
installer = Installer.getActivityInstaller(this, mPm,
|
||||
myInstallerCallback);
|
||||
|
||||
// Get the preferences we're going to use in this Activity...
|
||||
AppDetails old = (AppDetails) getLastNonConfigurationInstance();
|
||||
if (old != null) {
|
||||
@ -317,7 +347,6 @@ public class AppDetails extends ListActivity {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
resetRequired = false;
|
||||
}
|
||||
|
||||
SharedPreferences prefs = PreferenceManager
|
||||
@ -341,7 +370,6 @@ public class AppDetails extends ListActivity {
|
||||
private boolean pref_expert;
|
||||
private boolean pref_permissions;
|
||||
private boolean pref_incompatibleVersions;
|
||||
private boolean resetRequired;
|
||||
|
||||
// The signature of the installed version.
|
||||
private Signature mInstalledSignature;
|
||||
@ -349,13 +377,19 @@ public class AppDetails extends ListActivity {
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
Log.d(TAG, "onresume");
|
||||
super.onResume();
|
||||
if (resetRequired) {
|
||||
if (!reset()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
resetRequired = false;
|
||||
|
||||
// register observer to know when install status changes
|
||||
myAppObserver = new AppObserver(new Handler());
|
||||
getContentResolver().registerContentObserver(
|
||||
AppProvider.getContentUri(app.id),
|
||||
true,
|
||||
myAppObserver);
|
||||
|
||||
if (!reset()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
updateViews();
|
||||
|
||||
@ -368,6 +402,9 @@ public class AppDetails extends ListActivity {
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
if (myAppObserver != null) {
|
||||
getContentResolver().unregisterContentObserver(myAppObserver);
|
||||
}
|
||||
if (downloadHandler != null) {
|
||||
downloadHandler.stopUpdates();
|
||||
}
|
||||
@ -924,46 +961,73 @@ public class AppDetails extends ListActivity {
|
||||
downloadHandler = new DownloadHandler(apk, repoaddress,
|
||||
Utils.getApkCacheDir(getBaseContext()));
|
||||
}
|
||||
private void installApk(File file, String packageName) {
|
||||
setProgressBarIndeterminateVisibility(true);
|
||||
|
||||
private void removeApk(String id) {
|
||||
PackageInfo pkginfo;
|
||||
try {
|
||||
pkginfo = mPm.getPackageInfo(id, 0);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.d("FDroid", "Couldn't find package " + id + " to uninstall.");
|
||||
return;
|
||||
installer.installPackage(file);
|
||||
} catch (AndroidNotCompatibleException e) {
|
||||
Log.e(TAG, "Android not compatible with this Installer!", e);
|
||||
}
|
||||
Uri uri = Uri.fromParts("package", pkginfo.packageName, null);
|
||||
Intent intent = new Intent(Intent.ACTION_DELETE, uri);
|
||||
startActivityForResult(intent, REQUEST_UNINSTALL);
|
||||
notifyAppChanged(id);
|
||||
|
||||
}
|
||||
|
||||
@TargetApi(14)
|
||||
private void extraNotUnknownSource(Intent intent) {
|
||||
if (Build.VERSION.SDK_INT < 14) {
|
||||
return;
|
||||
private void removeApk(String packageName) {
|
||||
setProgressBarIndeterminateVisibility(true);
|
||||
|
||||
try {
|
||||
installer.deletePackage(packageName);
|
||||
} catch (AndroidNotCompatibleException e) {
|
||||
Log.e(TAG, "Android not compatible with this Installer!", e);
|
||||
}
|
||||
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
|
||||
}
|
||||
|
||||
private void installApk(File file, String id) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(Uri.parse("file://" + file.getPath()),
|
||||
"application/vnd.android.package-archive");
|
||||
extraNotUnknownSource(intent);
|
||||
startActivityForResult(intent, REQUEST_INSTALL);
|
||||
notifyAppChanged(id);
|
||||
}
|
||||
Installer.InstallerCallback myInstallerCallback = new Installer.InstallerCallback() {
|
||||
|
||||
/**
|
||||
* We could probably drop this, and let the PackageReceiver take care of notifications
|
||||
* for us, but I don't think the package receiver notifications are very instantaneous.
|
||||
*/
|
||||
private void notifyAppChanged(String id) {
|
||||
getContentResolver().notifyChange(AppProvider.getContentUri(id), null);
|
||||
}
|
||||
@Override
|
||||
public void onSuccess(final int operation) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (operation == Installer.InstallerCallback.OPERATION_INSTALL) {
|
||||
if (downloadHandler != null) {
|
||||
downloadHandler = null;
|
||||
}
|
||||
|
||||
PackageManagerCompat.setInstaller(mPm, app.id);
|
||||
}
|
||||
|
||||
setProgressBarIndeterminateVisibility(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int operation, final int errorCode) {
|
||||
if (errorCode == InstallerCallback.ERROR_CODE_CANCELED) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setProgressBarIndeterminateVisibility(false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setProgressBarIndeterminateVisibility(false);
|
||||
|
||||
Log.e(TAG, "Installer aborted with errorCode: " + errorCode);
|
||||
|
||||
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(AppDetails.this);
|
||||
alertBuilder.setTitle(R.string.installer_error_title);
|
||||
alertBuilder.setMessage(R.string.installer_error_title);
|
||||
alertBuilder.setNeutralButton(android.R.string.ok, null);
|
||||
alertBuilder.create().show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void launchApk(String id) {
|
||||
Intent intent = mPm.getLaunchIntentForPackage(id);
|
||||
@ -1111,20 +1175,15 @@ public class AppDetails extends ListActivity {
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// handle cases for install manager first
|
||||
if (installer.handleOnActivityResult(requestCode, resultCode, data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_INSTALL:
|
||||
if (downloadHandler != null) {
|
||||
downloadHandler = null;
|
||||
}
|
||||
|
||||
PackageManagerCompat.setInstaller(mPm, app.id);
|
||||
resetRequired = true;
|
||||
break;
|
||||
case REQUEST_UNINSTALL:
|
||||
resetRequired = true;
|
||||
break;
|
||||
case REQUEST_ENABLE_BLUETOOTH:
|
||||
fdroidApp.sendViaBluetooth(this, resultCode, app.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,10 +36,14 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
|
||||
public static final String PREF_CACHE_APK = "cacheDownloaded";
|
||||
public static final String PREF_EXPERT = "expert";
|
||||
public static final String PREF_UPD_LAST = "lastUpdateCheck";
|
||||
public static final String PREF_ROOT_INSTALLER = "rootInstaller";
|
||||
public static final String PREF_SYSTEM_INSTALLER = "systemInstaller";
|
||||
|
||||
private static final boolean DEFAULT_COMPACT_LAYOUT = false;
|
||||
private static final boolean DEFAULT_ROOTED = true;
|
||||
private static final int DEFAULT_UPD_HISTORY = 14;
|
||||
private static final boolean DEFAULT_ROOT_INSTALLER = false;
|
||||
private static final boolean DEFAULT_SYSTEM_INSTALLER = false;
|
||||
|
||||
private boolean compactLayout = DEFAULT_COMPACT_LAYOUT;
|
||||
private boolean filterAppsRequiringRoot = DEFAULT_ROOTED;
|
||||
@ -61,6 +65,14 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
|
||||
private void uninitialize(String key) {
|
||||
initialized.put(key, false);
|
||||
}
|
||||
|
||||
public boolean isRootInstallerEnabled() {
|
||||
return preferences.getBoolean(PREF_ROOT_INSTALLER, DEFAULT_ROOT_INSTALLER);
|
||||
}
|
||||
|
||||
public boolean isSystemInstallerEnabled() {
|
||||
return preferences.getBoolean(PREF_SYSTEM_INSTALLER, DEFAULT_SYSTEM_INSTALLER);
|
||||
}
|
||||
|
||||
public boolean hasCompactLayout() {
|
||||
if (!isInitialized(PREF_COMPACT_LAYOUT)) {
|
||||
@ -97,7 +109,7 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
|
||||
|
||||
/**
|
||||
* This is cached as it is called several times inside the AppListAdapter.
|
||||
* Providing it here means sthe shared preferences file only needs to be
|
||||
* Providing it here means the shared preferences file only needs to be
|
||||
* read once, and we will keep our copy up to date by listening to changes
|
||||
* in PREF_ROOTED.
|
||||
*/
|
||||
|
@ -20,22 +20,26 @@ package org.fdroid.fdroid;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.Preference.OnPreferenceClickListener;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import android.support.v4.app.NavUtils;
|
||||
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.compat.ActionBarCompat;
|
||||
import org.fdroid.fdroid.installer.CheckRootAsyncTask;
|
||||
import org.fdroid.fdroid.installer.CheckRootAsyncTask.CheckRootCallback;
|
||||
import org.fdroid.fdroid.installer.Installer;
|
||||
|
||||
public class PreferencesActivity extends PreferenceActivity implements
|
||||
OnSharedPreferenceChangeListener {
|
||||
|
||||
|
||||
public static final int RESULT_RESTART = 4;
|
||||
private int result = 0;
|
||||
|
||||
@ -51,7 +55,9 @@ public class PreferencesActivity extends PreferenceActivity implements
|
||||
Preferences.PREF_COMPACT_LAYOUT,
|
||||
Preferences.PREF_IGN_TOUCH,
|
||||
Preferences.PREF_CACHE_APK,
|
||||
Preferences.PREF_EXPERT
|
||||
Preferences.PREF_EXPERT,
|
||||
Preferences.PREF_ROOT_INSTALLER,
|
||||
Preferences.PREF_SYSTEM_INSTALLER
|
||||
};
|
||||
|
||||
@Override
|
||||
@ -148,24 +154,138 @@ public class PreferencesActivity extends PreferenceActivity implements
|
||||
onoffSummary(key, R.string.expert_on,
|
||||
R.string.expert_off);
|
||||
|
||||
} else if (key.equals(Preferences.PREF_ROOT_INSTALLER)) {
|
||||
onoffSummary(key, R.string.root_installer_on,
|
||||
R.string.root_installer_off);
|
||||
|
||||
} else if (key.equals(Preferences.PREF_SYSTEM_INSTALLER)) {
|
||||
onoffSummary(key, R.string.system_installer_on,
|
||||
R.string.system_installer_off);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes RootInstaller preference. This method ensures that the preference can only be checked and persisted
|
||||
* when the user grants root access for F-Droid.
|
||||
*/
|
||||
protected void initRootInstallerPreference() {
|
||||
CheckBoxPreference pref = (CheckBoxPreference) findPreference(Preferences.PREF_ROOT_INSTALLER);
|
||||
|
||||
// we are handling persistence ourself!
|
||||
pref.setPersistent(false);
|
||||
|
||||
pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
final CheckBoxPreference pref = (CheckBoxPreference) preference;
|
||||
|
||||
if (pref.isChecked()) {
|
||||
CheckRootAsyncTask checkTask = new CheckRootAsyncTask(PreferencesActivity.this, new CheckRootCallback() {
|
||||
|
||||
@Override
|
||||
public void onRootCheck(boolean rootGranted) {
|
||||
if (rootGranted) {
|
||||
// root access granted
|
||||
SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
|
||||
editor.putBoolean(Preferences.PREF_ROOT_INSTALLER, true);
|
||||
editor.commit();
|
||||
pref.setChecked(true);
|
||||
} else {
|
||||
// root access denied
|
||||
SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
|
||||
editor.putBoolean(Preferences.PREF_ROOT_INSTALLER, false);
|
||||
editor.commit();
|
||||
pref.setChecked(false);
|
||||
|
||||
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(PreferencesActivity.this);
|
||||
alertBuilder.setTitle(R.string.root_access_denied_title);
|
||||
alertBuilder.setMessage(PreferencesActivity.this.getString(R.string.root_access_denied_body));
|
||||
alertBuilder.setNeutralButton(android.R.string.ok, null);
|
||||
alertBuilder.create().show();
|
||||
}
|
||||
}
|
||||
});
|
||||
checkTask.execute();
|
||||
} else {
|
||||
SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
|
||||
editor.putBoolean(Preferences.PREF_ROOT_INSTALLER, false);
|
||||
editor.commit();
|
||||
pref.setChecked(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes SystemInstaller preference, which can only be enabled when F-Droid is installed as a system-app
|
||||
*/
|
||||
protected void initSystemInstallerPreference() {
|
||||
CheckBoxPreference pref = (CheckBoxPreference) findPreference(Preferences.PREF_SYSTEM_INSTALLER);
|
||||
|
||||
// we are handling persistence ourself!
|
||||
pref.setPersistent(false);
|
||||
|
||||
pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
final CheckBoxPreference pref = (CheckBoxPreference) preference;
|
||||
|
||||
if (pref.isChecked()) {
|
||||
if (Installer.hasSystemPermissions(PreferencesActivity.this, PreferencesActivity.this.getPackageManager())) {
|
||||
// system-permission are granted, i.e. F-Droid is a system-app
|
||||
SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
|
||||
editor.putBoolean(Preferences.PREF_SYSTEM_INSTALLER, true);
|
||||
editor.commit();
|
||||
pref.setChecked(true);
|
||||
} else {
|
||||
// system-permission not available
|
||||
SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
|
||||
editor.putBoolean(Preferences.PREF_SYSTEM_INSTALLER, false);
|
||||
editor.commit();
|
||||
pref.setChecked(false);
|
||||
|
||||
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(PreferencesActivity.this);
|
||||
alertBuilder.setTitle(R.string.system_permission_denied_title);
|
||||
alertBuilder.setMessage(PreferencesActivity.this.getString(R.string.system_permission_denied_body));
|
||||
alertBuilder.setNeutralButton(android.R.string.ok, null);
|
||||
alertBuilder.create().show();
|
||||
}
|
||||
} else {
|
||||
SharedPreferences.Editor editor = pref.getSharedPreferences().edit();
|
||||
editor.putBoolean(Preferences.PREF_SYSTEM_INSTALLER, false);
|
||||
editor.commit();
|
||||
pref.setChecked(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
|
||||
super.onResume();
|
||||
|
||||
getPreferenceScreen().getSharedPreferences()
|
||||
.registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
for (String key : summariesToUpdate) {
|
||||
updateSummary(key, false);
|
||||
}
|
||||
|
||||
initRootInstallerPreference();
|
||||
initSystemInstallerPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
getPreferenceScreen().getSharedPreferences()
|
||||
.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
@ -9,12 +9,12 @@ import android.util.Log;
|
||||
public class PackageManagerCompat extends Compatibility {
|
||||
|
||||
@TargetApi(11)
|
||||
public static void setInstaller(PackageManager mPm, String app_id) {
|
||||
public static void setInstaller(PackageManager mPm, String packageName) {
|
||||
if (!hasApi(11)) return;
|
||||
try {
|
||||
mPm.setInstallerPackageName(app_id, "org.fdroid.fdroid");
|
||||
mPm.setInstallerPackageName(packageName, "org.fdroid.fdroid");
|
||||
Log.d("FDroid", "Installer package name for " +
|
||||
app_id + " set successfully");
|
||||
packageName + " set successfully");
|
||||
} catch (Exception e) {
|
||||
// Many problems can occur:
|
||||
// * App wasn't installed due to incompatibility
|
||||
@ -22,8 +22,8 @@ public class PackageManagerCompat extends Compatibility {
|
||||
// * Another app interfered in the process
|
||||
// * Another app already set the target's installer package
|
||||
// * ...
|
||||
Log.d("FDroid", "Could not set installer package name for " +
|
||||
app_id + ": " + e.getMessage());
|
||||
Log.e("FDroid", "Could not set installer package name for " +
|
||||
packageName, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
70
src/org/fdroid/fdroid/installer/CheckRootAsyncTask.java
Normal file
70
src/org/fdroid/fdroid/installer/CheckRootAsyncTask.java
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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 org.fdroid.fdroid.R;
|
||||
|
||||
import eu.chainfire.libsuperuser.Shell;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
public class CheckRootAsyncTask extends AsyncTask<Void, Void, Boolean> {
|
||||
ProgressDialog mDialog;
|
||||
Context mContext;
|
||||
CheckRootCallback mCallback;
|
||||
|
||||
public interface CheckRootCallback {
|
||||
public void onRootCheck(boolean rootGranted);
|
||||
}
|
||||
|
||||
public CheckRootAsyncTask(Context context, CheckRootCallback callback) {
|
||||
super();
|
||||
this.mContext = context;
|
||||
this.mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
|
||||
mDialog = new ProgressDialog(mContext);
|
||||
mDialog.setTitle(R.string.requesting_root_access_title);
|
||||
mDialog.setMessage(mContext.getString(R.string.requesting_root_access_body));
|
||||
mDialog.setIndeterminate(true);
|
||||
mDialog.setCancelable(false);
|
||||
mDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
return Shell.SU.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
mDialog.dismiss();
|
||||
|
||||
mCallback.onRootCheck(result);
|
||||
}
|
||||
|
||||
}
|
115
src/org/fdroid/fdroid/installer/DefaultInstaller.java
Normal file
115
src/org/fdroid/fdroid/installer/DefaultInstaller.java
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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 java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* For Android < 4: Default Installer using the public PackageManager API of
|
||||
* Android to install/delete packages. This starts a Activity from the Android
|
||||
* OS showing all permissions/changed permissions. The the user needs to
|
||||
* manually press an install button, this Installer cannot be used for
|
||||
* unattended installations.
|
||||
*/
|
||||
public class DefaultInstaller extends Installer {
|
||||
private Activity mActivity;
|
||||
|
||||
public DefaultInstaller(Activity activity, PackageManager pm, InstallerCallback callback)
|
||||
throws AndroidNotCompatibleException {
|
||||
super(activity, pm, callback);
|
||||
this.mActivity = activity;
|
||||
}
|
||||
|
||||
private static final int REQUEST_CODE_INSTALL = 0;
|
||||
private static final int REQUEST_CODE_DELETE = 1;
|
||||
|
||||
@Override
|
||||
protected void installPackageInternal(File apkFile) throws AndroidNotCompatibleException {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(Uri.fromFile(apkFile),
|
||||
"application/vnd.android.package-archive");
|
||||
try {
|
||||
mActivity.startActivityForResult(intent, REQUEST_CODE_INSTALL);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
throw new AndroidNotCompatibleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installPackageInternal(List<File> apkFiles) throws AndroidNotCompatibleException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deletePackageInternal(String packageName) throws AndroidNotCompatibleException {
|
||||
PackageInfo pkgInfo = null;
|
||||
try {
|
||||
pkgInfo = mPm.getPackageInfo(packageName, 0);
|
||||
} catch (NameNotFoundException e) {
|
||||
// already checked in super class
|
||||
}
|
||||
|
||||
Uri uri = Uri.fromParts("package", pkgInfo.packageName, null);
|
||||
Intent intent = new Intent(Intent.ACTION_DELETE, uri);
|
||||
try {
|
||||
mActivity.startActivityForResult(intent, REQUEST_CODE_DELETE);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
throw new AndroidNotCompatibleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
/**
|
||||
* resultCode is always 0 on Android < 4.0. See
|
||||
* com.android.packageinstaller.PackageInstallerActivity: setResult is
|
||||
* never executed on Androids before 4.0
|
||||
*/
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_INSTALL:
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL);
|
||||
|
||||
return true;
|
||||
case REQUEST_CODE_DELETE:
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_DELETE);
|
||||
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsUnattendedOperations() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
141
src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java
Normal file
141
src/org/fdroid/fdroid/installer/DefaultInstallerSdk14.java
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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 java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* For Android >= 4.0: Default Installer using the public PackageManager API of
|
||||
* Android to install/delete packages. This starts a Activity from the Android
|
||||
* OS showing all permissions/changed permissions. The the user needs to
|
||||
* manually press an install button, this Installer cannot be used for
|
||||
* unattended installations.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||
public class DefaultInstallerSdk14 extends Installer {
|
||||
private Activity mActivity;
|
||||
|
||||
public DefaultInstallerSdk14(Activity activity, PackageManager pm, InstallerCallback callback)
|
||||
throws AndroidNotCompatibleException {
|
||||
super(activity, pm, callback);
|
||||
this.mActivity = activity;
|
||||
}
|
||||
|
||||
private static final int REQUEST_CODE_INSTALL = 0;
|
||||
private static final int REQUEST_CODE_DELETE = 1;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
protected void installPackageInternal(File apkFile) throws AndroidNotCompatibleException {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_INSTALL_PACKAGE);
|
||||
intent.setData(Uri.fromFile(apkFile));
|
||||
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
|
||||
|
||||
// following extras only work when being installed as system-app
|
||||
// https://code.google.com/p/android/issues/detail?id=42253
|
||||
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
|
||||
// deprecated in Android 4.1
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_REPLACE, true);
|
||||
}
|
||||
try {
|
||||
mActivity.startActivityForResult(intent, REQUEST_CODE_INSTALL);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
throw new AndroidNotCompatibleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installPackageInternal(List<File> apkFiles) throws AndroidNotCompatibleException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deletePackageInternal(String packageName) throws AndroidNotCompatibleException {
|
||||
PackageInfo pkgInfo = null;
|
||||
try {
|
||||
pkgInfo = mPm.getPackageInfo(packageName, 0);
|
||||
} catch (NameNotFoundException e) {
|
||||
// already checked in super class
|
||||
}
|
||||
|
||||
Uri uri = Uri.fromParts("package", pkgInfo.packageName, null);
|
||||
Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, uri);
|
||||
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
|
||||
try {
|
||||
mActivity.startActivityForResult(intent, REQUEST_CODE_DELETE);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
throw new AndroidNotCompatibleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_INSTALL:
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL);
|
||||
} else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_CANCELED);
|
||||
} else {
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
}
|
||||
|
||||
return true;
|
||||
case REQUEST_CODE_DELETE:
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_DELETE);
|
||||
} else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
mCallback.onError(InstallerCallback.OPERATION_DELETE,
|
||||
InstallerCallback.ERROR_CODE_CANCELED);
|
||||
} else {
|
||||
// UninstallAppProgress actually returns
|
||||
// Activity.RESULT_FIRST_USER if something breaks
|
||||
mCallback.onError(InstallerCallback.OPERATION_DELETE,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
}
|
||||
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsUnattendedOperations() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
246
src/org/fdroid/fdroid/installer/Installer.java
Normal file
246
src/org/fdroid/fdroid/installer/Installer.java
Normal file
@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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 java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
|
||||
import android.Manifest.permission;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Abstract Installer class. Also provides static methods to automatically
|
||||
* instantiate a working Installer based on F-Droids granted permissions.
|
||||
*/
|
||||
abstract public class Installer {
|
||||
protected Context mContext;
|
||||
protected PackageManager mPm;
|
||||
protected InstallerCallback mCallback;
|
||||
|
||||
public static final String TAG = "FDroid";
|
||||
|
||||
/**
|
||||
* This is thrown when an Installer is not compatible with the Android OS it
|
||||
* is running on. This could be due to a broken superuser in case of
|
||||
* RootInstaller or due to an incompatible Android version in case of
|
||||
* SystemPermissionInstaller
|
||||
*/
|
||||
public static class AndroidNotCompatibleException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -8343133906463328027L;
|
||||
|
||||
public AndroidNotCompatibleException() {
|
||||
}
|
||||
|
||||
public AndroidNotCompatibleException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AndroidNotCompatibleException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public AndroidNotCompatibleException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback from Installer. NOTE: This callback can be in a different thread
|
||||
* than the UI thread
|
||||
*/
|
||||
public interface InstallerCallback {
|
||||
|
||||
public static final int OPERATION_INSTALL = 1;
|
||||
public static final int OPERATION_DELETE = 2;
|
||||
|
||||
public static final int ERROR_CODE_CANCELED = 1;
|
||||
public static final int ERROR_CODE_OTHER = 2;
|
||||
|
||||
public void onSuccess(int operation);
|
||||
|
||||
public void onError(int operation, int errorCode);
|
||||
}
|
||||
|
||||
public Installer(Context context, PackageManager pm, InstallerCallback callback)
|
||||
throws AndroidNotCompatibleException {
|
||||
this.mContext = context;
|
||||
this.mPm = pm;
|
||||
this.mCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Installer for installing/deleting processes starting from
|
||||
* an Activity
|
||||
*
|
||||
* @param activity
|
||||
* @param pm
|
||||
* @param callback
|
||||
* @return
|
||||
* @throws AndroidNotCompatibleException
|
||||
*/
|
||||
public static Installer getActivityInstaller(Activity activity, PackageManager pm,
|
||||
InstallerCallback callback) {
|
||||
|
||||
// if root installer has been activated in preferences -> RootInstaller
|
||||
boolean isRootInstallerEnabled = Preferences.get().isRootInstallerEnabled();
|
||||
if (isRootInstallerEnabled) {
|
||||
Log.d(TAG, "root installer preference enabled -> RootInstaller");
|
||||
|
||||
try {
|
||||
return new RootInstaller(activity, pm, callback);
|
||||
} catch (AndroidNotCompatibleException e) {
|
||||
Log.e(TAG, "Android not compatible with RootInstaller!", e);
|
||||
}
|
||||
}
|
||||
|
||||
// system permissions and pref enabled -> SystemInstaller
|
||||
boolean isSystemInstallerEnabled = Preferences.get().isSystemInstallerEnabled();
|
||||
if (isSystemInstallerEnabled) {
|
||||
if (hasSystemPermissions(activity, pm)) {
|
||||
Log.d(TAG, "system permissions -> SystemInstaller");
|
||||
|
||||
try {
|
||||
return new SystemInstaller(activity, pm, callback);
|
||||
} catch (AndroidNotCompatibleException e) {
|
||||
Log.e(TAG, "Android not compatible with SystemInstaller!", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "SystemInstaller is enabled in prefs, but system-perms are not granted!");
|
||||
}
|
||||
|
||||
// Fallback -> DefaultInstaller
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
// Default installer on Android >= 4.0
|
||||
try {
|
||||
Log.d(TAG, "try default installer for Android >= 4");
|
||||
|
||||
return new DefaultInstallerSdk14(activity, pm, callback);
|
||||
} catch (AndroidNotCompatibleException e) {
|
||||
Log.e(TAG, "Android not compatible with DefaultInstallerSdk14!", e);
|
||||
}
|
||||
} else {
|
||||
// Default installer on Android < 4.0
|
||||
try {
|
||||
Log.d(TAG, "try default installer for Android < 4");
|
||||
|
||||
return new DefaultInstaller(activity, pm, callback);
|
||||
} catch (AndroidNotCompatibleException e) {
|
||||
Log.e(TAG, "Android not compatible with DefaultInstaller!", e);
|
||||
}
|
||||
}
|
||||
|
||||
// this should not happen!
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Installer getUnattendedInstaller(Context context, PackageManager pm,
|
||||
InstallerCallback callback) throws AndroidNotCompatibleException {
|
||||
|
||||
// if root installer has been activated in preferences -> RootInstaller
|
||||
boolean useRootInstaller = Preferences.get().isRootInstallerEnabled();
|
||||
if (useRootInstaller) {
|
||||
try {
|
||||
return new RootInstaller(context, pm, callback);
|
||||
} catch (AndroidNotCompatibleException e) {
|
||||
Log.e(TAG, "Android not compatible with RootInstaller!", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSystemPermissions(context, pm)) {
|
||||
// we have system permissions!
|
||||
return new SystemInstaller(context, pm, callback);
|
||||
} else {
|
||||
// nope!
|
||||
throw new AndroidNotCompatibleException();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasSystemPermissions(Context context, PackageManager pm) {
|
||||
int checkInstallPermission =
|
||||
pm.checkPermission(permission.INSTALL_PACKAGES, context.getPackageName());
|
||||
int checkDeletePermission =
|
||||
pm.checkPermission(permission.DELETE_PACKAGES, context.getPackageName());
|
||||
boolean permissionsGranted =
|
||||
(checkInstallPermission == PackageManager.PERMISSION_GRANTED
|
||||
&& checkDeletePermission == PackageManager.PERMISSION_GRANTED);
|
||||
|
||||
if (permissionsGranted) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void installPackage(File apkFile) throws AndroidNotCompatibleException {
|
||||
// check if file exists...
|
||||
if (!apkFile.exists()) {
|
||||
Log.e(TAG, "Couldn't find file " + apkFile + " to install.");
|
||||
return;
|
||||
}
|
||||
|
||||
installPackageInternal(apkFile);
|
||||
}
|
||||
|
||||
public void installPackage(List<File> apkFiles) throws AndroidNotCompatibleException {
|
||||
// check if files exist...
|
||||
for (File apkFile : apkFiles) {
|
||||
if (!apkFile.exists()) {
|
||||
Log.e(TAG, "Couldn't find file " + apkFile + " to install.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
installPackageInternal(apkFiles);
|
||||
}
|
||||
|
||||
public void deletePackage(String packageName) throws AndroidNotCompatibleException {
|
||||
// check if package exists before proceeding...
|
||||
try {
|
||||
mPm.getPackageInfo(packageName, 0);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.e(TAG, "Couldn't find package " + packageName + " to delete.");
|
||||
return;
|
||||
}
|
||||
|
||||
deletePackageInternal(packageName);
|
||||
}
|
||||
|
||||
protected abstract void installPackageInternal(File apkFile)
|
||||
throws AndroidNotCompatibleException;
|
||||
|
||||
protected abstract void installPackageInternal(List<File> apkFiles)
|
||||
throws AndroidNotCompatibleException;
|
||||
|
||||
protected abstract void deletePackageInternal(String packageName)
|
||||
throws AndroidNotCompatibleException;
|
||||
|
||||
public abstract boolean handleOnActivityResult(int requestCode, int resultCode, Intent data);
|
||||
|
||||
public abstract boolean supportsUnattendedOperations();
|
||||
}
|
223
src/org/fdroid/fdroid/installer/RootInstaller.java
Normal file
223
src/org/fdroid/fdroid/installer/RootInstaller.java
Normal file
@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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 java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import eu.chainfire.libsuperuser.Shell;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Installer using a root shell and "pm install", "pm uninstall" commands
|
||||
*/
|
||||
public class RootInstaller extends Installer {
|
||||
|
||||
Shell.Interactive rootSession;
|
||||
|
||||
public RootInstaller(Context context, PackageManager pm, InstallerCallback callback)
|
||||
throws AndroidNotCompatibleException {
|
||||
super(context, pm, callback);
|
||||
}
|
||||
|
||||
private Shell.Builder createShellBuilder() {
|
||||
Shell.Builder shellBuilder = new Shell.Builder()
|
||||
.useSU()
|
||||
.setWantSTDERR(true)
|
||||
.setWatchdogTimeout(5)
|
||||
.setMinimalLogging(true);
|
||||
|
||||
return shellBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installPackageInternal(final File apkFile) throws AndroidNotCompatibleException {
|
||||
rootSession = createShellBuilder().open(new Shell.OnCommandResultListener() {
|
||||
|
||||
// Callback to report whether the shell was successfully
|
||||
// started up
|
||||
@Override
|
||||
public void onCommandResult(int commandCode, int exitCode, List<String> output) {
|
||||
if (exitCode != Shell.OnCommandResultListener.SHELL_RUNNING) {
|
||||
// NOTE: Additional exit codes:
|
||||
// Shell.OnCommandResultListener.SHELL_WRONG_UID
|
||||
// Shell.OnCommandResultListener.SHELL_EXEC_FAILED
|
||||
|
||||
Log.e(TAG, "Error opening root shell with exitCode " + exitCode);
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
} else {
|
||||
addInstallCommand(apkFile);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installPackageInternal(final List<File> apkFiles)
|
||||
throws AndroidNotCompatibleException {
|
||||
rootSession = createShellBuilder().open(new Shell.OnCommandResultListener() {
|
||||
|
||||
// Callback to report whether the shell was successfully
|
||||
// started up
|
||||
@Override
|
||||
public void onCommandResult(int commandCode, int exitCode, List<String> output) {
|
||||
if (exitCode != Shell.OnCommandResultListener.SHELL_RUNNING) {
|
||||
// NOTE: Additional exit codes:
|
||||
// Shell.OnCommandResultListener.SHELL_WRONG_UID
|
||||
// Shell.OnCommandResultListener.SHELL_EXEC_FAILED
|
||||
|
||||
Log.e(TAG, "Error opening root shell with exitCode " + exitCode);
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
} else {
|
||||
addInstallCommand(apkFiles);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deletePackageInternal(final String packageName)
|
||||
throws AndroidNotCompatibleException {
|
||||
rootSession = createShellBuilder().open(new Shell.OnCommandResultListener() {
|
||||
|
||||
// Callback to report whether the shell was successfully
|
||||
// started up
|
||||
@Override
|
||||
public void onCommandResult(int commandCode, int exitCode, List<String> output) {
|
||||
if (exitCode != Shell.OnCommandResultListener.SHELL_RUNNING) {
|
||||
// NOTE: Additional exit codes:
|
||||
// Shell.OnCommandResultListener.SHELL_WRONG_UID
|
||||
// Shell.OnCommandResultListener.SHELL_EXEC_FAILED
|
||||
|
||||
Log.e(TAG, "Error opening root shell with exitCode " + exitCode);
|
||||
mCallback.onError(InstallerCallback.OPERATION_DELETE,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
} else {
|
||||
addDeleteCommand(packageName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// no need to handle onActivityResult
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addInstallCommand(File apkFile) {
|
||||
rootSession.addCommand("pm install -r " + apkFile.getAbsolutePath(), 0,
|
||||
new Shell.OnCommandResultListener() {
|
||||
public void onCommandResult(int commandCode, int exitCode, List<String> output) {
|
||||
// close su shell
|
||||
rootSession.close();
|
||||
|
||||
if (exitCode < 0) {
|
||||
Log.e(TAG, "Install failed with exit code " + exitCode);
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
} else {
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addInstallCommand(List<File> apkFiles) {
|
||||
ArrayList<String> commands = new ArrayList<String>();
|
||||
String pm = "pm install -r ";
|
||||
for (File apkFile : apkFiles) {
|
||||
commands.add(pm + apkFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
rootSession.addCommand(commands, 0,
|
||||
new Shell.OnCommandResultListener() {
|
||||
public void onCommandResult(int commandCode, int exitCode,
|
||||
List<String> output) {
|
||||
// close su shell
|
||||
rootSession.close();
|
||||
|
||||
if (exitCode < 0) {
|
||||
Log.e(TAG, "Install failed with exit code " + exitCode);
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
} else {
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void addDeleteCommand(String packageName) {
|
||||
rootSession.addCommand("pm uninstall " + packageName, 0,
|
||||
new Shell.OnCommandResultListener() {
|
||||
public void onCommandResult(int commandCode, int exitCode, List<String> output) {
|
||||
// close su shell
|
||||
rootSession.close();
|
||||
|
||||
if (exitCode < 0) {
|
||||
Log.e(TAG, "Delete failed with exit code " + exitCode);
|
||||
mCallback.onError(InstallerCallback.OPERATION_DELETE,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
} else {
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_DELETE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsUnattendedOperations() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f] [--algo
|
||||
* <algorithm name> --key <key-in-hex> --iv <IV-in-hex>] [--originating-uri
|
||||
* <URI>] [--referrer <URI>] PATH
|
||||
* <p/>
|
||||
* pm install: installs a package to the system.
|
||||
* <p/>
|
||||
* Options:<br/>
|
||||
* -l: install the package with FORWARD_LOCK.<br/>
|
||||
* -r: reinstall an existing app, keeping its data.<br/>
|
||||
* -t: allow test .apks to be installed.<br/>
|
||||
* -i: specify the installer package name.<br/>
|
||||
* -s: install package on sdcard.<br/>
|
||||
* -f: install package on internal flash.<br/>
|
||||
* -d: allow version code downgrade.<br/>
|
||||
* <p/>
|
||||
* pm uninstall [-k] PACKAGE
|
||||
* <p/>
|
||||
* pm uninstall: removes a package from the system.
|
||||
* <p/>
|
||||
* Options:<br/>
|
||||
* -k: keep the data and cache directories around after package removal.
|
||||
*/
|
||||
|
||||
}
|
473
src/org/fdroid/fdroid/installer/SystemInstaller.java
Normal file
473
src/org/fdroid/fdroid/installer/SystemInstaller.java
Normal file
@ -0,0 +1,473 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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 java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.IPackageDeleteObserver;
|
||||
import android.content.pm.IPackageInstallObserver;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Installer based on using internal hidden APIs of the Android OS, which are
|
||||
* protected by the permissions
|
||||
* <ul>
|
||||
* <li>android.permission.INSTALL_PACKAGES</li>
|
||||
* <li>android.permission.DELETE_PACKAGES</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* Both permissions are protected by systemOrSignature (in newer versions:
|
||||
* system|signature) and only granted on F-Droid's install in the following
|
||||
* cases:
|
||||
* <ul>
|
||||
* <li>On all Android versions if F-Droid is pre-deployed as a
|
||||
* system-application with the Rom</li>
|
||||
* <li>On Android < 4.4 also when moved into /system/app/</li>
|
||||
* <li>On Android >= 4.4 also when moved into /system/priv-app/</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* Sources for Android 4.4 change:
|
||||
* https://groups.google.com/forum/#!msg/android-
|
||||
* security-discuss/r7uL_OEMU5c/LijNHvxeV80J
|
||||
* https://android.googlesource.com/platform
|
||||
* /frameworks/base/+/ccbf84f44c9e6a5ed3c08673614826bb237afc54
|
||||
*/
|
||||
public class SystemInstaller extends Installer {
|
||||
|
||||
private PackageInstallObserver mInstallObserver;
|
||||
private PackageDeleteObserver mDeleteObserver;
|
||||
private Method mInstallMethod;
|
||||
private Method mDeleteMethod;
|
||||
|
||||
public SystemInstaller(Context context, PackageManager pm,
|
||||
InstallerCallback callback) throws AndroidNotCompatibleException {
|
||||
super(context, pm, callback);
|
||||
|
||||
// create internal callbacks
|
||||
mInstallObserver = new PackageInstallObserver();
|
||||
mDeleteObserver = new PackageDeleteObserver();
|
||||
|
||||
try {
|
||||
Class<?>[] installTypes = new Class[] {
|
||||
Uri.class, IPackageInstallObserver.class, int.class,
|
||||
String.class
|
||||
};
|
||||
Class<?>[] deleteTypes = new Class[] {
|
||||
String.class, IPackageDeleteObserver.class,
|
||||
int.class
|
||||
};
|
||||
|
||||
mInstallMethod = mPm.getClass().getMethod("installPackage", installTypes);
|
||||
mDeleteMethod = mPm.getClass().getMethod("deletePackage", deleteTypes);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new AndroidNotCompatibleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal install callback from the system
|
||||
*/
|
||||
class PackageInstallObserver extends IPackageInstallObserver.Stub {
|
||||
public void packageInstalled(String packageName, int returnCode) throws RemoteException {
|
||||
// TODO: propagate other return codes?
|
||||
if (returnCode == INSTALL_SUCCEEDED) {
|
||||
Log.d(TAG, "Install succeeded");
|
||||
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_INSTALL);
|
||||
} else {
|
||||
Log.e(TAG, "Install failed with returnCode " + returnCode);
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal delete callback from the system
|
||||
*/
|
||||
class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
|
||||
public void packageDeleted(String packageName, int returnCode) throws RemoteException {
|
||||
// TODO: propagate other return codes?
|
||||
if (returnCode == DELETE_SUCCEEDED) {
|
||||
Log.d(TAG, "Delete succeeded");
|
||||
|
||||
mCallback.onSuccess(InstallerCallback.OPERATION_DELETE);
|
||||
} else {
|
||||
Log.e(TAG, "Delete failed with returnCode " + returnCode);
|
||||
mCallback.onError(InstallerCallback.OPERATION_DELETE,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installPackageInternal(File apkFile) throws AndroidNotCompatibleException {
|
||||
Uri packageURI = Uri.fromFile(apkFile);
|
||||
try {
|
||||
mInstallMethod.invoke(mPm, new Object[] {
|
||||
packageURI, mInstallObserver, INSTALL_REPLACE_EXISTING, null
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw new AndroidNotCompatibleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installPackageInternal(List<File> apkFiles) throws AndroidNotCompatibleException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deletePackageInternal(String packageName) throws AndroidNotCompatibleException {
|
||||
try {
|
||||
mDeleteMethod.invoke(mPm, new Object[] {
|
||||
packageName, mDeleteObserver, 0
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw new AndroidNotCompatibleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// no need to handle onActivityResult
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsUnattendedOperations() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public final int INSTALL_REPLACE_EXISTING = 2;
|
||||
|
||||
/**
|
||||
* Following return codes are copied from Android 4.3 source code
|
||||
*/
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} on
|
||||
* success.
|
||||
*/
|
||||
public static final int INSTALL_SUCCEEDED = 1;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the package is already installed.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_ALREADY_EXISTS = -1;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the package archive file is invalid.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_INVALID_APK = -2;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the URI passed in is invalid.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_INVALID_URI = -3;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the package manager service found that the device didn't have enough
|
||||
* storage space to install the app.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* a package is already installed with the same name.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the requested shared user does not exist.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_NO_SHARED_USER = -6;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* a previously installed package of the same name has a different signature
|
||||
* than the new package (and the old package's data was not removed).
|
||||
*/
|
||||
public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the new package is requested a shared user which is already installed on
|
||||
* the device and does not have matching signature.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the new package uses a shared library that is not available.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the new package uses a shared library that is not available.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the new package failed while optimizing and validating its dex files,
|
||||
* either because there was not enough storage or the validation failed.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_DEXOPT = -11;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the new package failed because the current SDK version is older than that
|
||||
* required by the package.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_OLDER_SDK = -12;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the new package failed because it contains a content provider with the
|
||||
* same authority as a provider already installed in the system.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the new package failed because the current SDK version is newer than that
|
||||
* required by the package.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_NEWER_SDK = -14;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the new package failed because it has specified that it is a test-only
|
||||
* package and the caller has not supplied the {@link #INSTALL_ALLOW_TEST}
|
||||
* flag.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_TEST_ONLY = -15;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the package being installed contains native code, but none that is
|
||||
* compatible with the the device's CPU_ABI.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_CPU_ABI_INCOMPATIBLE = -16;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the new package uses a feature that is not available.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_MISSING_FEATURE = -17;
|
||||
|
||||
// ------ Errors related to sdcard
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* a secure container mount point couldn't be accessed on external media.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_CONTAINER_ERROR = -18;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the new package couldn't be installed in the specified install location.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19;
|
||||
|
||||
/**
|
||||
* Installation return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the new package couldn't be installed in the specified install location
|
||||
* because the media is not available.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20;
|
||||
|
||||
/**
|
||||
* Installation parse return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the parser was given a path that is not a file, or does not end with the
|
||||
* expected '.apk' extension.
|
||||
*/
|
||||
public static final int INSTALL_PARSE_FAILED_NOT_APK = -100;
|
||||
|
||||
/**
|
||||
* Installation parse return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the parser was unable to retrieve the AndroidManifest.xml file.
|
||||
*/
|
||||
public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101;
|
||||
|
||||
/**
|
||||
* Installation parse return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the parser encountered an unexpected exception.
|
||||
*/
|
||||
public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102;
|
||||
|
||||
/**
|
||||
* Installation parse return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the parser did not find any certificates in the .apk.
|
||||
*/
|
||||
public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103;
|
||||
|
||||
/**
|
||||
* Installation parse return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the parser found inconsistent certificates on the files in the .apk.
|
||||
*/
|
||||
public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104;
|
||||
|
||||
/**
|
||||
* Installation parse return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the parser encountered a CertificateEncodingException in one of the files
|
||||
* in the .apk.
|
||||
*/
|
||||
public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105;
|
||||
|
||||
/**
|
||||
* Installation parse return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the parser encountered a bad or missing package name in the manifest.
|
||||
*/
|
||||
public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106;
|
||||
|
||||
/**
|
||||
* Installation parse return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the parser encountered a bad shared user id name in the manifest.
|
||||
*/
|
||||
public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107;
|
||||
|
||||
/**
|
||||
* Installation parse return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the parser encountered some structural problem in the manifest.
|
||||
*/
|
||||
public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108;
|
||||
|
||||
/**
|
||||
* Installation parse return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the parser did not find any actionable tags (instrumentation or
|
||||
* application) in the manifest.
|
||||
*/
|
||||
public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109;
|
||||
|
||||
/**
|
||||
* Installation failed return code: this is passed to the
|
||||
* {@link IPackageInstallObserver} by
|
||||
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
|
||||
* the system failed to install the package because of system issues.
|
||||
*/
|
||||
public static final int INSTALL_FAILED_INTERNAL_ERROR = -110;
|
||||
|
||||
/**
|
||||
* Return code for when package deletion succeeds. This is passed to the
|
||||
* {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
|
||||
* succeeded in deleting the package.
|
||||
*/
|
||||
public static final int DELETE_SUCCEEDED = 1;
|
||||
|
||||
/**
|
||||
* Deletion failed return code: this is passed to the
|
||||
* {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
|
||||
* failed to delete the package for an unspecified reason.
|
||||
*/
|
||||
public static final int DELETE_FAILED_INTERNAL_ERROR = -1;
|
||||
|
||||
/**
|
||||
* Deletion failed return code: this is passed to the
|
||||
* {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
|
||||
* failed to delete the package because it is the active DevicePolicy
|
||||
* manager.
|
||||
*/
|
||||
public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2;
|
||||
|
||||
/**
|
||||
* Deletion failed return code: this is passed to the
|
||||
* {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
|
||||
* failed to delete the package since the user is restricted.
|
||||
*/
|
||||
public static final int DELETE_FAILED_USER_RESTRICTED = -3;
|
||||
|
||||
}
|
@ -1,13 +1,39 @@
|
||||
|
||||
package org.fdroid.fdroid.views.fragments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.installer.Installer;
|
||||
import org.fdroid.fdroid.views.AppListAdapter;
|
||||
import org.fdroid.fdroid.views.CanUpdateAppListAdapter;
|
||||
|
||||
public class CanUpdateAppsFragment extends AppListFragment {
|
||||
|
||||
// copied from ListFragment
|
||||
static final int INTERNAL_EMPTY_ID = 0x00ff0001;
|
||||
static final int INTERNAL_PROGRESS_CONTAINER_ID = 0x00ff0002;
|
||||
static final int INTERNAL_LIST_CONTAINER_ID = 0x00ff0003;
|
||||
// added for update button
|
||||
static final int UPDATE_ALL_BUTTON_ID = 0x00ff0004;
|
||||
|
||||
private Button mUpdateAllButton;
|
||||
private Installer mInstaller;
|
||||
|
||||
@Override
|
||||
protected AppListAdapter getAppListAdapter() {
|
||||
return new CanUpdateAppListAdapter(getActivity(), null);
|
||||
@ -23,4 +49,115 @@ public class CanUpdateAppsFragment extends AppListFragment {
|
||||
return AppProvider.getCanUpdateUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mInstaller = Installer.getActivityInstaller(getActivity(), getActivity()
|
||||
.getPackageManager(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mUpdateAllButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// TODO
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: not really called again after coming back from preference
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (mInstaller.supportsUnattendedOperations()) {
|
||||
// mUpdateAllButton.setVisibility(View.VISIBLE);
|
||||
mUpdateAllButton.setVisibility(View.GONE);
|
||||
} else {
|
||||
mUpdateAllButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from ListFragment and added Button on top of list. We do not use a
|
||||
* custom layout here, because this breaks the progress bar functionality of
|
||||
* ListFragment.
|
||||
*
|
||||
* @param inflater
|
||||
* @param container
|
||||
* @param savedInstanceState
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
final Context context = getActivity();
|
||||
|
||||
FrameLayout root = new FrameLayout(context);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
LinearLayout pframe = new LinearLayout(context);
|
||||
pframe.setId(INTERNAL_PROGRESS_CONTAINER_ID);
|
||||
pframe.setOrientation(LinearLayout.VERTICAL);
|
||||
pframe.setVisibility(View.GONE);
|
||||
pframe.setGravity(Gravity.CENTER);
|
||||
|
||||
ProgressBar progress = new ProgressBar(context, null,
|
||||
android.R.attr.progressBarStyleLarge);
|
||||
pframe.addView(progress, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
root.addView(pframe, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
FrameLayout lframe = new FrameLayout(context);
|
||||
lframe.setId(INTERNAL_LIST_CONTAINER_ID);
|
||||
|
||||
TextView tv = new TextView(getActivity());
|
||||
tv.setId(INTERNAL_EMPTY_ID);
|
||||
tv.setGravity(Gravity.CENTER);
|
||||
lframe.addView(tv, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
|
||||
|
||||
// Added update all button
|
||||
LinearLayout linearLayout = new LinearLayout(context);
|
||||
linearLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
mUpdateAllButton = new Button(context);
|
||||
mUpdateAllButton.setId(UPDATE_ALL_BUTTON_ID);
|
||||
mUpdateAllButton.setText(R.string.update_all);
|
||||
mUpdateAllButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||
getResources().getDrawable(R.drawable.ic_menu_refresh), null, null, null);
|
||||
|
||||
linearLayout.addView(mUpdateAllButton, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
ListView lv = new ListView(getActivity());
|
||||
lv.setId(android.R.id.list);
|
||||
lv.setDrawSelectorOnTop(false);
|
||||
linearLayout.addView(lv, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
|
||||
|
||||
lframe.addView(linearLayout, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
|
||||
|
||||
root.addView(lframe, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
root.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user