1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 package org.mozilla.mozstumbler.service.uploadthread; 6 7 import android.os.AsyncTask; 8 import android.util.Log; 9 import java.io.IOException; 10 import java.util.concurrent.atomic.AtomicBoolean; 11 12 import org.mozilla.mozstumbler.service.Prefs; 13 import org.mozilla.mozstumbler.service.utils.AbstractCommunicator; 14 import org.mozilla.mozstumbler.service.utils.AbstractCommunicator.SyncSummary; 15 import org.mozilla.mozstumbler.service.AppGlobals; 16 import org.mozilla.mozstumbler.service.stumblerthread.datahandling.DataStorageManager; 17 import org.mozilla.mozstumbler.service.utils.NetworkUtils; 18 19 /* Only one at a time may be uploading. If executed while another upload is in progress 20 * it will return immediately, and SyncResult is null. 21 * 22 * Threading: 23 * Uploads on a separate thread. ONLY DataStorageManager is thread-safe, do not call 24 * preferences, do not call any code that isn't thread-safe. You will cause suffering. 25 * An exception is made for AppGlobals.isDebug, a false reading is of no consequence. */ 26 public class AsyncUploader extends AsyncTask<Void, Void, SyncSummary> { 27 private static final String LOG_TAG = AppGlobals.makeLogTag(AsyncUploader.class.getSimpleName()); 28 private final AsyncUploadArgs mUploadArgs; 29 private final Object mListenerLock = new Object(); 30 private AsyncUploaderListener mListener; 31 private static final AtomicBoolean sIsUploading = new AtomicBoolean(); 32 private String mNickname; 33 34 public interface AsyncUploaderListener { onUploadComplete(SyncSummary result)35 public void onUploadComplete(SyncSummary result); onUploadProgress()36 public void onUploadProgress(); 37 } 38 39 public static class AsyncUploadArgs { 40 public final NetworkUtils mNetworkUtils; 41 public final boolean mShouldIgnoreWifiStatus; 42 public final boolean mUseWifiOnly; AsyncUploadArgs(NetworkUtils networkUtils, boolean shouldIgnoreWifiStatus, boolean useWifiOnly)43 public AsyncUploadArgs(NetworkUtils networkUtils, 44 boolean shouldIgnoreWifiStatus, 45 boolean useWifiOnly) { 46 mNetworkUtils = networkUtils; 47 mShouldIgnoreWifiStatus = shouldIgnoreWifiStatus; 48 mUseWifiOnly = useWifiOnly; 49 } 50 } 51 AsyncUploader(AsyncUploadArgs args, AsyncUploaderListener listener)52 public AsyncUploader(AsyncUploadArgs args, AsyncUploaderListener listener) { 53 mListener = listener; 54 mUploadArgs = args; 55 } 56 setNickname(String name)57 public void setNickname(String name) { 58 mNickname = name; 59 } 60 clearListener()61 public void clearListener() { 62 synchronized (mListenerLock) { 63 mListener = null; 64 } 65 } 66 isUploading()67 public static boolean isUploading() { 68 return sIsUploading.get(); 69 } 70 71 @Override doInBackground(Void... voids)72 protected SyncSummary doInBackground(Void... voids) { 73 if (sIsUploading.get()) { 74 // This if-block is not synchronized, don't care, this is an erroneous usage. 75 Log.d(LOG_TAG, "Usage error: check isUploading first, only one at a time task usage is permitted."); 76 return null; 77 } 78 79 sIsUploading.set(true); 80 SyncSummary result = new SyncSummary(); 81 Runnable progressListener = null; 82 83 // no need to lock here, lock is checked again later 84 if (mListener != null) { 85 progressListener = new Runnable() { 86 @Override 87 public void run() { 88 synchronized (mListenerLock) { 89 if (mListener != null) { 90 mListener.onUploadProgress(); 91 } 92 } 93 } 94 }; 95 } 96 97 uploadReports(result, progressListener); 98 99 return result; 100 } 101 @Override onPostExecute(SyncSummary result)102 protected void onPostExecute(SyncSummary result) { 103 sIsUploading.set(false); 104 105 synchronized (mListenerLock) { 106 if (mListener != null) { 107 mListener.onUploadComplete(result); 108 } 109 } 110 } 111 @Override onCancelled(SyncSummary result)112 protected void onCancelled(SyncSummary result) { 113 sIsUploading.set(false); 114 } 115 116 private class Submitter extends AbstractCommunicator { 117 private static final String SUBMIT_URL = "https://location.services.mozilla.com/v1/submit"; 118 119 @Override getUrlString()120 public String getUrlString() { 121 return SUBMIT_URL; 122 } 123 124 @Override getNickname()125 public String getNickname(){ 126 return mNickname; 127 } 128 129 @Override cleanSend(byte[] data)130 public NetworkSendResult cleanSend(byte[] data) { 131 final NetworkSendResult result = new NetworkSendResult(); 132 try { 133 result.bytesSent = this.send(data, ZippedState.eAlreadyZipped); 134 result.errorCode = 0; 135 } catch (IOException ex) { 136 String msg = "Error submitting: " + ex; 137 if (ex instanceof HttpErrorException) { 138 result.errorCode = ((HttpErrorException) ex).responseCode; 139 msg += " Code:" + result.errorCode; 140 } 141 Log.e(LOG_TAG, msg); 142 AppGlobals.guiLogError(msg); 143 } 144 return result; 145 } 146 } 147 uploadReports(AbstractCommunicator.SyncSummary syncResult, Runnable progressListener)148 private void uploadReports(AbstractCommunicator.SyncSummary syncResult, Runnable progressListener) { 149 long uploadedObservations = 0; 150 long uploadedCells = 0; 151 long uploadedWifis = 0; 152 153 if (!mUploadArgs.mShouldIgnoreWifiStatus && mUploadArgs.mUseWifiOnly && 154 !mUploadArgs.mNetworkUtils.isWifiAvailable()) { 155 if (AppGlobals.isDebug) { 156 Log.d(LOG_TAG, "not on WiFi, not sending"); 157 } 158 syncResult.numIoExceptions += 1; 159 return; 160 } 161 162 Submitter submitter = new Submitter(); 163 DataStorageManager dm = DataStorageManager.getInstance(); 164 165 String error = null; 166 167 try { 168 DataStorageManager.ReportBatch batch = dm.getFirstBatch(); 169 while (batch != null) { 170 AbstractCommunicator.NetworkSendResult result = submitter.cleanSend(batch.data); 171 172 if (result.errorCode == 0) { 173 syncResult.totalBytesSent += result.bytesSent; 174 175 dm.delete(batch.filename); 176 177 uploadedObservations += batch.reportCount; 178 uploadedWifis += batch.wifiCount; 179 uploadedCells += batch.cellCount; 180 } else { 181 if (result.errorCode / 100 == 4) { 182 // delete on 4xx, no point in resending 183 dm.delete(batch.filename); 184 } else { 185 DataStorageManager.getInstance().saveCurrentReportsSendBufferToDisk(); 186 } 187 syncResult.numIoExceptions += 1; 188 } 189 190 if (progressListener != null) { 191 progressListener.run(); 192 } 193 194 batch = dm.getNextBatch(); 195 } 196 } 197 catch (IOException ex) { 198 error = ex.toString(); 199 } 200 201 try { 202 dm.incrementSyncStats(syncResult.totalBytesSent, uploadedObservations, uploadedCells, uploadedWifis); 203 } catch (IOException ex) { 204 error = ex.toString(); 205 } finally { 206 if (error != null) { 207 syncResult.numIoExceptions += 1; 208 Log.d(LOG_TAG, error); 209 AppGlobals.guiLogError(error + " (uploadReports)"); 210 } 211 submitter.close(); 212 } 213 } 214 } 215