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