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