com.google.ytd.SubmitActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.google.ytd.SubmitActivity.java

Source

/* Copyright (c) 2010 Google Inc.
 *
 * 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.google.ytd;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.provider.Settings;
import android.provider.MediaStore.Video;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.ytd.Authorizer.AuthorizationListener;
import com.google.ytd.db.Assignment;
import com.google.ytd.db.DbHelper;

public class SubmitActivity extends Activity {
    private static final String LOG_TAG = SubmitActivity.class.getSimpleName();

    private static final String INITIAL_UPLOAD_URL = "http://uploads.gdata.youtube.com/resumable/feeds/api/users/default/uploads";
    private static final String DEFAULT_VIDEO_CATEGORY = "News";
    private static final String DEFAULT_VIDEO_TAGS = "mobile";

    private static final int DIALOG_LEGAL = 0;

    private static final int MAX_RETRIES = 5;
    private static final int BACKOFF = 4; // base of exponential backoff

    private ProgressDialog dialog = null;
    private DbHelper dbHelper = null;
    private String ytdDomain = null;
    private String assignmentId = null;
    private String title = null;
    private String instructions = null;
    private Assignment assignment = null;
    private Uri videoUri = null;
    private String clientLoginToken = null;
    private String youTubeName = null;
    private Date dateTaken = null;
    private Authorizer authorizer = null;
    private Location videoLocation = null;
    private String tags = null;
    private LocationListener locationListener = null;
    private LocationManager locationManager = null;
    private SharedPreferences preferences = null;
    private TextView domainHeader = null;
    // TODO - replace these counters with a state variable
    private double currentFileSize = 0;
    private double totalBytesUploaded = 0;
    private int numberOfRetries = 0;

    static class YouTubeAccountException extends Exception {
        public YouTubeAccountException(String msg) {
            super(msg);
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.submit);

        this.authorizer = new GlsAuthorizer.GlsAuthorizerFactory().getAuthorizer(this,
                GlsAuthorizer.YOUTUBE_AUTH_TOKEN_TYPE);

        dbHelper = new DbHelper(this);
        dbHelper = dbHelper.open();

        Intent intent = this.getIntent();
        this.videoUri = intent.getData();
        this.ytdDomain = intent.getExtras().getString(DbHelper.YTD_DOMAIN);
        this.assignmentId = intent.getExtras().getString(DbHelper.ASSIGNMENT_ID);
        this.title = intent.getExtras().getString(DbHelper.DESCRIPTION);
        this.instructions = intent.getExtras().getString(DbHelper.INSTRUCTIONS);

        this.domainHeader = (TextView) this.findViewById(R.id.domainHeader);
        domainHeader.setText(SettingActivity.getYtdDomains(this).get(this.ytdDomain));

        this.preferences = this.getSharedPreferences(MainActivity.SHARED_PREF_NAME, Activity.MODE_PRIVATE);
        this.youTubeName = preferences.getString(DbHelper.YT_ACCOUNT, null);

        final Button submitButton = (Button) findViewById(R.id.submitButton);
        submitButton.setEnabled(false);

        submitButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                showDialog(DIALOG_LEGAL);
            }
        });

        Button cancelButton = (Button) findViewById(R.id.cancelButton);
        cancelButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                setResult(RESULT_CANCELED);
                finish();
            }
        });

        EditText titleEdit = (EditText) findViewById(R.id.submitTitle);
        titleEdit.setText(title);
        titleEdit.setEnabled(false);
        enableSubmitIfReady();

        EditText descriptionEdit = (EditText) findViewById(R.id.submitDescription);
        descriptionEdit.setText(instructions);
        descriptionEdit.setEnabled(false);

        Cursor cursor = this.managedQuery(this.videoUri, null, null, null, null);

        if (cursor.getCount() == 0) {
            Log.d(LOG_TAG, "not a valid video uri");
            Toast.makeText(SubmitActivity.this, "not a valid video uri", Toast.LENGTH_LONG).show();
        } else {
            getVideoLocation();

            if (cursor.moveToFirst()) {

                long id = cursor.getLong(cursor.getColumnIndex(Video.VideoColumns._ID));
                this.dateTaken = new Date(cursor.getLong(cursor.getColumnIndex(Video.VideoColumns.DATE_TAKEN)));

                SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, MMM d, yyyy hh:mm aaa");
                Configuration userConfig = new Configuration();
                Settings.System.getConfiguration(getContentResolver(), userConfig);
                Calendar cal = Calendar.getInstance(userConfig.locale);
                TimeZone tz = cal.getTimeZone();

                dateFormat.setTimeZone(tz);

                TextView dateTakenView = (TextView) findViewById(R.id.dateCaptured);
                dateTakenView.setText("Date captured: " + dateFormat.format(dateTaken));

                ImageView thumbnail = (ImageView) findViewById(R.id.thumbnail);
                ContentResolver crThumb = getContentResolver();
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize = 1;
                Bitmap curThumb = MediaStore.Video.Thumbnails.getThumbnail(crThumb, id,
                        MediaStore.Video.Thumbnails.MICRO_KIND, options);
                thumbnail.setImageBitmap(curThumb);
            }
        }
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        final Dialog dialog = new Dialog(SubmitActivity.this);
        dialog.setTitle("Terms of Service");
        switch (id) {
        case DIALOG_LEGAL:
            dialog.setContentView(R.layout.legal);

            TextView legalText = (TextView) dialog.findViewById(R.id.legal);

            legalText.setText(Util.readFile(this, R.raw.legal).toString());

            dialog.findViewById(R.id.agree).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    dialog.cancel();
                    getAuthTokenWithPermission(youTubeName);
                }
            });
            dialog.findViewById(R.id.notagree).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    dialog.cancel();
                }
            });

            break;
        }

        return dialog;
    }

    @Override
    public void onRestart() {
        super.onRestart();
        hideKeyboard(this.getCurrentFocus());
    }

    private void requestDummyFocus() {
        this.findViewById(R.id.dummy).requestFocus();
    }

    private void hideKeyboard(View currentFocusView) {
        if (currentFocusView instanceof EditText) {
            InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(currentFocusView.getWindowToken(), 0);
            requestDummyFocus();
        }
    }

    public String getTitleText() {
        /*EditText titleEdit = (EditText) findViewById(R.id.submitTitle);
        return sanitize(titleEdit.getText().toString());
        */
        return sanitize(this.title);
    }

    public String getDescriptionText() {
        /* 
         EditText descriptionEdit = (EditText) findViewById(R.id.submitDescription);
         return sanitize(descriptionEdit.getText().toString());
         */
        return sanitize(this.instructions);
    }

    private String sanitize(String text) {
        return text.replaceAll("&", "&");
    }

    public String getTagsText() {
        EditText tagsEdit = (EditText) findViewById(R.id.submitTags);
        return sanitize(tagsEdit.getText().toString());
    }

    public void enableSubmitIfReady() {
        Button submit = (Button) findViewById(R.id.submitButton);
        boolean isReady = getTitleText().length() > 0 && getDescriptionText().length() > 0;

        if (isReady) {
            submit.setEnabled(true);
        } else {
            submit.setEnabled(false);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        dbHelper.close();
        if (this.locationListener != null) {
            this.locationManager.removeUpdates(locationListener);
        }
    }

    public void upload(Uri videoUri) {
        this.dialog = new ProgressDialog(this);
        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        dialog.setMessage("uploading ...");
        dialog.setCancelable(false);
        dialog.show();

        Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                dialog.dismiss();
                String videoId = msg.getData().getString("videoId");

                if (!Util.isNullOrEmpty(videoId)) {
                    currentFileSize = 0;
                    totalBytesUploaded = 0;
                    Intent result = new Intent();
                    result.putExtra("videoId", videoId);
                    setResult(RESULT_OK, result);
                    finish();
                } else {
                    String error = msg.getData().getString("error");
                    if (!Util.isNullOrEmpty(error)) {
                        Toast.makeText(SubmitActivity.this, error, Toast.LENGTH_LONG).show();
                    }
                }
            }
        };

        asyncUpload(videoUri, handler);
    }

    public void asyncUpload(final Uri uri, final Handler handler) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Message msg = new Message();
                Bundle bundle = new Bundle();
                msg.setData(bundle);

                String videoId = null;
                int submitCount = 0;
                try {
                    while (submitCount <= MAX_RETRIES && videoId == null) {
                        try {
                            submitCount++;
                            videoId = startUpload(uri);
                            assert videoId != null;
                        } catch (Internal500ResumeException e500) { // TODO - this should not really happen
                            if (submitCount < MAX_RETRIES) {
                                Log.w(LOG_TAG, e500.getMessage());
                                Log.d(LOG_TAG, String.format("Upload retry :%d.", submitCount));
                            } else {
                                Log.d(LOG_TAG, "Giving up");
                                Log.e(LOG_TAG, e500.getMessage());
                                throw new IOException(e500.getMessage());
                            }
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    bundle.putString("error", e.getMessage());
                    handler.sendMessage(msg);
                    return;
                } catch (YouTubeAccountException e) {
                    e.printStackTrace();
                    bundle.putString("error", e.getMessage());
                    handler.sendMessage(msg);
                    return;
                } catch (SAXException e) {
                    e.printStackTrace();
                    bundle.putString("error", e.getMessage());
                    handler.sendMessage(msg);
                } catch (ParserConfigurationException e) {
                    e.printStackTrace();
                    bundle.putString("error", e.getMessage());
                    handler.sendMessage(msg);
                }

                bundle.putString("videoId", videoId);
                handler.sendMessage(msg);
            }
        }).start();
    }

    private File getFileFromUri(Uri uri) throws IOException {
        Cursor cursor = managedQuery(uri, null, null, null, null);
        if (cursor.getCount() == 0) {
            throw new IOException(String.format("cannot find data from %s", uri.toString()));
        } else {
            cursor.moveToFirst();
        }

        String filePath = cursor.getString(cursor.getColumnIndex(Video.VideoColumns.DATA));

        File file = new File(filePath);
        cursor.close();
        return file;
    }

    private String startUpload(Uri uri) throws IOException, YouTubeAccountException, SAXException,
            ParserConfigurationException, Internal500ResumeException {
        File file = getFileFromUri(uri);

        if (this.clientLoginToken == null) {
            // The stored gmail account is not linked to YouTube
            throw new YouTubeAccountException(this.youTubeName + " is not linked to a YouTube account.");
        }

        String uploadUrl = uploadMetaData(file.getAbsolutePath(), true);

        Log.d(LOG_TAG, "uploadUrl=" + uploadUrl);
        Log.d(LOG_TAG, String.format("Client token : %s ", this.clientLoginToken));

        this.currentFileSize = file.length();
        this.totalBytesUploaded = 0;
        this.numberOfRetries = 0;

        int uploadChunk = 1024 * 1024 * 3; // 3MB

        int start = 0;
        int end = -1;

        String videoId = null;
        double fileSize = this.currentFileSize;
        while (fileSize > 0) {
            if (fileSize - uploadChunk > 0) {
                end = start + uploadChunk - 1;
            } else {
                end = start + (int) fileSize - 1;
            }
            Log.d(LOG_TAG, String.format("start=%s end=%s total=%s", start, end, file.length()));
            try {
                videoId = gdataUpload(file, uploadUrl, start, end);
                fileSize -= uploadChunk;
                start = end + 1;
                this.numberOfRetries = 0; // clear this counter as we had a succesfull upload
            } catch (IOException e) {
                Log.d(LOG_TAG, "Error during upload : " + e.getMessage());
                ResumeInfo resumeInfo = null;
                do {
                    if (!shouldResume()) {
                        Log.d(LOG_TAG, String.format("Giving up uploading '%s'.", uploadUrl));
                        throw e;
                    }
                    try {
                        resumeInfo = resumeFileUpload(uploadUrl);
                    } catch (IOException re) {
                        // ignore
                        Log.d(LOG_TAG, String.format("Failed retry attempt of : %s due to: '%s'.", uploadUrl,
                                re.getMessage()));
                    }
                } while (resumeInfo == null);
                Log.d(LOG_TAG, String.format("Resuming stalled upload to: %s.", uploadUrl));
                if (resumeInfo.videoId != null) { // upload actually complted despite the exception
                    videoId = resumeInfo.videoId;
                    Log.d(LOG_TAG, String.format("No need to resume video ID '%s'.", videoId));
                    break;
                } else {
                    int nextByteToUpload = resumeInfo.nextByteToUpload;
                    Log.d(LOG_TAG, String.format("Next byte to upload is '%d'.", nextByteToUpload));
                    this.totalBytesUploaded = nextByteToUpload; // possibly rolling back the previosuly saved value
                    fileSize = this.currentFileSize - nextByteToUpload;
                    start = nextByteToUpload;
                }
            }
        }

        if (videoId != null) {
            return videoId;
        }

        return null;
    }

    private String uploadMetaData(String filePath, boolean retry) throws IOException {
        String uploadUrl = INITIAL_UPLOAD_URL;

        HttpURLConnection urlConnection = getGDataUrlConnection(uploadUrl);
        urlConnection.setRequestMethod("POST");
        urlConnection.setDoOutput(true);
        urlConnection.setRequestProperty("Content-Type", "application/atom+xml");
        urlConnection.setRequestProperty("Slug", filePath);
        String atomData;

        String title = getTitleText();
        String description = getDescriptionText();
        String category = DEFAULT_VIDEO_CATEGORY;
        this.tags = DEFAULT_VIDEO_TAGS;

        if (!Util.isNullOrEmpty(this.getTagsText())) {
            this.tags = this.getTagsText();
        }

        if (this.videoLocation == null) {
            String template = Util.readFile(this, R.raw.gdata).toString();
            atomData = String.format(template, title, description, category, this.tags);
        } else {
            String template = Util.readFile(this, R.raw.gdata_geo).toString();
            atomData = String.format(template, title, description, category, this.tags, videoLocation.getLatitude(),
                    videoLocation.getLongitude());
        }

        OutputStreamWriter outStreamWriter = new OutputStreamWriter(urlConnection.getOutputStream());
        outStreamWriter.write(atomData);
        outStreamWriter.close();

        int responseCode = urlConnection.getResponseCode();
        if (responseCode < 200 || responseCode >= 300) {
            // The response code is 40X
            if ((responseCode + "").startsWith("4") && retry) {
                Log.d(LOG_TAG, "retrying to fetch auth token for " + youTubeName);
                this.clientLoginToken = authorizer.getFreshAuthToken(youTubeName, clientLoginToken);
                // Try again with fresh token
                return uploadMetaData(filePath, false);
            } else {
                throw new IOException(String.format("response code='%s' (code %d)" + " for %s",
                        urlConnection.getResponseMessage(), responseCode, urlConnection.getURL()));
            }
        }

        return urlConnection.getHeaderField("Location");
    }

    private String gdataUpload(File file, String uploadUrl, int start, int end) throws IOException {
        int chunk = end - start + 1;
        int bufferSize = 1024;
        byte[] buffer = new byte[bufferSize];
        FileInputStream fileStream = new FileInputStream(file);

        HttpURLConnection urlConnection = getGDataUrlConnection(uploadUrl);
        // some mobile proxies do not support PUT, using X-HTTP-Method-Override to get around this problem
        if (isFirstRequest()) {
            Log.d(LOG_TAG, String.format("Uploaded %d bytes so far, using POST method.", (int) totalBytesUploaded));
            urlConnection.setRequestMethod("POST");
        } else {
            urlConnection.setRequestMethod("POST");
            urlConnection.setRequestProperty("X-HTTP-Method-Override", "PUT");
            Log.d(LOG_TAG,
                    String.format("Uploaded %d bytes so far, using POST with X-HTTP-Method-Override PUT method.",
                            (int) totalBytesUploaded));
        }
        urlConnection.setDoOutput(true);
        urlConnection.setFixedLengthStreamingMode(chunk);
        urlConnection.setRequestProperty("Content-Type", "video/3gpp");
        urlConnection.setRequestProperty("Content-Range",
                String.format("bytes %d-%d/%d", start, end, file.length()));
        Log.d(LOG_TAG, urlConnection.getRequestProperty("Content-Range"));

        OutputStream outStreamWriter = urlConnection.getOutputStream();

        fileStream.skip(start);

        int bytesRead;
        int totalRead = 0;
        while ((bytesRead = fileStream.read(buffer, 0, bufferSize)) != -1) {
            outStreamWriter.write(buffer, 0, bytesRead);
            totalRead += bytesRead;
            this.totalBytesUploaded += bytesRead;

            double percent = (totalBytesUploaded / currentFileSize) * 99;

            /*
            Log.d(LOG_TAG, String.format(
            "fileSize=%f totalBytesUploaded=%f percent=%f", currentFileSize,
            totalBytesUploaded, percent));
            */

            dialog.setProgress((int) percent);

            if (totalRead == (end - start + 1)) {
                break;
            }
        }

        outStreamWriter.close();

        int responseCode = urlConnection.getResponseCode();

        Log.d(LOG_TAG, "responseCode=" + responseCode);
        Log.d(LOG_TAG, "responseMessage=" + urlConnection.getResponseMessage());

        try {
            if (responseCode == 201) {
                String videoId = parseVideoId(urlConnection.getInputStream());

                String latLng = null;
                if (this.videoLocation != null) {
                    latLng = String.format("lat=%f lng=%f", this.videoLocation.getLatitude(),
                            this.videoLocation.getLongitude());
                }

                submitToYtdDomain(this.ytdDomain, this.assignmentId, videoId, this.youTubeName,
                        SubmitActivity.this.clientLoginToken, getTitleText(), getDescriptionText(), this.dateTaken,
                        latLng, this.tags);
                dialog.setProgress(100);
                return videoId;
            } else if (responseCode == 200) {
                Set<String> keySet = urlConnection.getHeaderFields().keySet();
                String keys = urlConnection.getHeaderFields().keySet().toString();
                Log.d(LOG_TAG, String.format("Headers keys %s.", keys));
                for (String key : keySet) {
                    Log.d(LOG_TAG,
                            String.format("Header key %s value %s.", key, urlConnection.getHeaderField(key)));
                }
                Log.w(LOG_TAG, "Received 200 response during resumable uploading");
                throw new IOException(String.format("Unexpected response code : responseCode=%d responseMessage=%s",
                        responseCode, urlConnection.getResponseMessage()));
            } else {
                if ((responseCode + "").startsWith("5")) {
                    String error = String.format("responseCode=%d responseMessage=%s", responseCode,
                            urlConnection.getResponseMessage());
                    Log.w(LOG_TAG, error);
                    // TODO - this exception will trigger retry mechanism to kick in
                    // TODO - even though it should not, consider introducing a new type so
                    // TODO - resume does not kick in upon 5xx
                    throw new IOException(error);
                } else if (responseCode == 308) {
                    // OK, the chunk completed succesfully 
                    Log.d(LOG_TAG, String.format("responseCode=%d responseMessage=%s", responseCode,
                            urlConnection.getResponseMessage()));
                } else {
                    // TODO - this case is not handled properly yet
                    Log.w(LOG_TAG, String.format("Unexpected return code : %d %s while uploading :%s", responseCode,
                            urlConnection.getResponseMessage(), uploadUrl));
                }
            }
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }

        return null;
    }

    public boolean isFirstRequest() {
        return totalBytesUploaded == 0;
    }

    private ResumeInfo resumeFileUpload(String uploadUrl)
            throws IOException, ParserConfigurationException, SAXException, Internal500ResumeException {
        HttpURLConnection urlConnection = getGDataUrlConnection(uploadUrl);
        urlConnection.setRequestProperty("Content-Range", "bytes */*");
        urlConnection.setRequestMethod("POST");
        urlConnection.setRequestProperty("X-HTTP-Method-Override", "PUT");
        urlConnection.setFixedLengthStreamingMode(0);

        HttpURLConnection.setFollowRedirects(false);

        urlConnection.connect();
        int responseCode = urlConnection.getResponseCode();

        if (responseCode >= 300 && responseCode < 400) {
            int nextByteToUpload;
            String range = urlConnection.getHeaderField("Range");
            if (range == null) {
                Log.d(LOG_TAG, String.format("PUT to %s did not return 'Range' header.", uploadUrl));
                nextByteToUpload = 0;
            } else {
                Log.d(LOG_TAG, String.format("Range header is '%s'.", range));
                String[] parts = range.split("-");
                if (parts.length > 1) {
                    nextByteToUpload = Integer.parseInt(parts[1]) + 1;
                } else {
                    nextByteToUpload = 0;
                }
            }
            return new ResumeInfo(nextByteToUpload);
        } else if (responseCode >= 200 && responseCode < 300) {
            return new ResumeInfo(parseVideoId(urlConnection.getInputStream()));
        } else if (responseCode == 500) {
            // TODO this is a workaround for current problems with resuming uploads while switching transport (Wifi->EDGE)
            throw new Internal500ResumeException(
                    String.format("Unexpected response for PUT to %s: %s " + "(code %d)", uploadUrl,
                            urlConnection.getResponseMessage(), responseCode));
        } else {
            throw new IOException(String.format("Unexpected response for PUT to %s: %s " + "(code %d)", uploadUrl,
                    urlConnection.getResponseMessage(), responseCode));
        }
    }

    private boolean shouldResume() {
        this.numberOfRetries++;
        if (this.numberOfRetries > MAX_RETRIES) {
            return false;
        }
        try {
            int sleepSeconds = (int) Math.pow(BACKOFF, this.numberOfRetries);
            Log.d(LOG_TAG, String.format("Zzzzz for : %d sec.", sleepSeconds));
            Thread.currentThread().sleep(sleepSeconds * 1000);
            Log.d(LOG_TAG, String.format("Zzzzz for : %d sec done.", sleepSeconds));
        } catch (InterruptedException se) {
            se.printStackTrace();
            return false;
        }
        return true;
    }

    private String parseVideoId(InputStream atomDataStream)
            throws ParserConfigurationException, SAXException, IOException {
        DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
        Document doc = docBuilder.parse(atomDataStream);

        NodeList nodes = doc.getElementsByTagNameNS("*", "*");
        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);
            String nodeName = node.getNodeName();
            if (nodeName != null && nodeName.equals("yt:videoid")) {
                return node.getFirstChild().getNodeValue();
            }
        }
        return null;
    }

    private HttpURLConnection getGDataUrlConnection(String urlString) throws IOException {
        URL url = new URL(urlString);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestProperty("Authorization", String.format("GoogleLogin auth=\"%s\"", clientLoginToken));
        connection.setRequestProperty("GData-Version", "2");
        connection.setRequestProperty("X-GData-Client", this.getString(R.string.client_id));
        connection.setRequestProperty("X-GData-Key", String.format("key=%s", this.getString(R.string.dev_key)));
        return connection;
    }

    private void getAuthTokenWithPermission(String accountName) {
        this.authorizer.fetchAuthToken(accountName, this, new AuthorizationListener<String>() {
            @Override
            public void onCanceled() {
            }

            @Override
            public void onError(Exception e) {
            }

            @Override
            public void onSuccess(String result) {
                SubmitActivity.this.clientLoginToken = result;
                upload(SubmitActivity.this.videoUri);
            }
        });
    }

    private void getVideoLocation() {
        this.locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

        Criteria criteria = new Criteria();
        criteria.setAccuracy(Criteria.ACCURACY_FINE);
        criteria.setPowerRequirement(Criteria.POWER_HIGH);
        criteria.setAltitudeRequired(false);
        criteria.setBearingRequired(false);
        criteria.setSpeedRequired(false);
        criteria.setCostAllowed(true);

        String provider = locationManager.getBestProvider(criteria, true);

        this.locationListener = new LocationListener() {
            @Override
            public void onLocationChanged(Location location) {
                if (location != null) {
                    SubmitActivity.this.videoLocation = location;
                    double lat = location.getLatitude();
                    double lng = location.getLongitude();
                    Log.d(LOG_TAG, "lat=" + lat);
                    Log.d(LOG_TAG, "lng=" + lng);

                    TextView locationText = (TextView) findViewById(R.id.locationLabel);
                    locationText.setText("Geo Location: " + String.format("lat=%.2f lng=%.2f", lat, lng));
                    locationManager.removeUpdates(this);
                } else {
                    Log.d(LOG_TAG, "location is null");
                }
            }

            @Override
            public void onProviderDisabled(String provider) {
            }

            @Override
            public void onProviderEnabled(String provider) {
            }

            @Override
            public void onStatusChanged(String provider, int status, Bundle extras) {
            }

        };

        if (provider != null) {
            locationManager.requestLocationUpdates(provider, 2000, 10, locationListener);
        }
    }

    public void submitToYtdDomain(String ytdDomain, String assignmentId, String videoId, String youTubeName,
            String clientLoginToken, String title, String description, Date dateTaken, String videoLocation,
            String tags) {

        JSONObject payload = new JSONObject();
        try {
            payload.put("method", "NEW_MOBILE_VIDEO_SUBMISSION");
            JSONObject params = new JSONObject();

            params.put("videoId", videoId);
            params.put("youTubeName", youTubeName);
            params.put("clientLoginToken", clientLoginToken);
            params.put("title", title);
            params.put("description", description);
            params.put("videoDate", dateTaken.toString());
            params.put("tags", tags);

            if (videoLocation != null) {
                params.put("videoLocation", videoLocation);
            }

            if (assignmentId != null) {
                params.put("assignmentId", assignmentId);
            }

            payload.put("params", params);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        String jsonRpcUrl = "http://" + ytdDomain + "/jsonrpc";
        String json = Util.makeJsonRpcCall(jsonRpcUrl, payload);

        if (json != null) {
            try {
                JSONObject jsonObj = new JSONObject(json);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }

    class ResumeInfo {
        int nextByteToUpload;
        String videoId;

        ResumeInfo(int nextByteToUpload) {
            this.nextByteToUpload = nextByteToUpload;
        }

        ResumeInfo(String videoId) {
            this.videoId = videoId;
        }
    }

    /**
     * Need this for now to trigger entire upload transaction retry
     */
    class Internal500ResumeException extends Exception {
        Internal500ResumeException(String message) {
            super(message);
        }
    }
}