package net.hockeyapp.android.tasks; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Environment; import android.os.StrictMode; import net.hockeyapp.android.Constants; import net.hockeyapp.android.R; import net.hockeyapp.android.listeners.DownloadFileListener; import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.UUID; /** * <h3>Description</h3> * * Internal helper class. Downloads an .apk from HockeyApp and stores * it on external storage. If the download was successful, the file * is then opened to trigger the installation. **/ public class DownloadFileTask extends AsyncTask<Void, Integer, Long> { protected static final int MAX_REDIRECTS = 6; protected Context mContext; protected DownloadFileListener mNotifier; protected String mUrlString; protected String mFilename; protected String mFilePath; protected ProgressDialog mProgressDialog; private String mDownloadErrorMessage; public DownloadFileTask(Context context, String urlString, DownloadFileListener notifier) { this.mContext = context; this.mUrlString = urlString; this.mFilename = UUID.randomUUID() + ".apk"; this.mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Download"; this.mNotifier = notifier; this.mDownloadErrorMessage = null; } public void attach(Context context) { this.mContext = context; } public void detach() { mContext = null; mProgressDialog = null; } @Override protected Long doInBackground(Void... args) { InputStream input = null; OutputStream output = null; try { URL url = new URL(getURLString()); URLConnection connection = createConnection(url, MAX_REDIRECTS); connection.connect(); int lengthOfFile = connection.getContentLength(); String contentType = connection.getContentType(); if (contentType != null && contentType.contains("text")) { // This is not the expected APK file. Maybe the redirect could not be resolved. mDownloadErrorMessage = "The requested download does not appear to be a file."; return 0L; } File dir = new File(this.mFilePath); boolean result = dir.mkdirs(); if (!result && !dir.exists()) { throw new IOException("Could not create the dir(s):" + dir.getAbsolutePath()); } File file = new File(dir, this.mFilename); input = new BufferedInputStream(connection.getInputStream()); output = new FileOutputStream(file); byte data[] = new byte[1024]; int count; long total = 0; while ((count = input.read(data)) != -1) { total += count; publishProgress(Math.round(total * 100.0f / lengthOfFile)); output.write(data, 0, count); } output.flush(); return total; } catch (IOException e) { e.printStackTrace(); return 0L; } finally { try { if (output != null) { output.close(); } if (input != null) { input.close(); } } catch (IOException e) { e.printStackTrace(); } } } protected void setConnectionProperties(HttpURLConnection connection) { connection.addRequestProperty("User-Agent", Constants.SDK_USER_AGENT); connection.setInstanceFollowRedirects(true); // connection bug workaround for SDK<=2.x if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD) { connection.setRequestProperty("connection", "close"); } } /** * Recursive method for resolving redirects. Resolves at most MAX_REDIRECTS times. * * @param url a URL * @param remainingRedirects loop counter * @return instance of URLConnection * @throws IOException if connection fails */ protected URLConnection createConnection(URL url, int remainingRedirects) throws IOException { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); setConnectionProperties(connection); int code = connection.getResponseCode(); if (code == HttpURLConnection.HTTP_MOVED_PERM || code == HttpURLConnection.HTTP_MOVED_TEMP || code == HttpURLConnection.HTTP_SEE_OTHER) { if (remainingRedirects == 0) { // Stop redirecting. return connection; } URL movedUrl = new URL(connection.getHeaderField("Location")); if (!url.getProtocol().equals(movedUrl.getProtocol())) { // HttpURLConnection doesn't handle redirects across schemes, so handle it manually, see // http://code.google.com/p/android/issues/detail?id=41651 connection.disconnect(); return createConnection(movedUrl, --remainingRedirects); // Recursion } } return connection; } @Override protected void onProgressUpdate(Integer... args) { try { if (mProgressDialog == null) { mProgressDialog = new ProgressDialog(mContext); mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mProgressDialog.setMessage("Loading..."); mProgressDialog.setCancelable(false); mProgressDialog.show(); } mProgressDialog.setProgress(args[0]); } catch (Exception e) { // Ignore all exceptions } } @Override protected void onPostExecute(Long result) { if (mProgressDialog != null) { try { mProgressDialog.dismiss(); } catch (Exception e) { // Ignore all exceptions } } if (result > 0L) { mNotifier.downloadSuccessful(this); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(new File(this.mFilePath, this.mFilename)), "application/vnd.android.package-archive"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); StrictMode.VmPolicy oldVmPolicy = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { oldVmPolicy = StrictMode.getVmPolicy(); StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder() .penaltyLog() .build(); StrictMode.setVmPolicy(policy); } mContext.startActivity(intent); if (oldVmPolicy != null) { StrictMode.setVmPolicy(oldVmPolicy); } } else { try { AlertDialog.Builder builder = new AlertDialog.Builder(mContext); builder.setTitle(R.string.hockeyapp_download_failed_dialog_title); String message; if (mDownloadErrorMessage == null) { message = mContext.getString(R.string.hockeyapp_download_failed_dialog_message); } else { message = mDownloadErrorMessage; } builder.setMessage(message); builder.setNegativeButton(R.string.hockeyapp_download_failed_dialog_negative_button, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mNotifier.downloadFailed(DownloadFileTask.this, false); } }); builder.setPositiveButton(R.string.hockeyapp_download_failed_dialog_positive_button, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mNotifier.downloadFailed(DownloadFileTask.this, true); } }); builder.create().show(); } catch (Exception e) { // Ignore all exceptions } } } protected String getURLString() { return mUrlString + "&type=apk"; } }