LocalRepoService for setting up the local swap repo

This moves all logic for setting up the local fdroid repo to its own
IntentService.  That makes it much easier to interact with since things can
just use the static helper method to request it to update, and it'll do the
right thing.
This commit is contained in:
Hans-Christoph Steiner 2018-08-07 13:37:04 +02:00
parent 9fc1ecd5a4
commit 85410504da
7 changed files with 593 additions and 95 deletions

View File

@ -0,0 +1,199 @@
package org.fdroid.fdroid.updater;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.Looper;
import android.support.test.InstrumentationRegistry;
import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Hasher;
import org.fdroid.fdroid.IndexUpdater;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.Schema;
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
import org.fdroid.fdroid.localrepo.LocalRepoManager;
import org.fdroid.fdroid.localrepo.LocalRepoService;
import org.fdroid.fdroid.net.LocalHTTPD;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.security.cert.Certificate;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class SwapRepoEmulatorTest {
public static final String TAG = "SwapRepoEmulatorTest";
/**
* @see org.fdroid.fdroid.net.WifiStateChangeService.WifiInfoThread#run()
*/
@Test
public void testSwap()
throws IOException, LocalRepoKeyStore.InitException, IndexUpdater.UpdateException, InterruptedException {
Looper.prepare();
LocalHTTPD localHttpd = null;
try {
Log.i(TAG, "REPO: " + FDroidApp.repo);
final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
Preferences.setupForTests(context);
FDroidApp.initWifiSettings();
assertNull(FDroidApp.repo.address);
final CountDownLatch latch = new CountDownLatch(1);
new Thread() {
@Override
public void run() {
while (FDroidApp.repo.address == null) {
try {
Log.i(TAG, "Waiting for IP address... " + FDroidApp.repo.address);
Thread.sleep(1000);
} catch (InterruptedException e) {
// ignored
}
}
latch.countDown();
}
}.start();
latch.await(10, TimeUnit.MINUTES);
assertNotNull(FDroidApp.repo.address);
LocalRepoService.runProcess(context, new String[]{context.getPackageName()});
Log.i(TAG, "REPO: " + FDroidApp.repo);
File indexJarFile = LocalRepoManager.get(context).getIndexJar();
assertTrue(indexJarFile.isFile());
localHttpd = new LocalHTTPD(
context,
null,
FDroidApp.port,
LocalRepoManager.get(context).getWebRoot(),
false);
localHttpd.start();
Thread.sleep(100); // give the server some tine to start.
assertTrue(localHttpd.isAlive());
LocalRepoKeyStore localRepoKeyStore = LocalRepoKeyStore.get(context);
Certificate localCert = localRepoKeyStore.getCertificate();
String signingCert = Hasher.hex(localCert);
assertFalse(TextUtils.isEmpty(signingCert));
assertFalse(TextUtils.isEmpty(Utils.calcFingerprint(localCert)));
Repo repoToDelete = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.address);
while (repoToDelete != null) {
Log.d(TAG, "Removing old test swap repo matching this one: " + repoToDelete.address);
RepoProvider.Helper.remove(context, repoToDelete.getId());
repoToDelete = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.address);
}
ContentValues values = new ContentValues(4);
values.put(Schema.RepoTable.Cols.SIGNING_CERT, signingCert);
values.put(Schema.RepoTable.Cols.ADDRESS, FDroidApp.repo.address);
values.put(Schema.RepoTable.Cols.NAME, FDroidApp.repo.name);
values.put(Schema.RepoTable.Cols.IS_SWAP, true);
final String lastEtag = UUID.randomUUID().toString();
values.put(Schema.RepoTable.Cols.LAST_ETAG, lastEtag);
RepoProvider.Helper.insert(context, values);
Repo repo = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.address);
assertTrue(repo.isSwap);
assertNotEquals(-1, repo.getId());
assertTrue(repo.name.startsWith(FDroidApp.repo.name));
assertEquals(lastEtag, repo.lastetag);
assertNull(repo.lastUpdated);
assertTrue(isPortInUse(FDroidApp.ipAddressString, FDroidApp.port));
Thread.sleep(100);
IndexUpdater updater = new IndexUpdater(context, repo);
updater.update();
assertTrue(updater.hasChanged());
repo = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.address);
final Date lastUpdated = repo.lastUpdated;
assertTrue("repo lastUpdated should be updated", new Date(2019, 5, 13).compareTo(repo.lastUpdated) > 0);
App app = AppProvider.Helper.findSpecificApp(context.getContentResolver(),
context.getPackageName(), repo.getId());
assertEquals(context.getPackageName(), app.packageName);
List<Apk> apks = ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL);
assertEquals(1, apks.size());
for (Apk apk : apks) {
Log.i(TAG, "Apk: " + apk);
assertEquals(context.getPackageName(), apk.packageName);
assertEquals(BuildConfig.VERSION_NAME, apk.versionName);
assertEquals(BuildConfig.VERSION_CODE, apk.versionCode);
assertEquals(app.repoId, apk.repoId);
}
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> resolveInfoList = context.getPackageManager().queryIntentActivities(mainIntent, 0);
HashSet<String> packageNames = new HashSet<>();
for (ResolveInfo resolveInfo : resolveInfoList) {
if (!isSystemPackage(resolveInfo)) {
Log.i(TAG, "resolveInfo: " + resolveInfo);
packageNames.add(resolveInfo.activityInfo.packageName);
}
}
LocalRepoService.runProcess(context, packageNames.toArray(new String[0]));
updater = new IndexUpdater(context, repo);
updater.update();
assertTrue(updater.hasChanged());
assertTrue("repo lastUpdated should be updated", lastUpdated.compareTo(repo.lastUpdated) < 0);
for (String packageName : packageNames) {
assertNotNull(ApkProvider.Helper.findByPackageName(context, packageName));
}
} finally {
if (localHttpd != null) {
localHttpd.stop();
}
}
if (localHttpd != null) {
assertFalse(localHttpd.isAlive());
}
}
private boolean isPortInUse(String host, int port) {
boolean result = false;
try {
(new Socket(host, port)).close();
result = true;
} catch (IOException e) {
// Could not connect.
e.printStackTrace();
}
return result;
}
private boolean isSystemPackage(ResolveInfo resolveInfo) {
return (resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
}

View File

@ -80,6 +80,9 @@
<service
android:name=".localrepo.CacheSwapAppsService"
android:exported="false"/>
<service
android:name=".localrepo.LocalRepoService"
android:exported="false"/>
<service
android:name=".localrepo.TreeUriScannerIntentService"
android:exported="false"/>

View File

@ -21,6 +21,8 @@ import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.InstalledApp;
import org.fdroid.fdroid.data.InstalledAppProvider;
import org.fdroid.fdroid.data.SanitizedFile;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
@ -246,6 +248,10 @@ public final class LocalRepoManager {
return xmlIndexJar;
}
public File getWebRoot() {
return webRoot;
}
public void deleteRepo() {
deleteContents(repoDir);
}

View File

@ -0,0 +1,163 @@
package org.fdroid.fdroid.localrepo;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.views.swap.SwapWorkflowActivity;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import static org.fdroid.fdroid.views.swap.SwapWorkflowActivity.PrepareSwapRepo.EXTRA_TYPE;
import static org.fdroid.fdroid.views.swap.SwapWorkflowActivity.PrepareSwapRepo.TYPE_COMPLETE;
import static org.fdroid.fdroid.views.swap.SwapWorkflowActivity.PrepareSwapRepo.TYPE_ERROR;
import static org.fdroid.fdroid.views.swap.SwapWorkflowActivity.PrepareSwapRepo.TYPE_STATUS;
/**
* Handles setting up and generating the local repo used to swap apps, including
* the {@code index.jar}, the symlinks to the shared APKs, etc.
* <p/>
* The work is done in a {@link Thread} so that new incoming {@code Intents}
* are not blocked by processing. A new {@code Intent} immediately nullifies
* the current state because it means the user has chosen a different set of
* apps. That is also enforced here since new {@code Intent}s with the same
* {@link Set} of apps as the current one are ignored. Having the
* {@code Thread} also makes it easy to kill work that is in progress.
*/
public class LocalRepoService extends IntentService {
public static final String TAG = "LocalRepoService";
public static final String ACTION_PROGRESS = "org.fdroid.fdroid.localrepo.LocalRepoService.action.PROGRESS";
public static final String ACTION_COMPLETE = "org.fdroid.fdroid.localrepo.LocalRepoService.action.COMPLETE";
public static final String ACTION_ERROR = "org.fdroid.fdroid.localrepo.LocalRepoService.action.ERROR";
public static final String EXTRA_MESSAGE = "org.fdroid.fdroid.localrepo.LocalRepoService.extra.MESSAGE";
public static final String ACTION_CREATE = "org.fdroid.fdroid.localrepo.action.CREATE";
public static final String EXTRA_PACKAGE_NAMES = "org.fdroid.fdroid.localrepo.extra.PACKAGE_NAMES";
private final HashSet<String> selectedApps = new HashSet<>();
private GenerateLocalRepoThread thread;
public LocalRepoService() {
super("LocalRepoService");
}
/**
* Creates a skeleton swap repo with only F-Droid itself in it
*/
public static void create(Context context) {
create(context, Collections.singleton(context.getPackageName()));
}
/**
* Sets up the local repo with the included {@code packageNames}
*/
public static void create(Context context, Set<String> packageNames) {
Intent intent = new Intent(context, LocalRepoService.class);
intent.setAction(ACTION_CREATE);
intent.putExtra(EXTRA_PACKAGE_NAMES, packageNames.toArray(new String[0]));
context.startService(intent);
}
@Override
protected void onHandleIntent(Intent intent) {
String[] packageNames = intent.getStringArrayExtra(EXTRA_PACKAGE_NAMES);
if (packageNames == null || packageNames.length == 0) {
Utils.debugLog(TAG, "no packageNames found, quiting");
return;
}
boolean changed = Collections.addAll(selectedApps, packageNames);
if (!changed) {
Utils.debugLog(TAG, "packageNames list unchanged, quiting");
return;
}
if (thread != null) {
thread.interrupt();
}
thread = new GenerateLocalRepoThread();
thread.start();
}
private class GenerateLocalRepoThread extends Thread {
private static final String TAG = "GenerateLocalRepoThread";
@Override
public void run() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);
runProcess(LocalRepoService.this, selectedApps);
}
}
public static void runProcess(Context context, Set<String> selectedApps) {
try {
final LocalRepoManager lrm = LocalRepoManager.get(context);
broadcast(context, ACTION_PROGRESS, R.string.deleting_repo);
lrm.deleteRepo();
for (String app : selectedApps) {
broadcast(context, ACTION_PROGRESS, context.getString(R.string.adding_apks_format, app));
lrm.addApp(context, app);
}
String urlString = Utils.getSharingUri(FDroidApp.repo).toString();
lrm.writeIndexPage(urlString);
broadcast(context, ACTION_PROGRESS, R.string.writing_index_jar);
lrm.writeIndexJar();
broadcast(context, ACTION_PROGRESS, R.string.linking_apks);
lrm.copyApksToRepo();
broadcast(context, ACTION_PROGRESS, R.string.copying_icons);
// run the icon copy without progress, its not a blocker
new Thread() {
@Override
public void run() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);
lrm.copyIconsToRepo();
}
}.start();
broadcast(context, ACTION_COMPLETE, null);
} catch (IOException | XmlPullParserException | LocalRepoKeyStore.InitException e) {
broadcast(context, ACTION_ERROR, e.getLocalizedMessage());
e.printStackTrace();
}
}
/**
* Translate Android style broadcast {@link Intent}s to {@code PrepareSwapRepo}
*/
static void broadcast(Context context, String action, String message) {
Intent intent = new Intent(context, SwapWorkflowActivity.class);
intent.setAction(SwapWorkflowActivity.PrepareSwapRepo.ACTION);
switch (action) {
case ACTION_PROGRESS:
intent.putExtra(EXTRA_TYPE, TYPE_STATUS);
break;
case ACTION_COMPLETE:
intent.putExtra(EXTRA_TYPE, TYPE_COMPLETE);
break;
case ACTION_ERROR:
intent.putExtra(EXTRA_TYPE, TYPE_ERROR);
break;
default:
throw new IllegalArgumentException("unsupported action");
}
if (message != null) {
Utils.debugLog(TAG, "Preparing swap: " + message);
intent.putExtra(EXTRA_MESSAGE, message);
}
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
static void broadcast(Context context, String action, int resId) {
broadcast(context, action, context.getString(resId));
}
}

View File

@ -12,7 +12,6 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@ -51,19 +50,16 @@ import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.NewRepoConfig;
import org.fdroid.fdroid.installer.InstallManagerService;
import org.fdroid.fdroid.installer.Installer;
import org.fdroid.fdroid.localrepo.LocalRepoManager;
import org.fdroid.fdroid.localrepo.LocalRepoService;
import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.localrepo.SwapView;
import org.fdroid.fdroid.localrepo.peers.Peer;
import org.fdroid.fdroid.net.BluetoothDownloader;
import org.fdroid.fdroid.net.HttpDownloader;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
@ -103,7 +99,6 @@ public class SwapWorkflowActivity extends AppCompatActivity {
private Toolbar toolbar;
private SwapView currentView;
private boolean hasPreparedLocalRepo;
private PrepareSwapRepo updateSwappableAppsTask;
private NewRepoConfig confirmSwapConfig;
private LocalBroadcastManager localBroadcastManager;
private WifiManager wifiManager;
@ -504,12 +499,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
// as we are starting over now.
getService().swapWith(null);
if (!getService().isEnabled()) {
if (!LocalRepoManager.get(this).getIndexJar().exists()) {
Utils.debugLog(TAG, "Preparing initial repo with only F-Droid, until we have allowed the user to configure their own repo.");
new PrepareInitialSwapRepo().execute();
}
}
LocalRepoService.create(this);
inflateSwapView(R.layout.swap_start_swap);
}
@ -591,30 +581,34 @@ public class SwapWorkflowActivity extends AppCompatActivity {
((FDroidApp) getApplication()).sendViaBluetooth(this, Activity.RESULT_OK, BuildConfig.APPLICATION_ID);
}
// TODO: Figure out whether they have changed since last time UpdateAsyncTask was run.
// If the local repo is running, then we can ask it what apps it is swapping and compare with that.
// Otherwise, probably will need to scan the file system.
/**
* TODO: Figure out whether they have changed since last time LocalRepoService
* was run. If the local repo is running, then we can ask it what apps it is
* swapping and compare with that. Otherwise, probably will need to scan the
* file system.
*/
public void onAppsSelected() {
if (updateSwappableAppsTask == null && !hasPreparedLocalRepo) {
updateSwappableAppsTask = new PrepareSwapRepo(getService().getAppsToSwap());
updateSwappableAppsTask.execute();
if (hasPreparedLocalRepo) {
onLocalRepoPrepared();
} else {
LocalRepoService.create(this, getService().getAppsToSwap());
getService().setCurrentView(R.layout.swap_connecting);
inflateSwapView(R.layout.swap_connecting);
} else {
onLocalRepoPrepared();
}
}
/**
* Once the UpdateAsyncTask has finished preparing our repository index, we can
* show the next screen to the user. This will be one of two things:
* * If we directly selected a peer to swap with initially, we will skip straight to getting
* the list of apps from that device.
* * Alternatively, if we didn't have a person to connect to, and instead clicked "Scan QR Code",
* then we want to show a QR code or NFC dialog.
* <ol>
* <li>If we directly selected a peer to swap with initially, we will skip straight to getting
* the list of apps from that device.</li>
* <li>Alternatively, if we didn't have a person to connect to, and instead clicked "Scan QR Code",
* then we want to show a QR code or NFC dialog.</li>
* </ol>
*/
public void onLocalRepoPrepared() {
updateSwappableAppsTask = null;
// TODO ditch this, use a message from LocalRepoService. Maybe?
hasPreparedLocalRepo = true;
if (getService().isConnectingWithPeer()) {
startSwappingWithPeer();
@ -637,6 +631,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
// during the wifi qr code being shown too.
boolean nfcMessageReady = NfcHelper.setPushMessage(this, Utils.getSharingUri(FDroidApp.repo));
// TODO move all swap-specific preferences to a SharedPreferences instance for SwapWorkflowActivity
if (Preferences.get().showNfcDuringSwap() && nfcMessageReady) {
inflateSwapView(R.layout.swap_nfc);
return true;
@ -779,82 +774,13 @@ public class SwapWorkflowActivity extends AppCompatActivity {
service.getBluetoothSwap().startInBackground(); // TODO replace with Intent to SwapService
}
class PrepareInitialSwapRepo extends PrepareSwapRepo {
PrepareInitialSwapRepo() {
super(new HashSet<>(Arrays.asList(new String[]{BuildConfig.APPLICATION_ID})));
}
}
class PrepareSwapRepo extends AsyncTask<Void, Void, Void> {
public class PrepareSwapRepo {
public static final String ACTION = "PrepareSwapRepo.Action";
public static final String EXTRA_MESSAGE = "PrepareSwapRepo.Status.Message";
public static final String EXTRA_TYPE = "PrepareSwapRepo.Action.Type";
public static final int TYPE_STATUS = 0;
public static final int TYPE_COMPLETE = 1;
public static final int TYPE_ERROR = 2;
@NonNull
protected final Set<String> selectedApps;
@NonNull
protected final Uri sharingUri;
@NonNull
protected final Context context;
PrepareSwapRepo(@NonNull Set<String> apps) {
context = SwapWorkflowActivity.this;
selectedApps = apps;
sharingUri = Utils.getSharingUri(FDroidApp.repo);
}
private void broadcast(int type) {
broadcast(type, null);
}
private void broadcast(int type, String message) {
Intent intent = new Intent(ACTION);
intent.putExtra(EXTRA_TYPE, type);
if (message != null) {
Utils.debugLog(TAG, "Preparing swap: " + message);
intent.putExtra(EXTRA_MESSAGE, message);
}
LocalBroadcastManager.getInstance(SwapWorkflowActivity.this).sendBroadcast(intent);
}
@Override
protected Void doInBackground(Void... params) {
try {
final LocalRepoManager lrm = LocalRepoManager.get(context);
broadcast(TYPE_STATUS, getString(R.string.deleting_repo));
lrm.deleteRepo();
for (String app : selectedApps) {
broadcast(TYPE_STATUS, String.format(getString(R.string.adding_apks_format), app));
lrm.addApp(context, app);
}
lrm.writeIndexPage(sharingUri.toString());
broadcast(TYPE_STATUS, getString(R.string.writing_index_jar));
lrm.writeIndexJar();
broadcast(TYPE_STATUS, getString(R.string.linking_apks));
lrm.copyApksToRepo();
broadcast(TYPE_STATUS, getString(R.string.copying_icons));
// run the icon copy without progress, its not a blocker
new Thread() {
@Override
public void run() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
lrm.copyIconsToRepo();
}
}.start();
broadcast(TYPE_COMPLETE);
} catch (Exception e) {
broadcast(TYPE_ERROR);
Log.e(TAG, "", e);
}
return null;
}
}
/**

View File

@ -0,0 +1,14 @@
package org.fdroid.fdroid.data;
import android.content.Context;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@Implements(App.class)
public class ShadowApp extends ValueObject {
@Implementation
protected static int[] getMinTargetMaxSdkVersions(Context context, String packageName) {
return new int[]{10, 23, Apk.SDK_VERSION_MAX_VALUE};
}
}

View File

@ -0,0 +1,187 @@
package org.fdroid.fdroid.updater;
import android.content.ContentResolver;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import org.apache.commons.net.util.SubnetUtils;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Hasher;
import org.fdroid.fdroid.IndexUpdater;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.DBHelper;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.Schema;
import org.fdroid.fdroid.data.ShadowApp;
import org.fdroid.fdroid.data.TempAppProvider;
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
import org.fdroid.fdroid.localrepo.LocalRepoManager;
import org.fdroid.fdroid.localrepo.LocalRepoService;
import org.fdroid.fdroid.net.LocalHTTPD;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowContentResolver;
import org.robolectric.shadows.ShadowLog;
import org.robolectric.shadows.ShadowPackageManager;
import java.io.File;
import java.io.IOException;
import java.security.cert.Certificate;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.robolectric.Shadows.shadowOf;
/**
* This test almost works, it needs to have the {@link android.content.ContentProvider}
* and {@link ContentResolver} stuff worked out. It currently fails as
* {@code updater.update()}.
*/
@Ignore
@RunWith(RobolectricTestRunner.class)
@Config(shadows = ShadowApp.class)
public class SwapRepoTest {
private LocalHTTPD localHttpd;
protected ShadowContentResolver shadowContentResolver;
protected ContentResolver contentResolver;
protected ContextWrapper context;
@Before
public void setUp() {
ShadowLog.stream = System.out;
contentResolver = RuntimeEnvironment.application.getContentResolver();
shadowContentResolver = Shadows.shadowOf(contentResolver);
context = new ContextWrapper(RuntimeEnvironment.application.getApplicationContext()) {
@Override
public ContentResolver getContentResolver() {
return contentResolver;
}
};
TestUtils.registerContentProvider(ApkProvider.getAuthority(), ApkProvider.class);
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
TestUtils.registerContentProvider(RepoProvider.getAuthority(), RepoProvider.class);
TestUtils.registerContentProvider(TempAppProvider.getAuthority(), TempAppProvider.class);
Preferences.setupForTests(context);
}
@After
public final void tearDownBase() {
DBHelper.clearDbHelperSingleton();
}
/**
* @see org.fdroid.fdroid.net.WifiStateChangeService.WifiInfoThread#run()
*/
@Test
public void testSwap()
throws IOException, LocalRepoKeyStore.InitException, IndexUpdater.UpdateException, InterruptedException {
PackageManager packageManager = context.getPackageManager();
ShadowPackageManager shadowPackageManager = shadowOf(packageManager);
ApplicationInfo appInfo = new ApplicationInfo();
appInfo.flags = 0;
appInfo.packageName = context.getPackageName();
appInfo.minSdkVersion = 10;
appInfo.targetSdkVersion = 23;
appInfo.sourceDir = getClass().getClassLoader().getResource("F-Droid.apk").getPath();
appInfo.publicSourceDir = getClass().getClassLoader().getResource("F-Droid.apk").getPath();
System.out.println("appInfo.sourceDir " + appInfo.sourceDir);
appInfo.name = "F-Droid";
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = appInfo.packageName;
packageInfo.applicationInfo = appInfo;
packageInfo.versionCode = 1002001;
packageInfo.versionName = "1.2-fake";
shadowPackageManager.addPackage(packageInfo);
try {
FDroidApp.initWifiSettings();
FDroidApp.ipAddressString = "127.0.0.1";
FDroidApp.subnetInfo = new SubnetUtils("127.0.0.0/8").getInfo();
FDroidApp.repo.name = "test";
FDroidApp.repo.address = "http://" + FDroidApp.ipAddressString + ":" + FDroidApp.port + "/fdroid/repo";
LocalRepoService.runProcess(context, new String[]{context.getPackageName()});
File indexJarFile = LocalRepoManager.get(context).getIndexJar();
System.out.println("indexJarFile:" + indexJarFile);
assertTrue(indexJarFile.isFile());
localHttpd = new LocalHTTPD(
context,
FDroidApp.ipAddressString,
FDroidApp.port,
LocalRepoManager.get(context).getWebRoot(),
false);
localHttpd.start();
Thread.sleep(100); // give the server some tine to start.
assertTrue(localHttpd.isAlive());
LocalRepoKeyStore localRepoKeyStore = LocalRepoKeyStore.get(context);
Certificate localCert = localRepoKeyStore.getCertificate();
String signingCert = Hasher.hex(localCert);
assertFalse(TextUtils.isEmpty(signingCert));
assertFalse(TextUtils.isEmpty(Utils.calcFingerprint(localCert)));
Repo repo = MultiIndexUpdaterTest.createRepo(FDroidApp.repo.name, FDroidApp.repo.address,
context, signingCert);
IndexUpdater updater = new IndexUpdater(context, repo);
updater.update();
assertTrue(updater.hasChanged());
updater.processDownloadedFile(indexJarFile);
boolean foundRepo = false;
for (Repo repoFromDb : RepoProvider.Helper.all(context)) {
if (TextUtils.equals(repo.address, repoFromDb.address)) {
foundRepo = true;
repo = repoFromDb;
}
}
assertTrue(foundRepo);
assertNotEquals(-1, repo.getId());
List<Apk> apks = ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL);
assertEquals(1, apks.size());
for (Apk apk : apks) {
System.out.println(apk);
}
//MultiIndexUpdaterTest.assertApksExist(apks, context.getPackageName(), new int[]{BuildConfig.VERSION_CODE});
Thread.sleep(10000);
} finally {
if (localHttpd != null) {
localHttpd.stop();
}
}
}
class TestLocalRepoService extends LocalRepoService {
@Override
protected void onHandleIntent(Intent intent) {
super.onHandleIntent(intent);
}
}
}