diff --git a/.travis.yml b/.travis.yml index 9e290c0..cbe2286 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,5 +14,5 @@ android: - android-23 - extra-android-m2repository -#notifications: -# slack: literacyapp:HLjtHJdZ7DYJV2DlnDLrG6Gl +notifications: + email: false diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 56ab5c4..fdad011 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ package="org.literacyapp.analytics"> + @@ -10,6 +11,7 @@ android:name=".AnalyticsApplication" android:icon="@mipmap/ic_launcher" android:label="@string/app_name"> + @@ -29,72 +31,64 @@ android:enabled="true" android:exported="true" android:permission="android.permission.BIND_JOB_SERVICE" /> + - - - - - - - - - - - - - diff --git a/app/src/main/java/org/literacyapp/analytics/MainActivity.java b/app/src/main/java/org/literacyapp/analytics/MainActivity.java index 0b15726..d7e9b84 100644 --- a/app/src/main/java/org/literacyapp/analytics/MainActivity.java +++ b/app/src/main/java/org/literacyapp/analytics/MainActivity.java @@ -17,6 +17,7 @@ import android.widget.Toast; import org.literacyapp.analytics.service.ScreenshotJobService; +import org.literacyapp.analytics.service.ServerSynchronizationJobService; import org.literacyapp.analytics.util.RootHelper; public class MainActivity extends Activity { @@ -61,20 +62,34 @@ public void onClick(View view) { if (!isRootPermissionGranted) { Toast.makeText(getApplicationContext(), "Root permission was not granted. Please see log for details.", Toast.LENGTH_LONG).show(); } else { - Toast.makeText(getApplicationContext(), "Root permission was granted. Starting background job...", Toast.LENGTH_LONG).show(); + Toast.makeText(getApplicationContext(), "Root permission was granted. Starting background jobs...", Toast.LENGTH_LONG).show(); - // Initiate background job for recording screenshots - ComponentName componentName = new ComponentName(getApplicationContext(), ScreenshotJobService.class); + // Initiate background job for synchronizing events with server + // Note: This code block also exists in the BootReceiver + ComponentName componentName = new ComponentName(getApplicationContext(), ServerSynchronizationJobService.class); JobInfo.Builder builder = new JobInfo.Builder(1, componentName); + builder.setPeriodic(60 * 60 * 1000); // Every 60 minutes + JobInfo serverSynchronizationJobInfo = builder.build(); + JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); + int resultId = jobScheduler.schedule(serverSynchronizationJobInfo); + if (resultId == JobScheduler.RESULT_SUCCESS) { + Log.i(getClass().getName(), "Server synchronization Job scheduled with id: " + serverSynchronizationJobInfo.getId()); + } else { + Log.w(getClass().getName(), "Server synchronization Job scheduling failed. JobInfo id: " + serverSynchronizationJobInfo.getId()); + } + + + // Initiate background job for recording screenshots + // Note: This code block also exists in the BootReceiver + componentName = new ComponentName(getApplicationContext(), ScreenshotJobService.class); + builder = new JobInfo.Builder(2, componentName); builder.setPeriodic(5 * 60 * 1000); // Every 5 minutes JobInfo screenshotJobInfo = builder.build(); - - JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); - int resultId = jobScheduler.schedule(screenshotJobInfo); - if (resultId > 0) { - Log.i(getClass().getName(), "Job scheduled with id: " + resultId); + resultId = jobScheduler.schedule(screenshotJobInfo); + if (resultId == JobScheduler.RESULT_SUCCESS) { + Log.i(getClass().getName(), "Screenshot Job scheduled with id: " + screenshotJobInfo.getId()); } else { - Log.w(getClass().getName(), "Job scheduling failed. Error id: " + resultId); + Log.w(getClass().getName(), "Screenshot Job scheduling failed. JobInfo id: " + serverSynchronizationJobInfo.getId()); } } diff --git a/app/src/main/java/org/literacyapp/analytics/receiver/BootReceiver.java b/app/src/main/java/org/literacyapp/analytics/receiver/BootReceiver.java index dd3f4b0..b93fc47 100644 --- a/app/src/main/java/org/literacyapp/analytics/receiver/BootReceiver.java +++ b/app/src/main/java/org/literacyapp/analytics/receiver/BootReceiver.java @@ -16,6 +16,7 @@ import org.literacyapp.analytics.dao.BootCompletedEventDao; import org.literacyapp.analytics.model.BootCompletedEvent; import org.literacyapp.analytics.service.ScreenshotJobService; +import org.literacyapp.analytics.service.ServerSynchronizationJobService; import org.literacyapp.analytics.util.DeviceInfoHelper; import java.io.File; @@ -28,19 +29,6 @@ public class BootReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { Log.i(getClass().getName(), "onReceive"); - // Initiate background job for recording screenshots - ComponentName componentName = new ComponentName(context, ScreenshotJobService.class); - JobInfo.Builder builder = new JobInfo.Builder(1, componentName); - builder.setPeriodic(5 * 60 * 1000); // Every 5 minutes - JobInfo screenshotJobInfo = builder.build(); - JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - int resultId = jobScheduler.schedule(screenshotJobInfo); - if (resultId > 0) { - Log.i(getClass().getName(), "Job scheduled with id: " + resultId); - } else { - Log.w(getClass().getName(), "Job scheduling failed. Error id: " + resultId); - } - // Store event in database BootCompletedEvent bootCompletedEvent = new BootCompletedEvent(); @@ -73,5 +61,34 @@ public void onReceive(Context context, Intent intent) { } catch (IOException e) { Log.e(getClass().getName(), null, e); } + + + // Initiate background job for synchronizing events with server + // Note: This code block also exists in the MainActivity + ComponentName componentName = new ComponentName(context, ServerSynchronizationJobService.class); + JobInfo.Builder builder = new JobInfo.Builder(1, componentName); + builder.setPeriodic(60 * 60 * 1000); // Every 60 minutes + JobInfo serverSynchronizationJobInfo = builder.build(); + JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); + int resultId = jobScheduler.schedule(serverSynchronizationJobInfo); + if (resultId == JobScheduler.RESULT_SUCCESS) { + Log.i(getClass().getName(), "Server synchronization Job scheduled with id: " + serverSynchronizationJobInfo.getId()); + } else { + Log.w(getClass().getName(), "Server synchronization Job scheduling failed. JobInfo id: " + serverSynchronizationJobInfo.getId()); + } + + + // Initiate background job for recording screenshots + // Note: This code block also exists in the MainActivity + componentName = new ComponentName(context, ScreenshotJobService.class); + builder = new JobInfo.Builder(2, componentName); + builder.setPeriodic(5 * 60 * 1000); // Every 5 minutes + JobInfo screenshotJobInfo = builder.build(); + resultId = jobScheduler.schedule(screenshotJobInfo); + if (resultId == JobScheduler.RESULT_SUCCESS) { + Log.i(getClass().getName(), "Screenshot Job scheduled with id: " + screenshotJobInfo.getId()); + } else { + Log.w(getClass().getName(), "Screenshot Job scheduling failed. JobInfo id: " + serverSynchronizationJobInfo.getId()); + } } } diff --git a/app/src/main/java/org/literacyapp/analytics/service/ScreenshotJobService.java b/app/src/main/java/org/literacyapp/analytics/service/ScreenshotJobService.java index 386663d..2c3134f 100644 --- a/app/src/main/java/org/literacyapp/analytics/service/ScreenshotJobService.java +++ b/app/src/main/java/org/literacyapp/analytics/service/ScreenshotJobService.java @@ -15,6 +15,8 @@ /** * Service responsible for recording screenshots when the screen in switched on. + * + * This service is triggered in the @{link {@link org.literacyapp.analytics.receiver.BootReceiver}} */ public class ScreenshotJobService extends JobService { diff --git a/app/src/main/java/org/literacyapp/analytics/service/ServerSynchronizationJobService.java b/app/src/main/java/org/literacyapp/analytics/service/ServerSynchronizationJobService.java index be1c62c..973a8cf 100644 --- a/app/src/main/java/org/literacyapp/analytics/service/ServerSynchronizationJobService.java +++ b/app/src/main/java/org/literacyapp/analytics/service/ServerSynchronizationJobService.java @@ -1,16 +1,90 @@ package org.literacyapp.analytics.service; -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.os.Environment; +import android.util.Log; -public class ServerSynchronizationJobService extends Service { - public ServerSynchronizationJobService() { +import org.literacyapp.analytics.util.EnvironmentSettings; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * Service responsible for uploading event files to server. + * + * This service is triggered in the @{link {@link org.literacyapp.analytics.receiver.BootReceiver}} + */ +public class ServerSynchronizationJobService extends JobService { + + @Override + public boolean onStartJob(JobParameters jobParameters) { + Log.i(getClass().getName(), "onStartJob"); + + String logsPath = Environment.getExternalStorageDirectory() + "/.literacyapp-analytics/events"; + File eventsDir = new File(logsPath); + Log.i(getClass().getName(), "eventsDir: " + eventsDir); + Log.i(getClass().getName(), "eventsDir.exists(): " + eventsDir.exists()); + if (eventsDir.exists()) { + File[] deviceDirs = eventsDir.listFiles(); + for (int i = 0; i < deviceDirs.length; i++) { + File deviceDir = deviceDirs[i]; + Log.i(getClass().getName(), "deviceDir: " + deviceDir); + + File[] eventFiles = deviceDir.listFiles(); + for (int j = 0; j < eventFiles.length; j++) { + File eventFile = eventFiles[j]; + Log.i(getClass().getName(), "eventFile: " + eventFile); + // Expected filename: "application_opened_events_yyyy-MM-dd.log" + + if (eventFile.getName().startsWith("application_opened_events_")) { + // Skip files generated more than 7 days ago + String dateAsString = eventFile.getName().replace("application_opened_events_", "").replace(".log", ""); + Log.i(getClass().getName(), "dateAsString: " + dateAsString); + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); + try { + Date date = simpleDateFormat.parse(dateAsString); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + Log.i(getClass().getName(), "calendar.getTime(): " + calendar.getTime()); + + Calendar calendar7DaysAgo = Calendar.getInstance(); + calendar7DaysAgo.setTime(calendar.getTime()); + calendar7DaysAgo.add(Calendar.DAY_OF_MONTH, -7); + Log.i(getClass().getName(), "calendar7DaysAgo.getTime(): " + calendar7DaysAgo.getTime()); + + if (!calendar.before(calendar7DaysAgo)) { + Log.i(getClass().getName(), "Uploading to web server: " + eventFile); + + new UploadApplicationOpenedEventsAsyncTask().execute(eventFile); + } + } catch (ParseException e) { + Log.e(getClass().getName(), null, e); + } + } + } + } + } + + boolean asynchronousProcessing = false; + return asynchronousProcessing; } @Override - public IBinder onBind(Intent intent) { - // TODO: Return the communication channel to the service. - throw new UnsupportedOperationException("Not yet implemented"); + public boolean onStopJob(JobParameters jobParameters) { + Log.i(getClass().getName(), "onStopJob"); + + boolean restartJob = false; + return restartJob; } } diff --git a/app/src/main/java/org/literacyapp/analytics/service/UploadApplicationOpenedEventsAsyncTask.java b/app/src/main/java/org/literacyapp/analytics/service/UploadApplicationOpenedEventsAsyncTask.java index 4e3e302..d9f8a27 100644 --- a/app/src/main/java/org/literacyapp/analytics/service/UploadApplicationOpenedEventsAsyncTask.java +++ b/app/src/main/java/org/literacyapp/analytics/service/UploadApplicationOpenedEventsAsyncTask.java @@ -1,331 +1,78 @@ -package org.literacyapp.appstore.task; +package org.literacyapp.analytics.service; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.os.AsyncTask; -import android.os.Build; -import android.os.Environment; -import android.preference.PreferenceManager; -import android.text.TextUtils; import android.util.Log; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.literacyapp.appstore.AppstoreApplication; -import org.literacyapp.appstore.MainActivity; -import org.literacyapp.appstore.R; -import org.literacyapp.appstore.dao.ApplicationDao; -import org.literacyapp.appstore.model.Application; -import org.literacyapp.appstore.util.ApkLoader; -import org.literacyapp.appstore.util.ChecksumHelper; -import org.literacyapp.appstore.util.ConnectivityHelper; -import org.literacyapp.appstore.util.DeviceInfoHelper; -import org.literacyapp.appstore.util.EnvironmentSettings; -import org.literacyapp.appstore.util.JsonLoader; -import org.literacyapp.appstore.util.UserPrefsHelper; -import org.literacyapp.model.enums.admin.ApplicationStatus; -import org.literacyapp.model.gson.admin.ApplicationGson; -import org.literacyapp.model.gson.admin.ApplicationVersionGson; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.literacyapp.analytics.util.EnvironmentSettings; import java.io.BufferedReader; +import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.lang.reflect.Type; -import java.util.Calendar; -import java.util.Locale; - -public class DownloadApplicationsAsyncTask extends AsyncTask { - - private Context context; - - private ApplicationDao applicationDao; - - public DownloadApplicationsAsyncTask(Context context) { - this.context = context; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; - AppstoreApplication appstoreApplication = (AppstoreApplication) context; - applicationDao = appstoreApplication.getDaoSession().getApplicationDao(); - } +public class UploadApplicationOpenedEventsAsyncTask extends AsyncTask { @Override - protected Void doInBackground(Object... objects) { + protected Void doInBackground(File... files) { Log.i(getClass().getName(), "doInBackground"); - boolean isWifiEnabled = ConnectivityHelper.isWifiEnabled(context); - Log.i(getClass().getName(), "isWifiEnabled: " + isWifiEnabled); - boolean isWifiConnected = ConnectivityHelper.isWifiConnected(context); - Log.i(getClass().getName(), "isWifiConnected: " + isWifiConnected); - boolean isServerReachable = ConnectivityHelper.isServerReachable(context); - Log.i(getClass().getName(), "isServerReachable: " + isServerReachable); - if (!isWifiEnabled) { - Log.w(getClass().getName(), context.getString(R.string.wifi_needs_to_be_enabled)); - } else if (!isWifiConnected) { - Log.w(getClass().getName(), context.getString(R.string.wifi_needs_to_be_connected)); - } else if (!isServerReachable) { - Log.w(getClass().getName(), context.getString(R.string.server_is_not_reachable)); - } else { - // Download List of applications - String url = EnvironmentSettings.getBaseRestUrl() + "/admin/application/list" + - "?deviceId=" + DeviceInfoHelper.getDeviceId(context) + - "&checksum=" + ChecksumHelper.getChecksum(context) + - "&locale=" + UserPrefsHelper.getLocale(context) + - "&deviceModel=" + DeviceInfoHelper.getDeviceModel(context) + - "&osVersion=" + Build.VERSION.SDK_INT + - "&applicationId=" + DeviceInfoHelper.getApplicationId(context) + - "&appVersionCode=" + DeviceInfoHelper.getAppVersionCode(context); - String jsonResponse = JsonLoader.loadJson(url); - Log.i(getClass().getName(), "jsonResponse: " + jsonResponse); - try { - JSONObject jsonObject = new JSONObject(jsonResponse); - if (!"success".equals(jsonObject.getString("result"))) { - Log.w(getClass().getName(), "Download failed"); - String errorDescription = jsonObject.getString("description"); - } else { - JSONArray jsonArrayApplications = jsonObject.getJSONArray("applications"); - for (int i = 0; i < jsonArrayApplications.length(); i++) { - Type type = new TypeToken() {}.getType(); - ApplicationGson applicationGson = new Gson().fromJson(jsonArrayApplications.getString(i), type); - Log.i(getClass().getName(), "Synchronizing APK " + (i + 1) + "/" + jsonArrayApplications.length() + ": " + applicationGson.getPackageName() + " (status " + applicationGson.getApplicationStatus() + ")"); - - Application application = applicationDao.load(applicationGson.getId()); - if (application == null) { - // Store new Application in database - application = new Application(); - application.setId(applicationGson.getId()); - application.setLocale(applicationGson.getLocale()); - application.setPackageName(applicationGson.getPackageName()); - application.setLiteracySkills(applicationGson.getLiteracySkills()); - application.setNumeracySkills(applicationGson.getNumeracySkills()); - application.setApplicationStatus(applicationGson.getApplicationStatus()); - if (applicationGson.getApplicationStatus() == ApplicationStatus.ACTIVE) { - ApplicationVersionGson applicationVersionGson = applicationGson.getApplicationVersions().get(0); - application.setVersionCode(applicationVersionGson.getVersionCode()); - application.setStartCommand(applicationVersionGson.getStartCommand()); - } - long id = applicationDao.insert(application); - Log.i(getClass().getName(), "Stored Application in database with id " + id); - } else { - // Update existing Application in database - application.setId(applicationGson.getId()); - application.setLocale(applicationGson.getLocale()); - application.setPackageName(applicationGson.getPackageName()); - application.setLiteracySkills(applicationGson.getLiteracySkills()); - application.setNumeracySkills(applicationGson.getNumeracySkills()); - application.setApplicationStatus(applicationGson.getApplicationStatus()); - if (applicationGson.getApplicationStatus() == ApplicationStatus.ACTIVE) { - ApplicationVersionGson applicationVersionGson = applicationGson.getApplicationVersions().get(0); - application.setVersionCode(applicationVersionGson.getVersionCode()); - application.setStartCommand(applicationVersionGson.getStartCommand()); - } - applicationDao.update(application); - Log.i(getClass().getName(), "Updated Application in database with id " + application.getId()); - } - - // Download APK if missing from SD card - if (applicationGson.getApplicationStatus() == ApplicationStatus.ACTIVE) { - String language = Locale.getDefault().getLanguage(); - File apkDirectory = new File(Environment.getExternalStorageDirectory() + "/.literacyapp-appstore/apks/" + language); - ApplicationVersionGson applicationVersionGson = applicationGson.getApplicationVersions().get(0); - String fileName = applicationVersionGson.getApplication().getPackageName() + "-" + applicationVersionGson.getVersionCode() + ".apk"; - File apkFile = new File(apkDirectory, fileName); - Log.i(getClass().getName(), "apkFile: " + apkFile); - Log.i(getClass().getName(), "apkFile.exists(): " + apkFile.exists()); - if (!apkFile.exists()) { - Log.i(getClass().getName(), "APK file (" + fileName + ") missing from SD card. Downloading..."); - downloadApk(applicationVersionGson); - } - } - - // Delete/update/install application - PackageManager packageManager = context.getPackageManager(); - try { - PackageInfo packageInfo = packageManager.getPackageInfo(applicationGson.getPackageName(), PackageManager.GET_ACTIVITIES); - Log.i(getClass().getName(), "The application is already installed: " + applicationGson.getPackageName()); - - // Check if the Application has been deleted/deactivated on the website - if ((applicationGson.getApplicationStatus() == ApplicationStatus.DELETED) - || (applicationGson.getApplicationStatus() == ApplicationStatus.INACTIVE)) { - // Delete application - uninstallApk(applicationGson); - } else if (applicationGson.getApplicationStatus() == ApplicationStatus.ACTIVE) { - // Check if a newer version is available - Log.i(getClass().getName(), "packageInfo.versionCode: " + packageInfo.versionCode); - ApplicationVersionGson applicationVersionGson = applicationGson.getApplicationVersions().get(0); - Log.i(getClass().getName(), "Newest version available: " + applicationVersionGson.getVersionCode()); - if (packageInfo.versionCode < applicationVersionGson.getVersionCode()) { - // Download the APK and install it - installApk(applicationVersionGson); - } - } - } catch (PackageManager.NameNotFoundException e) { - Log.i(getClass().getName(), "The application is not installed: " + applicationGson.getPackageName()); - - if (applicationGson.getApplicationStatus() == ApplicationStatus.ACTIVE) { - // Install the APK - ApplicationVersionGson applicationVersionGson = applicationGson.getApplicationVersions().get(0); - installApk(applicationVersionGson); - } - } - } - Log.i(getClass().getName(), "Synchronization complete!"); - - // Update time of last synchronization - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); - sharedPreferences.edit().putLong(MainActivity.PREF_LAST_SYNCHRONIZATION, Calendar.getInstance().getTimeInMillis()).commit(); - } - } catch (JSONException e) { - Log.e(getClass().getName(), null, e); - } - } - - return null; - } - - private File downloadApk(ApplicationVersionGson applicationVersionGson) { - Log.i(getClass().getName(), "downloadApk"); - - String fileUrl = EnvironmentSettings.getBaseUrl() + applicationVersionGson.getFileUrl() + - "?deviceId=" + DeviceInfoHelper.getDeviceId(context) + - "&checksum=" + ChecksumHelper.getChecksum(context) + - "&locale=" + UserPrefsHelper.getLocale(context) + - "&deviceModel=" + DeviceInfoHelper.getDeviceModel(context) + - "&osVersion=" + Build.VERSION.SDK_INT + - "&applicationId=" + DeviceInfoHelper.getApplicationId(context) + - "&appVersionCode=" + DeviceInfoHelper.getAppVersionCode(context); - Log.i(getClass().getName(), "fileUrl: " + fileUrl); - - String fileName = applicationVersionGson.getApplication().getPackageName() + "-" + applicationVersionGson.getVersionCode() + ".apk"; - Log.i(getClass().getName(), "fileName: " + fileName); + File file = files[0]; + Log.i(getClass().getName(), "file: " + file); - Log.i(getClass().getName(), "Downloading APK: " + applicationVersionGson.getApplication().getPackageName() + " (version " + applicationVersionGson.getVersionCode() + ", " + applicationVersionGson.getFileSizeInKb() + "kB)"); - File apkFile = ApkLoader.loadApk(fileUrl, fileName, context); - Log.i(getClass().getName(), "apkFile: " + apkFile); - if ((apkFile == null) || !apkFile.exists()) { - Log.w(getClass().getName(), "APK download failed: " + fileUrl); - } else { - Log.i(getClass().getName(), "APK downloaded: " + applicationVersionGson.getApplication().getPackageName() + " (version " + applicationVersionGson.getVersionCode() + ")"); - } - - return apkFile; - } - - private void installApk(ApplicationVersionGson applicationVersionGson) { - Log.i(getClass().getName(), "installApk"); - - String fileName = applicationVersionGson.getApplication().getPackageName() + "-" + applicationVersionGson.getVersionCode() + ".apk"; - Log.i(getClass().getName(), "fileName: " + fileName); - - String language = Locale.getDefault().getLanguage(); - File apkDirectory = new File(Environment.getExternalStorageDirectory() + "/.literacyapp-appstore/apks/" + language); - Log.i(ApkLoader.class.getName(), "apkDirectory: " + apkDirectory); - if (!apkDirectory.exists()) { - apkDirectory.mkdirs(); - } - - File apkFile = new File(apkDirectory, fileName); - Log.i(ApkLoader.class.getName(), "apkFile: " + apkFile); - Log.i(ApkLoader.class.getName(), "apkFile.exists(): " + apkFile.exists()); - - if ((apkFile == null) || !apkFile.exists()) { - Log.w(getClass().getName(), "APK installation failed: " + apkFile); - } else { - Log.i(getClass().getName(), "Installing APK: " + applicationVersionGson.getApplication().getPackageName() + " (version " + applicationVersionGson.getVersionCode() + ")"); - String command = "pm install -r -g " + apkFile.getAbsolutePath(); // https://developer.android.com/studio/command-line/shell.html#pm - if ("KFFOWI".equals(DeviceInfoHelper.getDeviceModel(context))) { - // The '-g' command does not work on Amazon Fire: "Error: Unknown option: -g" - command = "pm install -r " + apkFile.getAbsolutePath(); - } - Log.i(getClass().getName(), "command: " + command); - try { - Process process = Runtime.getRuntime().exec(new String[]{"su", "-c", command}); - process.waitFor(); + try { + // See https://stackoverflow.com/a/11826317 - InputStream inputStreamSuccess = process.getInputStream(); - if (inputStreamSuccess != null) { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStreamSuccess)); - String successMessage = bufferedReader.readLine(); - Log.i(getClass().getName(), "successMessage: " + successMessage); - if (!"Success".equals(successMessage)) { - Log.i(getClass().getName(), "APK installation failed: " + applicationVersionGson.getApplication().getPackageName() + " (version " + applicationVersionGson.getVersionCode() + ")"); - } + URL url = new URL(EnvironmentSettings.getBaseRestUrl() + "/analytics/application-opened-event/create"); + Log.i(getClass().getName(), "url: " + url); - String startCommand = applicationVersionGson.getStartCommand(); - if (!TextUtils.isEmpty(startCommand)) { - // Expected format: "adb shell " - Log.i(getClass().getName(), "startCommand: " + startCommand); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setUseCaches(false); + connection.setDoOutput(true); - process = Runtime.getRuntime().exec(new String[]{"su", "-c", startCommand}); - process.waitFor(); + connection.setRequestProperty("Connection", "Keep-Alive"); + connection.setRequestProperty("Cache-Control", "no-cache"); + connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=*****"); - inputStreamSuccess = process.getInputStream(); - if (inputStreamSuccess != null) { - bufferedReader = new BufferedReader(new InputStreamReader(inputStreamSuccess)); - successMessage = bufferedReader.readLine(); - Log.i(getClass().getName(), "startCommand successMessage: " + successMessage); - } + DataOutputStream request = new DataOutputStream(connection.getOutputStream()); - InputStream inputStreamError = process.getErrorStream(); - if (inputStreamError != null) { - bufferedReader = new BufferedReader(new InputStreamReader(inputStreamError)); - String errorMessage = bufferedReader.readLine(); - Log.w(getClass().getName(), "startCommand errorMessage: " + errorMessage); - } - } - } + request.writeBytes("--*****\r\n"); + request.writeBytes("Content-Disposition: form-data; name=\"multipartFile\";filename=\"application_opened_events_2017-06-07.log\"\r\n"); + request.writeBytes("\r\n"); - InputStream inputStreamError = process.getErrorStream(); - if (inputStreamError != null) { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStreamError)); - String errorMessage = bufferedReader.readLine(); - Log.w(getClass().getName(), "errorMessage: " + errorMessage); - } - } catch (IOException e) { - Log.e(getClass().getName(), "IOException: " + command, e); - } catch (InterruptedException e) { - Log.e(getClass().getName(), "InterruptedException: " + command, e); - } - } - } + byte[] fileBytes = FileUtils.readFileToByteArray(file); + Log.i(getClass().getName(), "fileBytes.length: " + fileBytes.length); + request.write(fileBytes); - private void uninstallApk(ApplicationGson applicationGson) { - Log.i(getClass().getName(), "uninstallApk"); + request.writeBytes("\r\n"); + request.writeBytes("--*****--\r\n"); - Log.i(getClass().getName(), "Uninstalling APK: " + applicationGson.getPackageName()); - String command = "pm uninstall " + applicationGson.getPackageName(); - Log.i(getClass().getName(), "command: " + command); - try { - Process process = Runtime.getRuntime().exec(new String[]{"su", "-c", command}); - process.waitFor(); - - InputStream inputStreamSuccess = process.getInputStream(); - if (inputStreamSuccess != null) { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStreamSuccess)); - String successMessage = bufferedReader.readLine(); - Log.i(getClass().getName(), "successMessage: " + successMessage); - if (!"Success".equals(successMessage)) { - Log.e(getClass().getName(), "APK uninstallation failed: " + applicationGson.getPackageName()); - } + int responseCode = connection.getResponseCode(); + Log.i(getClass().getName(), "responseCode: " + responseCode); + InputStream inputStream = null; + if (responseCode == 200) { + inputStream = connection.getInputStream(); + } else { + inputStream = connection.getErrorStream(); } - InputStream inputStreamError = process.getErrorStream(); - if (inputStreamError != null) { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStreamError)); - String errorMessage = bufferedReader.readLine(); - Log.w(getClass().getName(), "errorMessage: " + errorMessage); - } + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + String response = bufferedReader.readLine(); + Log.i(getClass().getName(), "response: " + response); + } catch (MalformedURLException e) { + Log.e(getClass().getName(), null, e); } catch (IOException e) { - Log.e(getClass().getName(), "IOException: " + command, e); - } catch (InterruptedException e) { - Log.e(getClass().getName(), "InterruptedException: " + command, e); + Log.e(getClass().getName(), null, e); } + + return null; } @Override diff --git a/app/src/main/java/org/literacyapp/analytics/util/EnvironmentSettings.java b/app/src/main/java/org/literacyapp/analytics/util/EnvironmentSettings.java index 89559a0..6f90106 100644 --- a/app/src/main/java/org/literacyapp/analytics/util/EnvironmentSettings.java +++ b/app/src/main/java/org/literacyapp/analytics/util/EnvironmentSettings.java @@ -1,4 +1,4 @@ -package org.literacyapp.appstore.util; +package org.literacyapp.analytics.util; import org.literacyapp.model.enums.Environment;