├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build-config ├── checkstyle │ └── checkstyle.xml ├── gradle-push.gradle └── gradle-quality.gradle ├── build.gradle ├── demo ├── build.gradle ├── lint.xml ├── proguard.cfg └── src │ ├── gcm │ ├── AndroidManifest.xml │ └── res │ │ └── values │ │ └── strings.xml │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── evernote │ │ └── android │ │ └── job │ │ └── demo │ │ ├── App.java │ │ ├── DemoJobCreator.java │ │ ├── DemoSyncEngine.java │ │ ├── DemoSyncJob.java │ │ ├── FileUtils.java │ │ ├── MainActivity.java │ │ ├── SyncHistoryActivity.java │ │ └── UnitTestDatabaseCreator.java │ └── res │ ├── drawable-hdpi │ └── ic_notification.png │ ├── drawable-mdpi │ └── ic_notification.png │ ├── drawable-xhdpi │ └── ic_notification.png │ ├── drawable-xxhdpi │ └── ic_notification.png │ ├── layout │ ├── activity_main.xml │ └── activity_sync_history.xml │ ├── menu │ ├── activity_main.xml │ └── activity_sync_history.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ └── values │ ├── strings.xml │ └── styles.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── build.gradle ├── gradle.properties ├── lint.xml ├── proguard.cfg └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── evernote │ │ └── android │ │ └── job │ │ ├── DisabledServiceTest.java │ │ ├── Platform21Test.java │ │ ├── PlatformJobManagerRule.java │ │ ├── PlatformTest.java │ │ ├── PlatformWorkManagerRule.java │ │ ├── TransientBundleRescheduleTest.java │ │ ├── TransientBundleTest.java │ │ ├── TransientJobCleanUpTest.java │ │ ├── v21 │ │ └── TransientBundleCompatTest.java │ │ └── work │ │ └── PlatformWorkManagerTest.java │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ ├── android │ │ └── support │ │ │ └── v4 │ │ │ └── app │ │ │ ├── SafeJobIntentService.java │ │ │ └── SafeJobServiceEngineImpl.java │ │ └── com │ │ └── evernote │ │ └── android │ │ └── job │ │ ├── DailyJob.java │ │ ├── GcmAvailableHelper.java │ │ ├── Job.java │ │ ├── JobApi.java │ │ ├── JobBootReceiver.java │ │ ├── JobConfig.java │ │ ├── JobCreator.java │ │ ├── JobCreatorHolder.java │ │ ├── JobExecutor.java │ │ ├── JobIdsInternal.java │ │ ├── JobManager.java │ │ ├── JobManagerCreateException.java │ │ ├── JobProxy.java │ │ ├── JobProxyIllegalStateException.java │ │ ├── JobRequest.java │ │ ├── JobRescheduleService.java │ │ ├── JobStorage.java │ │ ├── JobStorageDatabaseErrorHandler.java │ │ ├── PendingIntentUtil.java │ │ ├── WakeLockUtil.java │ │ ├── WorkManagerAvailableHelper.java │ │ ├── gcm │ │ ├── JobProxyGcm.java │ │ └── PlatformGcmService.java │ │ ├── util │ │ ├── BatteryStatus.java │ │ ├── Clock.java │ │ ├── Device.java │ │ ├── JobCat.java │ │ ├── JobLogger.java │ │ ├── JobPreconditions.java │ │ ├── JobUtil.java │ │ └── support │ │ │ ├── FastXmlSerializer.java │ │ │ ├── PersistableBundleCompat.java │ │ │ └── XmlUtils.java │ │ ├── v14 │ │ ├── JobProxy14.java │ │ ├── PlatformAlarmReceiver.java │ │ ├── PlatformAlarmService.java │ │ └── PlatformAlarmServiceExact.java │ │ ├── v19 │ │ └── JobProxy19.java │ │ ├── v21 │ │ ├── JobProxy21.java │ │ ├── PlatformJobService.java │ │ └── TransientBundleCompat.java │ │ ├── v24 │ │ └── JobProxy24.java │ │ ├── v26 │ │ └── JobProxy26.java │ │ └── work │ │ ├── JobProxyWorkManager.java │ │ ├── PlatformWorker.java │ │ └── TransientBundleHolder.java │ └── test │ ├── java │ ├── android │ │ └── support │ │ │ └── v4 │ │ │ └── app │ │ │ └── JobIntentServiceReset.java │ └── com │ │ └── evernote │ │ └── android │ │ └── job │ │ ├── AsyncScheduleTest.java │ │ ├── BackoffCriteriaTests.java │ │ ├── BaseJobManagerTest.java │ │ ├── DailyJobTest.java │ │ ├── DatabaseCorruptionTest.java │ │ ├── DatabaseExistingTest.java │ │ ├── DatabaseFailureTest.java │ │ ├── DatabaseManualUpgradeTest.java │ │ ├── FailureCountTest.java │ │ ├── JobCanceledTest.java │ │ ├── JobConfigTest.java │ │ ├── JobCreatorHolderTest.java │ │ ├── JobExecutionTest.java │ │ ├── JobExecutorTest.java │ │ ├── JobManagerCreateTest.java │ │ ├── JobManagerRule.java │ │ ├── JobManagerTest.java │ │ ├── JobPeriodicCancelTest.java │ │ ├── JobProxyTest.java │ │ ├── JobRequestTest.java │ │ ├── JobRequirementTest.java │ │ ├── JobRescheduleTest.java │ │ ├── LastRunTest.java │ │ ├── test │ │ ├── DummyJobs.java │ │ ├── JobRobolectricTestRunner.java │ │ ├── TestClock.java │ │ └── TestLogger.java │ │ └── util │ │ ├── DeviceTest.java │ │ ├── JobUtilTest.java │ │ ├── LoggerTest.java │ │ └── support │ │ └── PersistableBundleCompatTest.java │ └── resources │ ├── databases │ ├── corrupted.db │ ├── evernote_jobs_v1.db │ ├── evernote_jobs_v2.db │ ├── evernote_jobs_v3.db │ ├── evernote_jobs_v4.db │ ├── evernote_jobs_v5.db │ └── evernote_jobs_v6.db │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker └── settings.gradle /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Before posting an issue, please check the following: 2 | 3 | * Do you have a question? Please take a look at the samples and read the FAQ first. 4 | ** https://github.com/evernote/android-job#usage 5 | ** https://github.com/evernote/android-job/wiki/FAQ 6 | 7 | * Does the library crash for you? Please provide the version of the library, a log, stacktrace and even better a sample. 8 | 9 | * Do your jobs not run as expected or do you see another weird behavior? A full log is necessary. Also keep in mind how Doze works and maintenance windows can delay your job. 10 | ** https://developer.android.com/training/monitoring-device-state/doze-standby.html 11 | ** https://speakerdeck.com/vrallev/scheduling-background-job-on-android-at-the-right-time-1?slide=59 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thanks for contributing to the library! The library should remain really lightweight, so please consider creating an issue first to discuss the problem you're facing. After that contributions are appreciated. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Android generated 2 | bin 3 | gen 4 | target 5 | out 6 | build 7 | 8 | #Eclipse 9 | .project 10 | .classpath 11 | .settings 12 | 13 | #IntelliJ IDEA 14 | .idea 15 | *.iml 16 | classes 17 | *.ipr 18 | *.iws 19 | gen-external-apklibs 20 | 21 | #Maven 22 | release.properties 23 | pom.xml.* 24 | 25 | #Ant 26 | build.xml 27 | ant.properties 28 | local.properties 29 | 30 | #Gradle 31 | .gradle 32 | 33 | #Command line 34 | proguard-project.txt 35 | .DS_Store 36 | .tmp -------------------------------------------------------------------------------- /build-config/checkstyle/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /build-config/gradle-push.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.vanniktech.maven.publish' 2 | 3 | //plugins.withId("com.vanniktech.maven.publish") { 4 | // mavenPublish { 5 | // sonatypeHost = "S01" 6 | // } 7 | //} 8 | -------------------------------------------------------------------------------- /build-config/gradle-quality.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'checkstyle' 2 | 3 | check.dependsOn 'checkstyle' 4 | 5 | task checkstyle(type: Checkstyle) { 6 | configFile file("${project.rootDir}/build-config/checkstyle/checkstyle.xml") 7 | source 'src' 8 | include '**/*.java' 9 | exclude '**/gen/**' 10 | exclude '**/XmlUtils.java' 11 | 12 | classpath = files() 13 | // 14 | // reports { 15 | // xml { 16 | // destination file("$project.buildDir/outputs/checkstyle-results.xml") 17 | // } 18 | // } 19 | } 20 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:7.1.3' 9 | classpath 'com.vanniktech:gradle-maven-publish-plugin:0.18.0' 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | mavenCentral() 17 | } 18 | 19 | tasks.withType(JavaCompile) { 20 | options.compilerArgs << "-Xlint:unchecked" 21 | options.compilerArgs << "-Xlint:deprecation" 22 | } 23 | } 24 | 25 | ext { 26 | compileSdkVersion = 31 27 | targetSdkVersion = compileSdkVersion 28 | minSdkVersion = 14 29 | 30 | playServicesVersion = '17.0.0' 31 | workVersion = '2.7.0' 32 | stethoVersion = '1.5.0' 33 | junitVersion = '4.13.2' 34 | assertjVersion = '3.6.2' 35 | mockitoVersion = '3.12.4' 36 | robolectricVersion = '4.7.3' 37 | } 38 | 39 | allprojects { 40 | configurations.all { 41 | resolutionStrategy.eachDependency { details -> 42 | if (details.requested.group == 'androidx.arch.core' 43 | && details.requested.name.contains('core-common')) { 44 | details.useVersion '2.0.1' 45 | } 46 | if (details.requested.group == 'androidx.collection' 47 | && details.requested.name.contains('collection')) { 48 | details.useVersion '1.0.0' 49 | } 50 | if (details.requested.group == 'androidx.lifecycle' 51 | && details.requested.name.contains('lifecycle-runtime')) { 52 | details.useVersion '2.0.0' 53 | } 54 | } 55 | } 56 | } 57 | 58 | task updateWrapper(type: Wrapper) { 59 | gradleVersion = '7.4.2' 60 | distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip" 61 | } 62 | -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply from: '../build-config/gradle-quality.gradle' 3 | 4 | android { 5 | compileSdkVersion rootProject.ext.compileSdkVersion 6 | 7 | defaultConfig { 8 | applicationId "com.evernote.android.job.demo" 9 | 10 | minSdkVersion rootProject.ext.minSdkVersion 11 | targetSdkVersion rootProject.ext.targetSdkVersion 12 | 13 | versionName project.VERSION_NAME 14 | versionCode Integer.parseInt(project.VERSION_CODE) 15 | } 16 | 17 | buildTypes { 18 | debug { 19 | minifyEnabled false 20 | proguardFiles 'proguard.cfg' 21 | } 22 | 23 | release { 24 | minifyEnabled true 25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard.cfg' 26 | signingConfig signingConfigs.debug 27 | } 28 | 29 | gcm.initWith(buildTypes.debug) 30 | gcm { 31 | applicationIdSuffix ".gcm" 32 | matchingFallbacks = ['debug', 'release'] 33 | } 34 | } 35 | 36 | compileOptions { 37 | sourceCompatibility JavaVersion.VERSION_1_7 38 | targetCompatibility JavaVersion.VERSION_1_7 39 | } 40 | 41 | lintOptions { 42 | abortOnError true 43 | } 44 | } 45 | 46 | dependencies { 47 | implementation project(':library') 48 | implementation "androidx.appcompat:appcompat:1.3.1" 49 | implementation "com.facebook.stetho:stetho:$stethoVersion" 50 | 51 | gcmImplementation "com.google.android.gms:play-services-gcm:$playServicesVersion" 52 | gcmImplementation "androidx.work:work-runtime:$workVersion" 53 | } 54 | 55 | //uploadArchives.enabled false -------------------------------------------------------------------------------- /demo/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /demo/proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 1 2 | -repackageclasses -------------------------------------------------------------------------------- /demo/src/gcm/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /demo/src/gcm/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Job GCM Demo 3 | 4 | -------------------------------------------------------------------------------- /demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /demo/src/main/java/com/evernote/android/job/demo/App.java: -------------------------------------------------------------------------------- 1 | package com.evernote.android.job.demo; 2 | 3 | import android.app.Application; 4 | import android.os.StrictMode; 5 | 6 | import com.evernote.android.job.JobManager; 7 | import com.facebook.stetho.Stetho; 8 | 9 | /** 10 | * @author rwondratschek 11 | */ 12 | public class App extends Application { 13 | 14 | @Override 15 | public void onCreate() { 16 | super.onCreate(); 17 | Stetho.initializeWithDefaults(this); 18 | 19 | if (BuildConfig.DEBUG) { 20 | StrictMode.setThreadPolicy( 21 | new StrictMode.ThreadPolicy.Builder() 22 | .detectAll() 23 | .penaltyLog() 24 | .penaltyDeath() 25 | .build()); 26 | 27 | StrictMode.setVmPolicy( 28 | new StrictMode.VmPolicy.Builder() 29 | .detectAll() 30 | .penaltyLog() 31 | .penaltyDeath() 32 | .build()); 33 | } 34 | 35 | JobManager.create(this).addJobCreator(new DemoJobCreator()); 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /demo/src/main/java/com/evernote/android/job/demo/DemoJobCreator.java: -------------------------------------------------------------------------------- 1 | package com.evernote.android.job.demo; 2 | 3 | import android.content.Context; 4 | import androidx.annotation.NonNull; 5 | 6 | import com.evernote.android.job.Job; 7 | import com.evernote.android.job.JobCreator; 8 | import com.evernote.android.job.JobManager; 9 | 10 | /** 11 | * @author rwondratschek 12 | */ 13 | public class DemoJobCreator implements JobCreator { 14 | 15 | @Override 16 | public Job create(@NonNull String tag) { 17 | switch (tag) { 18 | case DemoSyncJob.TAG: 19 | return new DemoSyncJob(); 20 | default: 21 | return null; 22 | } 23 | } 24 | 25 | public static final class AddReceiver extends AddJobCreatorReceiver { 26 | @Override 27 | protected void addJobCreator(@NonNull Context context, @NonNull JobManager manager) { 28 | // manager.addJobCreator(new DemoJobCreator()); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /demo/src/main/java/com/evernote/android/job/demo/DemoSyncEngine.java: -------------------------------------------------------------------------------- 1 | package com.evernote.android.job.demo; 2 | 3 | import android.content.Context; 4 | import android.os.Looper; 5 | import android.os.NetworkOnMainThreadException; 6 | import android.os.SystemClock; 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.WorkerThread; 9 | import android.util.Log; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.text.SimpleDateFormat; 14 | import java.util.Date; 15 | import java.util.Locale; 16 | 17 | /** 18 | * @author rwondratschek 19 | */ 20 | public class DemoSyncEngine { 21 | 22 | private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS", Locale.US); 23 | 24 | private final Context mContext; 25 | 26 | public DemoSyncEngine(Context context) { 27 | mContext = context; 28 | } 29 | 30 | @WorkerThread 31 | public boolean sync() { 32 | // do something fancy 33 | 34 | if (Looper.myLooper() == Looper.getMainLooper()) { 35 | throw new NetworkOnMainThreadException(); 36 | } 37 | 38 | SystemClock.sleep(1_000); 39 | boolean success = Math.random() > 0.1; // successful 90% of the time 40 | saveSuccess(success); 41 | return success; 42 | } 43 | 44 | @NonNull 45 | public String getSuccessHistory() { 46 | try { 47 | byte[] data = FileUtils.readFile(getSuccessFile()); 48 | if (data == null || data.length == 0) { 49 | return ""; 50 | } 51 | return new String(data); 52 | } catch (IOException e) { 53 | return ""; 54 | } 55 | } 56 | 57 | private void saveSuccess(boolean success) { 58 | String text = DATE_FORMAT.format(new Date()) + "\t\t" + success + '\n'; 59 | try { 60 | FileUtils.writeFile(getSuccessFile(), text, true); 61 | } catch (IOException e) { 62 | Log.e("Demo", e.getMessage(), e); 63 | } 64 | } 65 | 66 | private File getSuccessFile() { 67 | return new File(mContext.getCacheDir(), "success.txt"); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /demo/src/main/java/com/evernote/android/job/demo/DemoSyncJob.java: -------------------------------------------------------------------------------- 1 | package com.evernote.android.job.demo; 2 | 3 | import android.app.Notification; 4 | import android.app.NotificationChannel; 5 | import android.app.NotificationManager; 6 | import android.app.PendingIntent; 7 | import android.content.Intent; 8 | import android.graphics.Color; 9 | import android.os.Build; 10 | import androidx.annotation.NonNull; 11 | import androidx.core.app.NotificationCompat; 12 | import androidx.core.app.NotificationManagerCompat; 13 | 14 | import com.evernote.android.job.Job; 15 | 16 | import java.util.Random; 17 | 18 | import static com.evernote.android.job.PendingIntentUtil.flagImmutable; 19 | 20 | /** 21 | * @author rwondratschek 22 | */ 23 | public class DemoSyncJob extends Job { 24 | 25 | public static final String TAG = "job_demo_tag"; 26 | 27 | @Override 28 | @NonNull 29 | protected Result onRunJob(@NonNull final Params params) { 30 | boolean success = new DemoSyncEngine(getContext()).sync(); 31 | 32 | PendingIntent pendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(getContext(), MainActivity.class), flagImmutable()); 33 | 34 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 35 | NotificationChannel channel = new NotificationChannel(TAG, "Job Demo", NotificationManager.IMPORTANCE_LOW); 36 | channel.setDescription("Job demo job"); 37 | getContext().getSystemService(NotificationManager.class).createNotificationChannel(channel); 38 | } 39 | 40 | Notification notification = new NotificationCompat.Builder(getContext(), TAG) 41 | .setContentTitle("ID " + params.getId()) 42 | .setContentText("Job ran, exact " + params.isExact() + " , periodic " + params.isPeriodic() + ", transient " + params.isTransient()) 43 | .setAutoCancel(true) 44 | .setChannelId(TAG) 45 | .setSound(null) 46 | .setContentIntent(pendingIntent) 47 | .setSmallIcon(R.drawable.ic_notification) 48 | .setShowWhen(true) 49 | .setColor(Color.GREEN) 50 | .setLocalOnly(true) 51 | .build(); 52 | 53 | NotificationManagerCompat.from(getContext()).notify(new Random().nextInt(), notification); 54 | 55 | return success ? Result.SUCCESS : Result.FAILURE; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /demo/src/main/java/com/evernote/android/job/demo/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.evernote.android.job.demo; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.Closeable; 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.FileWriter; 9 | import java.io.IOException; 10 | 11 | /** 12 | * @author rwondratschek 13 | */ 14 | @SuppressWarnings("unused") 15 | public final class FileUtils { 16 | 17 | private FileUtils() { 18 | // no op 19 | } 20 | 21 | public static byte[] readFile(File file) throws IOException { 22 | FileInputStream fis = null; 23 | try { 24 | fis = new FileInputStream(file); 25 | byte[] buffer = new byte[(int) file.length()]; 26 | 27 | if (buffer.length != fis.read(buffer)) { 28 | return null; 29 | } else { 30 | return buffer; 31 | } 32 | 33 | } finally { 34 | close(fis); 35 | } 36 | } 37 | 38 | public static void writeFile(File file, String text, boolean append) throws IOException { 39 | if (file == null || text == null) { 40 | throw new IllegalArgumentException(); 41 | } 42 | 43 | if (!file.getParentFile().exists() && !file.getParentFile().mkdirs()) { 44 | throw new IOException("Could not create parent directory"); 45 | } 46 | 47 | if (!file.exists() && !file.createNewFile()) { 48 | throw new IOException("Could not create file"); 49 | } 50 | 51 | FileWriter writer = null; 52 | try { 53 | writer = new FileWriter(file, append); 54 | 55 | writer.write(text); 56 | 57 | } finally { 58 | close(writer); 59 | } 60 | } 61 | 62 | public static void delete(File file) throws IOException { 63 | if (!file.exists()) { 64 | return; 65 | } 66 | if (file.isDirectory()) { 67 | File[] files = file.listFiles(); 68 | for (File file1 : files) { 69 | delete(file1); 70 | } 71 | } 72 | if (!file.delete()) { 73 | throw new IOException("could not delete file " + file); 74 | } 75 | } 76 | 77 | public static void close(Closeable closeable) { 78 | if (closeable != null) { 79 | try { 80 | closeable.close(); 81 | } catch (Exception e) { 82 | Log.e("Demo", e.getMessage(), e); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /demo/src/main/java/com/evernote/android/job/demo/SyncHistoryActivity.java: -------------------------------------------------------------------------------- 1 | package com.evernote.android.job.demo; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.os.AsyncTask; 6 | import android.os.Bundle; 7 | import android.view.Menu; 8 | import android.view.MenuItem; 9 | import android.widget.TextView; 10 | 11 | /** 12 | * @author rwondratschek 13 | */ 14 | public class SyncHistoryActivity extends Activity { 15 | 16 | private TextView mTextView; 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_sync_history); 22 | 23 | mTextView = (TextView) findViewById(R.id.textView_log); 24 | refreshView(); 25 | } 26 | 27 | @Override 28 | public boolean onCreateOptionsMenu(Menu menu) { 29 | super.onCreateOptionsMenu(menu); 30 | getMenuInflater().inflate(R.menu.activity_sync_history, menu); 31 | return true; 32 | } 33 | 34 | @Override 35 | public boolean onOptionsItemSelected(MenuItem item) { 36 | switch (item.getItemId()) { 37 | case R.id.action_sync_now: 38 | syncAsynchronously(); 39 | return true; 40 | 41 | default: 42 | return super.onOptionsItemSelected(item); 43 | } 44 | } 45 | 46 | @SuppressLint("StaticFieldLeak") 47 | private void syncAsynchronously() { 48 | new AsyncTask() { 49 | @Override 50 | protected Boolean doInBackground(Void... params) { 51 | return new DemoSyncEngine(SyncHistoryActivity.this).sync(); 52 | } 53 | 54 | @Override 55 | protected void onPostExecute(Boolean aBoolean) { 56 | refreshView(); 57 | } 58 | }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 59 | } 60 | 61 | private void refreshView() { 62 | mTextView.setText(new DemoSyncEngine(this).getSuccessHistory()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /demo/src/main/res/drawable-hdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Evernote/android-job/adec0ae13d20e43b4d5dfce7e9eed7a07f98ae78/demo/src/main/res/drawable-hdpi/ic_notification.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-mdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Evernote/android-job/adec0ae13d20e43b4d5dfce7e9eed7a07f98ae78/demo/src/main/res/drawable-mdpi/ic_notification.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Evernote/android-job/adec0ae13d20e43b4d5dfce7e9eed7a07f98ae78/demo/src/main/res/drawable-xhdpi/ic_notification.png -------------------------------------------------------------------------------- /demo/src/main/res/drawable-xxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Evernote/android-job/adec0ae13d20e43b4d5dfce7e9eed7a07f98ae78/demo/src/main/res/drawable-xxhdpi/ic_notification.png -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 |