Merge branch 'JobIntentService-revamp' into 'master'
JobIntentService revamp Closes #1426 See merge request fdroid/fdroidclient!678
This commit is contained in:
commit
76150db0c2
@ -46,6 +46,7 @@
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.NFC"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
|
||||
<!-- Indicate that F-Droid may request root access (introduced by Koush's Superuser app)
|
||||
This permission is deprecated, but necessary for some old superuser
|
||||
@ -251,7 +252,10 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service android:name=".UpdateService"/>
|
||||
<service
|
||||
android:name=".UpdateService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:exported="false"/>
|
||||
<service
|
||||
android:name=".UpdateJobService"
|
||||
android:exported="false"
|
||||
@ -261,18 +265,26 @@
|
||||
android:exported="false"/>
|
||||
<service
|
||||
android:name=".installer.InstallerService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:exported="false"/>
|
||||
<service
|
||||
android:name=".CleanCacheService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:exported="false"/>
|
||||
<service
|
||||
android:name=".CleanCacheJobService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:exported="false"/>
|
||||
<service
|
||||
android:name=".DeleteCacheService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:exported="false"/>
|
||||
<service
|
||||
android:name=".net.WifiStateChangeService"
|
||||
android:exported="false"/>
|
||||
<service
|
||||
android:name=".net.ConnectivityMonitorService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:exported="false"/>
|
||||
<service android:name=".localrepo.SwapService"/>
|
||||
<service
|
||||
@ -286,6 +298,7 @@
|
||||
android:exported="false"/>
|
||||
<service
|
||||
android:name=".data.InstalledAppProviderService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:exported="false"/>
|
||||
<service
|
||||
android:name=".AppUpdateStatusService"
|
||||
|
@ -0,0 +1,22 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobService;
|
||||
|
||||
/**
|
||||
* Shim to run {@link CleanCacheService} with {@link android.app.job.JobScheduler}
|
||||
*/
|
||||
@TargetApi(21)
|
||||
public class CleanCacheJobService extends JobService {
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters jobParameters) {
|
||||
CleanCacheService.start(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters jobParameters) {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,13 +1,17 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.IntentService;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.JobIntentService;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.fdroid.fdroid.installer.ApkCache;
|
||||
|
||||
@ -27,7 +31,10 @@ import java.util.concurrent.TimeUnit;
|
||||
* and newer. On older Android, last modified time from {@link File#lastModified()}
|
||||
* is used.
|
||||
*/
|
||||
public class CleanCacheService extends IntentService {
|
||||
public class CleanCacheService extends JobIntentService {
|
||||
public static final String TAG = "CleanCacheService";
|
||||
|
||||
private static final int JOB_ID = 0x982374;
|
||||
|
||||
/**
|
||||
* Schedule or cancel this service to update the app index, according to the
|
||||
@ -41,28 +48,36 @@ public class CleanCacheService extends IntentService {
|
||||
interval = keepTime;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(context, CleanCacheService.class);
|
||||
PendingIntent pending = PendingIntent.getService(context, 0, intent, 0);
|
||||
if (Build.VERSION.SDK_INT < 21) {
|
||||
Intent intent = new Intent(context, CleanCacheService.class);
|
||||
PendingIntent pending = PendingIntent.getService(context, 0, intent, 0);
|
||||
|
||||
AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
alarm.cancel(pending);
|
||||
alarm.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
|
||||
SystemClock.elapsedRealtime() + 5000, interval, pending);
|
||||
AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
alarm.cancel(pending);
|
||||
alarm.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
|
||||
SystemClock.elapsedRealtime() + 5000, interval, pending);
|
||||
} else {
|
||||
Utils.debugLog(TAG, "Using android-21 JobScheduler for updates");
|
||||
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
|
||||
ComponentName componentName = new ComponentName(context, CleanCacheJobService.class);
|
||||
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, componentName)
|
||||
.setRequiresDeviceIdle(true)
|
||||
.setRequiresCharging(true)
|
||||
.setPeriodic(interval);
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
builder.setRequiresBatteryNotLow(true);
|
||||
}
|
||||
jobScheduler.schedule(builder.build());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void start(Context context) {
|
||||
context.startService(new Intent(context, CleanCacheService.class));
|
||||
}
|
||||
|
||||
public CleanCacheService() {
|
||||
super("CleanCacheService");
|
||||
enqueueWork(context, CleanCacheService.class, JOB_ID, new Intent(context, CleanCacheService.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
if (intent == null) {
|
||||
return;
|
||||
}
|
||||
protected void onHandleWork(@NonNull Intent intent) {
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
|
||||
deleteExpiredApksFromCache();
|
||||
deleteStrayIndexFiles();
|
||||
|
@ -1,9 +1,10 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Process;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.JobIntentService;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.Log;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
@ -11,25 +12,18 @@ import org.apache.commons.io.FileUtils;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* An {@link IntentService} subclass for deleting the full cache for this app.
|
||||
* An {@link JobIntentService} subclass for deleting the full cache for this app.
|
||||
*/
|
||||
public class DeleteCacheService extends IntentService {
|
||||
public class DeleteCacheService extends JobIntentService {
|
||||
public static final String TAG = "DeleteCacheService";
|
||||
|
||||
public DeleteCacheService() {
|
||||
super("DeleteCacheService");
|
||||
}
|
||||
|
||||
public static void deleteAll(Context context) {
|
||||
Intent intent = new Intent(context, DeleteCacheService.class);
|
||||
context.startService(intent);
|
||||
enqueueWork(context, DeleteCacheService.class, 0x523432, intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
if (intent == null) {
|
||||
return;
|
||||
}
|
||||
protected void onHandleWork(@NonNull Intent intent) {
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
|
||||
Log.w(TAG, "Deleting all cached contents!");
|
||||
try {
|
||||
|
@ -3,15 +3,10 @@ package org.fdroid.fdroid;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobService;
|
||||
import android.content.Intent;
|
||||
|
||||
/**
|
||||
* Interface between the new {@link android.app.job.JobScheduler} API and
|
||||
* our old {@link UpdateService}, which is based on {@link android.app.IntentService}.
|
||||
* This does not do things the way it should, e.g. stopping the job on
|
||||
* {@link #onStopJob(JobParameters)} and properly reporting
|
||||
* {@link #jobFinished(JobParameters, boolean)}, but this at least provides
|
||||
* the nice early triggering when there is good power/wifi available.
|
||||
* {@link UpdateService}, which is based on {@link android.support.v4.app.JobIntentService}.
|
||||
*
|
||||
* @see <a href="https://developer.android.com/about/versions/android-5.0.html#Power">Project Volta: Scheduling jobs</a>
|
||||
*/
|
||||
@ -19,25 +14,13 @@ import android.content.Intent;
|
||||
public class UpdateJobService extends JobService {
|
||||
@Override
|
||||
public boolean onStartJob(final JobParameters params) {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
// faking the actually run time
|
||||
try {
|
||||
startService(new Intent(UpdateJobService.this, UpdateService.class));
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {
|
||||
// ignored
|
||||
} finally {
|
||||
jobFinished(params, false);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
return true;
|
||||
UpdateService.updateNow(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters params) {
|
||||
return false;
|
||||
// TODO this should gracefully stop UpdateService
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -30,12 +30,15 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.JobIntentService;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.preference.PreferenceManager;
|
||||
@ -59,7 +62,7 @@ import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class UpdateService extends IntentService {
|
||||
public class UpdateService extends JobIntentService {
|
||||
|
||||
private static final String TAG = "UpdateService";
|
||||
|
||||
@ -68,7 +71,6 @@ public class UpdateService extends IntentService {
|
||||
public static final String EXTRA_MESSAGE = "msg";
|
||||
public static final String EXTRA_REPO_ERRORS = "repoErrors";
|
||||
public static final String EXTRA_STATUS_CODE = "status";
|
||||
public static final String EXTRA_ADDRESS = "address";
|
||||
public static final String EXTRA_MANUAL_UPDATE = "manualUpdate";
|
||||
public static final String EXTRA_FORCED_UPDATE = "forcedUpdate";
|
||||
public static final String EXTRA_PROGRESS = "progress";
|
||||
@ -81,6 +83,7 @@ public class UpdateService extends IntentService {
|
||||
public static final int STATUS_INFO = 5;
|
||||
|
||||
private static final String STATE_LAST_UPDATED = "lastUpdateCheck";
|
||||
private static final int JOB_ID = 0xfedcba;
|
||||
|
||||
private static final int NOTIFY_ID_UPDATING = 0;
|
||||
|
||||
@ -92,10 +95,6 @@ public class UpdateService extends IntentService {
|
||||
|
||||
private static boolean updating;
|
||||
|
||||
public UpdateService() {
|
||||
super("UpdateService");
|
||||
}
|
||||
|
||||
public static void updateNow(Context context) {
|
||||
updateRepoNow(context, null);
|
||||
}
|
||||
@ -104,9 +103,9 @@ public class UpdateService extends IntentService {
|
||||
Intent intent = new Intent(context, UpdateService.class);
|
||||
intent.putExtra(EXTRA_MANUAL_UPDATE, true);
|
||||
if (!TextUtils.isEmpty(address)) {
|
||||
intent.putExtra(EXTRA_ADDRESS, address);
|
||||
intent.setData(Uri.parse(address));
|
||||
}
|
||||
context.startService(intent);
|
||||
enqueueWork(context, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,7 +116,16 @@ public class UpdateService extends IntentService {
|
||||
public static void forceUpdateRepo(Context context) {
|
||||
Intent intent = new Intent(context, UpdateService.class);
|
||||
intent.putExtra(EXTRA_FORCED_UPDATE, true);
|
||||
context.startService(intent);
|
||||
enqueueWork(context, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add work to the queue for processing now
|
||||
*
|
||||
* @see JobIntentService#enqueueWork(Context, Class, int, Intent)
|
||||
*/
|
||||
private static void enqueueWork(Context context, @NonNull Intent intent) {
|
||||
enqueueWork(context, UpdateService.class, JOB_ID, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -150,7 +158,7 @@ public class UpdateService extends IntentService {
|
||||
Utils.debugLog(TAG, "Using android-21 JobScheduler for updates");
|
||||
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
|
||||
ComponentName componentName = new ComponentName(context, UpdateJobService.class);
|
||||
JobInfo.Builder builder = new JobInfo.Builder(0xfedcba, componentName)
|
||||
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, componentName)
|
||||
.setRequiresDeviceIdle(true)
|
||||
.setPeriodic(interval);
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
@ -368,18 +376,13 @@ public class UpdateService extends IntentService {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
protected void onHandleWork(@NonNull Intent intent) {
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
|
||||
|
||||
final long startTime = System.currentTimeMillis();
|
||||
boolean manualUpdate = false;
|
||||
boolean forcedUpdate = false;
|
||||
String address = null;
|
||||
if (intent != null) {
|
||||
address = intent.getStringExtra(EXTRA_ADDRESS); // TODO switch to Intent.setData()
|
||||
manualUpdate = intent.getBooleanExtra(EXTRA_MANUAL_UPDATE, false);
|
||||
forcedUpdate = intent.getBooleanExtra(EXTRA_FORCED_UPDATE, false);
|
||||
}
|
||||
boolean manualUpdate = intent.getBooleanExtra(EXTRA_MANUAL_UPDATE, false);
|
||||
boolean forcedUpdate = intent.getBooleanExtra(EXTRA_FORCED_UPDATE, false);
|
||||
String address = intent.getDataString();
|
||||
|
||||
try {
|
||||
final Preferences fdroidPrefs = Preferences.get();
|
||||
|
@ -492,6 +492,7 @@ public final class Utils {
|
||||
* probably warranted. See https://www.gitlab.com/fdroid/fdroidclient/issues/855
|
||||
* for more detail.
|
||||
*/
|
||||
@Nullable
|
||||
public static String getBinaryHash(File apk, String algo) {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
@ -514,12 +515,12 @@ public final class Utils {
|
||||
} else if (message.contains(" ENOENT ")) {
|
||||
Utils.debugLog(TAG, apk + " vanished: " + message);
|
||||
}
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} finally {
|
||||
closeQuietly(fis);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,6 +70,7 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
|
||||
public String versionName;
|
||||
public int versionCode;
|
||||
public int size; // Size in bytes - 0 means we don't know!
|
||||
@NonNull
|
||||
public String hash; // checksum of the APK, in lowercase hex
|
||||
public String hashType;
|
||||
public int minSdkVersion = SDK_VERSION_MIN_VALUE; // 0 if unknown
|
||||
@ -358,7 +359,7 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
|
||||
|
||||
@Override
|
||||
@TargetApi(19)
|
||||
public int compareTo(Apk apk) {
|
||||
public int compareTo(@NonNull Apk apk) {
|
||||
if (Build.VERSION.SDK_INT < 19) {
|
||||
return Integer.valueOf(versionCode).compareTo(apk.versionCode);
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.Schema.AntiFeatureTable;
|
||||
import org.fdroid.fdroid.data.Schema.ApkAntiFeatureJoinTable;
|
||||
@ -20,13 +19,13 @@ import org.fdroid.fdroid.data.Schema.PackageTable;
|
||||
import org.fdroid.fdroid.data.Schema.RepoTable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@SuppressWarnings("LineLength")
|
||||
public class ApkProvider extends FDroidProvider {
|
||||
|
||||
private static final String TAG = "ApkProvider";
|
||||
@ -41,7 +40,8 @@ public class ApkProvider extends FDroidProvider {
|
||||
|
||||
public static final class Helper {
|
||||
|
||||
private Helper() { }
|
||||
private Helper() {
|
||||
}
|
||||
|
||||
public static void update(Context context, Apk apk) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
@ -83,19 +83,21 @@ public class ApkProvider extends FDroidProvider {
|
||||
/**
|
||||
* Find an app which is closest to the version code suggested by the server, with some caveates:
|
||||
* <ul>
|
||||
* <li>If installed, limit to apks signed by the same signer as the installed apk.</li>
|
||||
* <li>Otherwise, limit to apks signed by the "preferred" signer (see {@link App#preferredSigner}).</li>
|
||||
* <li>If installed, limit to apks signed by the same signer as the installed apk.</li>
|
||||
* <li>Otherwise, limit to apks signed by the "preferred" signer (see {@link App#preferredSigner}).</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static Apk findSuggestedApk(Context context, App app) {
|
||||
return findApkFromAnyRepo(context, app.packageName, app.suggestedVersionCode, app.getMostAppropriateSignature());
|
||||
return findApkFromAnyRepo(context, app.packageName, app.suggestedVersionCode,
|
||||
app.getMostAppropriateSignature());
|
||||
}
|
||||
|
||||
public static Apk findApkFromAnyRepo(Context context, String packageName, int versionCode) {
|
||||
return findApkFromAnyRepo(context, packageName, versionCode, null, Cols.ALL);
|
||||
}
|
||||
|
||||
public static Apk findApkFromAnyRepo(Context context, String packageName, int versionCode, String signature) {
|
||||
public static Apk findApkFromAnyRepo(Context context, String packageName, int versionCode,
|
||||
String signature) {
|
||||
return findApkFromAnyRepo(context, packageName, versionCode, signature, Cols.ALL);
|
||||
}
|
||||
|
||||
@ -162,6 +164,9 @@ public class ApkProvider extends FDroidProvider {
|
||||
|
||||
@NonNull
|
||||
public static List<Apk> findApksByHash(Context context, String apkHash) {
|
||||
if (apkHash == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
final Uri uri = getContentUri();
|
||||
String selection = " apk." + Cols.HASH + " = ? ";
|
||||
@ -185,7 +190,7 @@ public class ApkProvider extends FDroidProvider {
|
||||
protected static final String PATH_REPO_APP = "repo-app";
|
||||
private static final String PATH_APKS = "apks";
|
||||
private static final String PATH_APP = "app";
|
||||
private static final String PATH_REPO = "repo";
|
||||
private static final String PATH_REPO = "repo";
|
||||
private static final String PATH_APK_ROW_ID = "apk-rowId";
|
||||
|
||||
private static final UriMatcher MATCHER = new UriMatcher(-1);
|
||||
@ -226,27 +231,27 @@ public class ApkProvider extends FDroidProvider {
|
||||
|
||||
public static Uri getAppUri(String packageName) {
|
||||
return getContentUri()
|
||||
.buildUpon()
|
||||
.appendPath(PATH_APP)
|
||||
.appendPath(packageName)
|
||||
.build();
|
||||
.buildUpon()
|
||||
.appendPath(PATH_APP)
|
||||
.appendPath(packageName)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Uri getRepoUri(long repoId) {
|
||||
return getContentUri()
|
||||
.buildUpon()
|
||||
.appendPath(PATH_REPO)
|
||||
.appendPath(Long.toString(repoId))
|
||||
.build();
|
||||
.buildUpon()
|
||||
.appendPath(PATH_REPO)
|
||||
.appendPath(Long.toString(repoId))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Uri getRepoUri(long repoId, String packageName) {
|
||||
return getContentUri()
|
||||
.buildUpon()
|
||||
.appendPath(PATH_REPO_APP)
|
||||
.appendPath(Long.toString(repoId))
|
||||
.appendPath(packageName)
|
||||
.build();
|
||||
.buildUpon()
|
||||
.appendPath(PATH_REPO_APP)
|
||||
.appendPath(Long.toString(repoId))
|
||||
.appendPath(packageName)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Uri getApkFromAnyRepoUri(Apk apk) {
|
||||
@ -296,10 +301,13 @@ public class ApkProvider extends FDroidProvider {
|
||||
private boolean antiFeaturesRequested;
|
||||
|
||||
/**
|
||||
* If the query includes anti features, then we group by apk id. This is because joining onto the anti-features
|
||||
* table will result in multiple result rows for each apk (potentially), so we will GROUP_CONCAT each of the
|
||||
* anti features into a single comma separated list for each apk. If we are _not_ including anti features, then
|
||||
* don't group by apk, because when doing a COUNT(*) this will result in the wrong result.
|
||||
* If the query includes anti features, then we group by apk id. This
|
||||
* is because joining onto the anti-features table will result in
|
||||
* multiple result rows for each apk (potentially), so we will
|
||||
* {@code GROUP_CONCAT} each of the anti-features into a single comma-
|
||||
* separated list for each apk. If we are _not_ including anti-
|
||||
* features, then don't group by apk, because when doing a COUNT(*)
|
||||
* this will result in the wrong result.
|
||||
*/
|
||||
@Override
|
||||
protected String groupBy() {
|
||||
@ -313,8 +321,8 @@ public class ApkProvider extends FDroidProvider {
|
||||
final String pkg = PackageTable.NAME;
|
||||
|
||||
return apk + " AS apk " +
|
||||
" LEFT JOIN " + app + " AS app ON (app." + AppMetadataTable.Cols.ROW_ID + " = apk." + Cols.APP_ID + ")" +
|
||||
" LEFT JOIN " + pkg + " AS pkg ON (pkg." + PackageTable.Cols.ROW_ID + " = app." + AppMetadataTable.Cols.PACKAGE_ID + ")";
|
||||
" LEFT JOIN " + app + " AS app ON (app." + AppMetadataTable.Cols.ROW_ID + " = apk." + Cols.APP_ID + ")" + // NOPMD NOCHECKSTYLE LineLength
|
||||
" LEFT JOIN " + pkg + " AS pkg ON (pkg." + PackageTable.Cols.ROW_ID + " = app." + AppMetadataTable.Cols.PACKAGE_ID + ")"; // NOPMD NOCHECKSTYLE LineLength
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -357,9 +365,11 @@ public class ApkProvider extends FDroidProvider {
|
||||
"apk." + Cols.ROW_ID + " = " + apkAntiFeature + "." + ApkAntiFeatureJoinTable.Cols.APK_ID);
|
||||
|
||||
leftJoin(AntiFeatureTable.NAME, antiFeature,
|
||||
apkAntiFeature + "." + ApkAntiFeatureJoinTable.Cols.ANTI_FEATURE_ID + " = " + antiFeature + "." + AntiFeatureTable.Cols.ROW_ID);
|
||||
apkAntiFeature + "." + ApkAntiFeatureJoinTable.Cols.ANTI_FEATURE_ID + " = "
|
||||
+ antiFeature + "." + AntiFeatureTable.Cols.ROW_ID);
|
||||
|
||||
appendField("group_concat(" + antiFeature + "." + AntiFeatureTable.Cols.NAME + ") as " + Cols.AntiFeatures.ANTI_FEATURES);
|
||||
appendField("group_concat(" + antiFeature + "." + AntiFeatureTable.Cols.NAME + ") as "
|
||||
+ Cols.AntiFeatures.ANTI_FEATURES);
|
||||
}
|
||||
}
|
||||
|
||||
@ -378,11 +388,11 @@ public class ApkProvider extends FDroidProvider {
|
||||
|
||||
String selection =
|
||||
alias + Cols.VERSION_CODE + " = ? AND " +
|
||||
alias + Cols.APP_ID + " IN (" + getMetadataIdFromPackageNameQuery() + ")";
|
||||
alias + Cols.APP_ID + " IN (" + getMetadataIdFromPackageNameQuery() + ")";
|
||||
|
||||
List<String> pathSegments = uri.getPathSegments();
|
||||
List<String> args = new ArrayList<>(3);
|
||||
args.add(pathSegments.get(1)); // First (0th) path segment is the word "apk" and we are not interested in it.
|
||||
args.add(pathSegments.get(1)); // 0th path segment is the word "apk" and we are not interested in it.
|
||||
args.add(pathSegments.get(2));
|
||||
|
||||
if (pathSegments.size() >= 4) {
|
||||
@ -438,8 +448,8 @@ public class ApkProvider extends FDroidProvider {
|
||||
final String[] apkDetails = apkKeys.split(",");
|
||||
if (apkDetails.length > MAX_APKS_TO_QUERY) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot query more than " + MAX_APKS_TO_QUERY + ". " +
|
||||
"You tried to query " + apkDetails.length);
|
||||
"Cannot query more than " + MAX_APKS_TO_QUERY + ". " +
|
||||
"You tried to query " + apkDetails.length);
|
||||
}
|
||||
String alias = includeAlias ? "apk." : "";
|
||||
final String[] args = new String[apkDetails.length * 2];
|
||||
@ -569,7 +579,9 @@ public class ApkProvider extends FDroidProvider {
|
||||
}
|
||||
|
||||
protected void ensureAntiFeatures(String[] antiFeatures, long apkId) {
|
||||
db().delete(getApkAntiFeatureJoinTableName(), ApkAntiFeatureJoinTable.Cols.APK_ID + " = ?", new String[] {Long.toString(apkId)});
|
||||
db().delete(getApkAntiFeatureJoinTableName(),
|
||||
ApkAntiFeatureJoinTable.Cols.APK_ID + " = ?",
|
||||
new String[]{Long.toString(apkId)});
|
||||
if (antiFeatures != null) {
|
||||
Set<String> antiFeatureSet = new HashSet<>();
|
||||
for (String antiFeatureName : antiFeatures) {
|
||||
@ -594,7 +606,8 @@ public class ApkProvider extends FDroidProvider {
|
||||
|
||||
protected long ensureAntiFeature(String antiFeatureName) {
|
||||
long antiFeatureId = 0;
|
||||
Cursor cursor = db().query(AntiFeatureTable.NAME, new String[] {AntiFeatureTable.Cols.ROW_ID}, AntiFeatureTable.Cols.NAME + " = ?", new String[]{antiFeatureName}, null, null, null);
|
||||
Cursor cursor = db().query(AntiFeatureTable.NAME, new String[]{AntiFeatureTable.Cols.ROW_ID},
|
||||
AntiFeatureTable.Cols.NAME + " = ?", new String[]{antiFeatureName}, null, null, null);
|
||||
if (cursor != null) {
|
||||
if (cursor.getCount() > 0) {
|
||||
cursor.moveToFirst();
|
||||
|
@ -369,15 +369,33 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
|
||||
|
||||
/**
|
||||
* Instantiate from a locally installed package.
|
||||
* <p>
|
||||
* Initializes an {@link App} instances from an APK file. Since the file
|
||||
* could in the cache, and files can disappear from the cache at any time,
|
||||
* this needs to be quite defensive ensuring that {@code apkFile} still
|
||||
* exists.
|
||||
*/
|
||||
public App(Context context, PackageManager pm, String packageName)
|
||||
@Nullable
|
||||
public static App getInstance(Context context, PackageManager pm, String packageName)
|
||||
throws CertificateEncodingException, IOException, PackageManager.NameNotFoundException {
|
||||
|
||||
App app = new App();
|
||||
PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
|
||||
setFromPackageInfo(pm, packageInfo);
|
||||
this.installedApk = new Apk();
|
||||
SanitizedFile apkFile = SanitizedFile.knownSanitized(packageInfo.applicationInfo.publicSourceDir);
|
||||
initApkFromApkFile(context, this.installedApk, packageInfo, apkFile);
|
||||
if (apkFile.canRead()) {
|
||||
String hashType = "SHA-256";
|
||||
String hash = Utils.getBinaryHash(apkFile, hashType);
|
||||
if (TextUtils.isEmpty(hash)) {
|
||||
return null;
|
||||
}
|
||||
app.installedApk.hashType = hashType;
|
||||
app.installedApk.hash = hash;
|
||||
app.installedApk.sig = Utils.getPackageSig(packageInfo);
|
||||
}
|
||||
|
||||
app.setFromPackageInfo(pm, packageInfo);
|
||||
app.installedApk = new Apk();
|
||||
app.initInstalledApk(context, app.installedApk, packageInfo, apkFile);
|
||||
return app;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -704,22 +722,6 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
|
||||
this.compatible = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes an {@link App} instances from an APK file. Since the file
|
||||
* could in the cache, and files can disappear from the cache at any time,
|
||||
* this needs to be quite defensive ensuring that {@code apkFile} still
|
||||
* exists.
|
||||
*/
|
||||
private void initApkFromApkFile(Context context, Apk apk, PackageInfo packageInfo, SanitizedFile apkFile)
|
||||
throws IOException, CertificateEncodingException {
|
||||
if (apkFile.canRead()) {
|
||||
apk.hashType = "sha256";
|
||||
apk.hash = Utils.getBinaryHash(apkFile, apk.hashType);
|
||||
apk.sig = Utils.getPackageSig(packageInfo);
|
||||
}
|
||||
initInstalledApk(context, apk, packageInfo, apkFile);
|
||||
}
|
||||
|
||||
public static void initInstalledObbFiles(Apk apk) {
|
||||
File obbdir = getObbDir(apk.packageName);
|
||||
FileFilter filter = new RegexFileFilter("(main|patch)\\.[0-9-][0-9]*\\." + apk.packageName + "\\.obb");
|
||||
@ -743,6 +745,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("EmptyForIteratorPad")
|
||||
private void initInstalledApk(Context context, Apk apk, PackageInfo packageInfo, SanitizedFile apkFile)
|
||||
throws IOException, CertificateEncodingException {
|
||||
apk.compatible = true;
|
||||
@ -775,7 +778,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
|
||||
JarFile apkJar = new JarFile(apkFile);
|
||||
HashSet<String> abis = new HashSet<>(3);
|
||||
Pattern pattern = Pattern.compile("^lib/([a-z0-9-]+)/.*");
|
||||
for (Enumeration<JarEntry> jarEntries = apkJar.entries(); jarEntries.hasMoreElements();) {
|
||||
for (Enumeration<JarEntry> jarEntries = apkJar.entries(); jarEntries.hasMoreElements(); ) {
|
||||
JarEntry jarEntry = jarEntries.nextElement();
|
||||
Matcher matcher = pattern.matcher(jarEntry.getName());
|
||||
if (matcher.matches()) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.fdroid.fdroid.data;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -8,7 +7,9 @@ import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Process;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.JobIntentService;
|
||||
import android.util.Log;
|
||||
import org.acra.ACRA;
|
||||
import org.fdroid.fdroid.AppUpdateStatusManager;
|
||||
@ -40,7 +41,7 @@ import java.util.concurrent.TimeUnit;
|
||||
* of this stuff.
|
||||
*/
|
||||
@SuppressWarnings("LineLength")
|
||||
public class InstalledAppProviderService extends IntentService {
|
||||
public class InstalledAppProviderService extends JobIntentService {
|
||||
private static final String TAG = "InstalledAppProviderSer";
|
||||
|
||||
private static final String ACTION_INSERT = "org.fdroid.fdroid.data.action.INSERT";
|
||||
@ -56,10 +57,6 @@ public class InstalledAppProviderService extends IntentService {
|
||||
*/
|
||||
private PublishSubject<String> packageChangeNotifier;
|
||||
|
||||
public InstalledAppProviderService() {
|
||||
super("InstalledAppProviderService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
@ -121,7 +118,7 @@ public class InstalledAppProviderService extends IntentService {
|
||||
intent.setAction(ACTION_INSERT);
|
||||
intent.setData(uri);
|
||||
intent.putExtra(EXTRA_PACKAGE_INFO, packageInfo);
|
||||
context.startService(intent);
|
||||
enqueueWork(context, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,7 +135,11 @@ public class InstalledAppProviderService extends IntentService {
|
||||
Intent intent = new Intent(context, InstalledAppProviderService.class);
|
||||
intent.setAction(ACTION_DELETE);
|
||||
intent.setData(uri);
|
||||
context.startService(intent);
|
||||
enqueueWork(context, intent);
|
||||
}
|
||||
|
||||
private static void enqueueWork(Context context, Intent intent) {
|
||||
enqueueWork(context, InstalledAppProviderService.class, 0x192834, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -153,7 +154,7 @@ public class InstalledAppProviderService extends IntentService {
|
||||
* The installed app cache could get out of sync, e.g. if F-Droid crashed/ or
|
||||
* ran out of battery half way through responding to {@link Intent#ACTION_PACKAGE_ADDED}.
|
||||
* This method returns immediately, and will continue to work in an
|
||||
* {@link IntentService}. It doesn't really matter where we put this in the
|
||||
* {@link JobIntentService}. It doesn't really matter where we put this in the
|
||||
* bootstrap process, because it runs in its own thread, at the lowest priority:
|
||||
* {@link Process#THREAD_PRIORITY_LOWEST}.
|
||||
* <p>
|
||||
@ -218,11 +219,8 @@ public class InstalledAppProviderService extends IntentService {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
protected void onHandleWork(@NonNull Intent intent) {
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
|
||||
if (intent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String packageName = intent.getData().getSchemeSpecificPart();
|
||||
final String action = intent.getAction();
|
||||
|
@ -24,6 +24,7 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
|
||||
/**
|
||||
@ -37,7 +38,7 @@ public class DefaultInstaller extends Installer {
|
||||
|
||||
public static final String TAG = "DefaultInstaller";
|
||||
|
||||
DefaultInstaller(Context context, Apk apk) {
|
||||
DefaultInstaller(Context context, @NonNull Apk apk) {
|
||||
super(context, apk);
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import org.fdroid.fdroid.BuildConfig;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.privileged.install.InstallExtensionDialogActivity;
|
||||
@ -44,7 +45,7 @@ import java.io.File;
|
||||
@Deprecated
|
||||
public class ExtensionInstaller extends Installer {
|
||||
|
||||
ExtensionInstaller(Context context, Apk apk) {
|
||||
ExtensionInstaller(Context context, @NonNull Apk apk) {
|
||||
super(context, apk);
|
||||
}
|
||||
|
||||
|
@ -24,11 +24,12 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
|
||||
public class FileInstaller extends Installer {
|
||||
|
||||
public FileInstaller(Context context, Apk apk) {
|
||||
public FileInstaller(Context context, @NonNull Apk apk) {
|
||||
super(context, apk);
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.PatternMatcher;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
@ -77,7 +78,7 @@ public abstract class Installer {
|
||||
* @param apk must be included so that all the phases of the install process
|
||||
* can get all the data about the app, even after F-Droid was killed
|
||||
*/
|
||||
Installer(Context context, Apk apk) {
|
||||
Installer(Context context, @NonNull Apk apk) {
|
||||
this.context = context;
|
||||
this.apk = apk;
|
||||
}
|
||||
@ -144,9 +145,10 @@ public abstract class Installer {
|
||||
}
|
||||
|
||||
PackageManager pm = context.getPackageManager();
|
||||
if (Build.VERSION.SDK_INT >= 24 && (
|
||||
pm.getInstallerPackageName(apk.packageName).equals("com.android.packageinstaller")
|
||||
|| pm.getInstallerPackageName(apk.packageName).equals("com.google.android.packageinstaller"))) {
|
||||
String installerPackageName = pm.getInstallerPackageName(apk.packageName);
|
||||
if (Build.VERSION.SDK_INT >= 24 &&
|
||||
("com.android.packageinstaller".equals(installerPackageName)
|
||||
|| "com.google.android.packageinstaller".equals(installerPackageName))) {
|
||||
Utils.debugLog(TAG, "Falling back to default installer for uninstall");
|
||||
Intent intent = new Intent(context, DefaultInstallerActivity.class);
|
||||
intent.setAction(DefaultInstallerActivity.ACTION_UNINSTALL_PACKAGE);
|
||||
|
@ -21,8 +21,8 @@
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
|
||||
@ -39,12 +39,11 @@ public class InstallerFactory {
|
||||
* @param apk to be installed, always required.
|
||||
* @return instance of an Installer
|
||||
*/
|
||||
public static Installer create(Context context, Apk apk) {
|
||||
if (apk == null || TextUtils.isEmpty(apk.packageName)) {
|
||||
public static Installer create(Context context, @NonNull Apk apk) {
|
||||
if (TextUtils.isEmpty(apk.packageName)) {
|
||||
throw new IllegalArgumentException("Apk.packageName must not be empty: " + apk);
|
||||
}
|
||||
|
||||
|
||||
Installer installer;
|
||||
if (!apk.isApk()) {
|
||||
Utils.debugLog(TAG, "Using FileInstaller for non-apk file");
|
||||
|
@ -20,11 +20,11 @@
|
||||
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.JobIntentService;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.filefilter.WildcardFileFilter;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
@ -37,11 +37,13 @@ import java.io.FileFilter;
|
||||
* This service handles the install process of apk files and
|
||||
* uninstall process of apps.
|
||||
* <p>
|
||||
* This service is based on an IntentService because:
|
||||
* - no parallel installs/uninstalls should be allowed,
|
||||
* i.e., runs sequentially
|
||||
* - no cancel operation is needed. Cancelling an installation
|
||||
* would be the same as starting uninstall afterwards
|
||||
* This service is based on an JobIntentService because:
|
||||
* <ul>
|
||||
* <li>no parallel installs/uninstalls should be allowed,
|
||||
* i.e., runs sequentially</li>
|
||||
* <li>no cancel operation is needed. Cancelling an installation
|
||||
* would be the same as starting uninstall afterwards</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The download URL is only used as the unique ID that represents this
|
||||
* particular apk throughout the whole install process in
|
||||
@ -52,23 +54,15 @@ import java.io.FileFilter;
|
||||
* <a href="https://developer.android.com/google/play/expansion-files.html">
|
||||
* APK Expansion Files</a> spec.
|
||||
*/
|
||||
public class InstallerService extends IntentService {
|
||||
public class InstallerService extends JobIntentService {
|
||||
public static final String TAG = "InstallerService";
|
||||
|
||||
private static final String ACTION_INSTALL = "org.fdroid.fdroid.installer.InstallerService.action.INSTALL";
|
||||
private static final String ACTION_UNINSTALL = "org.fdroid.fdroid.installer.InstallerService.action.UNINSTALL";
|
||||
|
||||
public InstallerService() {
|
||||
super("InstallerService");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
protected void onHandleWork(@NonNull Intent intent) {
|
||||
final Apk apk = intent.getParcelableExtra(Installer.EXTRA_APK);
|
||||
if (apk == null) {
|
||||
Utils.debugLog(TAG, "ignoring intent with null EXTRA_APK: " + intent);
|
||||
return;
|
||||
}
|
||||
Installer installer = InstallerFactory.create(this, apk);
|
||||
|
||||
if (ACTION_INSTALL.equals(intent.getAction())) {
|
||||
@ -117,7 +111,7 @@ public class InstallerService extends IntentService {
|
||||
intent.setData(localApkUri);
|
||||
intent.putExtra(Installer.EXTRA_DOWNLOAD_URI, downloadUri);
|
||||
intent.putExtra(Installer.EXTRA_APK, apk);
|
||||
context.startService(intent);
|
||||
enqueueWork(context, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,7 +124,10 @@ public class InstallerService extends IntentService {
|
||||
Intent intent = new Intent(context, InstallerService.class);
|
||||
intent.setAction(ACTION_UNINSTALL);
|
||||
intent.putExtra(Installer.EXTRA_APK, apk);
|
||||
context.startService(intent);
|
||||
enqueueWork(context, intent);
|
||||
}
|
||||
|
||||
private static void enqueueWork(Context context, @NonNull Intent intent) {
|
||||
enqueueWork(context, InstallerService.class, 0x872394, intent);
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.R;
|
||||
@ -256,7 +257,7 @@ public class PrivilegedInstaller extends Installer {
|
||||
"device owner has marked the package as uninstallable.");
|
||||
}
|
||||
|
||||
public PrivilegedInstaller(Context context, Apk apk) {
|
||||
public PrivilegedInstaller(Context context, @NonNull Apk apk) {
|
||||
super(context, apk);
|
||||
}
|
||||
|
||||
|
@ -74,8 +74,10 @@ public class CacheSwapAppsService extends IntentService {
|
||||
try {
|
||||
PackageManager pm = getPackageManager();
|
||||
String packageName = intent.getData().getSchemeSpecificPart();
|
||||
App app = new App(this, pm, packageName);
|
||||
SwapService.putAppInCache(packageName, app);
|
||||
App app = App.getInstance(this, pm, packageName);
|
||||
if (app != null) {
|
||||
SwapService.putAppInCache(packageName, app);
|
||||
}
|
||||
} catch (CertificateEncodingException | IOException | PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
@ -259,9 +259,9 @@ public final class LocalRepoManager {
|
||||
try {
|
||||
app = SwapService.getAppFromCache(packageName);
|
||||
if (app == null) {
|
||||
app = new App(context.getApplicationContext(), pm, packageName);
|
||||
app = App.getInstance(context.getApplicationContext(), pm, packageName);
|
||||
}
|
||||
if (!app.isValid()) {
|
||||
if (app == null || !app.isValid()) {
|
||||
return;
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException | CertificateEncodingException | IOException e) {
|
||||
|
@ -94,7 +94,7 @@ public class SwapService extends Service {
|
||||
return INSTALLED_APPS.get(packageName);
|
||||
}
|
||||
|
||||
static void putAppInCache(String packageName, App app) {
|
||||
static void putAppInCache(String packageName, @NonNull App app) {
|
||||
INSTALLED_APPS.put(packageName, app);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.fdroid.fdroid.net;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -8,16 +7,18 @@ import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.JobIntentService;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
|
||||
/**
|
||||
* An {@link IntentService} subclass for tracking whether there is metered or
|
||||
* An {@link JobIntentService} subclass for tracking whether there is metered or
|
||||
* unmetered internet available, based on
|
||||
* {@link android.net.ConnectivityManager#CONNECTIVITY_ACTION}
|
||||
*/
|
||||
public class ConnectivityMonitorService extends IntentService {
|
||||
public class ConnectivityMonitorService extends JobIntentService {
|
||||
public static final String TAG = "ConnectivityMonitorServ";
|
||||
|
||||
public static final int FLAG_NET_UNAVAILABLE = 0;
|
||||
@ -33,10 +34,10 @@ public class ConnectivityMonitorService extends IntentService {
|
||||
}
|
||||
};
|
||||
|
||||
public ConnectivityMonitorService() {
|
||||
super("ConnectivityMonitorService");
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the {@link BroadcastReceiver} which also starts this
|
||||
* {@code Service} since it is a sticky broadcast.
|
||||
*/
|
||||
public static void registerAndStart(Context context) {
|
||||
context.registerReceiver(CONNECTIVITY_RECEIVER, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
}
|
||||
@ -44,7 +45,7 @@ public class ConnectivityMonitorService extends IntentService {
|
||||
public static void start(Context context) {
|
||||
Intent intent = new Intent(context, ConnectivityMonitorService.class);
|
||||
intent.setAction(ACTION_START);
|
||||
context.startService(intent);
|
||||
enqueueWork(context, ConnectivityMonitorService.class, 0x982ae7b, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,8 +78,8 @@ public class ConnectivityMonitorService extends IntentService {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
if (intent != null && ACTION_START.equals(intent.getAction())) {
|
||||
protected void onHandleWork(@NonNull Intent intent) {
|
||||
if (ACTION_START.equals(intent.getAction())) {
|
||||
FDroidApp.networkState = getNetworkState(this);
|
||||
ImageLoader.getInstance().denyNetworkDownloads(!Preferences.get().isBackgroundDownloadAllowed());
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ import java.util.Locale;
|
||||
* This also schedules an update to encourage updates happening on
|
||||
* unmetered networks like typical WiFi rather than networks that can
|
||||
* cost money or have caps. The logic for checking the state of the
|
||||
* internet connection is in {@link org.fdroid.fdroid.UpdateService#onHandleIntent(Intent)}
|
||||
* internet connection is in {@link org.fdroid.fdroid.UpdateService#onHandleWork(Intent)}
|
||||
* <p>
|
||||
* Some devices send multiple copies of given events, like a Moto G often
|
||||
* sends three {@code CONNECTED} events. So they have to be debounced to
|
||||
|
@ -450,19 +450,24 @@ public class AppDetailsRecyclerViewAdapter
|
||||
}
|
||||
}
|
||||
});
|
||||
if (app.antiFeatures != null) {
|
||||
if (app.antiFeatures != null && app.antiFeatures.length > 0) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<ul>");
|
||||
for (String af : app.antiFeatures) {
|
||||
String afdesc = descAntiFeature(af);
|
||||
sb.append("\t• ").append(afdesc).append('\n');
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
sb.setLength(sb.length() - 1);
|
||||
antiFeaturesView.setText(sb.toString());
|
||||
} else {
|
||||
antiFeaturesView.setVisibility(View.GONE);
|
||||
sb.append("<li><a href=\"https://f-droid.org/wiki/page/Antifeature:")
|
||||
.append(af)
|
||||
.append("\">")
|
||||
.append(afdesc)
|
||||
.append("</a></li>");
|
||||
}
|
||||
sb.append("</ul>");
|
||||
antiFeaturesView.setText(Html.fromHtml(sb.toString()));
|
||||
antiFeaturesView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
} else {
|
||||
antiFeaturesView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
updateAntiFeaturesWarning();
|
||||
buttonSecondaryView.setText(R.string.menu_uninstall);
|
||||
buttonSecondaryView.setVisibility(app.isUninstallable(context) ? View.VISIBLE : View.INVISIBLE);
|
||||
@ -538,6 +543,10 @@ public class AppDetailsRecyclerViewAdapter
|
||||
return itemView.getContext().getString(R.string.antiupstreamnonfreelist);
|
||||
case "NonFreeAssets":
|
||||
return itemView.getContext().getString(R.string.antinonfreeassetslist);
|
||||
case "DisabledAlgorithm":
|
||||
return itemView.getContext().getString(R.string.antidisabledalgorithmlist);
|
||||
case "KnownVuln":
|
||||
return itemView.getContext().getString(R.string.antiknownvulnlist);
|
||||
default:
|
||||
return af;
|
||||
}
|
||||
@ -664,7 +673,8 @@ public class AppDetailsRecyclerViewAdapter
|
||||
contentView = (LinearLayout) view.findViewById(R.id.ll_content);
|
||||
}
|
||||
|
||||
protected abstract @DrawableRes int getIcon();
|
||||
@DrawableRes
|
||||
protected abstract int getIcon();
|
||||
|
||||
/**
|
||||
* Depending on whether we are expanded or not, update the icon which indicates whether the
|
||||
@ -694,7 +704,8 @@ public class AppDetailsRecyclerViewAdapter
|
||||
updateExpandableItem(showVersions);
|
||||
}
|
||||
|
||||
protected @DrawableRes int getIcon() {
|
||||
@DrawableRes
|
||||
protected int getIcon() {
|
||||
return R.drawable.ic_access_time_24dp_grey600;
|
||||
}
|
||||
}
|
||||
@ -794,7 +805,8 @@ public class AppDetailsRecyclerViewAdapter
|
||||
contentView.addView(perms.getPermissionsView(AppSecurityPermissions.WHICH_ALL));
|
||||
}
|
||||
|
||||
protected @DrawableRes int getIcon() {
|
||||
@DrawableRes
|
||||
protected int getIcon() {
|
||||
return R.drawable.ic_lock_24dp_grey600;
|
||||
}
|
||||
}
|
||||
@ -867,7 +879,8 @@ public class AppDetailsRecyclerViewAdapter
|
||||
}
|
||||
}
|
||||
|
||||
protected @DrawableRes int getIcon() {
|
||||
@DrawableRes
|
||||
protected int getIcon() {
|
||||
return R.drawable.ic_website;
|
||||
}
|
||||
}
|
||||
|
@ -203,6 +203,8 @@ This often occurs with apps installed via Google Play or other sources, if they
|
||||
<string name="antinonfreedeplist">This app depends on other non-free apps</string>
|
||||
<string name="antiupstreamnonfreelist">The upstream source code is not entirely Free</string>
|
||||
<string name="antinonfreeassetslist">This app contains non-free assets</string>
|
||||
<string name="antidisabledalgorithmlist">This app has a weak security signature</string>
|
||||
<string name="antiknownvulnlist">This app contains a known security vulnerability</string>
|
||||
|
||||
<string name="display">Display</string>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user