Merge branch 'swap/refinement-towards-stable'
This commit is contained in:
commit
0db2499666
@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
* Handle amazon.com and play.google.com app links
|
* Handle amazon.com and play.google.com app links
|
||||||
|
|
||||||
|
* Misc fixes to the "swap" workflow (especially on Android 2.3 devices)
|
||||||
|
|
||||||
### 0.83 (2015-03-26)
|
### 0.83 (2015-03-26)
|
||||||
|
|
||||||
* Fix possible crashes when installing or uninstalling apps
|
* Fix possible crashes when installing or uninstalling apps
|
||||||
|
@ -329,6 +329,14 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".views.swap.SwapAppListActivity$SwapAppDetails"
|
||||||
|
android:label="@string/app_details"
|
||||||
|
android:parentActivityName=".views.swap.SwapAppListActivity" >
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value=".views.swap.SwapAppListActivity" />
|
||||||
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:label="@string/menu_preferences"
|
android:label="@string/menu_preferences"
|
||||||
android:name=".PreferencesActivity"
|
android:name=".PreferencesActivity"
|
||||||
|
@ -39,6 +39,8 @@ if ( !hasProperty( 'sourceDeps' ) ) {
|
|||||||
|
|
||||||
// Upstream doesn't have a binary on mavenCentral.
|
// Upstream doesn't have a binary on mavenCentral.
|
||||||
compile(name: 'zipsigner')
|
compile(name: 'zipsigner')
|
||||||
|
|
||||||
|
androidTestCompile 'commons-io:commons-io:2.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -76,6 +78,8 @@ if ( !hasProperty( 'sourceDeps' ) ) {
|
|||||||
compile 'com.android.support:support-v4:20.0.+',
|
compile 'com.android.support:support-v4:20.0.+',
|
||||||
'com.android.support:appcompat-v7:20.0.+',
|
'com.android.support:appcompat-v7:20.0.+',
|
||||||
'com.android.support:support-annotations:20.0.+'
|
'com.android.support:support-annotations:20.0.+'
|
||||||
|
|
||||||
|
androidTestCompile 'commons-io:commons-io:2.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -135,7 +139,16 @@ android {
|
|||||||
assets.srcDirs = ['assets']
|
assets.srcDirs = ['assets']
|
||||||
}
|
}
|
||||||
|
|
||||||
instrumentTest.setRoot('test')
|
androidTest.setRoot('test')
|
||||||
|
androidTest {
|
||||||
|
manifest.srcFile 'test/AndroidManifest.xml'
|
||||||
|
java.srcDirs = ['test/src']
|
||||||
|
resources.srcDirs = ['test/src']
|
||||||
|
aidl.srcDirs = ['test/src']
|
||||||
|
renderscript.srcDirs = ['test/src']
|
||||||
|
res.srcDirs = ['test/res']
|
||||||
|
assets.srcDirs = ['test/assets']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
android:paddingTop="2dip" >
|
android:paddingTop="2dip" >
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
android:id="@android:id/icon"
|
||||||
android:layout_width="48dip"
|
android:layout_width="48dip"
|
||||||
android:layout_height="48dip"
|
android:layout_height="48dip"
|
||||||
android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
|
android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
|
||||||
|
@ -20,7 +20,13 @@
|
|||||||
android:paddingBottom="2dip"
|
android:paddingBottom="2dip"
|
||||||
android:paddingTop="2dip" >
|
android:paddingTop="2dip" >
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/checkbox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
android:id="@android:id/icon"
|
||||||
android:layout_width="48dip"
|
android:layout_width="48dip"
|
||||||
android:layout_height="48dip"
|
android:layout_height="48dip"
|
||||||
android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
|
android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
|
||||||
|
14
F-Droid/res/layout/swap_activity.xml
Normal file
14
F-Droid/res/layout/swap_activity.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/fragment_container"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
@ -8,14 +8,14 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_description"
|
android:id="@+id/text_description"
|
||||||
android:text="Your mobile device becomes an app store with Swap!"
|
android:text="@string/swap_introduction"
|
||||||
style="@style/SwapTheme.StartSwap.MainText"
|
style="@style/SwapTheme.StartSwap.MainText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/button_start_swap"
|
android:id="@+id/button_start_swap"
|
||||||
android:text="START A SWAP"
|
android:text="@string/swap_start"
|
||||||
style="@style/SwapTheme.StartSwap.StartButton"
|
style="@style/SwapTheme.StartSwap.StartButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_below="@+id/text_description"
|
android:layout_below="@+id/text_description"
|
||||||
|
14
F-Droid/res/menu/swap_next_search.xml
Normal file
14
F-Droid/res/menu/swap_next_search.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_next"
|
||||||
|
android:title="Next"
|
||||||
|
android:titleCondensed="Next"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_search"
|
||||||
|
android:icon="@android:drawable/ic_menu_search"
|
||||||
|
android:title="Search"
|
||||||
|
android:titleCondensed="Search"/>
|
||||||
|
|
||||||
|
</menu>
|
@ -307,4 +307,6 @@
|
|||||||
<string name="open_qr_code_scanner">Open QR Code Scanner</string>
|
<string name="open_qr_code_scanner">Open QR Code Scanner</string>
|
||||||
<string name="swap_welcome">Welcome to F-Droid!</string>
|
<string name="swap_welcome">Welcome to F-Droid!</string>
|
||||||
<string name="swap_confirm_connect">Do you want to get apps from %1$s now?</string>
|
<string name="swap_confirm_connect">Do you want to get apps from %1$s now?</string>
|
||||||
|
<string name="swap_introduction">Your mobile device becomes an app store with Swap!</string>
|
||||||
|
<string name="swap_start">START A SWAP</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -12,14 +12,14 @@
|
|||||||
<!-- backward-compatibility theme options go here -->
|
<!-- backward-compatibility theme options go here -->
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<color name="black">#FF000000</color>
|
<color name="black">#FF000000</color>
|
||||||
<color name="white">#FFFFFFFF</color>
|
<color name="white">#FFFFFFFF</color>
|
||||||
<color name="red">#FFFF0000</color>
|
<color name="red">#FFFF0000</color>
|
||||||
|
|
||||||
<style name="AboutDialogLight" parent="@android:style/Theme.Dialog">
|
<style name="AboutDialogLight" parent="@android:style/Theme.Dialog">
|
||||||
<item name="@android:windowBackground">@color/black</item>
|
<item name="@android:windowBackground">@color/black</item>
|
||||||
<item name="@android:textColor">@color/white</item>
|
<item name="@android:textColor">@color/white</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AppThemeDark" parent="AppBaseThemeDark">
|
<style name="AppThemeDark" parent="AppBaseThemeDark">
|
||||||
<!-- customizations that are not API-level specific go here. -->
|
<!-- customizations that are not API-level specific go here. -->
|
||||||
@ -65,6 +65,7 @@
|
|||||||
|
|
||||||
<style name="SwapTheme.AppList.SwapSuccess">
|
<style name="SwapTheme.AppList.SwapSuccess">
|
||||||
<item name="android:textAlignment">center</item>
|
<item name="android:textAlignment">center</item>
|
||||||
|
<item name="android:gravity">center</item>
|
||||||
<item name="android:textSize">25.7sp</item> <!-- 46px * 96dpi / 160dpi -->
|
<item name="android:textSize">25.7sp</item> <!-- 46px * 96dpi / 160dpi -->
|
||||||
<item name="android:paddingTop">28dp</item> <!-- 50px * 96dpi / 160dpi -->
|
<item name="android:paddingTop">28dp</item> <!-- 50px * 96dpi / 160dpi -->
|
||||||
<item name="android:paddingBottom">20.1dp</item> <!-- 36px * 96dpi / 160dpi -->
|
<item name="android:paddingBottom">20.1dp</item> <!-- 36px * 96dpi / 160dpi -->
|
||||||
@ -73,6 +74,7 @@
|
|||||||
|
|
||||||
<style name="SwapTheme.AppList.SwapSuccessDetails">
|
<style name="SwapTheme.AppList.SwapSuccessDetails">
|
||||||
<item name="android:textAlignment">center</item>
|
<item name="android:textAlignment">center</item>
|
||||||
|
<item name="android:gravity">center</item>
|
||||||
<item name="android:textSize">20.1sp</item> <!-- 36px * 96dpi / 160dpi -->
|
<item name="android:textSize">20.1sp</item> <!-- 36px * 96dpi / 160dpi -->
|
||||||
<item name="android:paddingTop">20.1dp</item> <!-- 36px * 96dpi / 160dpi -->
|
<item name="android:paddingTop">20.1dp</item> <!-- 36px * 96dpi / 160dpi -->
|
||||||
<item name="android:paddingBottom">20.1dp</item> <!-- 36px * 96dpi / 160dpi -->
|
<item name="android:paddingBottom">20.1dp</item> <!-- 36px * 96dpi / 160dpi -->
|
||||||
@ -81,6 +83,7 @@
|
|||||||
|
|
||||||
<style name="SwapTheme.StartSwap.MainText">
|
<style name="SwapTheme.StartSwap.MainText">
|
||||||
<item name="android:textAlignment">center</item>
|
<item name="android:textAlignment">center</item>
|
||||||
|
<item name="android:gravity">center</item>
|
||||||
<item name="android:textSize">20.1sp</item> <!-- 36px * 96dpi / 160dpi -->
|
<item name="android:textSize">20.1sp</item> <!-- 36px * 96dpi / 160dpi -->
|
||||||
<item name="android:paddingLeft">28dp</item> <!-- 50px * 96dpi / 160dpi -->
|
<item name="android:paddingLeft">28dp</item> <!-- 50px * 96dpi / 160dpi -->
|
||||||
<item name="android:paddingRight">28dp</item> <!-- 50px * 96dpi / 160dpi -->
|
<item name="android:paddingRight">28dp</item> <!-- 50px * 96dpi / 160dpi -->
|
||||||
@ -90,6 +93,7 @@
|
|||||||
|
|
||||||
<style name="SwapTheme.Wizard.Text">
|
<style name="SwapTheme.Wizard.Text">
|
||||||
<item name="android:textAlignment">center</item>
|
<item name="android:textAlignment">center</item>
|
||||||
|
<item name="android:gravity">center</item>
|
||||||
<item name="android:textColor">#fff</item>
|
<item name="android:textColor">#fff</item>
|
||||||
<item name="android:textColorPrimary">#fff</item>
|
<item name="android:textColorPrimary">#fff</item>
|
||||||
<item name="android:textColorSecondary">#fff</item>
|
<item name="android:textColorSecondary">#fff</item>
|
||||||
|
@ -768,13 +768,17 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
|||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void navigateUp() {
|
||||||
|
NavUtils.navigateUpFromSameTask(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
|
|
||||||
case android.R.id.home:
|
case android.R.id.home:
|
||||||
NavUtils.navigateUpFromSameTask(this);
|
navigateUp();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case LAUNCH:
|
case LAUNCH:
|
||||||
|
@ -346,23 +346,28 @@ public class UpdateService extends IntentService implements ProgressListener {
|
|||||||
List<Repo> repos = RepoProvider.Helper.all(this);
|
List<Repo> repos = RepoProvider.Helper.all(this);
|
||||||
|
|
||||||
// Process each repo...
|
// Process each repo...
|
||||||
Map<String, App> appsToUpdate = new HashMap<String, App>();
|
Map<String, App> appsToUpdate = new HashMap<>();
|
||||||
List<Apk> apksToUpdate = new ArrayList<Apk>();
|
List<Apk> apksToUpdate = new ArrayList<>();
|
||||||
List<Repo> unchangedRepos = new ArrayList<Repo>();
|
List<Repo> swapRepos = new ArrayList<>();
|
||||||
List<Repo> updatedRepos = new ArrayList<Repo>();
|
List<Repo> unchangedRepos = new ArrayList<>();
|
||||||
List<Repo> disabledRepos = new ArrayList<Repo>();
|
List<Repo> updatedRepos = new ArrayList<>();
|
||||||
List<CharSequence> errorRepos = new ArrayList<CharSequence>();
|
List<Repo> disabledRepos = new ArrayList<>();
|
||||||
ArrayList<CharSequence> repoErrors = new ArrayList<CharSequence>();
|
List<CharSequence> errorRepos = new ArrayList<>();
|
||||||
List<RepoUpdater.RepoUpdateRememberer> repoUpdateRememberers = new ArrayList<RepoUpdater.RepoUpdateRememberer>();
|
ArrayList<CharSequence> repoErrors = new ArrayList<>();
|
||||||
|
List<RepoUpdater.RepoUpdateRememberer> repoUpdateRememberers = new ArrayList<>();
|
||||||
boolean changes = false;
|
boolean changes = false;
|
||||||
|
boolean singleRepoUpdate = !TextUtils.isEmpty(address);
|
||||||
for (final Repo repo : repos) {
|
for (final Repo repo : repos) {
|
||||||
|
|
||||||
if (!repo.inuse) {
|
if (!repo.inuse) {
|
||||||
disabledRepos.add(repo);
|
disabledRepos.add(repo);
|
||||||
continue;
|
continue;
|
||||||
} else if (!TextUtils.isEmpty(address) && !repo.address.equals(address)) {
|
} else if (singleRepoUpdate && !repo.address.equals(address)) {
|
||||||
unchangedRepos.add(repo);
|
unchangedRepos.add(repo);
|
||||||
continue;
|
continue;
|
||||||
|
} else if (!singleRepoUpdate && repo.isSwap) {
|
||||||
|
swapRepos.add(repo);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendStatus(STATUS_INFO, getString(R.string.status_connecting_to_repo, repo.address));
|
sendStatus(STATUS_INFO, getString(R.string.status_connecting_to_repo, repo.address));
|
||||||
|
@ -23,6 +23,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
|||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import android.content.res.XmlResourceParser;
|
import android.content.res.XmlResourceParser;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
@ -59,6 +60,9 @@ import java.util.Locale;
|
|||||||
|
|
||||||
public final class Utils {
|
public final class Utils {
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
|
private static final String TAG = "fdroid.Utils";
|
||||||
|
|
||||||
public static final int BUFFER_SIZE = 4096;
|
public static final int BUFFER_SIZE = 4096;
|
||||||
|
|
||||||
// The date format used for storing dates (e.g. lastupdated, added) in the
|
// The date format used for storing dates (e.g. lastupdated, added) in the
|
||||||
@ -71,8 +75,6 @@ public final class Utils {
|
|||||||
public static final SimpleDateFormat LOG_DATE_FORMAT =
|
public static final SimpleDateFormat LOG_DATE_FORMAT =
|
||||||
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
|
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
|
||||||
|
|
||||||
private static final String TAG = "fdroid.Utils";
|
|
||||||
|
|
||||||
public static String getIconsDir(Context context) {
|
public static String getIconsDir(Context context) {
|
||||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||||
String iconsDir;
|
String iconsDir;
|
||||||
@ -230,11 +232,7 @@ public final class Utils {
|
|||||||
}
|
}
|
||||||
eventType = xml.nextToken();
|
eventType = xml.nextToken();
|
||||||
}
|
}
|
||||||
} catch (NameNotFoundException e) {
|
} catch (NameNotFoundException | IOException | XmlPullParserException e) {
|
||||||
e.printStackTrace();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (XmlPullParserException e) {
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return 8; // some kind of hopeful default
|
return 8; // some kind of hopeful default
|
||||||
@ -282,7 +280,8 @@ public final class Utils {
|
|||||||
return displayFP;
|
return displayFP;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri getSharingUri(Context context, Repo repo) {
|
@NonNull
|
||||||
|
public static Uri getSharingUri(Repo repo) {
|
||||||
if (TextUtils.isEmpty(repo.address))
|
if (TextUtils.isEmpty(repo.address))
|
||||||
return Uri.parse("http://wifi-not-enabled");
|
return Uri.parse("http://wifi-not-enabled");
|
||||||
Uri uri = Uri.parse(repo.address.replaceFirst("http", "fdroidrepo"));
|
Uri uri = Uri.parse(repo.address.replaceFirst("http", "fdroidrepo"));
|
||||||
@ -345,8 +344,8 @@ public final class Utils {
|
|||||||
digest.update(key);
|
digest.update(key);
|
||||||
byte[] fingerprint = digest.digest();
|
byte[] fingerprint = digest.digest();
|
||||||
Formatter formatter = new Formatter(new StringBuilder());
|
Formatter formatter = new Formatter(new StringBuilder());
|
||||||
for (int i = 0; i < fingerprint.length; i++) {
|
for (byte aFingerprint : fingerprint) {
|
||||||
formatter.format("%02X", fingerprint[i]);
|
formatter.format("%02X", aFingerprint);
|
||||||
}
|
}
|
||||||
ret = formatter.toString();
|
ret = formatter.toString();
|
||||||
formatter.close();
|
formatter.close();
|
||||||
@ -432,14 +431,14 @@ public final class Utils {
|
|||||||
|
|
||||||
public static String getBinaryHash(File apk, String algo) {
|
public static String getBinaryHash(File apk, String algo) {
|
||||||
FileInputStream fis = null;
|
FileInputStream fis = null;
|
||||||
BufferedInputStream bis = null;
|
BufferedInputStream bis;
|
||||||
try {
|
try {
|
||||||
MessageDigest md = MessageDigest.getInstance(algo);
|
MessageDigest md = MessageDigest.getInstance(algo);
|
||||||
fis = new FileInputStream(apk);
|
fis = new FileInputStream(apk);
|
||||||
bis = new BufferedInputStream(fis);
|
bis = new BufferedInputStream(fis);
|
||||||
|
|
||||||
byte[] dataBytes = new byte[524288];
|
byte[] dataBytes = new byte[524288];
|
||||||
int nread = 0;
|
int nread;
|
||||||
|
|
||||||
while ((nread = bis.read(dataBytes)) != -1)
|
while ((nread = bis.read(dataBytes)) != -1)
|
||||||
md.update(dataBytes, 0, nread);
|
md.update(dataBytes, 0, nread);
|
||||||
@ -483,7 +482,7 @@ public final class Utils {
|
|||||||
listNum = -1;
|
listNum = -1;
|
||||||
else
|
else
|
||||||
output.append('\n');
|
output.append('\n');
|
||||||
} else if (opening && tag.equals("ol")) {
|
} else if (tag.equals("ol")) {
|
||||||
if (opening)
|
if (opening)
|
||||||
listNum = 1;
|
listNum = 1;
|
||||||
else
|
else
|
||||||
|
@ -259,10 +259,17 @@ public class AppProvider extends FDroidProvider {
|
|||||||
private boolean isSuggestedApkTableAdded = false;
|
private boolean isSuggestedApkTableAdded = false;
|
||||||
private boolean requiresInstalledTable = false;
|
private boolean requiresInstalledTable = false;
|
||||||
private boolean categoryFieldAdded = false;
|
private boolean categoryFieldAdded = false;
|
||||||
|
private boolean countFieldAppended = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getRequiredTables() {
|
protected String getRequiredTables() {
|
||||||
return DBHelper.TABLE_APP;
|
final String app = DBHelper.TABLE_APP;
|
||||||
|
final String apk = DBHelper.TABLE_APK;
|
||||||
|
final String repo = DBHelper.TABLE_REPO;
|
||||||
|
|
||||||
|
return app +
|
||||||
|
" LEFT JOIN " + apk + " ON ( " + apk + ".id = " + app + ".id ) " +
|
||||||
|
" LEFT JOIN " + repo + " ON ( " + apk + ".repo = " + repo + "._id )";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -270,6 +277,12 @@ public class AppProvider extends FDroidProvider {
|
|||||||
return fieldCount() == 1 && categoryFieldAdded;
|
return fieldCount() == 1 && categoryFieldAdded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String groupBy() {
|
||||||
|
// If the count field has been requested, then we want to group all rows together.
|
||||||
|
return countFieldAppended ? null : DBHelper.TABLE_APP + ".id";
|
||||||
|
}
|
||||||
|
|
||||||
public void addSelection(AppQuerySelection selection) {
|
public void addSelection(AppQuerySelection selection) {
|
||||||
addSelection(selection.getSelection());
|
addSelection(selection.getSelection());
|
||||||
if (selection.naturalJoinToInstalled()) {
|
if (selection.naturalJoinToInstalled()) {
|
||||||
@ -318,7 +331,8 @@ public class AppProvider extends FDroidProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void appendCountField() {
|
private void appendCountField() {
|
||||||
appendField("COUNT(*) AS " + DataColumns._COUNT);
|
countFieldAppended = true;
|
||||||
|
appendField("COUNT( DISTINCT fdroid_app.id ) AS " + DataColumns._COUNT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSuggestedApkVersionField() {
|
private void addSuggestedApkVersionField() {
|
||||||
@ -372,6 +386,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
private static final String PATH_CATEGORY = "category";
|
private static final String PATH_CATEGORY = "category";
|
||||||
private static final String PATH_IGNORED = "ignored";
|
private static final String PATH_IGNORED = "ignored";
|
||||||
private static final String PATH_CALC_APP_DETAILS_FROM_INDEX = "calcDetailsFromIndex";
|
private static final String PATH_CALC_APP_DETAILS_FROM_INDEX = "calcDetailsFromIndex";
|
||||||
|
private static final String PATH_REPO = "repo";
|
||||||
|
|
||||||
private static final int CAN_UPDATE = CODE_SINGLE + 1;
|
private static final int CAN_UPDATE = CODE_SINGLE + 1;
|
||||||
private static final int INSTALLED = CAN_UPDATE + 1;
|
private static final int INSTALLED = CAN_UPDATE + 1;
|
||||||
@ -383,6 +398,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
private static final int CATEGORY = NEWLY_ADDED + 1;
|
private static final int CATEGORY = NEWLY_ADDED + 1;
|
||||||
private static final int IGNORED = CATEGORY + 1;
|
private static final int IGNORED = CATEGORY + 1;
|
||||||
private static final int CALC_APP_DETAILS_FROM_INDEX = IGNORED + 1;
|
private static final int CALC_APP_DETAILS_FROM_INDEX = IGNORED + 1;
|
||||||
|
private static final int REPO = CALC_APP_DETAILS_FROM_INDEX + 1;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
matcher.addURI(getAuthority(), null, CODE_LIST);
|
matcher.addURI(getAuthority(), null, CODE_LIST);
|
||||||
@ -392,6 +408,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
matcher.addURI(getAuthority(), PATH_NEWLY_ADDED, NEWLY_ADDED);
|
matcher.addURI(getAuthority(), PATH_NEWLY_ADDED, NEWLY_ADDED);
|
||||||
matcher.addURI(getAuthority(), PATH_CATEGORY + "/*", CATEGORY);
|
matcher.addURI(getAuthority(), PATH_CATEGORY + "/*", CATEGORY);
|
||||||
matcher.addURI(getAuthority(), PATH_SEARCH + "/*", SEARCH);
|
matcher.addURI(getAuthority(), PATH_SEARCH + "/*", SEARCH);
|
||||||
|
matcher.addURI(getAuthority(), PATH_REPO + "/#", REPO);
|
||||||
matcher.addURI(getAuthority(), PATH_CAN_UPDATE, CAN_UPDATE);
|
matcher.addURI(getAuthority(), PATH_CAN_UPDATE, CAN_UPDATE);
|
||||||
matcher.addURI(getAuthority(), PATH_INSTALLED, INSTALLED);
|
matcher.addURI(getAuthority(), PATH_INSTALLED, INSTALLED);
|
||||||
matcher.addURI(getAuthority(), PATH_NO_APKS, NO_APKS);
|
matcher.addURI(getAuthority(), PATH_NO_APKS, NO_APKS);
|
||||||
@ -438,6 +455,13 @@ public class AppProvider extends FDroidProvider {
|
|||||||
return Uri.withAppendedPath(getContentUri(), PATH_CAN_UPDATE);
|
return Uri.withAppendedPath(getContentUri(), PATH_CAN_UPDATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Uri getRepoUri(Repo repo) {
|
||||||
|
return getContentUri().buildUpon()
|
||||||
|
.appendPath(PATH_REPO)
|
||||||
|
.appendPath(String.valueOf(repo.id))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
public static Uri getContentUri(List<App> apps) {
|
public static Uri getContentUri(List<App> apps) {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
for (int i = 0; i < apps.size(); i ++) {
|
for (int i = 0; i < apps.size(); i ++) {
|
||||||
@ -494,6 +518,12 @@ public class AppProvider extends FDroidProvider {
|
|||||||
return new AppQuerySelection(where).requireNaturalInstalledTable();
|
return new AppQuerySelection(where).requireNaturalInstalledTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AppQuerySelection queryRepo(long repoId) {
|
||||||
|
String selection = " fdroid_apk.repo = ? ";
|
||||||
|
String[] args = { String.valueOf(repoId) };
|
||||||
|
return new AppQuerySelection(selection, args);
|
||||||
|
}
|
||||||
|
|
||||||
private AppQuerySelection queryInstalled() {
|
private AppQuerySelection queryInstalled() {
|
||||||
return new AppQuerySelection().requireNaturalInstalledTable();
|
return new AppQuerySelection().requireNaturalInstalledTable();
|
||||||
}
|
}
|
||||||
@ -555,6 +585,14 @@ public class AppProvider extends FDroidProvider {
|
|||||||
return new AppQuerySelection(selection);
|
return new AppQuerySelection(selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AppQuerySelection queryExcludeSwap() {
|
||||||
|
// fdroid_repo will have null fields if the LEFT JOIN didn't resolve, e.g. due to there
|
||||||
|
// being no apks for the app in the result set. In that case, we can't tell if it is from
|
||||||
|
// a swap repo or not.
|
||||||
|
String selection = " fdroid_repo.isSwap = 0 OR fdroid_repo.isSwap is null ";
|
||||||
|
return new AppQuerySelection(selection);
|
||||||
|
}
|
||||||
|
|
||||||
private AppQuerySelection queryNewlyAdded() {
|
private AppQuerySelection queryNewlyAdded() {
|
||||||
String selection = "fdroid_app.added > ?";
|
String selection = "fdroid_app.added > ?";
|
||||||
String[] args = { Utils.DATE_FORMAT.format(Preferences.get().calcMaxHistory()) };
|
String[] args = { Utils.DATE_FORMAT.format(Preferences.get().calcMaxHistory()) };
|
||||||
@ -599,8 +637,13 @@ public class AppProvider extends FDroidProvider {
|
|||||||
public Cursor query(Uri uri, String[] projection, String customSelection, String[] selectionArgs, String sortOrder) {
|
public Cursor query(Uri uri, String[] projection, String customSelection, String[] selectionArgs, String sortOrder) {
|
||||||
Query query = new Query();
|
Query query = new Query();
|
||||||
AppQuerySelection selection = new AppQuerySelection(customSelection, selectionArgs);
|
AppQuerySelection selection = new AppQuerySelection(customSelection, selectionArgs);
|
||||||
|
|
||||||
|
// Queries which are for the main list of apps should not include swap apps.
|
||||||
|
boolean includeSwap = true;
|
||||||
|
|
||||||
switch (matcher.match(uri)) {
|
switch (matcher.match(uri)) {
|
||||||
case CODE_LIST:
|
case CODE_LIST:
|
||||||
|
includeSwap = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CODE_SINGLE:
|
case CODE_SINGLE:
|
||||||
@ -609,14 +652,21 @@ public class AppProvider extends FDroidProvider {
|
|||||||
|
|
||||||
case CAN_UPDATE:
|
case CAN_UPDATE:
|
||||||
selection = selection.add(queryCanUpdate());
|
selection = selection.add(queryCanUpdate());
|
||||||
|
includeSwap = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case REPO:
|
||||||
|
selection = selection.add(queryRepo(Long.parseLong(uri.getLastPathSegment())));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case INSTALLED:
|
case INSTALLED:
|
||||||
selection = selection.add(queryInstalled());
|
selection = selection.add(queryInstalled());
|
||||||
|
includeSwap = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SEARCH:
|
case SEARCH:
|
||||||
selection = selection.add(querySearch(uri.getLastPathSegment()));
|
selection = selection.add(querySearch(uri.getLastPathSegment()));
|
||||||
|
includeSwap = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NO_APKS:
|
case NO_APKS:
|
||||||
@ -633,16 +683,19 @@ public class AppProvider extends FDroidProvider {
|
|||||||
|
|
||||||
case CATEGORY:
|
case CATEGORY:
|
||||||
selection = selection.add(queryCategory(uri.getLastPathSegment()));
|
selection = selection.add(queryCategory(uri.getLastPathSegment()));
|
||||||
|
includeSwap = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RECENTLY_UPDATED:
|
case RECENTLY_UPDATED:
|
||||||
sortOrder = " fdroid_app.lastUpdated DESC";
|
sortOrder = " fdroid_app.lastUpdated DESC";
|
||||||
selection = selection.add(queryRecentlyUpdated());
|
selection = selection.add(queryRecentlyUpdated());
|
||||||
|
includeSwap = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NEWLY_ADDED:
|
case NEWLY_ADDED:
|
||||||
sortOrder = " fdroid_app.added DESC";
|
sortOrder = " fdroid_app.added DESC";
|
||||||
selection = selection.add(queryNewlyAdded());
|
selection = selection.add(queryNewlyAdded());
|
||||||
|
includeSwap = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -650,6 +703,10 @@ public class AppProvider extends FDroidProvider {
|
|||||||
throw new UnsupportedOperationException("Invalid URI for app content provider: " + uri);
|
throw new UnsupportedOperationException("Invalid URI for app content provider: " + uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!includeSwap) {
|
||||||
|
selection = selection.add(queryExcludeSwap());
|
||||||
|
}
|
||||||
|
|
||||||
if (AppProvider.DataColumns.NAME.equals(sortOrder)) {
|
if (AppProvider.DataColumns.NAME.equals(sortOrder)) {
|
||||||
sortOrder = " lower( fdroid_app." + sortOrder + " ) ";
|
sortOrder = " lower( fdroid_app." + sortOrder + " ) ";
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,8 @@ public class DBHelper extends SQLiteOpenHelper {
|
|||||||
+ "priority integer not null, pubkey text, fingerprint text, "
|
+ "priority integer not null, pubkey text, fingerprint text, "
|
||||||
+ "maxage integer not null default 0, "
|
+ "maxage integer not null default 0, "
|
||||||
+ "version integer not null default 0, "
|
+ "version integer not null default 0, "
|
||||||
+ "lastetag text, lastUpdated string);";
|
+ "lastetag text, lastUpdated string,"
|
||||||
|
+ "isSwap integer boolean default 0);";
|
||||||
|
|
||||||
private static final String CREATE_TABLE_APK =
|
private static final String CREATE_TABLE_APK =
|
||||||
"CREATE TABLE " + TABLE_APK + " ( "
|
"CREATE TABLE " + TABLE_APK + " ( "
|
||||||
@ -98,7 +99,7 @@ public class DBHelper extends SQLiteOpenHelper {
|
|||||||
+ InstalledAppProvider.DataColumns.APPLICATION_LABEL + " TEXT NOT NULL "
|
+ InstalledAppProvider.DataColumns.APPLICATION_LABEL + " TEXT NOT NULL "
|
||||||
+ " );";
|
+ " );";
|
||||||
|
|
||||||
private static final int DB_VERSION = 46;
|
private static final int DB_VERSION = 47;
|
||||||
|
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@ -273,6 +274,7 @@ public class DBHelper extends SQLiteOpenHelper {
|
|||||||
populateRepoNames(db, oldVersion);
|
populateRepoNames(db, oldVersion);
|
||||||
if (oldVersion < 43) createInstalledApp(db);
|
if (oldVersion < 43) createInstalledApp(db);
|
||||||
addAppLabelToInstalledCache(db, oldVersion);
|
addAppLabelToInstalledCache(db, oldVersion);
|
||||||
|
addIsSwapToRepo(db, oldVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -400,6 +402,13 @@ public class DBHelper extends SQLiteOpenHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addIsSwapToRepo(SQLiteDatabase db, int oldVersion) {
|
||||||
|
if (oldVersion < 47 && !columnExists(db, TABLE_REPO, "isSwap")) {
|
||||||
|
Log.i(TAG, "Adding isSwap field to " + TABLE_REPO + " table in db.");
|
||||||
|
db.execSQL("alter table " + TABLE_REPO + " add column isSwap boolean default 0;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void resetTransient(SQLiteDatabase db, int oldVersion) {
|
private void resetTransient(SQLiteDatabase db, int oldVersion) {
|
||||||
// Before version 42, only transient info was stored in here. As of some time
|
// Before version 42, only transient info was stored in here. As of some time
|
||||||
// just before 42 (F-Droid 0.60ish) it now has "ignore this version" info which
|
// just before 42 (F-Droid 0.60ish) it now has "ignore this version" info which
|
||||||
|
@ -28,6 +28,10 @@ abstract class QueryBuilder {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String groupBy() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
protected void appendField(String field) {
|
protected void appendField(String field) {
|
||||||
appendField(field, null, null);
|
appendField(field, null, null);
|
||||||
}
|
}
|
||||||
@ -85,6 +89,10 @@ abstract class QueryBuilder {
|
|||||||
.append(')');
|
.append(')');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String distinctSql() {
|
||||||
|
return isDistinct() ? " DISTINCT " : "";
|
||||||
|
}
|
||||||
|
|
||||||
private String fieldsSql() {
|
private String fieldsSql() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (int i = 0; i < fields.size(); i ++) {
|
for (int i = 0; i < fields.size(); i ++) {
|
||||||
@ -104,12 +112,15 @@ abstract class QueryBuilder {
|
|||||||
return orderBy != null ? " ORDER BY " + orderBy : "";
|
return orderBy != null ? " ORDER BY " + orderBy : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String groupBySql() {
|
||||||
|
return groupBy() != null ? " GROUP BY " + groupBy() : "";
|
||||||
|
}
|
||||||
|
|
||||||
private String tablesSql() {
|
private String tablesSql() {
|
||||||
return tables.toString();
|
return tables.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
String distinct = isDistinct() ? " DISTINCT " : "";
|
return "SELECT " + distinctSql() + fieldsSql() + " FROM " + tablesSql() + whereSql() + groupBySql() + orderBySql();
|
||||||
return "SELECT " + distinct + fieldsSql() + " FROM " + tablesSql() + whereSql() + orderBySql();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ public class Repo extends ValueObject {
|
|||||||
public int maxage; // maximum age of index that will be accepted - 0 for any
|
public int maxage; // maximum age of index that will be accepted - 0 for any
|
||||||
public String lastetag; // last etag we updated from, null forces update
|
public String lastetag; // last etag we updated from, null forces update
|
||||||
public Date lastUpdated;
|
public Date lastUpdated;
|
||||||
|
public boolean isSwap;
|
||||||
|
|
||||||
public Repo() {
|
public Repo() {
|
||||||
|
|
||||||
@ -65,6 +66,8 @@ public class Repo extends ValueObject {
|
|||||||
pubkey = cursor.getString(i);
|
pubkey = cursor.getString(i);
|
||||||
} else if (column.equals(RepoProvider.DataColumns.PRIORITY)) {
|
} else if (column.equals(RepoProvider.DataColumns.PRIORITY)) {
|
||||||
priority = cursor.getInt(i);
|
priority = cursor.getInt(i);
|
||||||
|
} else if (column.equals(RepoProvider.DataColumns.IS_SWAP)) {
|
||||||
|
isSwap = cursor.getInt(i) == 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,7 +143,7 @@ public class Repo extends ValueObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (values.containsKey(RepoProvider.DataColumns.IN_USE)) {
|
if (values.containsKey(RepoProvider.DataColumns.IN_USE)) {
|
||||||
inuse = toInt(values.getAsInteger(RepoProvider.DataColumns.FINGERPRINT)) == 1;
|
inuse = toInt(values.getAsInteger(RepoProvider.DataColumns.IN_USE)) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values.containsKey(RepoProvider.DataColumns.LAST_UPDATED)) {
|
if (values.containsKey(RepoProvider.DataColumns.LAST_UPDATED)) {
|
||||||
@ -173,5 +176,9 @@ public class Repo extends ValueObject {
|
|||||||
if (values.containsKey(RepoProvider.DataColumns.PRIORITY)) {
|
if (values.containsKey(RepoProvider.DataColumns.PRIORITY)) {
|
||||||
priority = toInt(values.getAsInteger(RepoProvider.DataColumns.PRIORITY));
|
priority = toInt(values.getAsInteger(RepoProvider.DataColumns.PRIORITY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (values.containsKey(RepoProvider.DataColumns.IS_SWAP)) {
|
||||||
|
isSwap= toInt(values.getAsInteger(RepoProvider.DataColumns.IS_SWAP)) == 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,19 +218,24 @@ public class RepoProvider extends FDroidProvider {
|
|||||||
public static String LAST_ETAG = "lastetag";
|
public static String LAST_ETAG = "lastetag";
|
||||||
public static String LAST_UPDATED = "lastUpdated";
|
public static String LAST_UPDATED = "lastUpdated";
|
||||||
public static String VERSION = "version";
|
public static String VERSION = "version";
|
||||||
|
public static String IS_SWAP = "isSwap";
|
||||||
|
|
||||||
public static String[] ALL = {
|
public static String[] ALL = {
|
||||||
_ID, ADDRESS, NAME, DESCRIPTION, IN_USE, PRIORITY, PUBLIC_KEY,
|
_ID, ADDRESS, NAME, DESCRIPTION, IN_USE, PRIORITY, PUBLIC_KEY,
|
||||||
FINGERPRINT, MAX_AGE, LAST_UPDATED, LAST_ETAG, VERSION
|
FINGERPRINT, MAX_AGE, LAST_UPDATED, LAST_ETAG, VERSION, IS_SWAP
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String PROVIDER_NAME = "RepoProvider";
|
private static final String PROVIDER_NAME = "RepoProvider";
|
||||||
|
private static final String PATH_ALL_EXCEPT_SWAP = "allExceptSwap";
|
||||||
|
|
||||||
|
private static final int CODE_ALL_EXCEPT_SWAP = CODE_SINGLE + 1;
|
||||||
|
|
||||||
private static final UriMatcher matcher = new UriMatcher(-1);
|
private static final UriMatcher matcher = new UriMatcher(-1);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
matcher.addURI(AUTHORITY + "." + PROVIDER_NAME, null, CODE_LIST);
|
matcher.addURI(AUTHORITY + "." + PROVIDER_NAME, null, CODE_LIST);
|
||||||
|
matcher.addURI(AUTHORITY + "." + PROVIDER_NAME, PATH_ALL_EXCEPT_SWAP, CODE_ALL_EXCEPT_SWAP);
|
||||||
matcher.addURI(AUTHORITY + "." + PROVIDER_NAME, "#", CODE_SINGLE);
|
matcher.addURI(AUTHORITY + "." + PROVIDER_NAME, "#", CODE_SINGLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,6 +247,12 @@ public class RepoProvider extends FDroidProvider {
|
|||||||
return ContentUris.withAppendedId(getContentUri(), repoId);
|
return ContentUris.withAppendedId(getContentUri(), repoId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Uri allExceptSwapUri() {
|
||||||
|
return getContentUri().buildUpon()
|
||||||
|
.appendPath(PATH_ALL_EXCEPT_SWAP)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getTableName() {
|
protected String getTableName() {
|
||||||
return DBHelper.TABLE_REPO;
|
return DBHelper.TABLE_REPO;
|
||||||
@ -260,11 +271,13 @@ public class RepoProvider extends FDroidProvider {
|
|||||||
@Override
|
@Override
|
||||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(sortOrder)) {
|
||||||
|
sortOrder = "_ID ASC";
|
||||||
|
}
|
||||||
|
|
||||||
switch (matcher.match(uri)) {
|
switch (matcher.match(uri)) {
|
||||||
case CODE_LIST:
|
case CODE_LIST:
|
||||||
if (TextUtils.isEmpty(sortOrder)) {
|
// Do nothing (don't restrict query)
|
||||||
sortOrder = "_ID ASC";
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CODE_SINGLE:
|
case CODE_SINGLE:
|
||||||
@ -272,6 +285,10 @@ public class RepoProvider extends FDroidProvider {
|
|||||||
DataColumns._ID + " = " + uri.getLastPathSegment();
|
DataColumns._ID + " = " + uri.getLastPathSegment();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CODE_ALL_EXCEPT_SWAP:
|
||||||
|
selection = DataColumns.IS_SWAP + " = 0 OR " + DataColumns.IS_SWAP + " IS NULL ";
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Log.e(TAG, "Invalid URI for repo content provider: " + uri);
|
Log.e(TAG, "Invalid URI for repo content provider: " + uri);
|
||||||
throw new UnsupportedOperationException("Invalid URI for repo content provider: " + uri);
|
throw new UnsupportedOperationException("Invalid URI for repo content provider: " + uri);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.fdroid.fdroid.localrepo;
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
@ -15,27 +14,23 @@ import android.graphics.Canvas;
|
|||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.Hasher;
|
import org.fdroid.fdroid.Hasher;
|
||||||
import org.fdroid.fdroid.Preferences;
|
import org.fdroid.fdroid.Preferences;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.data.App;
|
import org.fdroid.fdroid.data.App;
|
||||||
import org.fdroid.fdroid.data.SanitizedFile;
|
import org.fdroid.fdroid.data.SanitizedFile;
|
||||||
import org.w3c.dom.Document;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
import org.w3c.dom.Element;
|
import org.xmlpull.v1.XmlPullParserFactory;
|
||||||
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import javax.xml.transform.Transformer;
|
|
||||||
import javax.xml.transform.TransformerException;
|
|
||||||
import javax.xml.transform.TransformerFactory;
|
|
||||||
import javax.xml.transform.dom.DOMSource;
|
|
||||||
import javax.xml.transform.stream.StreamResult;
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
@ -43,13 +38,17 @@ import java.io.BufferedWriter;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.Writer;
|
||||||
import java.security.cert.CertificateEncodingException;
|
import java.security.cert.CertificateEncodingException;
|
||||||
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -67,30 +66,30 @@ public class LocalRepoManager {
|
|||||||
private final Context context;
|
private final Context context;
|
||||||
private final PackageManager pm;
|
private final PackageManager pm;
|
||||||
private final AssetManager assetManager;
|
private final AssetManager assetManager;
|
||||||
private final SharedPreferences prefs;
|
|
||||||
private final String fdroidPackageName;
|
private final String fdroidPackageName;
|
||||||
|
|
||||||
private String ipAddressString = "UNSET";
|
|
||||||
private String uriString = "UNSET";
|
|
||||||
|
|
||||||
private static String[] WEB_ROOT_ASSET_FILES = {
|
private static String[] WEB_ROOT_ASSET_FILES = {
|
||||||
"swap-icon.png",
|
"swap-icon.png",
|
||||||
"swap-tick-done.png",
|
"swap-tick-done.png",
|
||||||
"swap-tick-not-done.png"
|
"swap-tick-not-done.png"
|
||||||
};
|
};
|
||||||
|
|
||||||
private Map<String, App> apps = new HashMap<String, App>();
|
private Map<String, App> apps = new HashMap<>();
|
||||||
|
|
||||||
public final SanitizedFile xmlIndex;
|
public final SanitizedFile xmlIndex;
|
||||||
private SanitizedFile xmlIndexJar = null;
|
private SanitizedFile xmlIndexJar = null;
|
||||||
private SanitizedFile xmlIndexJarUnsigned = null;
|
private SanitizedFile xmlIndexJarUnsigned = null;
|
||||||
public final SanitizedFile webRoot;
|
public final SanitizedFile webRoot;
|
||||||
public final SanitizedFile fdroidDir;
|
public final SanitizedFile fdroidDir;
|
||||||
|
public final SanitizedFile fdroidDirCaps;
|
||||||
public final SanitizedFile repoDir;
|
public final SanitizedFile repoDir;
|
||||||
|
public final SanitizedFile repoDirCaps;
|
||||||
public final SanitizedFile iconsDir;
|
public final SanitizedFile iconsDir;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private static LocalRepoManager localRepoManager;
|
private static LocalRepoManager localRepoManager;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public static LocalRepoManager get(Context context) {
|
public static LocalRepoManager get(Context context) {
|
||||||
if (localRepoManager == null)
|
if (localRepoManager == null)
|
||||||
localRepoManager = new LocalRepoManager(context);
|
localRepoManager = new LocalRepoManager(context);
|
||||||
@ -101,13 +100,14 @@ public class LocalRepoManager {
|
|||||||
context = c.getApplicationContext();
|
context = c.getApplicationContext();
|
||||||
pm = c.getPackageManager();
|
pm = c.getPackageManager();
|
||||||
assetManager = c.getAssets();
|
assetManager = c.getAssets();
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(c);
|
|
||||||
fdroidPackageName = c.getPackageName();
|
fdroidPackageName = c.getPackageName();
|
||||||
|
|
||||||
webRoot = SanitizedFile.knownSanitized(c.getFilesDir());
|
webRoot = SanitizedFile.knownSanitized(c.getFilesDir());
|
||||||
/* /fdroid/repo is the standard path for user repos */
|
/* /fdroid/repo is the standard path for user repos */
|
||||||
fdroidDir = new SanitizedFile(webRoot, "fdroid");
|
fdroidDir = new SanitizedFile(webRoot, "fdroid");
|
||||||
|
fdroidDirCaps = new SanitizedFile(webRoot, "FDROID");
|
||||||
repoDir = new SanitizedFile(fdroidDir, "repo");
|
repoDir = new SanitizedFile(fdroidDir, "repo");
|
||||||
|
repoDirCaps = new SanitizedFile(fdroidDirCaps, "REPO");
|
||||||
iconsDir = new SanitizedFile(repoDir, "icons");
|
iconsDir = new SanitizedFile(repoDir, "icons");
|
||||||
xmlIndex = new SanitizedFile(repoDir, "index.xml");
|
xmlIndex = new SanitizedFile(repoDir, "index.xml");
|
||||||
xmlIndexJar = new SanitizedFile(repoDir, "index.jar");
|
xmlIndexJar = new SanitizedFile(repoDir, "index.jar");
|
||||||
@ -126,10 +126,6 @@ public class LocalRepoManager {
|
|||||||
Log.e(TAG, "Unable to create icons folder: " + iconsDir);
|
Log.e(TAG, "Unable to create icons folder: " + iconsDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUriString(String uriString) {
|
|
||||||
this.uriString = uriString;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String writeFdroidApkToWebroot() {
|
private String writeFdroidApkToWebroot() {
|
||||||
ApplicationInfo appInfo;
|
ApplicationInfo appInfo;
|
||||||
String fdroidClientURL = "https://f-droid.org/FDroid.apk";
|
String fdroidClientURL = "https://f-droid.org/FDroid.apk";
|
||||||
@ -138,7 +134,7 @@ public class LocalRepoManager {
|
|||||||
appInfo = pm.getApplicationInfo(fdroidPackageName, PackageManager.GET_META_DATA);
|
appInfo = pm.getApplicationInfo(fdroidPackageName, PackageManager.GET_META_DATA);
|
||||||
SanitizedFile apkFile = SanitizedFile.knownSanitized(appInfo.publicSourceDir);
|
SanitizedFile apkFile = SanitizedFile.knownSanitized(appInfo.publicSourceDir);
|
||||||
SanitizedFile fdroidApkLink = new SanitizedFile(webRoot, "fdroid.client.apk");
|
SanitizedFile fdroidApkLink = new SanitizedFile(webRoot, "fdroid.client.apk");
|
||||||
fdroidApkLink.delete();
|
attemptToDelete(fdroidApkLink);
|
||||||
if (Utils.symlinkOrCopyFile(apkFile, fdroidApkLink))
|
if (Utils.symlinkOrCopyFile(apkFile, fdroidApkLink))
|
||||||
fdroidClientURL = "/" + fdroidApkLink.getName();
|
fdroidClientURL = "/" + fdroidApkLink.getName();
|
||||||
} catch (NameNotFoundException e) {
|
} catch (NameNotFoundException e) {
|
||||||
@ -171,18 +167,15 @@ public class LocalRepoManager {
|
|||||||
|
|
||||||
// make symlinks/copies in each subdir of the repo to make sure that
|
// make symlinks/copies in each subdir of the repo to make sure that
|
||||||
// the user will always find the bootstrap page.
|
// the user will always find the bootstrap page.
|
||||||
symlinkIndexPageElsewhere("../", fdroidDir);
|
symlinkEntireWebRootElsewhere("../", fdroidDir);
|
||||||
symlinkIndexPageElsewhere("../../", repoDir);
|
symlinkEntireWebRootElsewhere("../../", repoDir);
|
||||||
|
|
||||||
// add in /FDROID/REPO to support bad QR Scanner apps
|
// add in /FDROID/REPO to support bad QR Scanner apps
|
||||||
File fdroidCAPS = new File(fdroidDir.getParentFile(), "FDROID");
|
attemptToMkdir(fdroidDirCaps);
|
||||||
fdroidCAPS.mkdir();
|
attemptToMkdir(repoDirCaps);
|
||||||
|
|
||||||
File repoCAPS = new File(fdroidCAPS, "REPO");
|
symlinkEntireWebRootElsewhere("../", fdroidDirCaps);
|
||||||
repoCAPS.mkdir();
|
symlinkEntireWebRootElsewhere("../../", repoDirCaps);
|
||||||
|
|
||||||
symlinkIndexPageElsewhere("../", fdroidCAPS);
|
|
||||||
symlinkIndexPageElsewhere("../../", repoCAPS);
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Error writing local repo index: " + e.getMessage());
|
Log.e(TAG, "Error writing local repo index: " + e.getMessage());
|
||||||
@ -190,16 +183,37 @@ public class LocalRepoManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void symlinkIndexPageElsewhere(String symlinkPrefix, File directory) {
|
private static void attemptToMkdir(@NonNull File dir) throws IOException {
|
||||||
SanitizedFile index = new SanitizedFile(directory, "index.html");
|
if (dir.exists()) {
|
||||||
index.delete();
|
if (dir.isDirectory()) {
|
||||||
Utils.symlinkOrCopyFile(new SanitizedFile(new File(symlinkPrefix), "index.html"), index);
|
return;
|
||||||
|
} else {
|
||||||
for(String fileName : WEB_ROOT_ASSET_FILES) {
|
throw new IOException("Can't make directory " + dir + " - it is already a file.");
|
||||||
SanitizedFile file = new SanitizedFile(directory, fileName);
|
}
|
||||||
file.delete();
|
|
||||||
Utils.symlinkOrCopyFile(new SanitizedFile(new File(symlinkPrefix), fileName), file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!dir.mkdir()) {
|
||||||
|
throw new IOException("An error occurred trying to create directory " + dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void attemptToDelete(@NonNull File file) {
|
||||||
|
if (!file.delete()) {
|
||||||
|
Log.e(TAG, "Could not delete \"" + file.getAbsolutePath() + "\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void symlinkEntireWebRootElsewhere(String symlinkPrefix, File directory) {
|
||||||
|
symlinkFileElsewhere("index.html", symlinkPrefix, directory);
|
||||||
|
for(String fileName : WEB_ROOT_ASSET_FILES) {
|
||||||
|
symlinkFileElsewhere(fileName, symlinkPrefix, directory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void symlinkFileElsewhere(String fileName, String symlinkPrefix, File directory) {
|
||||||
|
SanitizedFile index = new SanitizedFile(directory, fileName);
|
||||||
|
attemptToDelete(index);
|
||||||
|
Utils.symlinkOrCopyFile(new SanitizedFile(new File(directory, symlinkPrefix), fileName), index);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteContents(File path) {
|
private void deleteContents(File path) {
|
||||||
@ -208,7 +222,7 @@ public class LocalRepoManager {
|
|||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
deleteContents(file);
|
deleteContents(file);
|
||||||
} else {
|
} else {
|
||||||
file.delete();
|
attemptToDelete(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,7 +233,7 @@ public class LocalRepoManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void copyApksToRepo() {
|
public void copyApksToRepo() {
|
||||||
copyApksToRepo(new ArrayList<String>(apps.keySet()));
|
copyApksToRepo(new ArrayList<>(apps.keySet()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyApksToRepo(List<String> appsToCopy) {
|
public void copyApksToRepo(List<String> appsToCopy) {
|
||||||
@ -236,11 +250,6 @@ public class LocalRepoManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ScanListener {
|
|
||||||
public void processedApp(String packageName, int index, int total);
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(9)
|
|
||||||
public void addApp(Context context, String packageName) {
|
public void addApp(Context context, String packageName) {
|
||||||
App app;
|
App app;
|
||||||
try {
|
try {
|
||||||
@ -249,15 +258,7 @@ public class LocalRepoManager {
|
|||||||
return;
|
return;
|
||||||
PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_META_DATA);
|
PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_META_DATA);
|
||||||
app.icon = getIconFile(packageName, packageInfo.versionCode).getName();
|
app.icon = getIconFile(packageName, packageInfo.versionCode).getName();
|
||||||
} catch (NameNotFoundException e) {
|
} catch (NameNotFoundException | CertificateEncodingException | IOException e) {
|
||||||
Log.e(TAG, "Error adding app to local repo: " + e.getMessage());
|
|
||||||
Log.e(TAG, Log.getStackTraceString(e));
|
|
||||||
return;
|
|
||||||
} catch (CertificateEncodingException e) {
|
|
||||||
Log.e(TAG, "Error adding app to local repo: " + e.getMessage());
|
|
||||||
Log.e(TAG, Log.getStackTraceString(e));
|
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Error adding app to local repo: " + e.getMessage());
|
Log.e(TAG, "Error adding app to local repo: " + e.getMessage());
|
||||||
Log.e(TAG, Log.getStackTraceString(e));
|
Log.e(TAG, Log.getStackTraceString(e));
|
||||||
return;
|
return;
|
||||||
@ -266,12 +267,8 @@ public class LocalRepoManager {
|
|||||||
apps.put(packageName, app);
|
apps.put(packageName, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeApp(String packageName) {
|
|
||||||
apps.remove(packageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getApps() {
|
public List<String> getApps() {
|
||||||
return new ArrayList<String>(apps.keySet());
|
return new ArrayList<>(apps.keySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyIconsToRepo() {
|
public void copyIconsToRepo() {
|
||||||
@ -290,8 +287,6 @@ public class LocalRepoManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the icon from an APK and writes it to the repo as a PNG
|
* Extracts the icon from an APK and writes it to the repo as a PNG
|
||||||
*
|
|
||||||
* @return path to the PNG file
|
|
||||||
*/
|
*/
|
||||||
public void copyIconToRepo(Drawable drawable, String packageName, int versionCode) {
|
public void copyIconToRepo(Drawable drawable, String packageName, int versionCode) {
|
||||||
Bitmap bitmap;
|
Bitmap bitmap;
|
||||||
@ -319,141 +314,139 @@ public class LocalRepoManager {
|
|||||||
return new File(iconsDir, packageName + "_" + versionCode + ".png");
|
return new File(iconsDir, packageName + "_" + versionCode + ".png");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this needs to be ported to < android-8
|
/**
|
||||||
@TargetApi(8)
|
* Helper class to aid in constructing index.xml file.
|
||||||
private void writeIndexXML() throws TransformerException, ParserConfigurationException, LocalRepoKeyStore.InitException {
|
* It uses the PullParser API, because the DOM api is only able to be serialized from
|
||||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
* API 8 upwards, but we support 7 at time of implementation.
|
||||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
*/
|
||||||
|
public static class IndexXmlBuilder {
|
||||||
|
|
||||||
Document doc = builder.newDocument();
|
@NonNull
|
||||||
Element rootElement = doc.createElement("fdroid");
|
private final XmlSerializer serializer;
|
||||||
doc.appendChild(rootElement);
|
|
||||||
|
|
||||||
// max age is an EditTextPreference, which is always a String
|
@NonNull
|
||||||
int repoMaxAge = Float.valueOf(prefs.getString("max_repo_age_days",
|
private final Map<String, App> apps;
|
||||||
DEFAULT_REPO_MAX_AGE_DAYS)).intValue();
|
|
||||||
|
|
||||||
String repoName = Preferences.get().getLocalRepoName();
|
@NonNull
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
Element repo = doc.createElement("repo");
|
@NonNull
|
||||||
repo.setAttribute("icon", "blah.png");
|
private final DateFormat dateToStr = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||||
repo.setAttribute("maxage", String.valueOf(repoMaxAge));
|
|
||||||
repo.setAttribute("name", repoName + " on " + ipAddressString);
|
|
||||||
repo.setAttribute("pubkey", Hasher.hex(LocalRepoKeyStore.get(context).getCertificate()));
|
|
||||||
long timestamp = System.currentTimeMillis() / 1000L;
|
|
||||||
repo.setAttribute("timestamp", String.valueOf(timestamp));
|
|
||||||
rootElement.appendChild(repo);
|
|
||||||
|
|
||||||
Element repoDesc = doc.createElement("description");
|
public IndexXmlBuilder(@NonNull Context context, @NonNull Map<String, App> apps) throws XmlPullParserException, IOException {
|
||||||
repoDesc.setTextContent("A local FDroid repo generated from apps installed on " + repoName);
|
this.context = context;
|
||||||
repo.appendChild(repoDesc);
|
this.apps = apps;
|
||||||
|
serializer = XmlPullParserFactory.newInstance().newSerializer();
|
||||||
|
}
|
||||||
|
|
||||||
SimpleDateFormat dateToStr = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
public void build(Writer output) throws IOException, LocalRepoKeyStore.InitException {
|
||||||
for (Entry<String, App> entry : apps.entrySet()) {
|
serializer.setOutput(output);
|
||||||
App app = entry.getValue();
|
serializer.startDocument(null, null);
|
||||||
Element application = doc.createElement("application");
|
tagFdroid();
|
||||||
application.setAttribute("id", app.id);
|
serializer.endDocument();
|
||||||
rootElement.appendChild(application);
|
}
|
||||||
|
|
||||||
Element appID = doc.createElement("id");
|
private void tagFdroid() throws IOException, LocalRepoKeyStore.InitException {
|
||||||
appID.setTextContent(app.id);
|
serializer.startTag("", "fdroid");
|
||||||
application.appendChild(appID);
|
tagRepo();
|
||||||
|
for (Entry<String, App> entry : apps.entrySet()) {
|
||||||
|
tagApplication(entry.getValue());
|
||||||
|
}
|
||||||
|
serializer.endTag("", "fdroid");
|
||||||
|
}
|
||||||
|
|
||||||
Element added = doc.createElement("added");
|
private void tagRepo() throws IOException, LocalRepoKeyStore.InitException {
|
||||||
added.setTextContent(dateToStr.format(app.added));
|
|
||||||
application.appendChild(added);
|
|
||||||
|
|
||||||
Element lastUpdated = doc.createElement("lastupdated");
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
lastUpdated.setTextContent(dateToStr.format(app.lastUpdated));
|
|
||||||
application.appendChild(lastUpdated);
|
|
||||||
|
|
||||||
Element name = doc.createElement("name");
|
// max age is an EditTextPreference, which is always a String
|
||||||
name.setTextContent(app.name);
|
int repoMaxAge = Float.valueOf(prefs.getString("max_repo_age_days", DEFAULT_REPO_MAX_AGE_DAYS)).intValue();
|
||||||
application.appendChild(name);
|
|
||||||
|
|
||||||
Element summary = doc.createElement("summary");
|
serializer.startTag("", "repo");
|
||||||
summary.setTextContent(app.summary);
|
|
||||||
application.appendChild(summary);
|
|
||||||
|
|
||||||
Element desc = doc.createElement("desc");
|
serializer.attribute("", "icon", "blah.png");
|
||||||
desc.setTextContent(app.description);
|
serializer.attribute("", "maxage", String.valueOf(repoMaxAge));
|
||||||
application.appendChild(desc);
|
serializer.attribute("", "name", Preferences.get().getLocalRepoName() + " on " + FDroidApp.ipAddressString);
|
||||||
|
serializer.attribute("", "pubkey", Hasher.hex(LocalRepoKeyStore.get(context).getCertificate()));
|
||||||
|
long timestamp = System.currentTimeMillis() / 1000L;
|
||||||
|
serializer.attribute("", "timestamp", String.valueOf(timestamp));
|
||||||
|
|
||||||
Element icon = doc.createElement("icon");
|
tag("description", "A local FDroid repo generated from apps installed on " + Preferences.get().getLocalRepoName());
|
||||||
icon.setTextContent(app.icon);
|
|
||||||
application.appendChild(icon);
|
|
||||||
|
|
||||||
Element license = doc.createElement("license");
|
serializer.endTag("", "repo");
|
||||||
license.setTextContent("Unknown");
|
|
||||||
application.appendChild(license);
|
|
||||||
|
|
||||||
Element categories = doc.createElement("categories");
|
}
|
||||||
categories.setTextContent("LocalRepo," + repoName);
|
|
||||||
application.appendChild(categories);
|
|
||||||
|
|
||||||
Element category = doc.createElement("category");
|
/**
|
||||||
category.setTextContent("LocalRepo," + repoName);
|
* Helper function to start a tag called "name", fill it with text "text", and then
|
||||||
application.appendChild(category);
|
* end the tag in a more concise manner.
|
||||||
|
*/
|
||||||
|
private void tag(String name, String text) throws IOException {
|
||||||
|
serializer.startTag("", name).text(text).endTag("", name);
|
||||||
|
}
|
||||||
|
|
||||||
Element web = doc.createElement("web");
|
/**
|
||||||
application.appendChild(web);
|
* Alias for {@link org.fdroid.fdroid.localrepo.LocalRepoManager.IndexXmlBuilder#tag(String, String)}
|
||||||
|
* That accepts a number instead of string.
|
||||||
|
* @see IndexXmlBuilder#tag(String, String)
|
||||||
|
*/
|
||||||
|
private void tag(String name, long number) throws IOException {
|
||||||
|
tag(name, String.valueOf(number));
|
||||||
|
}
|
||||||
|
|
||||||
Element source = doc.createElement("source");
|
/**
|
||||||
application.appendChild(source);
|
* Alias for {@link org.fdroid.fdroid.localrepo.LocalRepoManager.IndexXmlBuilder#tag(String, String)}
|
||||||
|
* that accepts a date instead of a string.
|
||||||
|
* @see IndexXmlBuilder#tag(String, String)
|
||||||
|
*/
|
||||||
|
private void tag(String name, Date date) throws IOException {
|
||||||
|
tag(name, dateToStr.format(date));
|
||||||
|
}
|
||||||
|
|
||||||
Element tracker = doc.createElement("tracker");
|
private void tagApplication(App app) throws IOException {
|
||||||
application.appendChild(tracker);
|
serializer.startTag("", "application");
|
||||||
|
serializer.attribute("", "id", app.id);
|
||||||
|
|
||||||
Element marketVersion = doc.createElement("marketversion");
|
tag("id", app.id);
|
||||||
marketVersion.setTextContent(app.installedApk.version);
|
tag("added", app.added);
|
||||||
application.appendChild(marketVersion);
|
tag("lastupdated", app.lastUpdated);
|
||||||
|
tag("name", app.name);
|
||||||
|
tag("summary", app.summary);
|
||||||
|
tag("icon", app.icon);
|
||||||
|
tag("desc", app.description);
|
||||||
|
tag("license", "Unknown");
|
||||||
|
tag("categories", "LocalRepo," + Preferences.get().getLocalRepoName());
|
||||||
|
tag("category", "LocalRepo," + Preferences.get().getLocalRepoName());
|
||||||
|
tag("web", "web");
|
||||||
|
tag("source", "source");
|
||||||
|
tag("tracker", "tracker");
|
||||||
|
tag("marketversion", app.installedApk.version);
|
||||||
|
tag("marketvercode", app.installedApk.vercode);
|
||||||
|
|
||||||
Element marketVerCode = doc.createElement("marketvercode");
|
tagPackage(app);
|
||||||
marketVerCode.setTextContent(String.valueOf(app.installedApk.vercode));
|
|
||||||
application.appendChild(marketVerCode);
|
|
||||||
|
|
||||||
Element packageNode = doc.createElement("package");
|
serializer.endTag("", "application");
|
||||||
|
}
|
||||||
|
|
||||||
Element version = doc.createElement("version");
|
private void tagPackage(App app) throws IOException {
|
||||||
version.setTextContent(app.installedApk.version);
|
serializer.startTag("", "package");
|
||||||
packageNode.appendChild(version);
|
|
||||||
|
|
||||||
// F-Droid unfortunately calls versionCode versioncode...
|
tag("version", app.installedApk.version);
|
||||||
Element versioncode = doc.createElement("versioncode");
|
tag("versioncode", app.installedApk.vercode);
|
||||||
versioncode.setTextContent(String.valueOf(app.installedApk.vercode));
|
tag("apkname", app.installedApk.apkName);
|
||||||
packageNode.appendChild(versioncode);
|
tagHash(app);
|
||||||
|
tag("sig", app.installedApk.sig.toLowerCase(Locale.US));
|
||||||
|
tag("size", app.installedApk.installedFile.length());
|
||||||
|
tag("sdkver", app.installedApk.minSdkVersion);
|
||||||
|
tag("added", app.installedApk.added);
|
||||||
|
tagFeatures(app);
|
||||||
|
tagPermissions(app);
|
||||||
|
|
||||||
Element apkname = doc.createElement("apkname");
|
serializer.endTag("", "package");
|
||||||
apkname.setTextContent(app.installedApk.apkName);
|
}
|
||||||
packageNode.appendChild(apkname);
|
|
||||||
|
|
||||||
Element hash = doc.createElement("hash");
|
private void tagPermissions(App app) throws IOException {
|
||||||
hash.setAttribute("type", app.installedApk.hashType);
|
serializer.startTag("", "permissions");
|
||||||
hash.setTextContent(app.installedApk.hash.toLowerCase(Locale.US));
|
|
||||||
packageNode.appendChild(hash);
|
|
||||||
|
|
||||||
Element sig = doc.createElement("sig");
|
|
||||||
sig.setTextContent(app.installedApk.sig.toLowerCase(Locale.US));
|
|
||||||
packageNode.appendChild(sig);
|
|
||||||
|
|
||||||
Element size = doc.createElement("size");
|
|
||||||
size.setTextContent(String.valueOf(app.installedApk.installedFile.length()));
|
|
||||||
packageNode.appendChild(size);
|
|
||||||
|
|
||||||
Element sdkver = doc.createElement("sdkver");
|
|
||||||
sdkver.setTextContent(String.valueOf(app.installedApk.minSdkVersion));
|
|
||||||
packageNode.appendChild(sdkver);
|
|
||||||
|
|
||||||
Element apkAdded = doc.createElement("added");
|
|
||||||
apkAdded.setTextContent(dateToStr.format(app.installedApk.added));
|
|
||||||
packageNode.appendChild(apkAdded);
|
|
||||||
|
|
||||||
Element features = doc.createElement("features");
|
|
||||||
if (app.installedApk.features != null)
|
|
||||||
features.setTextContent(Utils.CommaSeparatedList.str(app.installedApk.features));
|
|
||||||
packageNode.appendChild(features);
|
|
||||||
|
|
||||||
Element permissions = doc.createElement("permissions");
|
|
||||||
if (app.installedApk.permissions != null) {
|
if (app.installedApk.permissions != null) {
|
||||||
StringBuilder buff = new StringBuilder();
|
StringBuilder buff = new StringBuilder();
|
||||||
|
|
||||||
@ -463,28 +456,32 @@ public class LocalRepoManager {
|
|||||||
}
|
}
|
||||||
String out = buff.toString();
|
String out = buff.toString();
|
||||||
if (!TextUtils.isEmpty(out))
|
if (!TextUtils.isEmpty(out))
|
||||||
permissions.setTextContent(out.substring(0, out.length() - 1));
|
serializer.text(out.substring(0, out.length() - 1));
|
||||||
}
|
}
|
||||||
packageNode.appendChild(permissions);
|
serializer.endTag("", "permissions");
|
||||||
|
|
||||||
application.appendChild(packageNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
private void tagFeatures(App app) throws IOException {
|
||||||
Transformer transformer = transformerFactory.newTransformer();
|
serializer.startTag("", "features");
|
||||||
|
if (app.installedApk.features != null)
|
||||||
|
serializer.text(Utils.CommaSeparatedList.str(app.installedApk.features));
|
||||||
|
serializer.endTag("", "features");
|
||||||
|
}
|
||||||
|
|
||||||
DOMSource domSource = new DOMSource(doc);
|
private void tagHash(App app) throws IOException {
|
||||||
StreamResult result = new StreamResult(xmlIndex);
|
serializer.startTag("", "hash");
|
||||||
|
serializer.attribute("", "type", app.installedApk.hashType);
|
||||||
transformer.transform(domSource, result);
|
serializer.text(app.installedApk.hash.toLowerCase(Locale.US));
|
||||||
|
serializer.endTag("", "hash");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeIndexJar() throws IOException {
|
public void writeIndexJar() throws IOException {
|
||||||
try {
|
try {
|
||||||
writeIndexXML();
|
new IndexXmlBuilder(context, apps).build(new FileWriter(xmlIndex));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Toast.makeText(context, R.string.failed_to_create_index, Toast.LENGTH_LONG).show();
|
|
||||||
Log.e(TAG, Log.getStackTraceString(e));
|
Log.e(TAG, Log.getStackTraceString(e));
|
||||||
|
Toast.makeText(context, R.string.failed_to_create_index, Toast.LENGTH_LONG).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,8 +509,9 @@ public class LocalRepoManager {
|
|||||||
} catch (LocalRepoKeyStore.InitException e) {
|
} catch (LocalRepoKeyStore.InitException e) {
|
||||||
throw new IOException("Could not sign index - keystore failed to initialize");
|
throw new IOException("Could not sign index - keystore failed to initialize");
|
||||||
} finally {
|
} finally {
|
||||||
xmlIndexJarUnsigned.delete();
|
attemptToDelete(xmlIndexJarUnsigned);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -82,8 +82,7 @@ public class WifiStateChangeService extends Service {
|
|||||||
|
|
||||||
Context context = WifiStateChangeService.this.getApplicationContext();
|
Context context = WifiStateChangeService.this.getApplicationContext();
|
||||||
LocalRepoManager lrm = LocalRepoManager.get(context);
|
LocalRepoManager lrm = LocalRepoManager.get(context);
|
||||||
lrm.setUriString(FDroidApp.repo.address);
|
lrm.writeIndexPage(Utils.getSharingUri(FDroidApp.repo).toString());
|
||||||
lrm.writeIndexPage(Utils.getSharingUri(context, FDroidApp.repo).toString());
|
|
||||||
|
|
||||||
if (isCancelled())
|
if (isCancelled())
|
||||||
return null;
|
return null;
|
||||||
|
@ -246,7 +246,7 @@ public class LocalRepoActivity extends ActionBarActivity {
|
|||||||
* wifi AP to join. Lots of QR Scanners are buggy and do not respect
|
* wifi AP to join. Lots of QR Scanners are buggy and do not respect
|
||||||
* custom URI schemes, so we have to use http:// or https:// :-(
|
* custom URI schemes, so we have to use http:// or https:// :-(
|
||||||
*/
|
*/
|
||||||
final String qrUriString = Utils.getSharingUri(this, FDroidApp.repo).toString()
|
final String qrUriString = Utils.getSharingUri(FDroidApp.repo).toString()
|
||||||
.replaceFirst("fdroidrepo", "http")
|
.replaceFirst("fdroidrepo", "http")
|
||||||
.replaceAll("ssid=[^?]*", "")
|
.replaceAll("ssid=[^?]*", "")
|
||||||
.toUpperCase(Locale.ENGLISH);
|
.toUpperCase(Locale.ENGLISH);
|
||||||
@ -270,7 +270,7 @@ public class LocalRepoActivity extends ActionBarActivity {
|
|||||||
if (nfcAdapter == null)
|
if (nfcAdapter == null)
|
||||||
return;
|
return;
|
||||||
nfcAdapter.setNdefPushMessage(new NdefMessage(new NdefRecord[] {
|
nfcAdapter.setNdefPushMessage(new NdefMessage(new NdefRecord[] {
|
||||||
NdefRecord.createUri(Utils.getSharingUri(this, FDroidApp.repo)),
|
NdefRecord.createUri(Utils.getSharingUri(FDroidApp.repo)),
|
||||||
}), this);
|
}), this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -292,7 +292,7 @@ public class LocalRepoActivity extends ActionBarActivity {
|
|||||||
progressDialog = new ProgressDialog(c);
|
progressDialog = new ProgressDialog(c);
|
||||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||||
progressDialog.setTitle(R.string.updating);
|
progressDialog.setTitle(R.string.updating);
|
||||||
sharingUri = Utils.getSharingUri(c, FDroidApp.repo);
|
sharingUri = Utils.getSharingUri(FDroidApp.repo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -611,7 +611,7 @@ public class ManageReposActivity extends ActionBarActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||||
Uri uri = RepoProvider.getContentUri();
|
Uri uri = RepoProvider.allExceptSwapUri();
|
||||||
Log.i(TAG, "Creating repo loader '" + uri + "'.");
|
Log.i(TAG, "Creating repo loader '" + uri + "'.");
|
||||||
String[] projection = {
|
String[] projection = {
|
||||||
RepoProvider.DataColumns._ID,
|
RepoProvider.DataColumns._ID,
|
||||||
|
@ -66,7 +66,7 @@ public class RepoDetailsActivity extends ActionBarActivity {
|
|||||||
|
|
||||||
@TargetApi(14)
|
@TargetApi(14)
|
||||||
private void setNfc() {
|
private void setNfc() {
|
||||||
if (NfcHelper.setPushMessage(this, Utils.getSharingUri(this, repo))) {
|
if (NfcHelper.setPushMessage(this, Utils.getSharingUri(repo))) {
|
||||||
findViewById(android.R.id.content).post(new Runnable() {
|
findViewById(android.R.id.content).post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -142,11 +142,19 @@ abstract public class AppListFragment extends ThemeableListFragment implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
final App app = new App((Cursor)getListView().getItemAtPosition(position));
|
// Cursor is null in the swap list when touching the first item.
|
||||||
Intent intent = new Intent(getActivity(), AppDetails.class);
|
Cursor cursor = (Cursor)getListView().getItemAtPosition(position);
|
||||||
intent.putExtra(AppDetails.EXTRA_APPID, app.id);
|
if (cursor != null) {
|
||||||
intent.putExtra(AppDetails.EXTRA_FROM, getFromTitle());
|
final App app = new App(cursor);
|
||||||
startActivityForResult(intent, FDroid.REQUEST_APPDETAILS);
|
Intent intent = getAppDetailsIntent();
|
||||||
|
intent.putExtra(AppDetails.EXTRA_APPID, app.id);
|
||||||
|
intent.putExtra(AppDetails.EXTRA_FROM, getFromTitle());
|
||||||
|
startActivityForResult(intent, FDroid.REQUEST_APPDETAILS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Intent getAppDetailsIntent() {
|
||||||
|
return new Intent(getActivity(), AppDetails.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -4,7 +4,10 @@ import android.app.Activity;
|
|||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -18,8 +21,13 @@ import org.fdroid.fdroid.data.RepoProvider;
|
|||||||
|
|
||||||
public class ConfirmReceiveSwapFragment extends Fragment implements ProgressListener {
|
public class ConfirmReceiveSwapFragment extends Fragment implements ProgressListener {
|
||||||
|
|
||||||
|
private static final String TAG = "org.fdroid.fdroid.views.swap.ConfirmReceiveSwapFragment";
|
||||||
|
|
||||||
private NewRepoConfig newRepoConfig;
|
private NewRepoConfig newRepoConfig;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Repo repo;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
|
||||||
@ -60,26 +68,33 @@ public class ConfirmReceiveSwapFragment extends Fragment implements ProgressList
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void confirm() {
|
private void confirm() {
|
||||||
Repo repo = ensureRepoExists();
|
this.repo = ensureRepoExists();
|
||||||
UpdateService.updateRepoNow(repo.address, getActivity()).setListener(this);
|
UpdateService.updateRepoNow(this.repo.address, getActivity()).setListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
private Repo ensureRepoExists() {
|
private Repo ensureRepoExists() {
|
||||||
// TODO: newRepoConfig.getUri() will include a fingerprint, which may not match with
|
// TODO: newRepoConfig.getUri() will include a fingerprint, which may not match with
|
||||||
// the repos address in the database.
|
// the repos address in the database.
|
||||||
Repo repo = RepoProvider.Helper.findByAddress(getActivity(), newRepoConfig.getUriString());
|
Repo repo = RepoProvider.Helper.findByAddress(getActivity(), newRepoConfig.getUriString());
|
||||||
if (repo == null) {
|
if (repo == null) {
|
||||||
ContentValues values = new ContentValues(5);
|
ContentValues values = new ContentValues(6);
|
||||||
|
|
||||||
// TODO: i18n and think about most appropriate name. Although ideally, it will not be seen often,
|
// TODO: i18n and think about most appropriate name. Although it wont be visible in
|
||||||
// because we're whacking a pretty UI over the swap process so they don't need to "Manage repos"...
|
// the "Manage repos" UI after being marked as a swap repo here...
|
||||||
values.put(RepoProvider.DataColumns.NAME, "Swap");
|
values.put(RepoProvider.DataColumns.NAME, "Swap");
|
||||||
values.put(RepoProvider.DataColumns.ADDRESS, newRepoConfig.getUriString());
|
values.put(RepoProvider.DataColumns.ADDRESS, newRepoConfig.getUriString());
|
||||||
values.put(RepoProvider.DataColumns.DESCRIPTION, ""); // TODO;
|
values.put(RepoProvider.DataColumns.DESCRIPTION, ""); // TODO;
|
||||||
values.put(RepoProvider.DataColumns.FINGERPRINT, newRepoConfig.getFingerprint());
|
values.put(RepoProvider.DataColumns.FINGERPRINT, newRepoConfig.getFingerprint());
|
||||||
values.put(RepoProvider.DataColumns.IN_USE, true);
|
values.put(RepoProvider.DataColumns.IN_USE, true);
|
||||||
|
values.put(RepoProvider.DataColumns.IS_SWAP, true);
|
||||||
Uri uri = RepoProvider.Helper.insert(getActivity(), values);
|
Uri uri = RepoProvider.Helper.insert(getActivity(), values);
|
||||||
repo = RepoProvider.Helper.findByUri(getActivity(), uri);
|
repo = RepoProvider.Helper.findByUri(getActivity(), uri);
|
||||||
|
} else if (!repo.isSwap) {
|
||||||
|
Log.d(TAG, "Old local repo being marked as \"Swap\" repo, so that it wont appear in the list of repositories in the future.");
|
||||||
|
ContentValues values = new ContentValues(1);
|
||||||
|
values.put(RepoProvider.DataColumns.IS_SWAP, true);
|
||||||
|
RepoProvider.Helper.update(getActivity(), repo, values);
|
||||||
}
|
}
|
||||||
return repo;
|
return repo;
|
||||||
}
|
}
|
||||||
@ -93,7 +108,7 @@ public class ConfirmReceiveSwapFragment extends Fragment implements ProgressList
|
|||||||
|
|
||||||
if (event.type.equals(UpdateService.EVENT_COMPLETE_AND_SAME) ||
|
if (event.type.equals(UpdateService.EVENT_COMPLETE_AND_SAME) ||
|
||||||
event.type.equals(UpdateService.EVENT_COMPLETE_WITH_CHANGES)) {
|
event.type.equals(UpdateService.EVENT_COMPLETE_WITH_CHANGES)) {
|
||||||
((ConnectSwapActivity)getActivity()).onRepoUpdated();
|
((ConnectSwapActivity)getActivity()).onRepoUpdated(repo);
|
||||||
/*Intent intent = new Intent();
|
/*Intent intent = new Intent();
|
||||||
intent.putExtra("category", newRepoConfig.getHost()); // TODO: Load repo from database to get proper name. This is what the category we want to select will be called.
|
intent.putExtra("category", newRepoConfig.getHost()); // TODO: Load repo from database to get proper name. This is what the category we want to select will be called.
|
||||||
getActivity().setResult(Activity.RESULT_OK, intent);
|
getActivity().setResult(Activity.RESULT_OK, intent);
|
||||||
|
@ -5,10 +5,11 @@ import android.os.Bundle;
|
|||||||
import android.support.v4.app.FragmentActivity;
|
import android.support.v4.app.FragmentActivity;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.data.Repo;
|
||||||
|
|
||||||
public class ConnectSwapActivity extends FragmentActivity {
|
public class ConnectSwapActivity extends FragmentActivity {
|
||||||
|
|
||||||
private static final String STATE_CONFIRM = "startSwap";
|
private static final String STATE_CONFIRM = "startSwap";
|
||||||
private static final String STATE_APP_LIST = "swapAppList";
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@ -42,10 +43,9 @@ public class ConnectSwapActivity extends FragmentActivity {
|
|||||||
return lastFragment.getName();
|
return lastFragment.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onRepoUpdated() {
|
public void onRepoUpdated(Repo repo) {
|
||||||
|
|
||||||
Intent intent = new Intent(this, SwapAppListActivity.class);
|
Intent intent = new Intent(this, SwapAppListActivity.class);
|
||||||
|
intent.putExtra(SwapAppListActivity.EXTRA_REPO_ID, repo.getId());
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,22 @@
|
|||||||
package org.fdroid.fdroid.views.swap;
|
package org.fdroid.fdroid.views.swap;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v4.content.CursorLoader;
|
import android.support.v4.content.CursorLoader;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
import android.support.v4.view.MenuItemCompat;
|
import android.support.v4.view.MenuItemCompat;
|
||||||
import android.support.v4.widget.SimpleCursorAdapter;
|
import android.support.v4.widget.CursorAdapter;
|
||||||
|
import android.support.v7.widget.SearchView;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.ActionMode;
|
import android.view.*;
|
||||||
import android.view.ContextThemeWrapper;
|
import android.widget.*;
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.SearchView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.data.InstalledAppProvider;
|
import org.fdroid.fdroid.data.InstalledAppProvider;
|
||||||
@ -34,11 +29,13 @@ import java.util.Set;
|
|||||||
public class SelectAppsFragment extends ThemeableListFragment
|
public class SelectAppsFragment extends ThemeableListFragment
|
||||||
implements LoaderManager.LoaderCallbacks<Cursor>, SearchView.OnQueryTextListener {
|
implements LoaderManager.LoaderCallbacks<Cursor>, SearchView.OnQueryTextListener {
|
||||||
|
|
||||||
private PackageManager packageManager;
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
private Drawable defaultAppIcon;
|
private static final String TAG = "org.fdroid.fdroid.views.swap.SelectAppsFragment";
|
||||||
private ActionMode mActionMode = null;
|
|
||||||
private String mCurrentFilterString;
|
private String mCurrentFilterString;
|
||||||
private Set<String> previouslySelectedApps = new HashSet<String>();
|
|
||||||
|
@NonNull
|
||||||
|
private final Set<String> previouslySelectedApps = new HashSet<>();
|
||||||
|
|
||||||
public Set<String> getSelectedApps() {
|
public Set<String> getSelectedApps() {
|
||||||
return FDroidApp.selectedApps;
|
return FDroidApp.selectedApps;
|
||||||
@ -52,10 +49,18 @@ public class SelectAppsFragment extends ThemeableListFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
|
||||||
menuInflater.inflate(R.menu.swap_next, menu);
|
menuInflater.inflate(R.menu.swap_next_search, menu);
|
||||||
MenuItem nextMenuItem = menu.findItem(R.id.action_next);
|
MenuItem nextMenuItem = menu.findItem(R.id.action_next);
|
||||||
int flags = MenuItemCompat.SHOW_AS_ACTION_ALWAYS | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT;
|
int flags = MenuItemCompat.SHOW_AS_ACTION_ALWAYS | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT;
|
||||||
MenuItemCompat.setShowAsAction(nextMenuItem, flags);
|
MenuItemCompat.setShowAsAction(nextMenuItem, flags);
|
||||||
|
|
||||||
|
SearchView searchView = new SearchView(getActivity());
|
||||||
|
|
||||||
|
MenuItem searchMenuItem = menu.findItem(R.id.action_search);
|
||||||
|
MenuItemCompat.setActionView(searchMenuItem, searchView);
|
||||||
|
MenuItemCompat.setShowAsAction(searchMenuItem, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
|
||||||
|
|
||||||
|
searchView.setOnQueryTextListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -96,46 +101,10 @@ public class SelectAppsFragment extends ThemeableListFragment
|
|||||||
|
|
||||||
setEmptyText(getString(R.string.no_applications_found));
|
setEmptyText(getString(R.string.no_applications_found));
|
||||||
|
|
||||||
packageManager = getActivity().getPackageManager();
|
|
||||||
defaultAppIcon = getResources().getDrawable(android.R.drawable.sym_def_app_icon);
|
|
||||||
|
|
||||||
ListView listView = getListView();
|
ListView listView = getListView();
|
||||||
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||||
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
|
|
||||||
new ContextThemeWrapper(getActivity(), R.style.SwapTheme_AppList_ListItem),
|
|
||||||
R.layout.select_local_apps_list_item,
|
|
||||||
null,
|
|
||||||
new String[] {
|
|
||||||
InstalledAppProvider.DataColumns.APPLICATION_LABEL,
|
|
||||||
InstalledAppProvider.DataColumns.APP_ID,
|
|
||||||
},
|
|
||||||
new int[] {
|
|
||||||
R.id.application_label,
|
|
||||||
R.id.package_name,
|
|
||||||
});
|
|
||||||
adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
|
|
||||||
|
|
||||||
@Override
|
setListAdapter(new AppListAdapter(listView, getActivity(), null));
|
||||||
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
|
|
||||||
if (columnIndex == cursor.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID)) {
|
|
||||||
String packageName = cursor.getString(columnIndex);
|
|
||||||
TextView textView = (TextView) view.findViewById(R.id.package_name);
|
|
||||||
textView.setText(packageName);
|
|
||||||
LinearLayout ll = (LinearLayout) view.getParent().getParent();
|
|
||||||
ImageView iconView = (ImageView) ll.getChildAt(0);
|
|
||||||
Drawable icon;
|
|
||||||
try {
|
|
||||||
icon = packageManager.getApplicationIcon(packageName);
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
icon = defaultAppIcon;
|
|
||||||
}
|
|
||||||
iconView.setImageDrawable(icon);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setListAdapter(adapter);
|
|
||||||
setListShown(false); // start out with a progress indicator
|
setListShown(false); // start out with a progress indicator
|
||||||
|
|
||||||
// either reconnect with an existing loader or start a new one
|
// either reconnect with an existing loader or start a new one
|
||||||
@ -143,7 +112,7 @@ public class SelectAppsFragment extends ThemeableListFragment
|
|||||||
|
|
||||||
// build list of existing apps from what is on the file system
|
// build list of existing apps from what is on the file system
|
||||||
if (FDroidApp.selectedApps == null) {
|
if (FDroidApp.selectedApps == null) {
|
||||||
FDroidApp.selectedApps = new HashSet<String>();
|
FDroidApp.selectedApps = new HashSet<>();
|
||||||
for (String filename : LocalRepoManager.get(getActivity()).repoDir.list()) {
|
for (String filename : LocalRepoManager.get(getActivity()).repoDir.list()) {
|
||||||
if (filename.matches(".*\\.apk")) {
|
if (filename.matches(".*\\.apk")) {
|
||||||
String packageName = filename.substring(0, filename.indexOf("_"));
|
String packageName = filename.substring(0, filename.indexOf("_"));
|
||||||
@ -155,7 +124,11 @@ public class SelectAppsFragment extends ThemeableListFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||||
Cursor c = (Cursor) l.getAdapter().getItem(position);
|
toggleAppSelected(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleAppSelected(int position) {
|
||||||
|
Cursor c = (Cursor) getListAdapter().getItem(position);
|
||||||
String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID));
|
String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID));
|
||||||
if (FDroidApp.selectedApps.contains(packageName)) {
|
if (FDroidApp.selectedApps.contains(packageName)) {
|
||||||
FDroidApp.selectedApps.remove(packageName);
|
FDroidApp.selectedApps.remove(packageName);
|
||||||
@ -183,7 +156,7 @@ public class SelectAppsFragment extends ThemeableListFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||||
((SimpleCursorAdapter) this.getListAdapter()).swapCursor(cursor);
|
((AppListAdapter)getListAdapter()).swapCursor(cursor);
|
||||||
|
|
||||||
ListView listView = getListView();
|
ListView listView = getListView();
|
||||||
String fdroid = loader.getContext().getPackageName();
|
String fdroid = loader.getContext().getPackageName();
|
||||||
@ -191,7 +164,7 @@ public class SelectAppsFragment extends ThemeableListFragment
|
|||||||
Cursor c = ((Cursor) listView.getItemAtPosition(i + 1));
|
Cursor c = ((Cursor) listView.getItemAtPosition(i + 1));
|
||||||
String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID));
|
String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID));
|
||||||
if (TextUtils.equals(packageName, fdroid)) {
|
if (TextUtils.equals(packageName, fdroid)) {
|
||||||
listView.setItemChecked(i, true); // always include FDroid
|
listView.setItemChecked(i + 1, true); // always include FDroid
|
||||||
} else {
|
} else {
|
||||||
for (String selected : FDroidApp.selectedApps) {
|
for (String selected : FDroidApp.selectedApps) {
|
||||||
if (TextUtils.equals(packageName, selected)) {
|
if (TextUtils.equals(packageName, selected)) {
|
||||||
@ -210,7 +183,7 @@ public class SelectAppsFragment extends ThemeableListFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoaderReset(Loader<Cursor> loader) {
|
public void onLoaderReset(Loader<Cursor> loader) {
|
||||||
((SimpleCursorAdapter) this.getListAdapter()).swapCursor(null);
|
((AppListAdapter)getListAdapter()).swapCursor(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -233,10 +206,6 @@ public class SelectAppsFragment extends ThemeableListFragment
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCurrentFilterString() {
|
|
||||||
return mCurrentFilterString;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getThemeStyle() {
|
protected int getThemeStyle() {
|
||||||
return R.style.SwapTheme_StartSwap;
|
return R.style.SwapTheme_StartSwap;
|
||||||
@ -246,4 +215,92 @@ public class SelectAppsFragment extends ThemeableListFragment
|
|||||||
protected int getHeaderLayout() {
|
protected int getHeaderLayout() {
|
||||||
return R.layout.swap_create_header;
|
return R.layout.swap_create_header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class AppListAdapter extends CursorAdapter {
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
|
private static final String TAG = "org.fdroid.fdroid.views.swap.SelectAppsFragment.AppListAdapter";
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private LayoutInflater inflater;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Drawable defaultAppIcon;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final ListView listView;
|
||||||
|
|
||||||
|
public AppListAdapter(@NonNull ListView listView, @NonNull Context context, @Nullable Cursor c) {
|
||||||
|
super(context, c, FLAG_REGISTER_CONTENT_OBSERVER);
|
||||||
|
this.listView = listView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private LayoutInflater getInflater(Context context) {
|
||||||
|
if (inflater == null) {
|
||||||
|
Context themedContext = new ContextThemeWrapper(context, R.style.SwapTheme_AppList_ListItem);
|
||||||
|
inflater = (LayoutInflater)themedContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
}
|
||||||
|
return inflater;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Drawable getDefaultAppIcon(Context context) {
|
||||||
|
if (defaultAppIcon == null) {
|
||||||
|
defaultAppIcon = context.getResources().getDrawable(android.R.drawable.sym_def_app_icon);
|
||||||
|
}
|
||||||
|
return defaultAppIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||||
|
View view = getInflater(context).inflate(R.layout.select_local_apps_list_item, null);
|
||||||
|
bindView(view, context, cursor);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindView(final View view, final Context context, final Cursor cursor) {
|
||||||
|
|
||||||
|
TextView packageView = (TextView)view.findViewById(R.id.package_name);
|
||||||
|
TextView labelView = (TextView)view.findViewById(R.id.application_label);
|
||||||
|
ImageView iconView = (ImageView)view.findViewById(android.R.id.icon);
|
||||||
|
|
||||||
|
String packageName = cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID));
|
||||||
|
String appLabel = cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.APPLICATION_LABEL));
|
||||||
|
|
||||||
|
Drawable icon;
|
||||||
|
try {
|
||||||
|
icon = context.getPackageManager().getApplicationIcon(packageName);
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
icon = getDefaultAppIcon(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
packageView.setText(packageName);
|
||||||
|
labelView.setText(appLabel);
|
||||||
|
iconView.setImageDrawable(icon);
|
||||||
|
|
||||||
|
// Since v11, the Android SDK provided the ability to show selected list items
|
||||||
|
// by highlighting their background. Prior to this, we need to handle this ourselves
|
||||||
|
// by adding a checkbox which can toggle selected items.
|
||||||
|
View checkBoxView = view.findViewById(R.id.checkbox);
|
||||||
|
if (checkBoxView != null) {
|
||||||
|
CheckBox checkBox = (CheckBox)checkBoxView;
|
||||||
|
checkBox.setOnCheckedChangeListener(null);
|
||||||
|
|
||||||
|
final int cursorPosition = cursor.getPosition();
|
||||||
|
final int listPosition = cursor.getPosition() + 1; // To account for the header view.
|
||||||
|
|
||||||
|
checkBox.setChecked(listView.isItemChecked(listPosition));
|
||||||
|
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
|
listView.setItemChecked(listPosition, isChecked);
|
||||||
|
toggleAppSelected(cursorPosition);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import android.content.Context;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.os.Handler;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v7.app.ActionBarActivity;
|
import android.support.v7.app.ActionBarActivity;
|
||||||
@ -80,15 +82,24 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
|
|||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
|
|
||||||
showFragment(new StartSwapFragment(), STATE_START_SWAP);
|
setContentView(R.layout.swap_activity);
|
||||||
|
|
||||||
if (FDroidApp.isLocalRepoServiceRunning()) {
|
// Necessary to run on an Android 2.3.[something] device.
|
||||||
showSelectApps();
|
new Handler().post(new Runnable() {
|
||||||
showJoinWifi();
|
@Override
|
||||||
attemptToShowNfc();
|
public void run() {
|
||||||
showWifiQr();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
showFragment(new StartSwapFragment(), STATE_START_SWAP);
|
||||||
|
|
||||||
|
if (FDroidApp.isLocalRepoServiceRunning()) {
|
||||||
|
showSelectApps();
|
||||||
|
showJoinWifi();
|
||||||
|
attemptToShowNfc();
|
||||||
|
showWifiQr();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -112,7 +123,7 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
|
|||||||
// Even if they opted to skip the message which says "Touch devices to swap",
|
// Even if they opted to skip the message which says "Touch devices to swap",
|
||||||
// we still want to actually enable the feature, so that they could touch
|
// we still want to actually enable the feature, so that they could touch
|
||||||
// during the wifi qr code being shown too.
|
// during the wifi qr code being shown too.
|
||||||
boolean nfcMessageReady = NfcHelper.setPushMessage(this, Utils.getSharingUri(this, FDroidApp.repo));
|
boolean nfcMessageReady = NfcHelper.setPushMessage(this, Utils.getSharingUri(FDroidApp.repo));
|
||||||
|
|
||||||
if (Preferences.get().showNfcDuringSwap() && nfcMessageReady) {
|
if (Preferences.get().showNfcDuringSwap() && nfcMessageReady) {
|
||||||
showFragment(new NfcSwapFragment(), STATE_NFC);
|
showFragment(new NfcSwapFragment(), STATE_NFC);
|
||||||
@ -133,7 +144,7 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
|
|||||||
private void showFragment(Fragment fragment, String name) {
|
private void showFragment(Fragment fragment, String name) {
|
||||||
getSupportFragmentManager()
|
getSupportFragmentManager()
|
||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
.replace(android.R.id.content, fragment, name)
|
.replace(R.id.fragment_container, fragment, name)
|
||||||
.addToBackStack(name)
|
.addToBackStack(name)
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
@ -197,17 +208,25 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
|
|||||||
}
|
}
|
||||||
|
|
||||||
class UpdateAsyncTask extends AsyncTask<Void, String, Void> {
|
class UpdateAsyncTask extends AsyncTask<Void, String, Void> {
|
||||||
private static final String TAG = "fdroid.SwapActivity.UpdateAsyncTask";
|
|
||||||
private ProgressDialog progressDialog;
|
|
||||||
private Set<String> selectedApps;
|
|
||||||
private Uri sharingUri;
|
|
||||||
|
|
||||||
public UpdateAsyncTask(Context c, Set<String> apps) {
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
|
private static final String TAG = "fdroid.SwapActivity.UpdateAsyncTask";
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final ProgressDialog progressDialog;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Set<String> selectedApps;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Uri sharingUri;
|
||||||
|
|
||||||
|
public UpdateAsyncTask(Context c, @NonNull Set<String> apps) {
|
||||||
selectedApps = apps;
|
selectedApps = apps;
|
||||||
progressDialog = new ProgressDialog(c);
|
progressDialog = new ProgressDialog(c);
|
||||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||||
progressDialog.setTitle(R.string.updating);
|
progressDialog.setTitle(R.string.updating);
|
||||||
sharingUri = Utils.getSharingUri(c, FDroidApp.repo);
|
sharingUri = Utils.getSharingUri(FDroidApp.repo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,33 +1,79 @@
|
|||||||
package org.fdroid.fdroid.views.swap;
|
package org.fdroid.fdroid.views.swap;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.support.v4.app.NavUtils;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.app.ActionBarActivity;
|
import android.support.v7.app.ActionBarActivity;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.AppDetails;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.data.AppProvider;
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
|
import org.fdroid.fdroid.data.Repo;
|
||||||
|
import org.fdroid.fdroid.data.RepoProvider;
|
||||||
import org.fdroid.fdroid.views.AppListAdapter;
|
import org.fdroid.fdroid.views.AppListAdapter;
|
||||||
import org.fdroid.fdroid.views.AvailableAppListAdapter;
|
import org.fdroid.fdroid.views.AvailableAppListAdapter;
|
||||||
import org.fdroid.fdroid.views.fragments.AppListFragment;
|
import org.fdroid.fdroid.views.fragments.AppListFragment;
|
||||||
|
|
||||||
public class SwapAppListActivity extends ActionBarActivity {
|
public class SwapAppListActivity extends ActionBarActivity {
|
||||||
|
|
||||||
|
private static final String TAG = "fdroid.SwapAppListActivity";
|
||||||
|
|
||||||
|
public static String EXTRA_REPO_ID = "repoId";
|
||||||
|
|
||||||
|
private Repo repo;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
getSupportFragmentManager()
|
|
||||||
.beginTransaction()
|
// Necessary to run on an Android 2.3.[something] device.
|
||||||
.add(android.R.id.content, new SwapAppListFragment())
|
new Handler().post(new Runnable() {
|
||||||
.commit();
|
@Override
|
||||||
|
public void run() {
|
||||||
|
getSupportFragmentManager()
|
||||||
|
.beginTransaction()
|
||||||
|
.add(android.R.id.content, new SwapAppListFragment())
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
long repoAddress = getIntent().getLongExtra(EXTRA_REPO_ID, -1);
|
||||||
|
repo = RepoProvider.Helper.findById(this, repoAddress);
|
||||||
|
if (repo == null) {
|
||||||
|
Log.e(TAG, "Couldn't show swap app list for repo " + repoAddress);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Repo getRepo() {
|
||||||
|
return repo;
|
||||||
|
}
|
||||||
|
|
||||||
public static class SwapAppListFragment extends AppListFragment {
|
public static class SwapAppListFragment extends AppListFragment {
|
||||||
|
|
||||||
|
private Repo repo;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
repo = ((SwapAppListActivity)activity).getRepo();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getHeaderLayout() {
|
protected int getHeaderLayout() {
|
||||||
return R.layout.swap_success_header;
|
return R.layout.swap_success_header;
|
||||||
@ -51,7 +97,39 @@ public class SwapAppListActivity extends ActionBarActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Uri getDataUri() {
|
protected Uri getDataUri() {
|
||||||
return AppProvider.getCategoryUri("LocalRepo");
|
return AppProvider.getRepoUri(repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Intent getAppDetailsIntent() {
|
||||||
|
Intent intent = new Intent(getActivity(), SwapAppDetails.class);
|
||||||
|
intent.putExtra(EXTRA_REPO_ID, repo.getId());
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only difference from base class is that it navigates up to a different task.
|
||||||
|
* It will go to the {@link org.fdroid.fdroid.views.swap.SwapAppListActivity}
|
||||||
|
* whereas the baseclass will go back to the main list of apps. Need to juggle
|
||||||
|
* the repoId in order to be able to return to an appropriately configured swap
|
||||||
|
* list (see {@link org.fdroid.fdroid.views.swap.SwapAppListActivity.SwapAppListFragment#getAppDetailsIntent()}).
|
||||||
|
*/
|
||||||
|
public static class SwapAppDetails extends AppDetails {
|
||||||
|
|
||||||
|
private long repoId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
repoId = getIntent().getLongExtra(EXTRA_REPO_ID, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void navigateUp() {
|
||||||
|
Intent parentIntent = NavUtils.getParentActivityIntent(this);
|
||||||
|
parentIntent.putExtra(EXTRA_REPO_ID, repoId);
|
||||||
|
NavUtils.navigateUpTo(this, parentIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
package org.fdroid.fdroid.views.swap;
|
package org.fdroid.fdroid.views.swap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the contract between the {@link org.fdroid.fdroid.views.swap.SwapActivity}
|
||||||
|
* and the fragments which live in it. The fragments each have the responsibility of
|
||||||
|
* moving to the next stage of the process, and are entitled to stop swapping too
|
||||||
|
* (e.g. when a "Cancel" button is pressed).
|
||||||
|
*/
|
||||||
public interface SwapProcessManager {
|
public interface SwapProcessManager {
|
||||||
public void nextStep();
|
public void nextStep();
|
||||||
public void stopSwapping();
|
public void stopSwapping();
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.fdroid.fdroid.views.swap;
|
package org.fdroid.fdroid.views.swap;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -23,6 +22,8 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import com.google.zxing.integration.android.IntentIntegrator;
|
import com.google.zxing.integration.android.IntentIntegrator;
|
||||||
import com.google.zxing.integration.android.IntentResult;
|
import com.google.zxing.integration.android.IntentResult;
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
import org.apache.http.client.utils.URLEncodedUtils;
|
||||||
import org.fdroid.fdroid.FDroid;
|
import org.fdroid.fdroid.FDroid;
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.Preferences;
|
import org.fdroid.fdroid.Preferences;
|
||||||
@ -32,12 +33,16 @@ import org.fdroid.fdroid.Utils;
|
|||||||
import org.fdroid.fdroid.data.NewRepoConfig;
|
import org.fdroid.fdroid.data.NewRepoConfig;
|
||||||
import org.fdroid.fdroid.net.WifiStateChangeService;
|
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class WifiQrFragment extends Fragment {
|
public class WifiQrFragment extends Fragment {
|
||||||
|
|
||||||
private static final int CONNECT_TO_SWAP = 1;
|
private static final int CONNECT_TO_SWAP = 1;
|
||||||
|
|
||||||
|
private static final String TAG = "org.fdroid.fdroid.views.swap.WifiQrFragment";
|
||||||
|
|
||||||
private BroadcastReceiver onWifiChange = new BroadcastReceiver() {
|
private BroadcastReceiver onWifiChange = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent i) {
|
public void onReceive(Context context, Intent i) {
|
||||||
@ -105,10 +110,9 @@ public class WifiQrFragment extends Fragment {
|
|||||||
new IntentFilter(WifiStateChangeService.BROADCAST));
|
new IntentFilter(WifiStateChangeService.BROADCAST));
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(14)
|
|
||||||
private void setUIFromWifi() {
|
private void setUIFromWifi() {
|
||||||
|
|
||||||
if (TextUtils.isEmpty(FDroidApp.repo.address))
|
if (TextUtils.isEmpty(FDroidApp.repo.address) || getView() == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
String scheme = Preferences.get().isLocalRepoHttpsEnabled() ? "https://" : "http://";
|
String scheme = Preferences.get().isLocalRepoHttpsEnabled() ? "https://" : "http://";
|
||||||
@ -125,26 +129,33 @@ public class WifiQrFragment extends Fragment {
|
|||||||
* wifi AP to join. Lots of QR Scanners are buggy and do not respect
|
* wifi AP to join. Lots of QR Scanners are buggy and do not respect
|
||||||
* custom URI schemes, so we have to use http:// or https:// :-(
|
* custom URI schemes, so we have to use http:// or https:// :-(
|
||||||
*/
|
*/
|
||||||
Uri sharingUri = Utils.getSharingUri(getActivity(), FDroidApp.repo);
|
Uri sharingUri = Utils.getSharingUri(FDroidApp.repo);
|
||||||
String qrUriString = (scheme + sharingUri.getHost()).toUpperCase(Locale.ENGLISH);
|
String qrUriString = (scheme + sharingUri.getHost()).toUpperCase(Locale.ENGLISH);
|
||||||
if (sharingUri.getPort() != 80) {
|
if (sharingUri.getPort() != 80) {
|
||||||
qrUriString += ":" + sharingUri.getPort();
|
qrUriString += ":" + sharingUri.getPort();
|
||||||
}
|
}
|
||||||
qrUriString += sharingUri.getPath().toUpperCase(Locale.ENGLISH);
|
qrUriString += sharingUri.getPath().toUpperCase(Locale.ENGLISH);
|
||||||
boolean first = true;
|
boolean first = true;
|
||||||
for (String parameterName : sharingUri.getQueryParameterNames()) {
|
|
||||||
if (!parameterName.equals("ssid")) {
|
// Andorid provides an API for getting the query parameters and iterating over them:
|
||||||
|
// Uri.getQueryParameterNames()
|
||||||
|
// But it is only available on later Android versions. As such we use URLEncodedUtils instead.
|
||||||
|
List<NameValuePair> parameters = URLEncodedUtils.parse(URI.create(sharingUri.toString()), "UTF-8");
|
||||||
|
for (NameValuePair parameter : parameters) {
|
||||||
|
if (!parameter.getName().equals("ssid")) {
|
||||||
if (first) {
|
if (first) {
|
||||||
qrUriString += "?";
|
qrUriString += "?";
|
||||||
first = false;
|
first = false;
|
||||||
} else {
|
} else {
|
||||||
qrUriString += "&";
|
qrUriString += "&";
|
||||||
}
|
}
|
||||||
qrUriString += parameterName.toUpperCase(Locale.ENGLISH) + "=" +
|
qrUriString += parameter.getName().toUpperCase(Locale.ENGLISH) + "=" +
|
||||||
sharingUri.getQueryParameter(parameterName).toUpperCase(Locale.ENGLISH);
|
parameter.getValue().toUpperCase(Locale.ENGLISH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "Encoded swap URI in QR Code: " + qrUriString);
|
||||||
|
|
||||||
// zxing requires >= 8
|
// zxing requires >= 8
|
||||||
// TODO: What about 7? I don't feel comfortable bumping the min version for this...
|
// TODO: What about 7? I don't feel comfortable bumping the min version for this...
|
||||||
// I would suggest show some alternate info, with directions for how to add a new repository manually.
|
// I would suggest show some alternate info, with directions for how to add a new repository manually.
|
||||||
|
19
F-Droid/test/src/mock/MockApplicationInfo.java
Normal file
19
F-Droid/test/src/mock/MockApplicationInfo.java
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package mock;
|
||||||
|
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
|
||||||
|
public class MockApplicationInfo extends ApplicationInfo {
|
||||||
|
|
||||||
|
private final PackageInfo info;
|
||||||
|
|
||||||
|
public MockApplicationInfo(PackageInfo info) {
|
||||||
|
this.info = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence loadLabel(PackageManager pm) {
|
||||||
|
return "Mock app: " + info.packageName;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package mock;
|
package mock;
|
||||||
|
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.test.mock.MockPackageManager;
|
import android.test.mock.MockPackageManager;
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ import java.util.List;
|
|||||||
|
|
||||||
public class MockInstallablePackageManager extends MockPackageManager {
|
public class MockInstallablePackageManager extends MockPackageManager {
|
||||||
|
|
||||||
private List<PackageInfo> info = new ArrayList<PackageInfo>();
|
private List<PackageInfo> info = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PackageInfo> getInstalledPackages(int flags) {
|
public List<PackageInfo> getInstalledPackages(int flags) {
|
||||||
@ -30,6 +31,11 @@ public class MockInstallablePackageManager extends MockPackageManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ApplicationInfo getApplicationInfo(String packageName, int flags) throws NameNotFoundException {
|
||||||
|
return new MockApplicationInfo(getPackageInfo(packageName));
|
||||||
|
}
|
||||||
|
|
||||||
public PackageInfo getPackageInfo(String id) {
|
public PackageInfo getPackageInfo(String id) {
|
||||||
for (PackageInfo i : info) {
|
for (PackageInfo i : info) {
|
||||||
if (i.packageName.equals(id)) {
|
if (i.packageName.equals(id)) {
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
|
||||||
import mock.MockCategoryResources;
|
import mock.MockCategoryResources;
|
||||||
|
import mock.MockContextSwappableComponents;
|
||||||
|
import mock.MockInstallablePackageManager;
|
||||||
|
|
||||||
import org.fdroid.fdroid.data.ApkProvider;
|
import org.fdroid.fdroid.data.ApkProvider;
|
||||||
import org.fdroid.fdroid.data.App;
|
import org.fdroid.fdroid.data.App;
|
||||||
import org.fdroid.fdroid.data.AppProvider;
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
|
import org.fdroid.fdroid.data.InstalledAppCacheUpdater;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -43,7 +47,6 @@ public class AppProviderTest extends FDroidProviderTest<AppProvider> {
|
|||||||
* the AppProvider used to stumble across this bug when asking for installed apps,
|
* the AppProvider used to stumble across this bug when asking for installed apps,
|
||||||
* and the device had over 1000 apps installed.
|
* and the device had over 1000 apps installed.
|
||||||
*/
|
*/
|
||||||
/* TODO fix me
|
|
||||||
public void testMaxSqliteParams() {
|
public void testMaxSqliteParams() {
|
||||||
|
|
||||||
MockInstallablePackageManager pm = new MockInstallablePackageManager();
|
MockInstallablePackageManager pm = new MockInstallablePackageManager();
|
||||||
@ -74,7 +77,7 @@ public class AppProviderTest extends FDroidProviderTest<AppProvider> {
|
|||||||
|
|
||||||
assertResultCount(3, AppProvider.getInstalledUri());
|
assertResultCount(3, AppProvider.getInstalledUri());
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
public void testCantFindApp() {
|
public void testCantFindApp() {
|
||||||
assertNull(AppProvider.Helper.findById(getMockContentResolver(), "com.example.doesnt-exist"));
|
assertNull(AppProvider.Helper.findById(getMockContentResolver(), "com.example.doesnt-exist"));
|
||||||
}
|
}
|
||||||
@ -92,7 +95,7 @@ public class AppProviderTest extends FDroidProviderTest<AppProvider> {
|
|||||||
App app = new App();
|
App app = new App();
|
||||||
app.id = "org.fdroid.fdroid";
|
app.id = "org.fdroid.fdroid";
|
||||||
|
|
||||||
List<App> apps = new ArrayList<App>(1);
|
List<App> apps = new ArrayList<>(1);
|
||||||
apps.add(app);
|
apps.add(app);
|
||||||
|
|
||||||
assertValidUri(AppProvider.getContentUri(app));
|
assertValidUri(AppProvider.getContentUri(app));
|
||||||
@ -105,7 +108,6 @@ public class AppProviderTest extends FDroidProviderTest<AppProvider> {
|
|||||||
assertNotNull(cursor);
|
assertNotNull(cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO fix me
|
|
||||||
private void insertApps(int count) {
|
private void insertApps(int count) {
|
||||||
for (int i = 0; i < count; i ++) {
|
for (int i = 0; i < count; i ++) {
|
||||||
insertApp("com.example.test." + i, "Test app " + i);
|
insertApp("com.example.test." + i, "Test app " + i);
|
||||||
@ -122,7 +124,7 @@ public class AppProviderTest extends FDroidProviderTest<AppProvider> {
|
|||||||
values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, ignoreVercode);
|
values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, ignoreVercode);
|
||||||
insertApp(id, "App: " + id, values);
|
insertApp(id, "App: " + id, values);
|
||||||
|
|
||||||
TestUtils.installAndBroadcast(getMockContext(), packageManager, id, installedVercode, "v" + installedVercode);
|
TestUtils.installAndBroadcast(getSwappableContext(), packageManager, id, installedVercode, "v" + installedVercode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testCanUpdate() {
|
public void testCanUpdate() {
|
||||||
@ -173,7 +175,7 @@ public class AppProviderTest extends FDroidProviderTest<AppProvider> {
|
|||||||
|
|
||||||
Cursor canUpdateCursor = r.query(AppProvider.getCanUpdateUri(), AppProvider.DataColumns.ALL, null, null, null);
|
Cursor canUpdateCursor = r.query(AppProvider.getCanUpdateUri(), AppProvider.DataColumns.ALL, null, null, null);
|
||||||
canUpdateCursor.moveToFirst();
|
canUpdateCursor.moveToFirst();
|
||||||
List<String> canUpdateIds = new ArrayList<String>(canUpdateCursor.getCount());
|
List<String> canUpdateIds = new ArrayList<>(canUpdateCursor.getCount());
|
||||||
while (!canUpdateCursor.isAfterLast()) {
|
while (!canUpdateCursor.isAfterLast()) {
|
||||||
canUpdateIds.add(new App(canUpdateCursor).id);
|
canUpdateIds.add(new App(canUpdateCursor).id);
|
||||||
canUpdateCursor.moveToNext();
|
canUpdateCursor.moveToNext();
|
||||||
@ -224,7 +226,7 @@ public class AppProviderTest extends FDroidProviderTest<AppProvider> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void assertContainsOnlyIds(List<App> actualApps, String[] expectedIds) {
|
private void assertContainsOnlyIds(List<App> actualApps, String[] expectedIds) {
|
||||||
List<String> actualIds = new ArrayList<String>(actualApps.size());
|
List<String> actualIds = new ArrayList<>(actualApps.size());
|
||||||
for (App app : actualApps) {
|
for (App app : actualApps) {
|
||||||
actualIds.add(app.id);
|
actualIds.add(app.id);
|
||||||
}
|
}
|
||||||
@ -241,12 +243,11 @@ public class AppProviderTest extends FDroidProviderTest<AppProvider> {
|
|||||||
assertResultCount(0, AppProvider.getInstalledUri());
|
assertResultCount(0, AppProvider.getInstalledUri());
|
||||||
|
|
||||||
for (int i = 10; i < 20; i ++) {
|
for (int i = 10; i < 20; i ++) {
|
||||||
TestUtils.installAndBroadcast(getMockContext(), pm, "com.example.test." + i, i, "v1");
|
TestUtils.installAndBroadcast(getSwappableContext(), pm, "com.example.test." + i, i, "v1");
|
||||||
}
|
}
|
||||||
|
|
||||||
assertResultCount(10, AppProvider.getInstalledUri());
|
assertResultCount(10, AppProvider.getInstalledUri());
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
public void testInsert() {
|
public void testInsert() {
|
||||||
|
|
||||||
|
@ -25,8 +25,13 @@ public class FileCompatTest extends InstrumentationTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
assertTrue("Can't delete " + sourceFile.getAbsolutePath() + ".", sourceFile.delete());
|
if (sourceFile.exists()) {
|
||||||
assertTrue("Can't delete " + destFile.getAbsolutePath() + ".", destFile.delete());
|
assertTrue("Can't delete " + sourceFile.getAbsolutePath() + ".", sourceFile.delete());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destFile.exists()) {
|
||||||
|
assertTrue("Can't delete " + destFile.getAbsolutePath() + ".", destFile.delete());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSymlinkRuntime() {
|
public void testSymlinkRuntime() {
|
||||||
|
@ -37,7 +37,6 @@ public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppPro
|
|||||||
assertValidUri(InstalledAppProvider.getAppUri("blah"));
|
assertValidUri(InstalledAppProvider.getAppUri("blah"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO fix me
|
|
||||||
public void testInsert() {
|
public void testInsert() {
|
||||||
|
|
||||||
assertResultCount(0, InstalledAppProvider.getContentUri());
|
assertResultCount(0, InstalledAppProvider.getContentUri());
|
||||||
@ -134,7 +133,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppPro
|
|||||||
assertIsInstalledVersionInDb("com.example.toKeep", 1, "v0.1");
|
assertIsInstalledVersionInDb("com.example.toKeep", 1, "v0.1");
|
||||||
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
protected String[] getMinimalProjection() {
|
protected String[] getMinimalProjection() {
|
||||||
return new String[] {
|
return new String[] {
|
||||||
@ -153,6 +152,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppPro
|
|||||||
if (appId != null) {
|
if (appId != null) {
|
||||||
values.put(InstalledAppProvider.DataColumns.APP_ID, appId);
|
values.put(InstalledAppProvider.DataColumns.APP_ID, appId);
|
||||||
}
|
}
|
||||||
|
values.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL, "Mock app: " + appId);
|
||||||
values.put(InstalledAppProvider.DataColumns.VERSION_CODE, versionCode);
|
values.put(InstalledAppProvider.DataColumns.VERSION_CODE, versionCode);
|
||||||
values.put(InstalledAppProvider.DataColumns.VERSION_NAME, versionNumber);
|
values.put(InstalledAppProvider.DataColumns.VERSION_NAME, versionNumber);
|
||||||
return values;
|
return values;
|
||||||
@ -164,15 +164,15 @@ public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppPro
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void removeAndBroadcast(String appId) {
|
private void removeAndBroadcast(String appId) {
|
||||||
TestUtils.removeAndBroadcast(getMockContext(), getPackageManager(), appId);
|
TestUtils.removeAndBroadcast(getSwappableContext(), getPackageManager(), appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void upgradeAndBroadcast(String appId, int versionCode, String versionName) {
|
private void upgradeAndBroadcast(String appId, int versionCode, String versionName) {
|
||||||
TestUtils.upgradeAndBroadcast(getMockContext(), getPackageManager(), appId, versionCode, versionName);
|
TestUtils.upgradeAndBroadcast(getSwappableContext(), getPackageManager(), appId, versionCode, versionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void installAndBroadcast(String appId, int versionCode, String versionName) {
|
private void installAndBroadcast(String appId, int versionCode, String versionName) {
|
||||||
TestUtils.installAndBroadcast(getMockContext(), getPackageManager(), appId, versionCode, versionName);
|
TestUtils.installAndBroadcast(getSwappableContext(), getPackageManager(), appId, versionCode, versionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,14 @@ import android.net.Uri;
|
|||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import junit.framework.AssertionFailedError;
|
import junit.framework.AssertionFailedError;
|
||||||
|
|
||||||
|
import mock.MockContextSwappableComponents;
|
||||||
import mock.MockInstallablePackageManager;
|
import mock.MockInstallablePackageManager;
|
||||||
import org.fdroid.fdroid.data.ApkProvider;
|
import org.fdroid.fdroid.data.ApkProvider;
|
||||||
import org.fdroid.fdroid.data.AppProvider;
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
|
import org.fdroid.fdroid.receiver.PackageAddedReceiver;
|
||||||
|
import org.fdroid.fdroid.receiver.PackageRemovedReceiver;
|
||||||
|
import org.fdroid.fdroid.receiver.PackageUpgradedReceiver;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -17,7 +22,7 @@ import java.util.List;
|
|||||||
|
|
||||||
public class TestUtils {
|
public class TestUtils {
|
||||||
|
|
||||||
private static final String TAG = "org.fdroid.fdroid.TestUtils";
|
private static final String TAG = "fdroid.TestUtils";
|
||||||
|
|
||||||
public static <T extends Comparable> void assertContainsOnly(List<T> actualList, T[] expectedArray) {
|
public static <T extends Comparable> void assertContainsOnly(List<T> actualList, T[] expectedArray) {
|
||||||
List<T> expectedList = new ArrayList<T>(expectedArray.length);
|
List<T> expectedList = new ArrayList<T>(expectedArray.length);
|
||||||
@ -132,19 +137,14 @@ public class TestUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Will tell {@code pm} that we are installing {@code appId}, and then alert the
|
* Will tell {@code pm} that we are installing {@code appId}, and then alert the
|
||||||
* {@link org.fdroid.fdroid.PackageAddedReceiver}. This will in turn update the
|
* {@link org.fdroid.fdroid.receiver.PackageAddedReceiver}. This will in turn update the
|
||||||
* "installed apps" table in the database.
|
* "installed apps" table in the database.
|
||||||
*
|
|
||||||
* Note: in order for this to work, the {@link AppProviderTest#getSwappableContext()}
|
|
||||||
* will need to be aware of the package manager that we have passed in. Therefore,
|
|
||||||
* you will have to have called
|
|
||||||
* {@link mock.MockContextSwappableComponents#setPackageManager(android.content.pm.PackageManager)}
|
|
||||||
* on the {@link AppProviderTest#getSwappableContext()} before invoking this method.
|
|
||||||
*/
|
*/
|
||||||
public static void installAndBroadcast(
|
public static void installAndBroadcast(
|
||||||
Context context, MockInstallablePackageManager pm,
|
MockContextSwappableComponents context, MockInstallablePackageManager pm,
|
||||||
String appId, int versionCode, String versionName) {
|
String appId, int versionCode, String versionName) {
|
||||||
|
|
||||||
|
context.setPackageManager(pm);
|
||||||
pm.install(appId, versionCode, versionName);
|
pm.install(appId, versionCode, versionName);
|
||||||
Intent installIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
|
Intent installIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
|
||||||
installIntent.setData(Uri.parse("package:" + appId));
|
installIntent.setData(Uri.parse("package:" + appId));
|
||||||
@ -153,15 +153,16 @@ public class TestUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.fdroid.fdroid.TestUtils#installAndBroadcast(android.content.Context context, mock.MockInstallablePackageManager, String, int, String)
|
* @see org.fdroid.fdroid.TestUtils#installAndBroadcast(mock.MockContextSwappableComponents, mock.MockInstallablePackageManager, String, int, String)
|
||||||
*/
|
*/
|
||||||
public static void upgradeAndBroadcast(
|
public static void upgradeAndBroadcast(
|
||||||
Context context, MockInstallablePackageManager pm,
|
MockContextSwappableComponents context, MockInstallablePackageManager pm,
|
||||||
String appId, int versionCode, String versionName) {
|
String appId, int versionCode, String versionName) {
|
||||||
/*
|
/*
|
||||||
removeAndBroadcast(context, pm, appId);
|
removeAndBroadcast(context, pm, appId);
|
||||||
installAndBroadcast(context, pm, appId, versionCode, versionName);
|
installAndBroadcast(context, pm, appId, versionCode, versionName);
|
||||||
*/
|
*/
|
||||||
|
context.setPackageManager(pm);
|
||||||
pm.install(appId, versionCode, versionName);
|
pm.install(appId, versionCode, versionName);
|
||||||
Intent installIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
|
Intent installIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
|
||||||
installIntent.setData(Uri.parse("package:" + appId));
|
installIntent.setData(Uri.parse("package:" + appId));
|
||||||
@ -170,10 +171,11 @@ public class TestUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.fdroid.fdroid.TestUtils#installAndBroadcast(android.content.Context context, mock.MockInstallablePackageManager, String, int, String)
|
* @see org.fdroid.fdroid.TestUtils#installAndBroadcast(mock.MockContextSwappableComponents, mock.MockInstallablePackageManager, String, int, String)
|
||||||
*/
|
*/
|
||||||
public static void removeAndBroadcast(Context context, MockInstallablePackageManager pm, String appId) {
|
public static void removeAndBroadcast(MockContextSwappableComponents context, MockInstallablePackageManager pm, String appId) {
|
||||||
|
|
||||||
|
context.setPackageManager(pm);
|
||||||
pm.remove(appId);
|
pm.remove(appId);
|
||||||
Intent installIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
|
Intent installIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
|
||||||
installIntent.setData(Uri.parse("package:" + appId));
|
installIntent.setData(Uri.parse("package:" + appId));
|
||||||
|
@ -3,21 +3,15 @@ package org.fdroid.fdroid.updater;
|
|||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Environment;
|
|
||||||
import android.test.InstrumentationTestCase;
|
import android.test.InstrumentationTestCase;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.fdroid.fdroid.TestUtils;
|
import org.fdroid.fdroid.TestUtils;
|
||||||
import org.fdroid.fdroid.Utils;
|
|
||||||
import org.fdroid.fdroid.data.Repo;
|
import org.fdroid.fdroid.data.Repo;
|
||||||
import org.fdroid.fdroid.updater.RepoUpdater.UpdateException;
|
import org.fdroid.fdroid.updater.RepoUpdater.UpdateException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
@TargetApi(8)
|
@TargetApi(8)
|
||||||
public class SignedRepoUpdaterTest extends InstrumentationTestCase {
|
public class SignedRepoUpdaterTest extends InstrumentationTestCase {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user