jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/388269 )

Change subject: Allow users to cancel/pause/resume the downloading process when 
syncing  - Use BroadcastReceiver to receive the actions from notification bar  
- For < API19, use regular activity to receive the cancel action from 
notification bar  - When login / syncing, 
......................................................................


Allow users to cancel/pause/resume the downloading process when syncing
 - Use BroadcastReceiver to receive the actions from notification bar
 - For < API19, use regular activity to receive the cancel action from 
notification bar
 - When login / syncing, the notification bar will be displayed.

Please follow the QA steps on Phabricator

Bug: T177518
Change-Id: If53a948051a30fe76e3a164b03c1167271fa7f1a
---
M app/src/main/AndroidManifest.xml
M app/src/main/java/org/wikipedia/Constants.java
M app/src/main/java/org/wikipedia/activity/BaseActivity.java
M app/src/main/java/org/wikipedia/feed/FeedFragment.java
M app/src/main/java/org/wikipedia/login/LoginActivity.java
M app/src/main/java/org/wikipedia/readinglist/database/ReadingListTable.java
M app/src/main/java/org/wikipedia/readinglist/page/ReadingListPage.java
M 
app/src/main/java/org/wikipedia/readinglist/page/database/ReadingListPageDao.java
M app/src/main/java/org/wikipedia/readinglist/sync/ReadingListSynchronizer.java
A app/src/main/java/org/wikipedia/savedpages/SavedPageSyncBroadcastReceiver.java
A app/src/main/java/org/wikipedia/savedpages/SavedPageSyncNotification.java
M app/src/main/java/org/wikipedia/savedpages/SavedPageSyncService.java
M app/src/main/java/org/wikipedia/util/MathUtil.java
A app/src/main/res/drawable/ic_cancel_black_24dp.xml
A app/src/main/res/drawable/ic_file_download_white_24dp.xml
A app/src/main/res/drawable/ic_pause_black_24dp.xml
A app/src/main/res/drawable/ic_play_arrow_black_24dp.xml
M app/src/main/res/values-qq/strings.xml
M app/src/main/res/values/strings.xml
19 files changed, 454 insertions(+), 23 deletions(-)

Approvals:
  Dbrant: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 02b4856..e01cd5d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -346,5 +346,9 @@
             android:name=".savedpages.SavedPageSyncService"
             android:permission="android.permission.BIND_JOB_SERVICE" />
 
+        <!-- TODO: Investigate the addAction buttons with same Intent class 
issue -->
+        <receiver 
android:name=".savedpages.SavedPageSyncBroadcastReceiver$SyncCancelReceiver" />
+        <receiver 
android:name=".savedpages.SavedPageSyncBroadcastReceiver$SyncPauseReceiver" />
+
     </application>
 </manifest>
diff --git a/app/src/main/java/org/wikipedia/Constants.java 
b/app/src/main/java/org/wikipedia/Constants.java
index 8d42d6e..8f26db9 100644
--- a/app/src/main/java/org/wikipedia/Constants.java
+++ b/app/src/main/java/org/wikipedia/Constants.java
@@ -36,6 +36,9 @@
     public static final String INTENT_EXTRA_REVERT_QNUMBER = "revertQNumber";
     public static final String INTENT_EXTRA_DELETE_READING_LIST = 
"deleteReadingList";
 
+    public static final String INTENT_EXTRA_NOTIFICATION_SYNC_PAUSE = 
"syncPause";
+    public static final String INTENT_EXTRA_NOTIFICATION_SYNC_CANCEL = 
"syncCancel";
+
     public static final int PROGRESS_BAR_MAX_VALUE = 10_000;
 
     public static final int MAX_SUGGESTION_RESULTS = 3;
diff --git a/app/src/main/java/org/wikipedia/activity/BaseActivity.java 
b/app/src/main/java/org/wikipedia/activity/BaseActivity.java
index 0e805cf..1b0a2e9 100644
--- a/app/src/main/java/org/wikipedia/activity/BaseActivity.java
+++ b/app/src/main/java/org/wikipedia/activity/BaseActivity.java
@@ -198,7 +198,7 @@
         public void onReceive(Context context, Intent intent) {
             if (DeviceUtil.isOnline()) {
                 onGoOnline();
-                ReadingListSynchronizer.instance().syncSavedPages();
+                ReadingListSynchronizer.instance().syncSavedPages(true);
             } else {
                 onGoOffline();
             }
@@ -220,7 +220,7 @@
         }
 
         @Subscribe public void on(NetworkConnectEvent event) {
-            ReadingListSynchronizer.instance().syncSavedPages();
+            ReadingListSynchronizer.instance().syncSavedPages(false);
         }
 
         @Subscribe public void on(ThemeChangeEvent event) {
diff --git a/app/src/main/java/org/wikipedia/feed/FeedFragment.java 
b/app/src/main/java/org/wikipedia/feed/FeedFragment.java
index afacfa9..e4ba419 100644
--- a/app/src/main/java/org/wikipedia/feed/FeedFragment.java
+++ b/app/src/main/java/org/wikipedia/feed/FeedFragment.java
@@ -154,7 +154,7 @@
             getCallback().updateToolbarElevation(shouldElevateToolbar());
         }
 
-        ReadingListSynchronizer.instance().sync();
+        ReadingListSynchronizer.instance().sync(true);
 
         return view;
     }
diff --git a/app/src/main/java/org/wikipedia/login/LoginActivity.java 
b/app/src/main/java/org/wikipedia/login/LoginActivity.java
index 5960daa..dc0c012 100644
--- a/app/src/main/java/org/wikipedia/login/LoginActivity.java
+++ b/app/src/main/java/org/wikipedia/login/LoginActivity.java
@@ -242,7 +242,7 @@
                     hideSoftKeyboard(LoginActivity.this);
                     setResult(RESULT_LOGIN_SUCCESS);
 
-                    ReadingListSynchronizer.instance().sync();
+                    ReadingListSynchronizer.instance().sync(true);
                     finish();
                 } else if (result.fail()) {
                     String message = result.getMessage();
diff --git 
a/app/src/main/java/org/wikipedia/readinglist/database/ReadingListTable.java 
b/app/src/main/java/org/wikipedia/readinglist/database/ReadingListTable.java
index 0dd886b..dda7fbd 100644
--- a/app/src/main/java/org/wikipedia/readinglist/database/ReadingListTable.java
+++ b/app/src/main/java/org/wikipedia/readinglist/database/ReadingListTable.java
@@ -157,7 +157,7 @@
                 } finally {
                     c.close();
                 }
-                ReadingListSynchronizer.instance().syncSavedPages();
+                ReadingListSynchronizer.instance().syncSavedPages(true);
                 return null;
             }
         }, null);
diff --git 
a/app/src/main/java/org/wikipedia/readinglist/page/ReadingListPage.java 
b/app/src/main/java/org/wikipedia/readinglist/page/ReadingListPage.java
index ccbefa1..1de9372 100644
--- a/app/src/main/java/org/wikipedia/readinglist/page/ReadingListPage.java
+++ b/app/src/main/java/org/wikipedia/readinglist/page/ReadingListPage.java
@@ -20,6 +20,13 @@
                 .build();
     }
 
+    public static ReadingListPage fromDiskRow(@NonNull ReadingListPageDiskRow 
diskRow) {
+        return builder()
+                .copy(diskRow.dat())
+                .diskStatus(diskRow.status())
+                .build();
+    }
+
     public static Builder builder() {
         return new Builder();
     }
diff --git 
a/app/src/main/java/org/wikipedia/readinglist/page/database/ReadingListPageDao.java
 
b/app/src/main/java/org/wikipedia/readinglist/page/database/ReadingListPageDao.java
index d9f7f15..f3e29aa 100644
--- 
a/app/src/main/java/org/wikipedia/readinglist/page/database/ReadingListPageDao.java
+++ 
b/app/src/main/java/org/wikipedia/readinglist/page/database/ReadingListPageDao.java
@@ -124,8 +124,19 @@
         diskDao.markOutdated(row);
     }
 
+    @NonNull public synchronized Collection<ReadingListPageDiskRow> 
collectPausedDiskTransactions() {
+        Collection<ReadingListPageDiskRow> rows = 
queryPausedDiskTransactions();
+        return rows;
+    }
+
     @NonNull public synchronized Collection<ReadingListPageDiskRow> 
startDiskTransaction() {
         Collection<ReadingListPageDiskRow> rows = 
queryPendingDiskTransactions();
+        diskDao.startTransaction(rows);
+        return rows;
+    }
+
+    @NonNull public synchronized Collection<ReadingListPageDiskRow> 
startPausedDiskTransaction() {
+        Collection<ReadingListPageDiskRow> rows = 
queryPausedDiskTransactions();
         diskDao.startTransaction(rows);
         return rows;
     }
@@ -176,6 +187,25 @@
         return rows;
     }
 
+    @NonNull private Collection<ReadingListPageDiskRow> 
queryPausedDiskTransactions() {
+        Uri uri = ReadingListPageContract.DiskWithPage.URI;
+        String selection = Sql.SELECT_ROWS_PAUSED_DISK_TRANSACTION;
+        final String[] selectionArgs = null;
+        final String order = null;
+        Cursor cursor = client().select(uri, selection,
+                selectionArgs, order);
+
+        Collection<ReadingListPageDiskRow> rows = new ArrayList<>();
+        try {
+            while (cursor.moveToNext()) {
+                rows.add(ReadingListPageDiskRow.fromCursor(cursor));
+            }
+        } finally {
+            cursor.close();
+        }
+        return rows;
+    }
+
     // TODO: expose HTTP DAO methods.
 
     private ReadingListPageDao() {
@@ -206,5 +236,9 @@
         private static String SELECT_ROWS_PENDING_DISK_TRANSACTION = 
":transactionIdCol == :noTransactionId"
             .replaceAll(":transactionIdCol", 
ReadingListPageContract.DiskWithPage.DISK_TRANSACTION_ID.qualifiedName())
             .replaceAll(":noTransactionId", 
String.valueOf(AsyncConstant.NO_TRANSACTION_ID));
+
+        private static String SELECT_ROWS_PAUSED_DISK_TRANSACTION = 
":diskStatusCol == :diskStatus"
+                .replaceAll(":diskStatusCol", 
ReadingListPageContract.DiskWithPage.DISK_STATUS.qualifiedName())
+                .replaceAll(":diskStatus", 
String.valueOf(DiskStatus.OUTDATED.code()));
     }
 }
diff --git 
a/app/src/main/java/org/wikipedia/readinglist/sync/ReadingListSynchronizer.java 
b/app/src/main/java/org/wikipedia/readinglist/sync/ReadingListSynchronizer.java
index d559bc8..5bf25ea 100644
--- 
a/app/src/main/java/org/wikipedia/readinglist/sync/ReadingListSynchronizer.java
+++ 
b/app/src/main/java/org/wikipedia/readinglist/sync/ReadingListSynchronizer.java
@@ -39,6 +39,7 @@
 public class ReadingListSynchronizer {
     private static final String READING_LISTS_SYNC_OPTION = 
"userjs-reading-lists-v1";
     private static final ReadingListSynchronizer INSTANCE = new 
ReadingListSynchronizer();
+    private boolean showNotification = false;
 
     private final Handler syncHandler = new 
Handler(WikipediaApp.getInstance().getMainLooper());
     private final SyncRunnable syncRunnable = new SyncRunnable();
@@ -56,31 +57,38 @@
         syncHandler.postDelayed(syncRunnable, TimeUnit.SECONDS.toMillis(1));
     }
 
+    public void sync(@NonNull boolean showNotification) {
+        this.showNotification = showNotification;
+        sync();
+    }
+
     public void sync() {
         if (!ReleaseUtil.isPreBetaRelease()  // TODO: remove when ready for 
beta/production
                 || !AccountUtil.isLoggedIn()
                 || !(isReadingListSyncEnabled() || 
isReadingListsRemoteDeletePending())) {
-            syncSavedPages();
+            syncSavedPages(showNotification);
+            showNotification = false;
             L.d("Skipped sync of reading lists.");
             return;
         }
-        UserOptionDataClientSingleton.instance().get(new 
UserOptionDataClient.UserInfoCallback() {
-            @Override
-            public void success(@NonNull final UserInfo info) {
-                CallbackTask.execute(new CallbackTask.Task<Void>() {
-                    @Override public Void execute() throws Throwable {
+        UserOptionDataClientSingleton.instance().get((UserInfo info) ->
+                CallbackTask.execute(() -> {
                         syncFromRemote(info);
-                        syncSavedPages();
+                        syncSavedPages(showNotification);
+                        showNotification = false;
                         return null;
-                    }
-                });
-            }
-        });
+                })
+        );
+    }
+
+    public void syncSavedPages(@NonNull boolean showNotification) {
+        SavedPageSyncService.enqueueService(WikipediaApp.getInstance(), 
showNotification);
     }
 
     public void syncSavedPages() {
-        SavedPageSyncService.enqueueService(WikipediaApp.getInstance());
+        SavedPageSyncService.enqueueService(WikipediaApp.getInstance(), false);
     }
+
 
     private synchronized void syncFromRemote(@NonNull UserInfo info) {
         long localRev = Prefs.getReadingListSyncRev();
@@ -229,7 +237,7 @@
                         .mtime(now)
                         .atime(now)
                         .description(remoteList.desc())
-                        .pages(new ArrayList<ReadingListPage>())
+                        .pages(new ArrayList<>())
                         .build();
                 ReadingList.DAO.addList(localList);
                 localLists.add(localList);
diff --git 
a/app/src/main/java/org/wikipedia/savedpages/SavedPageSyncBroadcastReceiver.java
 
b/app/src/main/java/org/wikipedia/savedpages/SavedPageSyncBroadcastReceiver.java
new file mode 100644
index 0000000..f5a0492
--- /dev/null
+++ 
b/app/src/main/java/org/wikipedia/savedpages/SavedPageSyncBroadcastReceiver.java
@@ -0,0 +1,33 @@
+package org.wikipedia.savedpages;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import org.wikipedia.Constants;
+
+public class SavedPageSyncBroadcastReceiver{
+
+
+    public static class SyncCancelReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+
+            if 
(intent.getBooleanExtra(Constants.INTENT_EXTRA_NOTIFICATION_SYNC_CANCEL, 
false)) {
+                // cancel sync service
+                
SavedPageSyncNotification.getInstance().setCancelSyncDownload();
+            }
+        }
+    }
+
+    public static class SyncPauseReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+
+            if 
(intent.getBooleanExtra(Constants.INTENT_EXTRA_NOTIFICATION_SYNC_PAUSE, false)) 
{
+                // pause or resume sync service
+                SavedPageSyncNotification.getInstance().setPauseSyncDownload();
+            }
+        }
+    }
+}
diff --git 
a/app/src/main/java/org/wikipedia/savedpages/SavedPageSyncNotification.java 
b/app/src/main/java/org/wikipedia/savedpages/SavedPageSyncNotification.java
new file mode 100644
index 0000000..aafcfe5
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/savedpages/SavedPageSyncNotification.java
@@ -0,0 +1,219 @@
+package org.wikipedia.savedpages;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.BitmapFactory;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.v4.app.NotificationCompat;
+
+import org.wikipedia.Constants;
+import org.wikipedia.R;
+import org.wikipedia.WikipediaApp;
+import org.wikipedia.util.MathUtil;
+
+
+public final class SavedPageSyncNotification{
+
+    private static final SavedPageSyncNotification INSTANCE = new 
SavedPageSyncNotification();
+    private NotificationCompat.Builder mBuilder;
+    private NotificationManager mNotificationManager;
+    private Context mContext;
+    private static final String CHANNEL_ID = "SYNCING_CHANNEL";
+    private static final int NOTIFICATION_ID_FOR_SYNCING = 1001;
+    private boolean syncCanceled;
+    private boolean syncPaused;
+    private boolean syncNotificationVisible;
+    private int queueSize;
+    private int syncCount;
+
+    private SavedPageSyncNotification() {
+        mContext = WikipediaApp.getInstance();
+        mBuilder = new NotificationCompat.Builder(mContext, CHANNEL_ID);
+        mNotificationManager = (NotificationManager) 
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    public static SavedPageSyncNotification getInstance() {
+        return INSTANCE;
+    }
+
+
+    public void setCancelSyncDownload() {
+        syncCanceled = true;
+        doCancel();
+        SavedPageSyncService.cancelService(mContext);
+    }
+
+    public void clearAll() {
+        queueSize = 0;
+        syncCount = 0;
+        syncCanceled = false;
+        syncPaused = false;
+    }
+
+    public void setPauseSyncDownload() {
+
+        if (isSyncPaused()) {
+            syncPaused = false;
+            SavedPageSyncService.resumeService(mContext);
+        } else {
+            syncPaused = true;
+            getInstance().show(false);
+        }
+    }
+
+    public void setQueueSize(int queueSize) {
+        this.queueSize = queueSize;
+    }
+
+    public void setVisible(boolean visible) {
+        syncNotificationVisible = visible;
+    }
+
+    public boolean isVisible() {
+        return syncNotificationVisible;
+    }
+
+    public boolean isSyncCanceled() {
+        return syncCanceled;
+    }
+
+    public boolean isSyncPaused() {
+        return syncPaused;
+    }
+
+
+    public void setup() {
+
+        if (queueSize > 0) {
+            int notificationIcon;
+            String notificationTitle;
+            String notificationDescription;
+            String notificationInfo;
+
+            // Notification channel ( >= API 26 )
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                CharSequence name = 
mContext.getString(R.string.notification_channel_name);
+                String description = 
mContext.getString(R.string.notification_channel_description);
+                int importance = NotificationManager.IMPORTANCE_LOW;
+                NotificationChannel mChannel = new 
NotificationChannel(CHANNEL_ID, name, importance);
+                mChannel.setDescription(description);
+                mChannel.setSound(null, null);
+                mNotificationManager.createNotificationChannel(mChannel);
+            }
+
+            // setup notification content
+            mBuilder = new NotificationCompat.Builder(mContext, CHANNEL_ID);
+
+            // build action buttons
+            int pauseButton = R.string.notification_syncing_pause_button;
+            int pauseButtonIcon = Build.VERSION.SDK_INT >= 
Build.VERSION_CODES.LOLLIPOP ? R.drawable.ic_pause_black_24dp : 
android.R.color.transparent;
+            if (getInstance().isSyncPaused()) {
+                pauseButton = R.string.notification_syncing_resume_button;
+                pauseButtonIcon = Build.VERSION.SDK_INT >= 
Build.VERSION_CODES.LOLLIPOP ? R.drawable.ic_play_arrow_black_24dp : 
android.R.color.transparent;
+            }
+
+            // The reason why registering two receivers because the 
"addAction" has issues when the target classes are the same.
+            // If the target classes are e.g.: "MainActivity.java", and we 
have put different data with different key,
+            // The MainActivity can only receive the last data we have 
assigned.
+            // e.g. assign PAUSE and then CANCEL. the action of both buttons 
will be assigned as CANCEL
+            NotificationCompat.Action actionPause = actionBuilder(
+                    SavedPageSyncBroadcastReceiver.SyncPauseReceiver.class,
+                    Constants.INTENT_EXTRA_NOTIFICATION_SYNC_PAUSE,
+                    pauseButtonIcon,
+                    pauseButton);
+
+
+            NotificationCompat.Action actionCancel = actionBuilder(
+                    SavedPageSyncBroadcastReceiver.SyncCancelReceiver.class,
+                    Constants.INTENT_EXTRA_NOTIFICATION_SYNC_CANCEL,
+                    Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 
R.drawable.ic_cancel_black_24dp : android.R.color.transparent,
+                    R.string.notification_syncing_cancel_button);
+
+
+            notificationIcon = Build.VERSION.SDK_INT >= 
Build.VERSION_CODES.LOLLIPOP ? R.drawable.ic_file_download_white_24dp : 
R.mipmap.launcher;
+            notificationTitle = 
String.format(mContext.getString(R.string.notification_syncing_title), 
queueSize);
+            notificationInfo = MathUtil.percentage(syncCount, queueSize) + "%";
+            notificationDescription = 
String.format(mContext.getString(R.string.notification_syncing_description), 
queueSize - syncCount);
+
+
+            mBuilder.setSmallIcon(notificationIcon)
+                    
.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), 
notificationIcon))
+                    .setStyle(new 
NotificationCompat.BigTextStyle().bigText(notificationDescription))
+                    .setContentTitle(notificationTitle)
+                    .setContentText(notificationDescription)
+                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+                    .setSound(null)
+                    .setContentInfo(notificationInfo)
+                    .setOngoing(true)
+                    .addAction(actionPause)
+                    .addAction(actionCancel);
+
+            // Reference: 
https://developer.android.com/reference/android/app/Notification.Builder.html#setContentInfo(java.lang.CharSequence)
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                mBuilder.setSubText(notificationInfo);
+            }
+        }
+    }
+
+
+    public void show(boolean init) {
+        if (queueSize > 0) {
+            if (init && !isSyncCanceled() && !isSyncPaused()) {
+                syncCount = 0;
+                setup();
+                mBuilder.setProgress(queueSize, syncCount, true);
+                doNotify();
+                syncCount++;
+            } else {
+                setup();
+                if (!isSyncPaused()) {
+                    syncCount++;
+                }
+                mBuilder.setProgress(queueSize, syncCount, false);
+                doNotify();
+                if (queueSize <= syncCount) {
+                    doCancel();
+                }
+            }
+        }
+    }
+
+    private void doCancel() {
+        mNotificationManager.cancel(NOTIFICATION_ID_FOR_SYNCING);
+    }
+
+    private void doNotify() {
+        if (isVisible() && !isSyncCanceled()) {
+            mNotificationManager.notify(NOTIFICATION_ID_FOR_SYNCING, 
mBuilder.build());
+        }
+    }
+
+
+    @NonNull private NotificationCompat.Action actionBuilder(@NonNull Class<?> 
targetClass,
+                                                             @NonNull String 
intentExtra,
+                                                             @NonNull int 
buttonDrawable,
+                                                             @NonNull int 
buttonText) {
+        return new NotificationCompat.Action.Builder(buttonDrawable, 
mContext.getString(buttonText), pendingIntentBuilder(targetClass, intentExtra, 
true)).build();
+    }
+
+    @NonNull private PendingIntent pendingIntentBuilder(@NonNull Class<?> 
targetClass,
+                                                        @NonNull String 
intentExtra,
+                                                        boolean isBroadcast) {
+        Intent resultIntent = new Intent(mContext, targetClass);
+        resultIntent.putExtra(intentExtra, true);
+
+        if (!isBroadcast) {
+            resultIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        }
+
+        PendingIntent resultPendingIntent = isBroadcast
+                ? PendingIntent.getBroadcast(mContext, 0, resultIntent, 
PendingIntent.FLAG_UPDATE_CURRENT)
+                : PendingIntent.getActivity(mContext, 0, resultIntent, 
PendingIntent.FLAG_UPDATE_CURRENT);
+
+        return resultPendingIntent;
+    }
+}
diff --git 
a/app/src/main/java/org/wikipedia/savedpages/SavedPageSyncService.java 
b/app/src/main/java/org/wikipedia/savedpages/SavedPageSyncService.java
index fbc4237..63f52f8 100644
--- a/app/src/main/java/org/wikipedia/savedpages/SavedPageSyncService.java
+++ b/app/src/main/java/org/wikipedia/savedpages/SavedPageSyncService.java
@@ -54,6 +54,9 @@
 public class SavedPageSyncService extends JobIntentService {
     // Unique job ID for this service (do not duplicate).
     private static final int JOB_ID = 1000;
+    private static final String RESUME_OR_PAUSE_SYNCING = 
"RESUME_OR_PAUSE_SYNCING";
+    private static final String CANCEL_SYNCING = "CANCEL_SYNCING";
+    private static final String SHOW_NOTIFICATION_WHEN_SYNCING = 
"SHOW_NOTIFICATION_WHEN_SYNCING";
 
     @NonNull private ReadingListPageDao dao;
     @NonNull private final CacheDelegate cacheDelegate = new 
CacheDelegate(SAVE_CACHE);
@@ -61,19 +64,57 @@
             = new PageImageUrlParser(new ImageTagParser(), new 
PixelDensityDescriptorParser());
     private long blockSize;
 
+    private SavedPageSyncNotification savedPageSyncNotification;
+
     public SavedPageSyncService() {
         dao = ReadingListPageDao.instance();
         blockSize = 
FileUtil.blockSize(cacheDelegate.diskLruCache().getDirectory());
+        savedPageSyncNotification = SavedPageSyncNotification.getInstance();
     }
 
-    public static void enqueueService(@NonNull Context context) {
-        enqueueWork(context, SavedPageSyncService.class, JOB_ID,
-                new Intent(context, SavedPageSyncService.class));
+
+    public static void enqueueService(@NonNull Context context, @NonNull 
boolean showNotification) {
+        Intent intent = new Intent(context, SavedPageSyncService.class);
+        intent.putExtra(RESUME_OR_PAUSE_SYNCING, false);
+        intent.putExtra(CANCEL_SYNCING, false);
+        intent.putExtra(SHOW_NOTIFICATION_WHEN_SYNCING, showNotification);
+        enqueueWork(context, SavedPageSyncService.class, JOB_ID, intent);
+    }
+
+    public static void resumeService(@NonNull Context context) {
+        Intent intent = new Intent(context, SavedPageSyncService.class);
+        intent.putExtra(RESUME_OR_PAUSE_SYNCING, true);
+        intent.putExtra(CANCEL_SYNCING, false);
+        intent.putExtra(SHOW_NOTIFICATION_WHEN_SYNCING, true);
+        enqueueWork(context, SavedPageSyncService.class, JOB_ID, intent);
+    }
+
+    public static void cancelService(@NonNull Context context) {
+        Intent intent = new Intent(context, SavedPageSyncService.class);
+        intent.putExtra(RESUME_OR_PAUSE_SYNCING, false);
+        intent.putExtra(CANCEL_SYNCING, true);
+        intent.putExtra(SHOW_NOTIFICATION_WHEN_SYNCING, true);
+        enqueueWork(context, SavedPageSyncService.class, JOB_ID, intent);
     }
 
     @Override protected void onHandleWork(@NonNull Intent intent) {
+        
savedPageSyncNotification.setVisible(intent.getBooleanExtra(SHOW_NOTIFICATION_WHEN_SYNCING,
 false));
+        if (!intent.getBooleanExtra(CANCEL_SYNCING, false)) {
+            setupSyncEvent(intent.getBooleanExtra(RESUME_OR_PAUSE_SYNCING, 
false));
+        } else {
+            setupCancelSyncEvent();
+        }
+    }
+
+    private void setupSyncEvent(boolean resumeTransaction) {
         List<ReadingListPageDiskRow> queue = new ArrayList<>();
-        Collection<ReadingListPageDiskRow> rows = dao.startDiskTransaction();
+        Collection<ReadingListPageDiskRow> rows;
+
+        if (!resumeTransaction) {
+            rows = dao.startDiskTransaction();
+        } else {
+            rows = dao.startPausedDiskTransaction();
+        }
 
         for (ReadingListPageDiskRow row : rows) {
             switch (row.status()) {
@@ -94,7 +135,23 @@
                             + row.status().name());
             }
         }
+
+        savedPageSyncNotification.setQueueSize(queue.size());
         saveNewEntries(queue);
+    }
+
+    private void setupCancelSyncEvent() {
+        Collection<ReadingListPageDiskRow> rows = 
dao.collectPausedDiskTransactions();
+
+        // not sure this is a better way to "actually" delete the cache and 
turn the status into ONLINE rather than just change the disk status
+        for (ReadingListPageDiskRow row : rows) {
+            ReadingListPage tempPage = ReadingListPage.fromDiskRow(row);
+            if (tempPage != null) {
+                ReadingListData.instance().setPageOffline(tempPage, false);
+            }
+        }
+
+        savedPageSyncNotification.clearAll();
     }
 
     private void sendSyncEvent() {
@@ -102,6 +159,9 @@
         // received on the main thread.
         WikipediaApp.getInstance().getBus().post(new ReadingListSyncEvent());
     }
+
+
+
 
     private void deleteRow(@NonNull ReadingListPageDiskRow row) {
         ReadingListPageRow dat = row.dat();
@@ -139,7 +199,8 @@
 
     private void saveNewEntries(List<ReadingListPageDiskRow> queue) {
         sendSyncEvent();
-        while (!queue.isEmpty()) {
+        savedPageSyncNotification.show(true);
+        while (!queue.isEmpty() && !savedPageSyncNotification.isSyncCanceled() 
&& !savedPageSyncNotification.isSyncPaused()) {
 
             // Pick off the DB row that we'll be working on...
             ReadingListPageDiskRow tempRow = queue.remove(0);
@@ -190,6 +251,7 @@
             if (success) {
                 dao.completeDiskTransaction(updatedRow);
                 sendSyncEvent();
+                savedPageSyncNotification.show(false);
             } else {
                 dao.failDiskTransaction(updatedRow);
             }
diff --git a/app/src/main/java/org/wikipedia/util/MathUtil.java 
b/app/src/main/java/org/wikipedia/util/MathUtil.java
index 04e5002..99edcc4 100644
--- a/app/src/main/java/org/wikipedia/util/MathUtil.java
+++ b/app/src/main/java/org/wikipedia/util/MathUtil.java
@@ -1,6 +1,10 @@
 package org.wikipedia.util;
 
+import android.support.annotation.NonNull;
+
 public final class MathUtil {
+
+    private static final int PERCENTAGE_BASE = 100;
 
     public static float constrain(float f, float min, float max) {
         return Math.min(Math.max(min, f), max);
@@ -25,6 +29,10 @@
         }
     }
 
+    public static int percentage(@NonNull float numerator, @NonNull float 
denominator) {
+        return (int) (numerator / denominator * PERCENTAGE_BASE);
+    }
+
     private MathUtil() {
     }
 }
diff --git a/app/src/main/res/drawable/ic_cancel_black_24dp.xml 
b/app/src/main/res/drawable/ic_cancel_black_24dp.xml
new file mode 100644
index 0000000..ede4b71
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cancel_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 
5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_file_download_white_24dp.xml 
b/app/src/main/res/drawable/ic_file_download_white_24dp.xml
new file mode 100644
index 0000000..e43b864
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_download_white_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_pause_black_24dp.xml 
b/app/src/main/res/drawable/ic_pause_black_24dp.xml
new file mode 100644
index 0000000..bb28a6c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pause_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_play_arrow_black_24dp.xml 
b/app/src/main/res/drawable/ic_play_arrow_black_24dp.xml
new file mode 100644
index 0000000..bf9b895
--- /dev/null
+++ b/app/src/main/res/drawable/ic_play_arrow_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android";
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M8,5v14l11,-7z"/>
+</vector>
diff --git a/app/src/main/res/values-qq/strings.xml 
b/app/src/main/res/values-qq/strings.xml
index 755b5fc..f813fd4 100644
--- a/app/src/main/res/values-qq/strings.xml
+++ b/app/src/main/res/values-qq/strings.xml
@@ -371,6 +371,14 @@
   <string name="notification_reverted">Text of notification when an edit made 
by the user is reverted. The \"%1$s\" symbol corresponds to the user who 
reverted the edit, and the \"%2$s\" symbol is the title of the page that was 
edited.</string>
   <string name="notification_thanks_title">Title for notification when the 
user receives a Thanks from another user.</string>
   <string name="notification_thanks">Text of notification when the user 
receives a Thanks from another user. The \"%1$s\" symbol corresponds to the 
user who sent the thanks, and the \"%2$s\" symbol is the title of the page for 
which the user was thanked.</string>
+  <string name="notification_channel_name">Name of the notification 
channel</string>
+  <string name="notification_channel_description">Description of the 
notification channel</string>
+  <string name="notification_syncing_title">Title for notification when the 
syncing process begins. The \"%1$d\" symbol corresponds to the amount of 
articles.</string>
+  <string name="notification_syncing_description">Text of notification shows 
the remaining articles. The \"%1$d\" symbol corresponds to the amount of 
remaining articles.</string>
+  <string name="notification_syncing_pause_button">Text of button on 
notification bar to pause the syncing process</string>
+  <string name="notification_syncing_resume_button">Text of button on 
notification bar to resume the syncing process</string>
+  <string name="notification_syncing_cancel_button">Text of button on 
notification bar to cancel the syncing process</string>
+
   <string name="view_continue_reading_card_title">Label for card in the feed 
that reminds the user to continue reading an article from their browsing 
history.</string>
   <string name="view_because_you_read_card_title">Label for card in the feed 
that gives the user reading suggestions based on an article from their browsing 
history.</string>
   <string name="view_random_card_title">Title of a card that the user can 
click to view a randomly selected Wikipedia article</string>
diff --git a/app/src/main/res/values/strings.xml 
b/app/src/main/res/values/strings.xml
index 8fd8c41..9877e8d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -402,6 +402,13 @@
     <string name="notification_reverted"><![CDATA[Your edit to the article 
<strong>%2$s</strong> was reverted by <em>%1$s</em>.]]></string>
     <string name="notification_thanks_title">You\'ve been thanked!</string>
     <string name="notification_thanks"><![CDATA[<em>%1$s</em> thanked you for 
your edit on the page <strong>%2$s</strong>]]></string>
+    <string name="notification_channel_name">Syncing</string>
+    <string name="notification_channel_description">Syncing progress</string>
+    <string name="notification_syncing_title">Syncing %1$d 
articles&#8230;</string>
+    <string name="notification_syncing_description">%1$d articles left</string>
+    <string name="notification_syncing_pause_button">PAUSE</string>
+    <string name="notification_syncing_resume_button">RESUME</string>
+    <string name="notification_syncing_cancel_button">CANCEL</string>
     <!-- /Notifications -->
 
     <!-- The Feed -->
@@ -575,4 +582,6 @@
         <item quantity="one">Last year</item>
         <item quantity="other">%d years ago</item>
     </plurals>
+    <!-- /On This Day -->
+
 </resources>

-- 
To view, visit https://gerrit.wikimedia.org/r/388269
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: If53a948051a30fe76e3a164b03c1167271fa7f1a
Gerrit-PatchSet: 13
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Cooltey <cf...@wikimedia.org>
Gerrit-Reviewer: Brion VIBBER <br...@wikimedia.org>
Gerrit-Reviewer: Cooltey <cf...@wikimedia.org>
Gerrit-Reviewer: Dbrant <dbr...@wikimedia.org>
Gerrit-Reviewer: Sharvaniharan <sha...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to