Merge branch 'prep-for-InstalledAppProviderService' into 'master'

Prep for InstalledAppProviderService

This is basically a collection of little fixes in preparations for !299 .

See merge request !313
This commit is contained in:
Daniel Martí 2016-05-27 20:31:00 +00:00
commit 1bfd3425c9
22 changed files with 158 additions and 140 deletions

View File

@ -14,6 +14,7 @@ import junit.framework.AssertionFailedError;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.FDroidProviderTest;
import org.fdroid.fdroid.receiver.PackageAddedReceiver;
import org.fdroid.fdroid.receiver.PackageRemovedReceiver;
import org.fdroid.fdroid.receiver.PackageUpgradedReceiver;

View File

@ -1,11 +1,11 @@
package org.fdroid.fdroid;
package org.fdroid.fdroid.data;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.mock.MockApk;
import java.util.ArrayList;

View File

@ -1,12 +1,10 @@
package org.fdroid.fdroid;
package org.fdroid.fdroid.data;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.mock.MockApk;
import org.fdroid.fdroid.mock.MockApp;
import org.fdroid.fdroid.mock.MockRepo;
@ -333,5 +331,4 @@ public class ApkProviderTest extends BaseApkProviderTest {
assertEquals(1, apk.versionCode);
assertEquals(10, apk.repo);
}
}

View File

@ -1,14 +1,12 @@
package org.fdroid.fdroid;
package org.fdroid.fdroid.data;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.res.Resources;
import android.database.Cursor;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.InstalledAppCacheUpdater;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.TestUtils;
import java.util.ArrayList;
import java.util.List;

View File

@ -1,11 +1,10 @@
package org.fdroid.fdroid;
package org.fdroid.fdroid.data;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.TestUtils;
import java.util.List;

View File

@ -1,4 +1,4 @@
package org.fdroid.fdroid;
package org.fdroid.fdroid.data;
import android.annotation.TargetApi;
import android.content.ContentValues;
@ -10,12 +10,6 @@ import android.os.Build;
import android.provider.ContactsContract;
import android.test.ProviderTestCase2MockContext;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.FDroidProvider;
import org.fdroid.fdroid.data.InstalledAppProvider;
import org.fdroid.fdroid.data.RepoProvider;
import java.util.List;
import mock.MockContextEmptyComponents;

View File

@ -1,11 +1,9 @@
package org.fdroid.fdroid;
import org.fdroid.fdroid.data.InstalledAppProvider;
package org.fdroid.fdroid.data;
import mock.MockInstallablePackageManager;
/**
* Tests the ability of the {@link org.fdroid.fdroid.data.InstalledAppCacheUpdater} to stay in sync with
* Tests the ability of the {@link InstalledAppCacheUpdater} to stay in sync with
* the {@link android.content.pm.PackageManager}.
* For practical reasons, it extends FDroidProviderTest<InstalledAppProvider>, although there is also a
* separate test for the InstalledAppProvider which tests the CRUD operations in more detail.

View File

@ -1,11 +1,8 @@
package org.fdroid.fdroid;
package org.fdroid.fdroid.data;
import android.content.ContentValues;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.InstalledAppProvider;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.TestUtils;
import mock.MockInstallablePackageManager;

View File

@ -1571,9 +1571,6 @@ public class AppDetails extends AppCompatActivity {
btMain.setText(R.string.menu_launch);
} else {
btMain.setText(R.string.menu_uninstall);
if (!app.uninstallable) {
btMain.setVisibility(View.GONE);
}
}
}
btMain.setOnClickListener(mOnClickListener);

View File

@ -32,19 +32,25 @@ public class Apk extends ValueObject implements Comparable<Apk> {
public Utils.CommaSeparatedList nativecode; // null if empty or unknown
// ID (md5 sum of public key) of signature. Might be null, in the
// transition to this field existing.
/**
* ID (md5 sum of public key) of signature. Might be null, in the
* transition to this field existing.
*/
public String sig;
// True if compatible with the device.
/**
* True if compatible with the device.
*/
public boolean compatible;
public String apkName; // F-Droid style APK name
public SanitizedFile installedFile; // the .apk file on this device's filesystem
// If not null, this is the name of the source tarball for the
// application. Null indicates that it's a developer's binary
// build - otherwise it's built from source.
/**
* If not null, this is the name of the source tarball for the
* application. Null indicates that it's a developer's binary
* build - otherwise it's built from source.
*/
public String srcname;
public int repoVersion;

View File

@ -37,7 +37,9 @@ public class App extends ValueObject implements Comparable<App> {
private static final String TAG = "App";
// True if compatible with the device (i.e. if at least one apk is)
/**
* True if compatible with the device (i.e. if at least one apk is)
*/
public boolean compatible;
public String packageName = "unknown";
@ -84,27 +86,39 @@ public class App extends ValueObject implements Comparable<App> {
public Date added;
public Date lastUpdated;
// List of categories (as defined in the metadata
// documentation) or null if there aren't any.
/**
* List of categories (as defined in the metadata documentation) or null if there aren't any.
*/
public Utils.CommaSeparatedList categories;
// List of anti-features (as defined in the metadata
// documentation) or null if there aren't any.
/**
* List of anti-features (as defined in the metadata documentation) or null if there aren't any.
*/
public Utils.CommaSeparatedList antiFeatures;
// List of special requirements (such as root privileges) or
// null if there aren't any.
/**
* List of special requirements (such as root privileges) or null if there aren't any.
*/
public Utils.CommaSeparatedList requirements;
// True if all updates for this app are to be ignored
/**
* True if all updates for this app are to be ignored
*/
public boolean ignoreAllUpdates;
// True if the current update for this app is to be ignored
/**
* True if the current update for this app is to be ignored
*/
public int ignoreThisUpdate;
// To be displayed at 48dp (x1.0)
/**
* To be displayed at 48dp (x1.0)
*/
public String iconUrl;
// To be displayed at 72dp (x1.5)
/**
* To be displayed at 72dp (x1.5)
*/
public String iconUrlLarge;
public String installedVersionName;
@ -115,8 +129,6 @@ public class App extends ValueObject implements Comparable<App> {
public String installedSig;
public boolean uninstallable;
public static String getIconName(String packageName, int versionCode) {
return packageName + "_" + versionCode + ".png";
}
@ -250,14 +262,21 @@ public class App extends ValueObject implements Comparable<App> {
/**
* Instantiate from a locally installed package.
*/
@TargetApi(9)
public App(Context context, PackageManager pm, String packageName)
throws CertificateEncodingException, IOException, PackageManager.NameNotFoundException {
final ApplicationInfo appInfo = pm.getApplicationInfo(packageName,
PackageManager.GET_META_DATA);
final PackageInfo packageInfo = pm.getPackageInfo(packageName,
PackageManager.GET_SIGNATURES | PackageManager.GET_PERMISSIONS);
PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
setFromPackageInfo(pm, packageInfo);
this.installedApk = new Apk();
SanitizedFile apkFile = SanitizedFile.knownSanitized(packageInfo.applicationInfo.publicSourceDir);
initApkFromApkFile(context, this.installedApk, packageInfo, apkFile);
}
@TargetApi(9)
private void setFromPackageInfo(PackageManager pm, PackageInfo packageInfo)
throws CertificateEncodingException, IOException, PackageManager.NameNotFoundException {
this.packageName = packageInfo.packageName;
final String installerPackageName = pm.getInstallerPackageName(packageName);
CharSequence installerPackageLabel = null;
if (!TextUtils.isEmpty(installerPackageName)) {
@ -273,13 +292,13 @@ public class App extends ValueObject implements Comparable<App> {
installerPackageLabel = installerPackageName;
}
ApplicationInfo appInfo = packageInfo.applicationInfo;
final CharSequence appDescription = appInfo.loadDescription(pm);
if (TextUtils.isEmpty(appDescription)) {
this.summary = "(installed by " + installerPackageLabel + ")";
} else {
this.summary = (String) appDescription.subSequence(0, 40);
}
this.packageName = appInfo.packageName;
this.added = new Date(packageInfo.firstInstallTime);
this.lastUpdated = new Date(packageInfo.lastUpdateTime);
this.description = "<p>";
@ -292,8 +311,20 @@ public class App extends ValueObject implements Comparable<App> {
this.name = (String) appInfo.loadLabel(pm);
this.icon = getIconName(packageName, packageInfo.versionCode);
this.compatible = true;
}
final Apk apk = new Apk();
private void initApkFromApkFile(Context context, Apk apk, PackageInfo packageInfo, SanitizedFile apkFile)
throws IOException, CertificateEncodingException {
// TODO include signature hash calculation here
apk.hashType = "sha256";
apk.hash = Utils.getBinaryHash(apkFile, apk.hashType);
initInstalledApk(context, apk, packageInfo, apkFile);
}
private void initInstalledApk(Context context, Apk apk, PackageInfo packageInfo, SanitizedFile apkFile)
throws IOException, CertificateEncodingException {
apk.compatible = true;
apk.versionName = packageInfo.versionName;
apk.versionCode = packageInfo.versionCode;
apk.added = this.added;
@ -303,16 +334,12 @@ public class App extends ValueObject implements Comparable<App> {
apk.packageName = this.packageName;
apk.permissions = Utils.CommaSeparatedList.make(packageInfo.requestedPermissions);
apk.apkName = apk.packageName + "_" + apk.versionCode + ".apk";
final SanitizedFile apkFile = SanitizedFile.knownSanitized(appInfo.publicSourceDir);
apk.hashType = "sha256";
apk.hash = Utils.getBinaryHash(apkFile, apk.hashType);
apk.installedFile = apkFile;
JarFile jarFile = new JarFile(apkFile);
JarFile apkJar = new JarFile(apkFile);
HashSet<String> abis = new HashSet<>(3);
Pattern pattern = Pattern.compile("^lib/([a-z0-9-]+)/.*");
for (Enumeration<JarEntry> jarEntries = jarFile.entries(); jarEntries.hasMoreElements();) {
for (Enumeration<JarEntry> jarEntries = apkJar.entries(); jarEntries.hasMoreElements();) {
JarEntry jarEntry = jarEntries.nextElement();
Matcher matcher = pattern.matcher(jarEntry.getName());
if (matcher.matches()) {
@ -330,7 +357,6 @@ public class App extends ValueObject implements Comparable<App> {
apk.features = Utils.CommaSeparatedList.make(featureNames);
}
final JarFile apkJar = new JarFile(apkFile);
final JarEntry aSignedEntry = (JarEntry) apkJar.getEntry("AndroidManifest.xml");
if (aSignedEntry == null) {
@ -387,11 +413,6 @@ public class App extends ValueObject implements Comparable<App> {
fdroidSig[j * 2 + 1] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
}
apk.sig = Utils.hashBytes(fdroidSig, "md5");
this.installedApk = apk;
boolean system = (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
boolean updatedSystemApp = (appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
this.uninstallable = !system || updatedSystemApp;
}
public boolean isValid() {
@ -464,16 +485,20 @@ public class App extends ValueObject implements Comparable<App> {
return updates;
}
// True if there are new versions (apks) available and the user wants
// to be notified about them
/**
* True if there are new versions (apks) available and the user wants
* to be notified about them
*/
public boolean canAndWantToUpdate() {
boolean canUpdate = hasUpdates();
boolean wantsUpdate = !ignoreAllUpdates && ignoreThisUpdate < suggestedVersionCode;
return canUpdate && wantsUpdate && !isFiltered();
}
// Whether the app is filtered or not based on AntiFeatures and root
// permission (set in the Settings page)
/**
* Whether the app is filtered or not based on AntiFeatures and root
* permission (set in the Settings page)
*/
public boolean isFiltered() {
return new AppFilter().filter(this);
}

View File

@ -121,8 +121,10 @@ public class Repo extends ValueObject {
return !TextUtils.isEmpty(this.signingCertificate);
}
// this happens when a repo is configed with a fingerprint, but the client
// has not connected to it yet to download its signing certificate
/**
* This happens when a repo is configed with a fingerprint, but the client
* has not connected to it yet to download its signing certificate
*/
public boolean isSignedButUnverified() {
return TextUtils.isEmpty(this.signingCertificate) && !TextUtils.isEmpty(this.fingerprint);
}

View File

@ -229,7 +229,7 @@ public class WifiStateChangeService extends IntentService {
}
}
private String formatIpAddress(int ipAddress) {
static String formatIpAddress(int ipAddress) {
if (ipAddress == 0) {
return null;
}

View File

@ -238,7 +238,6 @@ public class SwapAppsView extends ListView implements
TextView nameView;
ImageView iconView;
Button btnInstall;
TextView btnAttemptInstall;
TextView statusInstalled;
TextView statusIncompatible;
@ -343,22 +342,24 @@ public class SwapAppsView extends ListView implements
ImageLoader.getInstance().displayImage(app.iconUrl, iconView, displayImageOptions);
btnInstall.setVisibility(View.GONE);
btnAttemptInstall.setVisibility(View.GONE);
statusInstalled.setVisibility(View.GONE);
statusIncompatible.setVisibility(View.GONE);
if (app.hasUpdates()) {
btnInstall.setText(R.string.menu_upgrade);
btnInstall.setVisibility(View.VISIBLE);
statusIncompatible.setVisibility(View.GONE);
statusInstalled.setVisibility(View.GONE);
} else if (app.isInstalled()) {
btnInstall.setVisibility(View.GONE);
statusIncompatible.setVisibility(View.GONE);
statusInstalled.setVisibility(View.VISIBLE);
} else if (!app.compatible) {
btnAttemptInstall.setVisibility(View.VISIBLE);
btnInstall.setVisibility(View.GONE);
statusIncompatible.setVisibility(View.VISIBLE);
statusInstalled.setVisibility(View.GONE);
} else {
btnInstall.setText(R.string.menu_install);
btnInstall.setVisibility(View.VISIBLE);
statusIncompatible.setVisibility(View.GONE);
statusInstalled.setVisibility(View.GONE);
}
OnClickListener installListener = new OnClickListener() {
@ -372,14 +373,11 @@ public class SwapAppsView extends ListView implements
};
btnInstall.setOnClickListener(installListener);
btnAttemptInstall.setOnClickListener(installListener);
}
private void showProgress() {
progressView.setVisibility(View.VISIBLE);
btnInstall.setVisibility(View.GONE);
btnAttemptInstall.setVisibility(View.GONE);
statusInstalled.setVisibility(View.GONE);
statusIncompatible.setVisibility(View.GONE);
}
@ -410,7 +408,6 @@ public class SwapAppsView extends ListView implements
holder.nameView = (TextView) view.findViewById(R.id.name);
holder.iconView = (ImageView) view.findViewById(android.R.id.icon);
holder.btnInstall = (Button) view.findViewById(R.id.btn_install);
holder.btnAttemptInstall = (TextView) view.findViewById(R.id.btn_attempt_install);
holder.statusInstalled = (TextView) view.findViewById(R.id.status_installed);
holder.statusIncompatible = (TextView) view.findViewById(R.id.status_incompatible);

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true">
</RelativeLayout>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true">
</RelativeLayout>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="130dp"
android:layout_alignParentTop="true"
tools:showIn="@layout/swap_blank">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/swap_start_header" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:padding="20dp"
android:paddingLeft="30dp"
android:paddingRight="30dp"
android:paddingEnd="30dp"
android:gravity="center"
android:textAlignment="center"
android:text="@string/swap_intro"
android:textColor="@android:color/white"
android:textSize="18sp"
tools:ignore="UnusedAttribute" />
</RelativeLayout>

View File

@ -1,32 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="130dp"
android:layout_alignParentTop="true"
tools:showIn="@layout/swap_blank">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/swap_start_header" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:padding="20dp"
android:paddingLeft="30dp"
android:paddingRight="30dp"
android:paddingEnd="30dp"
android:gravity="center"
android:textAlignment="center"
android:text="@string/swap_intro"
android:textColor="@android:color/white"
android:textSize="18sp"
tools:ignore="UnusedAttribute" />
android:layout_height="wrap_content"
android:layout_alignParentTop="true">
</RelativeLayout>

View File

@ -55,13 +55,6 @@
android:textColor="@color/swap_incompatible"
android:text="@string/app_incompatible" />
<TextView
android:id="@+id/btn_attempt_install"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/swap_light_blue"
android:text="@string/swap_attempt_install" />
</LinearLayout>
<TextView

View File

@ -335,7 +335,6 @@
<string name="bluetooth_unavailable">Bluetooth unavailable</string>
<string name="swap_cant_send_no_bluetooth">Cannot send F-Droid, because Bluetooth is unavailable on this device.</string>
<string name="loading">Loading…</string>
<string name="swap_attempt_install">Try to install</string>
<string name="swap_connection_misc_error">An error occurred while connecting to device, we can\'t seem to swap with it.</string>
<string name="swap_not_enabled">Swapping not enabled</string>
<string name="swap_not_enabled_description">Before swapping, your device must be made visible.</string>

View File

@ -5,11 +5,13 @@ import org.junit.Test;
import java.io.File;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
public class SanitizedFileTest {
@Test
public void testSanitizedFile() {
assumeTrue("/".equals(System.getProperty("file.separator")));
File directory = new File("/tmp/blah");

View File

@ -0,0 +1,20 @@
package org.fdroid.fdroid.net;
import org.junit.Test;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class WifiStateChangeServiceTest {
@Test
public void testFormatIpAddress() throws UnknownHostException {
for (long i = Integer.MIN_VALUE; i <= Integer.MAX_VALUE; i += 98273) {
String ip = WifiStateChangeService.formatIpAddress((int) i);
InetAddress.getByName(ip);
}
InetAddress.getByName(WifiStateChangeService.formatIpAddress(Integer.MAX_VALUE));
InetAddress.getByName(WifiStateChangeService.formatIpAddress(Integer.MIN_VALUE));
InetAddress.getByName(WifiStateChangeService.formatIpAddress(0));
}
}