ResearchLogging upload via service
Upload using an intent service rather than just a thread. More robust in case the keyboard is closed and the upload hasn't finished yet. multi-project commit with Idf39453f6ccc70af63e7b2c912f61f2533339870 Bug: 6188932 Change-Id: Id5484f2b6349e316e828d5bdc0ed9ca92887d5b8main
parent
a1ec8195d6
commit
7bc4bde41c
|
@ -289,6 +289,10 @@
|
||||||
<!-- TODO: remove translatable=false attribute once text is stable -->
|
<!-- TODO: remove translatable=false attribute once text is stable -->
|
||||||
<string name="research_send_usage_info" translatable="false">Send usage info</string>
|
<string name="research_send_usage_info" translatable="false">Send usage info</string>
|
||||||
|
|
||||||
|
<!-- Name for the research uploading service to be displayed to users. [CHAR LIMIT=50] -->
|
||||||
|
<!-- TODO: remove translatable=false attribute once text is stable -->
|
||||||
|
<string name="research_log_uploader_name" translatable="false">Research Uploader Service</string>
|
||||||
|
|
||||||
<!-- Preference for input language selection -->
|
<!-- Preference for input language selection -->
|
||||||
<string name="select_language">Input languages</string>
|
<string name="select_language">Input languages</string>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2012 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.inputmethod.research;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arrange for the uploading service to be run on regular intervals.
|
||||||
|
*/
|
||||||
|
public final class BootBroadcastReceiver extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
|
||||||
|
ResearchLogger.scheduleUploadingService(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,240 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2012 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.inputmethod.research;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.ConnectivityManager;
|
|
||||||
import android.net.NetworkInfo;
|
|
||||||
import android.os.BatteryManager;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.android.inputmethod.latin.R;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileFilter;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public final class ResearchLogUploader {
|
|
||||||
private static final String TAG = ResearchLogUploader.class.getSimpleName();
|
|
||||||
private static final int UPLOAD_INTERVAL_IN_MS = 1000 * 60 * 15; // every 15 min
|
|
||||||
private static final int BUF_SIZE = 1024 * 8;
|
|
||||||
protected static final int TIMEOUT_IN_MS = 1000 * 4;
|
|
||||||
|
|
||||||
private final boolean mCanUpload;
|
|
||||||
private final Context mContext;
|
|
||||||
private final File mFilesDir;
|
|
||||||
private final URL mUrl;
|
|
||||||
private final ScheduledExecutorService mExecutor;
|
|
||||||
|
|
||||||
public ResearchLogUploader(final Context context, final File filesDir) {
|
|
||||||
mContext = context;
|
|
||||||
mFilesDir = filesDir;
|
|
||||||
final PackageManager packageManager = context.getPackageManager();
|
|
||||||
final boolean hasPermission = packageManager.checkPermission(Manifest.permission.INTERNET,
|
|
||||||
context.getPackageName()) == PackageManager.PERMISSION_GRANTED;
|
|
||||||
if (!hasPermission) {
|
|
||||||
mCanUpload = false;
|
|
||||||
mUrl = null;
|
|
||||||
mExecutor = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
URL tempUrl = null;
|
|
||||||
boolean canUpload = false;
|
|
||||||
ScheduledExecutorService executor = null;
|
|
||||||
try {
|
|
||||||
final String urlString = context.getString(R.string.research_logger_upload_url);
|
|
||||||
if (urlString == null || urlString.equals("")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tempUrl = new URL(urlString);
|
|
||||||
canUpload = true;
|
|
||||||
executor = Executors.newSingleThreadScheduledExecutor();
|
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
tempUrl = null;
|
|
||||||
e.printStackTrace();
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
mCanUpload = canUpload;
|
|
||||||
mUrl = tempUrl;
|
|
||||||
mExecutor = executor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
if (mCanUpload) {
|
|
||||||
mExecutor.scheduleWithFixedDelay(new UploadRunnable(null /* logToWaitFor */,
|
|
||||||
null /* callback */, false /* forceUpload */),
|
|
||||||
UPLOAD_INTERVAL_IN_MS, UPLOAD_INTERVAL_IN_MS, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void uploadAfterCompletion(final ResearchLog researchLog, final Callback callback) {
|
|
||||||
if (mCanUpload) {
|
|
||||||
mExecutor.submit(new UploadRunnable(researchLog, callback, true /* forceUpload */));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void uploadNow(final Callback callback) {
|
|
||||||
// Perform an immediate upload. Note that this should happen even if there is
|
|
||||||
// another upload happening right now, as it may have missed the latest changes.
|
|
||||||
// TODO: Reschedule regular upload tests starting from now.
|
|
||||||
if (mCanUpload) {
|
|
||||||
mExecutor.submit(new UploadRunnable(null /* logToWaitFor */, callback,
|
|
||||||
true /* forceUpload */));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Callback {
|
|
||||||
public void onUploadCompleted(final boolean success);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isExternallyPowered() {
|
|
||||||
final Intent intent = mContext.registerReceiver(null, new IntentFilter(
|
|
||||||
Intent.ACTION_BATTERY_CHANGED));
|
|
||||||
final int pluggedState = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
|
|
||||||
return pluggedState == BatteryManager.BATTERY_PLUGGED_AC
|
|
||||||
|| pluggedState == BatteryManager.BATTERY_PLUGGED_USB;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasWifiConnection() {
|
|
||||||
final ConnectivityManager manager =
|
|
||||||
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
||||||
final NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
|
|
||||||
return wifiInfo.isConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
class UploadRunnable implements Runnable {
|
|
||||||
private final ResearchLog mLogToWaitFor;
|
|
||||||
private final Callback mCallback;
|
|
||||||
private final boolean mForceUpload;
|
|
||||||
|
|
||||||
public UploadRunnable(final ResearchLog logToWaitFor, final Callback callback,
|
|
||||||
final boolean forceUpload) {
|
|
||||||
mLogToWaitFor = logToWaitFor;
|
|
||||||
mCallback = callback;
|
|
||||||
mForceUpload = forceUpload;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (mLogToWaitFor != null) {
|
|
||||||
waitFor(mLogToWaitFor);
|
|
||||||
}
|
|
||||||
doUpload();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void waitFor(final ResearchLog researchLog) {
|
|
||||||
try {
|
|
||||||
researchLog.awaitTermination(TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doUpload() {
|
|
||||||
if (!mForceUpload && (!isExternallyPowered() || !hasWifiConnection())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mFilesDir == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final File[] files = mFilesDir.listFiles(new FileFilter() {
|
|
||||||
@Override
|
|
||||||
public boolean accept(File pathname) {
|
|
||||||
return pathname.getName().startsWith(ResearchLogger.FILENAME_PREFIX)
|
|
||||||
&& !pathname.canWrite();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
boolean success = true;
|
|
||||||
if (files.length == 0) {
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
for (final File file : files) {
|
|
||||||
if (!uploadFile(file)) {
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mCallback != null) {
|
|
||||||
mCallback.onUploadCompleted(success);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean uploadFile(File file) {
|
|
||||||
Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
|
|
||||||
boolean success = false;
|
|
||||||
final int contentLength = (int) file.length();
|
|
||||||
HttpURLConnection connection = null;
|
|
||||||
InputStream fileIs = null;
|
|
||||||
try {
|
|
||||||
fileIs = new FileInputStream(file);
|
|
||||||
connection = (HttpURLConnection) mUrl.openConnection();
|
|
||||||
connection.setRequestMethod("PUT");
|
|
||||||
connection.setDoOutput(true);
|
|
||||||
connection.setFixedLengthStreamingMode(contentLength);
|
|
||||||
final OutputStream os = connection.getOutputStream();
|
|
||||||
final byte[] buf = new byte[BUF_SIZE];
|
|
||||||
int numBytesRead;
|
|
||||||
while ((numBytesRead = fileIs.read(buf)) != -1) {
|
|
||||||
os.write(buf, 0, numBytesRead);
|
|
||||||
}
|
|
||||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
|
||||||
Log.d(TAG, "upload failed: " + connection.getResponseCode());
|
|
||||||
InputStream netIs = connection.getInputStream();
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(netIs));
|
|
||||||
String line;
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
Log.d(TAG, "| " + reader.readLine());
|
|
||||||
}
|
|
||||||
reader.close();
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
file.delete();
|
|
||||||
success = true;
|
|
||||||
Log.d(TAG, "upload successful");
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
if (fileIs != null) {
|
|
||||||
try {
|
|
||||||
fileIs.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (connection != null) {
|
|
||||||
connection.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,11 +18,14 @@ package com.android.inputmethod.research;
|
||||||
|
|
||||||
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
|
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
|
||||||
|
|
||||||
|
import android.app.AlarmManager;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.DialogInterface.OnCancelListener;
|
import android.content.DialogInterface.OnCancelListener;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.SharedPreferences.Editor;
|
import android.content.SharedPreferences.Editor;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
|
@ -133,7 +136,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
|
||||||
private KeyboardSwitcher mKeyboardSwitcher;
|
private KeyboardSwitcher mKeyboardSwitcher;
|
||||||
private InputMethodService mInputMethodService;
|
private InputMethodService mInputMethodService;
|
||||||
private final Statistics mStatistics;
|
private final Statistics mStatistics;
|
||||||
private ResearchLogUploader mResearchLogUploader;
|
|
||||||
|
private Intent mUploadIntent;
|
||||||
|
private PendingIntent mUploadPendingIntent;
|
||||||
|
|
||||||
private LogUnit mCurrentLogUnit = new LogUnit();
|
private LogUnit mCurrentLogUnit = new LogUnit();
|
||||||
|
|
||||||
|
@ -176,11 +181,34 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
|
||||||
e.apply();
|
e.apply();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mResearchLogUploader = new ResearchLogUploader(ims, mFilesDir);
|
|
||||||
mResearchLogUploader.start();
|
|
||||||
mKeyboardSwitcher = keyboardSwitcher;
|
mKeyboardSwitcher = keyboardSwitcher;
|
||||||
mInputMethodService = ims;
|
mInputMethodService = ims;
|
||||||
mPrefs = prefs;
|
mPrefs = prefs;
|
||||||
|
mUploadIntent = new Intent(mInputMethodService, UploaderService.class);
|
||||||
|
mUploadPendingIntent = PendingIntent.getService(mInputMethodService, 0, mUploadIntent, 0);
|
||||||
|
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
scheduleUploadingService(mInputMethodService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arrange for the UploaderService to be run on a regular basis.
|
||||||
|
*
|
||||||
|
* Any existing scheduled invocation of UploaderService is removed and rescheduled. This may
|
||||||
|
* cause problems if this method is called often and frequent updates are required, but since
|
||||||
|
* the user will likely be sleeping at some point, if the interval is less that the expected
|
||||||
|
* sleep duration and this method is not called during that time, the service should be invoked
|
||||||
|
* at some point.
|
||||||
|
*/
|
||||||
|
public static void scheduleUploadingService(Context context) {
|
||||||
|
final Intent intent = new Intent(context, UploaderService.class);
|
||||||
|
final PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
|
||||||
|
final AlarmManager manager =
|
||||||
|
(AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||||
|
manager.cancel(pendingIntent);
|
||||||
|
manager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||||
|
UploaderService.RUN_INTERVAL, UploaderService.RUN_INTERVAL, pendingIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cleanupLoggingDir(final File dir, final long time) {
|
private void cleanupLoggingDir(final File dir, final long time) {
|
||||||
|
@ -257,6 +285,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
|
||||||
final Editor e = mPrefs.edit();
|
final Editor e = mPrefs.edit();
|
||||||
e.putBoolean(PREF_RESEARCH_HAS_SEEN_SPLASH, true);
|
e.putBoolean(PREF_RESEARCH_HAS_SEEN_SPLASH, true);
|
||||||
e.apply();
|
e.apply();
|
||||||
|
restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLoggingAllowed(boolean enableLogging) {
|
private void setLoggingAllowed(boolean enableLogging) {
|
||||||
|
@ -477,10 +506,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
|
||||||
if (mFeedbackLogBuffer == null) {
|
if (mFeedbackLogBuffer == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!includeHistory) {
|
if (includeHistory) {
|
||||||
|
commitCurrentLogUnit();
|
||||||
|
} else {
|
||||||
mFeedbackLogBuffer.clear();
|
mFeedbackLogBuffer.clear();
|
||||||
}
|
}
|
||||||
commitCurrentLogUnit();
|
|
||||||
final LogUnit feedbackLogUnit = new LogUnit();
|
final LogUnit feedbackLogUnit = new LogUnit();
|
||||||
final Object[] values = {
|
final Object[] values = {
|
||||||
feedbackContents
|
feedbackContents
|
||||||
|
@ -490,10 +520,14 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
|
||||||
mFeedbackLogBuffer.shiftIn(feedbackLogUnit);
|
mFeedbackLogBuffer.shiftIn(feedbackLogUnit);
|
||||||
publishLogBuffer(mFeedbackLogBuffer, mFeedbackLog, true /* isIncludingPrivateData */);
|
publishLogBuffer(mFeedbackLogBuffer, mFeedbackLog, true /* isIncludingPrivateData */);
|
||||||
mFeedbackLog.close();
|
mFeedbackLog.close();
|
||||||
mResearchLogUploader.uploadAfterCompletion(mFeedbackLog, null);
|
uploadNow();
|
||||||
mFeedbackLog = new ResearchLog(createLogFile(mFilesDir));
|
mFeedbackLog = new ResearchLog(createLogFile(mFilesDir));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void uploadNow() {
|
||||||
|
mInputMethodService.startService(mUploadIntent);
|
||||||
|
}
|
||||||
|
|
||||||
public void onLeavingSendFeedbackDialog() {
|
public void onLeavingSendFeedbackDialog() {
|
||||||
mInFeedbackDialog = false;
|
mInFeedbackDialog = false;
|
||||||
}
|
}
|
||||||
|
@ -731,10 +765,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String[] EVENTKEYS_USER_FEEDBACK = {
|
|
||||||
"UserFeedback", "FeedbackContents"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Regular logging methods
|
// Regular logging methods
|
||||||
|
|
||||||
private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_PROCESSMOTIONEVENT = {
|
private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_PROCESSMOTIONEVENT = {
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2012 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.inputmethod.research;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.IntentService;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
import android.os.BatteryManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.inputmethod.latin.R;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public final class UploaderService extends IntentService {
|
||||||
|
private static final String TAG = UploaderService.class.getSimpleName();
|
||||||
|
public static final long RUN_INTERVAL = AlarmManager.INTERVAL_HOUR;
|
||||||
|
private static final String EXTRA_UPLOAD_UNCONDITIONALLY = UploaderService.class.getName()
|
||||||
|
+ ".extra.UPLOAD_UNCONDITIONALLY";
|
||||||
|
private static final int BUF_SIZE = 1024 * 8;
|
||||||
|
protected static final int TIMEOUT_IN_MS = 1000 * 4;
|
||||||
|
|
||||||
|
private boolean mCanUpload;
|
||||||
|
private File mFilesDir;
|
||||||
|
private URL mUrl;
|
||||||
|
|
||||||
|
public UploaderService() {
|
||||||
|
super("Research Uploader Service");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
|
||||||
|
mCanUpload = false;
|
||||||
|
mFilesDir = null;
|
||||||
|
mUrl = null;
|
||||||
|
|
||||||
|
final PackageManager packageManager = getPackageManager();
|
||||||
|
final boolean hasPermission = packageManager.checkPermission(Manifest.permission.INTERNET,
|
||||||
|
getPackageName()) == PackageManager.PERMISSION_GRANTED;
|
||||||
|
if (!hasPermission) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final String urlString = getString(R.string.research_logger_upload_url);
|
||||||
|
if (urlString == null || urlString.equals("")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mFilesDir = getFilesDir();
|
||||||
|
mUrl = new URL(urlString);
|
||||||
|
mCanUpload = true;
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onHandleIntent(Intent intent) {
|
||||||
|
if (!mCanUpload) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean isUploadingUnconditionally = false;
|
||||||
|
Bundle bundle = intent.getExtras();
|
||||||
|
if (bundle != null && bundle.containsKey(EXTRA_UPLOAD_UNCONDITIONALLY)) {
|
||||||
|
isUploadingUnconditionally = bundle.getBoolean(EXTRA_UPLOAD_UNCONDITIONALLY);
|
||||||
|
}
|
||||||
|
doUpload(isUploadingUnconditionally);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isExternallyPowered() {
|
||||||
|
final Intent intent = registerReceiver(null, new IntentFilter(
|
||||||
|
Intent.ACTION_BATTERY_CHANGED));
|
||||||
|
final int pluggedState = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
|
||||||
|
return pluggedState == BatteryManager.BATTERY_PLUGGED_AC
|
||||||
|
|| pluggedState == BatteryManager.BATTERY_PLUGGED_USB;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasWifiConnection() {
|
||||||
|
final ConnectivityManager manager =
|
||||||
|
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
final NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
|
||||||
|
return wifiInfo.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doUpload(final boolean isUploadingUnconditionally) {
|
||||||
|
if (!isUploadingUnconditionally && (!isExternallyPowered() || !hasWifiConnection())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mFilesDir == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final File[] files = mFilesDir.listFiles(new FileFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File pathname) {
|
||||||
|
return pathname.getName().startsWith(ResearchLogger.FILENAME_PREFIX)
|
||||||
|
&& !pathname.canWrite();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
boolean success = true;
|
||||||
|
if (files.length == 0) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
for (final File file : files) {
|
||||||
|
if (!uploadFile(file)) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean uploadFile(File file) {
|
||||||
|
Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
|
||||||
|
boolean success = false;
|
||||||
|
final int contentLength = (int) file.length();
|
||||||
|
HttpURLConnection connection = null;
|
||||||
|
InputStream fileInputStream = null;
|
||||||
|
try {
|
||||||
|
fileInputStream = new FileInputStream(file);
|
||||||
|
connection = (HttpURLConnection) mUrl.openConnection();
|
||||||
|
connection.setRequestMethod("PUT");
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
connection.setFixedLengthStreamingMode(contentLength);
|
||||||
|
final OutputStream os = connection.getOutputStream();
|
||||||
|
final byte[] buf = new byte[BUF_SIZE];
|
||||||
|
int numBytesRead;
|
||||||
|
while ((numBytesRead = fileInputStream.read(buf)) != -1) {
|
||||||
|
os.write(buf, 0, numBytesRead);
|
||||||
|
}
|
||||||
|
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||||
|
Log.d(TAG, "upload failed: " + connection.getResponseCode());
|
||||||
|
InputStream netInputStream = connection.getInputStream();
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(netInputStream));
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
Log.d(TAG, "| " + reader.readLine());
|
||||||
|
}
|
||||||
|
reader.close();
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
file.delete();
|
||||||
|
success = true;
|
||||||
|
Log.d(TAG, "upload successful");
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
if (fileInputStream != null) {
|
||||||
|
try {
|
||||||
|
fileInputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (connection != null) {
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue