From 9c539d5a5c8e9b36be482fd7ebb2a71a22ef6af0 Mon Sep 17 00:00:00 2001 From: Kurt Partridge Date: Wed, 18 Jul 2012 17:05:48 -0700 Subject: [PATCH] ResearchLog uploading - uploads files in the background to server multi-project commit with Ie0d937773e04b2fbefc8d76c231aaa52ebc392c9 Bug: 6188932 Change-Id: I90bb0e237eeb567e4cbb51085f2229f17f1fe71c --- java/res/values/urls.xml | 22 ++ .../research/ResearchLogUploader.java | 223 ++++++++++++++++++ .../inputmethod/research/ResearchLogger.java | 7 +- 3 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 java/res/values/urls.xml create mode 100644 java/src/com/android/inputmethod/research/ResearchLogUploader.java diff --git a/java/res/values/urls.xml b/java/res/values/urls.xml new file mode 100644 index 000000000..a8e9ad7d3 --- /dev/null +++ b/java/res/values/urls.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/java/src/com/android/inputmethod/research/ResearchLogUploader.java b/java/src/com/android/inputmethod/research/ResearchLogUploader.java new file mode 100644 index 000000000..3b1213009 --- /dev/null +++ b/java/src/com/android/inputmethod/research/ResearchLogUploader.java @@ -0,0 +1,223 @@ +/* + * 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 com.android.inputmethod.latin.R.string; + +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; + + private final boolean mCanUpload; + private final Context mContext; + private final File mFilesDir; + private final URL mUrl; + private final ScheduledExecutorService mExecutor; + + private Runnable doUploadRunnable = new UploadRunnable(null, false); + + 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) { + Log.d(TAG, "scheduling regular uploading"); + mExecutor.scheduleWithFixedDelay(doUploadRunnable, UPLOAD_INTERVAL_IN_MS, + UPLOAD_INTERVAL_IN_MS, TimeUnit.MILLISECONDS); + } else { + Log.d(TAG, "no permission to upload"); + } + } + + 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(callback, true)); + } + } + + 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 Callback mCallback; + private final boolean mForceUpload; + + public UploadRunnable(final Callback callback, final boolean forceUpload) { + mCallback = callback; + mForceUpload = forceUpload; + } + + @Override + public void run() { + doUpload(); + } + + 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; + } + } +} diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index e27e84f53..b40bfe387 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -120,6 +120,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private KeyboardSwitcher mKeyboardSwitcher; private Context mContext; + private ResearchLogUploader mResearchLogUploader; + private ResearchLogger() { } @@ -158,6 +160,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang e.apply(); } } + mResearchLogUploader = new ResearchLogUploader(ims, mFilesDir); + mResearchLogUploader.start(); mKeyboardSwitcher = keyboardSwitcher; mContext = ims; mPrefs = prefs; @@ -327,7 +331,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang latinIME.getString(R.string.note_timestamp_for_researchlog), showEnable ? latinIME.getString(R.string.enable_session_logging) : latinIME.getString(R.string.do_not_log_this_session), - latinIME.getString(R.string.log_whole_session_history), + latinIME.getString(R.string.log_whole_session_history) }; final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { @Override @@ -536,6 +540,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang return; } if (mMainResearchLog == null) { + Log.w(TAG, "ResearchLog was not properly set up"); return; } if (isPrivacySensitive) {