github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/clients/android/src/org/camlistore/UploadService.java (about) 1 /* 2 Copyright 2011 Google Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package org.camlistore; 18 19 import java.io.File; 20 import java.io.FileNotFoundException; 21 import java.io.IOException; 22 import java.util.ArrayList; 23 import java.util.HashMap; 24 import java.util.LinkedList; 25 import java.util.List; 26 import java.util.Map; 27 import java.util.Map.Entry; 28 import java.util.TreeMap; 29 30 import org.camlistore.UploadThread.CamputChunkUploadedMessage; 31 import org.camlistore.UploadThread.CamputStatsMessage; 32 33 import android.app.Notification; 34 import android.app.NotificationManager; 35 import android.app.PendingIntent; 36 import android.app.Service; 37 import android.content.ContentResolver; 38 import android.content.Context; 39 import android.content.Intent; 40 import android.database.Cursor; 41 import android.net.Uri; 42 import android.net.wifi.WifiManager; 43 import android.os.Bundle; 44 import android.os.Environment; 45 import android.os.FileObserver; 46 import android.os.IBinder; 47 import android.os.ParcelFileDescriptor; 48 import android.os.Parcelable; 49 import android.os.PowerManager; 50 import android.os.RemoteException; 51 import android.provider.MediaStore; 52 import android.util.Log; 53 54 public class UploadService extends Service { 55 private static final String TAG = "UploadService"; 56 57 private static int NOTIFY_ID_UPLOADING = 0x001; 58 59 public static final String INTENT_POWER_CONNECTED = "POWER_CONNECTED"; 60 public static final String INTENT_POWER_DISCONNECTED = "POWER_DISCONNECTED"; 61 public static final String INTENT_UPLOAD_ALL = "UPLOAD_ALL"; 62 public static final String INTENT_NETWORK_WIFI = "WIFI_NOW"; 63 public static final String INTENT_NETWORK_NOT_WIFI = "NOT_WIFI_NOW"; 64 65 // Everything in this block guarded by 'this': 66 private boolean mUploading = false; // user's desired state (notified 67 // quickly) 68 private UploadThread mUploadThread = null; // last thread created; null when 69 // thread exits 70 private final Map<QueuedFile, Long> mFileBytesRemain = new HashMap<QueuedFile, Long>(); 71 private final LinkedList<QueuedFile> mQueueList = new LinkedList<QueuedFile>(); 72 private final Map<String, Long> mStatValue = new TreeMap<String, Long>(); 73 private IStatusCallback mCallback = DummyNullCallback.instance(); 74 private String mLastUploadStatusText = null; // single line 75 private String mLastUploadStatsText = null; // multi-line stats 76 private int mBytesInFlight = 0; 77 private int mFilesInFlight = 0; 78 79 // Stats, all guarded by 'this', and all reset to 0 on queue size transition 80 // from 0 -> 1. 81 private long mBytesTotal = 0; 82 private long mBytesUploaded = 0; 83 private int mFilesTotal = 0; 84 private int mFilesUploaded = 0; 85 86 // Effectively final, initialized in onCreate(): 87 PowerManager mPowerManager; 88 WifiManager mWifiManager; 89 NotificationManager mNotificationManager; 90 Preferences mPrefs; 91 92 // File Observers. Need to keep a reference to them, as there's no JNI 93 // reference and their finalizers would run otherwise, stopping their 94 // inotify. 95 private final ArrayList<FileObserver> mObservers = new ArrayList<FileObserver>(); 96 97 @Override 98 public void onCreate() { 99 super.onCreate(); 100 Log.d(TAG, "onCreate"); 101 102 mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); 103 mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); 104 mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 105 mPrefs = new Preferences(getSharedPreferences(Preferences.NAME, 0)); 106 107 updateBackgroundWatchers(); 108 } 109 110 @Override 111 public IBinder onBind(Intent intent) { 112 Log.d(TAG, "onBind intent=" + intent); 113 return service; 114 } 115 116 @Override 117 public void onStart(Intent intent, int startId) { 118 handleCommand(intent); 119 } 120 121 private void startUploadService() { 122 startService(new Intent(UploadService.this, UploadService.class)); 123 } 124 125 // This is @Override as of SDK version 5, but we're targetting 4 (Android 126 // 1.6) 127 private static final int START_STICKY = 1; // in SDK 5 128 129 @Override 130 public int onStartCommand(Intent intent, int flags, int startId) { 131 handleCommand(intent); 132 // We want this service to continue running until it is explicitly 133 // stopped, so return sticky. 134 return START_STICKY; 135 } 136 137 private void handleCommand(Intent intent) { 138 Log.d(TAG, "in handleCommand() for onStart() intent: " + intent); 139 if (intent == null) { 140 stopServiceIfEmpty(); 141 return; 142 } 143 144 String action = intent.getAction(); 145 if (Intent.ACTION_SEND.equals(action)) { 146 handleSend(intent); 147 stopServiceIfEmpty(); 148 return; 149 } 150 151 if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { 152 handleSendMultiple(intent); 153 stopServiceIfEmpty(); 154 return; 155 } 156 157 if (INTENT_UPLOAD_ALL.equals(action)) { 158 handleUploadAll(); 159 return; 160 } 161 162 try { 163 if (mPrefs.autoUpload()) { 164 boolean startAuto = false; 165 boolean stopAuto = false; 166 167 if (INTENT_POWER_CONNECTED.equals(action)) { 168 if (!mPrefs.autoRequiresWifi() || WifiPowerReceiver.onWifi(this)) { 169 startAuto = true; 170 } 171 } else if (INTENT_NETWORK_WIFI.equals(action)) { 172 if (!mPrefs.autoRequiresPower() || WifiPowerReceiver.onPower(this)) { 173 startAuto = true; 174 } 175 } else if (INTENT_POWER_DISCONNECTED.equals(action)) { 176 stopAuto = mPrefs.autoRequiresPower(); 177 } else if (INTENT_NETWORK_NOT_WIFI.equals(action)) { 178 stopAuto = mPrefs.autoRequiresWifi(); 179 } 180 181 if (startAuto) { 182 Log.d(TAG, "Starting automatic uploads"); 183 service.resume(); 184 handleUploadAll(); 185 return; 186 } 187 if (stopAuto) { 188 Log.d(TAG, "Stopping automatic uploads"); 189 service.pause(); 190 stopBackgroundWatchers(); 191 stopServiceIfEmpty(); 192 return; 193 } 194 } 195 } catch (RemoteException e) { 196 // Ignore. 197 } 198 } 199 200 private void handleSend(Intent intent) { 201 Bundle extras = intent.getExtras(); 202 if (extras == null) { 203 Log.w(TAG, "expected extras in handleSend"); 204 return; 205 } 206 207 extras.keySet(); // unparcel 208 Log.d(TAG, "handleSend; extras=" + extras); 209 210 Object streamValue = extras.get("android.intent.extra.STREAM"); 211 if (!(streamValue instanceof Uri)) { 212 Log.w(TAG, "Expected URI for STREAM; got: " + streamValue); 213 return; 214 } 215 216 final Uri uri = (Uri) streamValue; 217 Util.runAsync(new Runnable() { 218 @Override 219 public void run() { 220 try { 221 service.enqueueUpload(uri); 222 } catch (RemoteException e) { 223 } finally { 224 stopServiceIfEmpty(); 225 } 226 } 227 }); 228 } 229 230 private void handleUploadAll() { 231 startService(new Intent(UploadService.this, UploadService.class)); 232 final PowerManager.WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Camli Upload All"); 233 wakeLock.acquire(); 234 Util.runAsync(new Runnable() { 235 @Override 236 public void run() { 237 try { 238 List<String> dirs = getBackupDirs(); 239 List<Uri> filesToQueue = new ArrayList<Uri>(); 240 for (String dirName : dirs) { 241 File dir = new File(dirName); 242 if (!dir.exists()) { 243 continue; 244 } 245 File[] files = dir.listFiles(); 246 if (files != null) { 247 for (int i = 0; i < files.length; ++i) { 248 File f = files[i]; 249 if (f.isDirectory()) { 250 // Skip thumbnails directory. 251 // TODO: are any interesting enough to recurse into? 252 // Definitely don't need to upload thumbnails, but 253 // but maybe some other app in the the future creates 254 // sharded directories. Eye-Fi doesn't, though. 255 continue; 256 } 257 filesToQueue.add(Uri.fromFile(f)); 258 } 259 } 260 } 261 try { 262 service.enqueueUploadList(filesToQueue); 263 } catch (RemoteException e) { 264 } finally { 265 stopServiceIfEmpty(); 266 } 267 } finally { 268 wakeLock.release(); 269 } 270 } 271 }); 272 } 273 274 private List<String> getBackupDirs() { 275 ArrayList<String> dirs = new ArrayList<String>(); 276 if (mPrefs.autoDirPhotos()) { 277 dirs.add(Environment.getExternalStorageDirectory() + "/DCIM/Camera"); 278 dirs.add(Environment.getExternalStorageDirectory() + "/Eye-Fi"); 279 } 280 if (mPrefs.autoDirMyTracks()) { 281 dirs.add(Environment.getExternalStorageDirectory() + "/gpx"); 282 dirs.add(Environment.getExternalStorageDirectory() + "/kml"); 283 } 284 return dirs; 285 } 286 287 private void handleSendMultiple(Intent intent) { 288 ArrayList<Parcelable> items = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); 289 ArrayList<Uri> uris = new ArrayList<Uri>(items.size()); 290 for (Parcelable p : items) { 291 if (!(p instanceof Uri)) { 292 Log.d(TAG, "uh, unknown thing " + p); 293 continue; 294 } 295 uris.add((Uri) p); 296 } 297 final ArrayList<Uri> finalUris = uris; 298 Util.runAsync(new Runnable() { 299 @Override 300 public void run() { 301 try { 302 service.enqueueUploadList(finalUris); 303 } catch (RemoteException e) { 304 } finally { 305 stopServiceIfEmpty(); 306 } 307 } 308 }); 309 } 310 311 private void stopBackgroundWatchers() { 312 synchronized (UploadService.this) { 313 if (mObservers.isEmpty()) { 314 return; 315 } 316 Log.d(TAG, "Stopping background watchers..."); 317 for (FileObserver fo : mObservers) { 318 fo.stopWatching(); 319 } 320 mObservers.clear(); 321 } 322 } 323 324 private void updateBackgroundWatchers() { 325 stopBackgroundWatchers(); 326 if (!mPrefs.autoUpload()) { 327 return; 328 } 329 startBackgroundWatchers(); 330 } 331 332 private void startBackgroundWatchers() { 333 Log.d(TAG, "Starting background watchers..."); 334 synchronized (UploadService.this) { 335 maybeAddObserver("DCIM/Camera"); 336 maybeAddObserver("Eye-Fi"); 337 maybeAddObserver("gpx"); 338 } 339 } 340 341 // Requires that UploadService.this is locked. 342 private void maybeAddObserver(String suffix) { 343 File f = new File(Environment.getExternalStorageDirectory(), suffix); 344 if (f.exists()) { 345 mObservers.add(new CamliFileObserver(service, f)); 346 } 347 } 348 349 @Override 350 public void onDestroy() { 351 synchronized (this) { 352 Log.d(TAG, "onDestroy of camli UploadService; thread=" + mUploadThread + "; uploading=" + mUploading + "; queue size=" + mFileBytesRemain.size()); 353 } 354 super.onDestroy(); 355 if (mUploadThread != null) { 356 Log.e(TAG, "Unexpected onDestroy with active upload thread. Killing it."); 357 mUploadThread.interrupt(); 358 mUploadThread = null; 359 } 360 } 361 362 // Called by UploadThread to get stuff to do. Caller owns the returned new 363 // LinkedList. Doesn't return null. 364 LinkedList<QueuedFile> uploadQueue() { 365 synchronized (this) { 366 LinkedList<QueuedFile> copy = new LinkedList<QueuedFile>(); 367 copy.addAll(mQueueList); 368 return copy; 369 } 370 } 371 372 void setUploadStatusText(String status) { 373 IStatusCallback cb; 374 synchronized (this) { 375 mLastUploadStatusText = status; 376 cb = mCallback; 377 } 378 try { 379 cb.setUploadStatusText(status); 380 } catch (RemoteException e) { 381 } 382 } 383 384 void setInFlightBytes(int v) { 385 synchronized (this) { 386 mBytesInFlight = v; 387 } 388 broadcastByteStatus(); 389 } 390 391 void broadcastByteStatus() { 392 synchronized (this) { 393 try { 394 mCallback.setByteStatus(mBytesUploaded, mBytesInFlight, mBytesTotal); 395 } catch (RemoteException e) { 396 } 397 } 398 } 399 400 void broadcastFileStatus() { 401 synchronized (this) { 402 try { 403 mCallback.setFileStatus(mFilesUploaded, mFilesInFlight, mFilesTotal); 404 } catch (RemoteException e) { 405 } 406 } 407 } 408 409 void broadcastAllState() { 410 synchronized (this) { 411 try { 412 mCallback.setUploading(mUploading); 413 mCallback.setUploadStatusText(mLastUploadStatusText); 414 mCallback.setUploadStatsText(mLastUploadStatsText); 415 } catch (RemoteException e) { 416 } 417 } 418 broadcastFileStatus(); 419 broadcastByteStatus(); 420 } 421 422 private void onUploadThreadEnded() { 423 synchronized (this) { 424 Log.d(TAG, "UploadThread ended"); 425 mNotificationManager.cancel(NOTIFY_ID_UPLOADING); 426 mUploadThread = null; 427 mUploading = false; 428 try { 429 mCallback.setUploading(false); 430 } catch (RemoteException e) { 431 } 432 } 433 stopServiceIfEmpty(); 434 } 435 436 /** 437 * Callback from the UploadThread to the service. 438 * 439 * @param qf 440 * the queued file that was successfully uploaded. 441 */ 442 void onUploadComplete(QueuedFile qf) { 443 Log.d(TAG, "onUploadComplete of " + qf); 444 synchronized (this) { 445 if (!mFileBytesRemain.containsKey(qf)) { 446 Log.w(TAG, "onUploadComplete of un-queued file: " + qf); 447 return; 448 } 449 incrBytes(qf, qf.getSize()); 450 mFileBytesRemain.remove(qf); 451 if (mFileBytesRemain.isEmpty()) { 452 // Fill up the percentage bars, since we could get 453 // this event before the periodic stats event. 454 // And at the end, we could kill camput between 455 // getting the final "file uploaded" event and the final 456 // stats event. 457 mFilesUploaded = mFilesTotal; 458 mBytesUploaded = mBytesTotal; 459 mNotificationManager.cancel(NOTIFY_ID_UPLOADING); 460 stopUploadThread(); 461 } 462 mQueueList.remove(qf); // TODO: ghetto, linear scan 463 } 464 broadcastAllState(); 465 stopServiceIfEmpty(); 466 } 467 468 // incrBytes notes that size bytes of qf have been uploaded 469 // and updates mBytesUploaded. 470 private void incrBytes(QueuedFile qf, long size) { 471 synchronized (this) { 472 Long remain = mFileBytesRemain.get(qf); 473 if (remain != null) { 474 long actual = Math.min(size, remain.longValue()); 475 mBytesUploaded += actual; 476 mFileBytesRemain.put(qf, remain - actual); 477 } 478 } 479 } 480 481 private void stopServiceIfEmpty() { 482 // Convenient place to drop this cache. 483 synchronized (this) { 484 if (mFileBytesRemain.isEmpty() && !mUploading && mUploadThread == null && !mPrefs.autoUpload()) { 485 Log.d(TAG, "stopServiceIfEmpty; stopping"); 486 stopSelf(); 487 } else { 488 Log.d(TAG, "stopServiceIfEmpty; NOT stopping; " + mFileBytesRemain.isEmpty() + "; " + mUploading + "; " + (mUploadThread != null)); 489 return; 490 } 491 } 492 } 493 494 ParcelFileDescriptor getFileDescriptor(Uri uri) { 495 ContentResolver cr = getContentResolver(); 496 try { 497 return cr.openFileDescriptor(uri, "r"); 498 } catch (FileNotFoundException e) { 499 Log.w(TAG, "FileNotFound in getFileDescriptor() for " + uri); 500 return null; 501 } 502 } 503 504 private void incrementFilesToUpload(int size) throws RemoteException { 505 synchronized (UploadService.this) { 506 mFilesTotal += size; 507 } 508 broadcastFileStatus(); 509 } 510 511 // pathOfURI tries to return the on-disk absolute path of uri. 512 // It may return null if it fails. 513 public String pathOfURI(Uri uri) { 514 if (uri == null) { 515 return null; 516 } 517 if ("file".equals(uri.getScheme())) { 518 return uri.getPath(); 519 } 520 String[] proj = { MediaStore.Images.Media.DATA }; 521 Cursor cursor = null; 522 try { 523 cursor = getContentResolver().query(uri, proj, null, null, null); 524 if (cursor == null) { 525 return null; 526 } 527 cursor.moveToFirst(); 528 int columnIndex = cursor.getColumnIndex(proj[0]); 529 return cursor.getString(columnIndex); // might still be null 530 } finally { 531 if (cursor != null) { 532 cursor.close(); 533 } 534 } 535 } 536 537 private final IUploadService.Stub service = new IUploadService.Stub() { 538 539 @Override 540 public int enqueueUploadList(List<Uri> uriList) throws RemoteException { 541 startService(new Intent(UploadService.this, UploadService.class)); 542 Log.d(TAG, "enqueuing list of " + uriList.size() + " URIs"); 543 incrementFilesToUpload(uriList.size()); 544 int goodCount = 0; 545 for (Uri uri : uriList) { 546 goodCount += enqueueSingleUri(uri) ? 1 : 0; 547 } 548 Log.d(TAG, "...goodCount = " + goodCount); 549 return goodCount; 550 } 551 552 @Override 553 public boolean enqueueUpload(Uri uri) throws RemoteException { 554 startUploadService(); 555 incrementFilesToUpload(1); 556 return enqueueSingleUri(uri); 557 } 558 559 private boolean enqueueSingleUri(Uri uri) throws RemoteException { 560 long statSize = 0; 561 { 562 ParcelFileDescriptor pfd = getFileDescriptor(uri); 563 if (pfd == null) { 564 incrementFilesToUpload(-1); 565 stopServiceIfEmpty(); 566 return false; 567 } 568 569 try { 570 statSize = pfd.getStatSize(); 571 } finally { 572 try { 573 pfd.close(); 574 } catch (IOException e) { 575 } 576 } 577 } 578 579 String diskPath = pathOfURI(uri); 580 if (diskPath == null) { 581 Log.e(TAG, "failed to find pathOfURI(" + uri + ")"); 582 return false; 583 } 584 Log.d(TAG, "diskPath of " + uri + " = " + diskPath); 585 586 QueuedFile qf = new QueuedFile(uri, statSize, diskPath); 587 588 boolean needResume = false; 589 synchronized (UploadService.this) { 590 if (mFileBytesRemain.containsKey(qf)) { 591 Log.d(TAG, "Dup blob enqueue, ignoring " + qf); 592 stopServiceIfEmpty(); 593 return false; 594 } 595 Log.d(TAG, "Enqueueing blob: " + qf); 596 mFileBytesRemain.put(qf, qf.getSize()); 597 mQueueList.add(qf); 598 599 if (mFileBytesRemain.size() == 1) { 600 mBytesTotal = 0; 601 mFilesTotal = 0; 602 mBytesUploaded = 0; 603 mFilesUploaded = 0; 604 } 605 mBytesTotal += qf.getSize(); 606 mFilesTotal += 1; 607 needResume = !mUploading; 608 609 if (mUploadThread != null) { 610 mUploadThread.enqueueFile(qf); 611 } 612 } 613 broadcastFileStatus(); 614 broadcastByteStatus(); 615 if (needResume) { 616 resume(); 617 } 618 return true; 619 } 620 621 @Override 622 public boolean isUploading() throws RemoteException { 623 synchronized (UploadService.this) { 624 return mUploading; 625 } 626 } 627 628 @Override 629 public void registerCallback(IStatusCallback cb) throws RemoteException { 630 // TODO: permit multiple listeners? when need comes. 631 synchronized (UploadService.this) { 632 if (cb == null) { 633 cb = DummyNullCallback.instance(); 634 } 635 mCallback = cb; 636 } 637 broadcastAllState(); 638 } 639 640 @Override 641 public void unregisterCallback(IStatusCallback cb) throws RemoteException { 642 synchronized (UploadService.this) { 643 mCallback = DummyNullCallback.instance(); 644 } 645 } 646 647 @Override 648 public boolean resume() throws RemoteException { 649 Log.d(TAG, "Resuming upload..."); 650 HostPort hp = mPrefs.hostPort(); 651 if (!hp.isValid()) { 652 setUploadStatusText("Upload server not configured."); 653 return false; 654 } 655 656 final PowerManager.WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Camli Upload"); 657 final WifiManager.WifiLock wifiLock = mWifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "Camli Upload"); 658 659 synchronized (UploadService.this) { 660 if (mUploadThread != null) { 661 Log.d(TAG, "Already uploading; aborting resume."); 662 return false; 663 } 664 665 wakeLock.acquire(); 666 wifiLock.acquire(); 667 668 Notification n = new Notification(android.R.drawable.stat_sys_upload, "Uploading", System.currentTimeMillis()); 669 n.flags = Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT; 670 PendingIntent pIntent = PendingIntent.getActivity(UploadService.this, 0, new Intent(UploadService.this, CamliActivity.class), 0); 671 n.setLatestEventInfo(UploadService.this, "Uploading", "Camlistore uploader running", pIntent); 672 mNotificationManager.notify(NOTIFY_ID_UPLOADING, n); 673 674 mUploading = true; 675 mUploadThread = new UploadThread(UploadService.this, hp, mPrefs.trustedCert(), mPrefs.username(), mPrefs.password()); 676 mUploadThread.start(); 677 678 // Start a thread to release the wakelock... 679 final Thread threadToWatch = mUploadThread; 680 new Thread("UploadThread-waiter") { 681 @Override 682 public void run() { 683 while (true) { 684 try { 685 threadToWatch.join(10000); // 10 seconds 686 } catch (InterruptedException e) { 687 Log.d(TAG, "Interrupt waiting for uploader thread.", e); 688 } 689 synchronized (UploadService.this) { 690 if (threadToWatch.getState() == Thread.State.TERMINATED) { 691 break; 692 } 693 if (threadToWatch == mUploadThread) { 694 Log.d(TAG, "UploadThread-waiter still waiting."); 695 continue; 696 } 697 } 698 break; 699 } 700 Log.d(TAG, "UploadThread done; releasing the wakelock"); 701 wakeLock.release(); 702 wifiLock.release(); 703 onUploadThreadEnded(); 704 } 705 }.start(); 706 } 707 mCallback.setUploading(true); 708 return true; 709 } 710 711 @Override 712 public boolean pause() throws RemoteException { 713 synchronized (UploadService.this) { 714 if (mUploadThread != null) { 715 stopUploadThread(); 716 return true; 717 } 718 return false; 719 } 720 } 721 722 @Override 723 public int queueSize() throws RemoteException { 724 synchronized (UploadService.this) { 725 return mQueueList.size(); 726 } 727 } 728 729 @Override 730 public void stopEverything() throws RemoteException { 731 synchronized (UploadService.this) { 732 mNotificationManager.cancel(NOTIFY_ID_UPLOADING); 733 mFileBytesRemain.clear(); 734 mQueueList.clear(); 735 mLastUploadStatusText = "Stopped"; 736 mBytesInFlight = 0; 737 mFilesInFlight = 0; 738 mBytesTotal = 0; 739 mBytesUploaded = 0; 740 mFilesTotal = 0; 741 mFilesUploaded = 0; 742 stopUploadThread(); // recursive lock: okay 743 } 744 broadcastAllState(); 745 } 746 747 @Override 748 public void setBackgroundWatchersEnabled(boolean enabled) throws RemoteException { 749 if (enabled) { 750 startUploadService(); 751 UploadService.this.stopBackgroundWatchers(); 752 UploadService.this.startBackgroundWatchers(); 753 } else { 754 UploadService.this.stopBackgroundWatchers(); 755 stopServiceIfEmpty(); 756 } 757 } 758 }; 759 760 public void onChunkUploaded(CamputChunkUploadedMessage msg) { 761 Log.d(TAG, "chunked uploaded for " + msg.queuedFile() + " with size " + msg.size()); 762 synchronized (UploadService.this) { 763 incrBytes(msg.queuedFile(), msg.size()); 764 } 765 broadcastAllState(); 766 } 767 768 public void onStatReceived(String stat, long value) { 769 String v; 770 synchronized (UploadService.this) { 771 if (stat == null) { 772 mStatValue.clear(); 773 } else { 774 mStatValue.put(stat, value); 775 } 776 StringBuilder sb = new StringBuilder(); 777 for (Entry<String, Long> ent : mStatValue.entrySet()) { 778 sb.append(ent.getKey()); 779 sb.append(": "); 780 sb.append(ent.getValue()); 781 sb.append("\n"); 782 } 783 v = sb.toString(); 784 mLastUploadStatsText = v; 785 } 786 try { 787 mCallback.setUploadStatsText(v); 788 } catch (RemoteException e) { 789 } 790 } 791 792 protected void stopUploadThread() { 793 synchronized (UploadService.this) { 794 mNotificationManager.cancel(NOTIFY_ID_UPLOADING); 795 if (mUploadThread != null) { 796 mUploadThread.stopUploads(); 797 mUploadThread = null; 798 try { 799 mCallback.setUploading(false); 800 } catch (RemoteException e) { 801 } 802 } 803 mUploading = false; 804 } 805 } 806 807 public void onStatsReceived(CamputStatsMessage msg) { 808 synchronized (UploadService.this) { 809 mBytesTotal = msg.totalBytes(); 810 mFilesTotal = (int) msg.totalFiles(); 811 mBytesUploaded = msg.skippedBytes() + msg.uploadedBytes(); 812 mFilesUploaded = (int) (msg.skippedFiles() + msg.uploadedFiles()); 813 } 814 broadcastAllState(); 815 } 816 }