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:
parent
9fc1ecd5a4
commit
85410504da
@ -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;
|
||||
}
|
||||
}
|
@ -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"/>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
14
app/src/testFull/java/org/fdroid/fdroid/data/ShadowApp.java
Normal file
14
app/src/testFull/java/org/fdroid/fdroid/data/ShadowApp.java
Normal 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};
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user