info.guardianproject.mrapp.server.YouTubeSubmit.java Source code

Java tutorial

Introduction

Here is the source code for info.guardianproject.mrapp.server.YouTubeSubmit.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 info.guardianproject.mrapp.server;

import info.guardianproject.mrapp.AppConstants;
import info.guardianproject.mrapp.R;
import info.guardianproject.mrapp.server.Authorizer.AuthorizationListener;
import info.guardianproject.onionkit.trust.StrongHttpsClient;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.Locale;

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

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import android.accounts.Account;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.Log;
import ch.boye.httpclientandroidlib.Header;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.client.methods.HttpPost;
import ch.boye.httpclientandroidlib.entity.AbstractHttpEntity;
import ch.boye.httpclientandroidlib.entity.StringEntity;
import ch.boye.httpclientandroidlib.protocol.HTTP;

public class YouTubeSubmit {

    public static final String RESUMABLE_UPLOAD_URL = "http://uploads.gdata.youtube.com/resumable/feeds/api/users/default/uploads";

    public static final String STANDARD_UPLOAD_URL = "http://gdata.youtube.com/feeds/api/users/default/uploads";

    private static final String CONTENT_TYPE = "application/atom+xml; charset=UTF-8";
    private static final String DEFAULT_VIDEO_CATEGORY = "News";
    private static final String DEFAULT_VIDEO_TAGS = "mobile, storymaker";

    // private String mServerDomain = "uploads.gdata.youtube.com";

    private static final int MAX_RETRIES = 10;
    private static final int BACKOFF = 6; // base of exponential backoff

    private boolean mUseTor = false;

    public String videoId = null;

    //private String ytdDomain = null;
    //private String assignmentId = null;
    //private Uri videoUri = null;
    private File videoFile = null;
    private String clientLoginToken = null;
    private Account accountYouTube = null;
    private String title = null;
    private String description = null;

    private GlsAuthorizer authorizer = null;

    private String mDevKey = null;
    private String mAuthMode = "GoogleLogin";

    private String tags = null;

    private Date dateTaken = null;
    /*
    private Location videoLocation = 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;

    private Activity activity;

    private String videoContentType = "video/mp4";

    private Handler handler;
    private Context mContext;

    private StrongHttpsClient httpClient;

    private final static String LOG_TAG = "SM.YouTubeSubmit";

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

    public YouTubeSubmit(File videoFile, String title, String description, Date dateTaken, Activity activity,
            Handler handler, Context context) {

        this.videoFile = videoFile;
        this.activity = activity;
        this.title = title;
        this.description = description;
        this.dateTaken = dateTaken;
        this.handler = handler;
        this.mContext = context;

        SharedPreferences settings = PreferenceManager
                .getDefaultSharedPreferences(mContext.getApplicationContext());

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

        authorizer.setAccountFeatures(GlsAuthorizer.YOUTUBE_FEATURES);
        authorizer.setAccountType(GlsAuthorizer.ACCOUNT_TYPE_GOOGLE);

        try {
            authorizer.setAuthMethod(Integer.parseInt(settings.getString("glsauthmethod", "0")));
        } catch (NumberFormatException nfe) {
            Log.e(AppConstants.TAG, "someone put a bad value in the youtube auth method: "
                    + settings.getString("glsauthmethod", "0"));

            authorizer.setAuthMethod(0);

        }

        authorizer.setHandler(handler);

        httpClient = new StrongHttpsClient(mContext);

        httpClient.getStrongTrustManager().setNotifyVerificationSuccess(true);
        httpClient.getStrongTrustManager().setNotifyVerificationFail(true);

        mUseTor = settings.getBoolean("pusetor", false);

        if (mUseTor) {
            httpClient.useProxy(true, "SOCKS", AppConstants.TOR_PROXY_HOST, AppConstants.TOR_PROXY_PORT);
        } else {
            httpClient.useProxy(false, null, null, -1);

        }

    }

    public void setAuthMode(String authMode) {
        mAuthMode = authMode;
    }

    public void setDeveloperKey(String devKey) {
        mDevKey = devKey;
    }

    public void setVideoFile(File videoFile, String contentType) {
        this.videoFile = videoFile;
        this.videoContentType = contentType;
    }

    public void upload(File videoFile, String contentType, String uploadEndPoint) {

        this.videoFile = videoFile;

        asyncUpload(videoFile, contentType, uploadEndPoint);
    }

    public void asyncUpload(final File videoFile, final String contentType, final String uploadEndPoint) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Message msg = new Message();
                Bundle bundle = new Bundle();
                msg.setData(bundle);
                msg.what = 888;

                int submitCount = 0;
                try {
                    while (submitCount <= MAX_RETRIES && videoId == null) {
                        try {
                            submitCount++;
                            videoId = startUpload(videoFile, contentType, uploadEndPoint);
                            assert videoId != null;
                            break;
                        } 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, Locale.US));
                            } 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());
                    msg.what = -1;

                    handler.sendMessage(msg);
                    return;
                } catch (YouTubeAccountException e) {
                    e.printStackTrace();
                    msg.what = -1;

                    bundle.putString("err", e.getMessage());
                    handler.sendMessage(msg);
                    return;
                } catch (SAXException e) {
                    e.printStackTrace();
                    msg.what = -1;

                    bundle.putString("err", e.getMessage());
                    handler.sendMessage(msg);
                } catch (ParserConfigurationException e) {
                    e.printStackTrace();
                    msg.what = -1;

                    bundle.putString("err", e.getMessage());
                    handler.sendMessage(msg);
                }

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

    private String startUpload(File file, String contentType, String uploadEndPoint) throws IOException,
            YouTubeAccountException, SAXException, ParserConfigurationException, Internal500ResumeException {

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

        String slug = file.getName();
        String uploadUrl = uploadMetaDataToGetLocation(uploadEndPoint, slug, contentType, file.length(), true);

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

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

        int start = -1;

        String videoId = null;
        double fileSize = this.currentFileSize;
        while (fileSize > 0) {

            try {
                videoId = gdataUpload(file, uploadUrl, start);

                this.numberOfRetries = 0; // clear this counter as we had a succesfull upload

                break;

            } 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, Locale.US));
                        throw e;
                    }
                    try {
                        resumeInfo = resumeFileUpload(uploadUrl, file);
                    } catch (IOException re) {
                        // ignore
                        Log.d(LOG_TAG, String.format("Failed retry attempt of : %s due to: '%s'.", uploadUrl,
                                re.getMessage(), Locale.US));
                    }
                } while (resumeInfo == null);

                Log.d(LOG_TAG, String.format("Resuming stalled upload to: %s.", uploadUrl, Locale.US));

                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, Locale.US));
                    break;
                } else {
                    int nextByteToUpload = resumeInfo.nextByteToUpload;
                    Log.d(LOG_TAG,
                            String.format("Next byte to upload is '%d'.", nextByteToUpload, Locale.US, Locale.US));
                    this.totalBytesUploaded = nextByteToUpload; // possibly rolling back the previosuly saved value
                    fileSize = this.currentFileSize - nextByteToUpload;
                    start = nextByteToUpload;
                }
            }
        }

        return videoId;
    }

    private String uploadMetaDataToGetLocation(String uploadUrl, String slug, String contentType,
            long contentLength, boolean retry) throws IOException {

        HttpPost hPost = getGDataHttpPost(new URL(uploadUrl).getHost(), uploadUrl, slug);

        //provide information about the media that is being uploaded
        hPost.setHeader("X-Upload-Content-Type", contentType);
        hPost.setHeader("X-Upload-Content-Length", contentLength + "");
        hPost.setHeader("X-Upload-Content-Encoding", "utf-8");

        String atomData;

        String category = DEFAULT_VIDEO_CATEGORY;
        tags = DEFAULT_VIDEO_TAGS;

        String template = Util.readFile(activity, R.raw.gdata).toString();

        atomData = String.format(template, StringEscapeUtils.escapeHtml4(title),
                StringEscapeUtils.escapeHtml4(description), category, tags);

        StringEntity entity = new StringEntity(atomData, HTTP.UTF_8);
        hPost.setEntity(entity);

        HttpResponse hResp = httpClient.execute(hPost);

        int responseCode = hResp.getStatusLine().getStatusCode();

        StringBuffer errMsg = new StringBuffer();
        InputStream is = hResp.getEntity().getContent();
        List<String> list = IOUtils.readLines(is);
        for (String line : list) {
            Log.d(LOG_TAG, "http resp line: " + line);
            errMsg.append(line).append("\n");
        }

        if (responseCode < 200 || responseCode >= 300) {
            // The response code is 40X
            if ((responseCode + "").startsWith("4") && retry && accountYouTube != null) {

                //invalidate our old one, that is locally cached
                this.clientLoginToken = authorizer.getFreshAuthToken(accountYouTube.name, clientLoginToken);
                // Try again with fresh token
                return uploadMetaDataToGetLocation(uploadUrl, slug, contentType, contentLength, false);
            } else {

                throw new IOException(
                        String.format(Locale.US, "response code='%s' (code %d)" + " for %s. Output=%s",
                                hResp.getStatusLine().getReasonPhrase(), responseCode,
                                hPost.getRequestLine().getUri(), errMsg.toString()));

            }
        }

        // return urlConnection.getHeaderField("Location");
        return hResp.getFirstHeader("Location").getValue();
    }

    private String gdataUpload(File file, String uploadUrl, int start) throws IOException {

        //int chunk = end - start + 1;
        //int bufferSize = 1024;
        //byte[] buffer = new byte[bufferSize];
        FileInputStream fileStream = new FileInputStream(file);
        URL url = new URL(uploadUrl);
        HttpPost hPut = getGDataHttpPost(url.getHost(), uploadUrl, null);
        hPut.setHeader("X-HTTP-Method-Override", "PUT");

        // 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("First time...Uploaded %d bytes so far.", (int) totalBytesUploaded, Locale.US));

        } else {

            Log.d(LOG_TAG, String.format(Locale.US, "Retry: Uploaded %d bytes so far.", (int) totalBytesUploaded));
        }

        String mimeType = "video/mp4";

        hPut.setHeader("Content-Type", mimeType);

        long fileLength = file.length();
        if (start != -1) {
            String cRange = String.format(Locale.US, "bytes %d-%d/%d", start, fileLength, fileLength);
            hPut.setHeader("Content-Range", cRange);
            Log.d(LOG_TAG, "upload content-range: " + cRange);
        }

        InputStreamEntityWithProgress fileEntity = new InputStreamEntityWithProgress(fileStream, start, fileLength,
                mimeType);//"binary/octet-stream");
        hPut.setEntity(fileEntity);

        HttpResponse hResp = httpClient.execute(hPut);

        int responseCode = hResp.getStatusLine().getStatusCode();

        Log.d(LOG_TAG, "responseCode=" + responseCode);
        Log.d(LOG_TAG, "responseMessage=" + hResp.getStatusLine().getReasonPhrase());

        InputStream isResp = hResp.getEntity().getContent();

        try {
            if (responseCode == 201) {
                String videoId = parseVideoId(isResp);

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

                Message msg = handler.obtainMessage(888);
                msg.getData().putInt("progress", 100);
                handler.sendMessage(msg);

                return videoId;
            } else if (responseCode == 200) {
                Header[] headers = hResp.getAllHeaders();

                Log.d(LOG_TAG, String.format("Headers keys %s.", headers.length, Locale.US));
                for (Header header : headers) {
                    Log.d(LOG_TAG, String.format("Header key %s value %s.", header.getName(), header.getValue(),
                            Locale.US));
                }
                Log.w(LOG_TAG, "Received 200 response during resumable uploading");
                throw new IOException(
                        String.format(Locale.US, "Unexpected response code : responseCode=%d responseMessage=%s",
                                responseCode, hResp.getStatusLine().getReasonPhrase()));
            } else {

                if ((responseCode + "").startsWith("5")) {
                    String error = String.format(Locale.US, "responseCode=%d responseMessage=%s", responseCode,
                            hResp.getStatusLine().getReasonPhrase());
                    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) { // these actually means "resume incomplete"
                    // OK, the chunk completed succesfully 
                    Log.d(LOG_TAG, String.format("responseCode=%d responseMessage=%s", responseCode,
                            hResp.getStatusLine().getReasonPhrase()));
                } else {
                    // TODO - this case is not handled properly yet
                    Log.w(LOG_TAG, String.format("Unexpected return code : %d %s while uploading :%s", responseCode,
                            hResp.getStatusLine().getReasonPhrase(), uploadUrl, Locale.US));
                }

            }
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * File entity which supports a progress bar.<br/>
     * Based on "org.apache.http.entity.FileEntity".
     * @author Benny Neugebauer (www.bennyn.de)
     */
    class InputStreamEntityWithProgress extends AbstractHttpEntity implements Cloneable {

        protected final InputStream is;
        // private final ProgressBarListener listener;
        // private long transferredBytes;
        private long mContentLength;
        private long mStart;

        public InputStreamEntityWithProgress(final InputStream is, long start, long contentLength,
                final String contentType) {
            super();

            this.is = is;
            mStart = start;
            mContentLength = contentLength;
            setContentType(contentType);
        }

        public boolean isRepeatable() {
            return true;
        }

        public long getContentLength() {
            return mContentLength;
        }

        public InputStream getContent() throws IOException {
            return is;
        }

        public void writeTo(final OutputStream outstream) throws IOException {
            try {

                byte[] tmp = new byte[1024 * 8]; //16kb chunks
                int bytesRead;

                BufferedOutputStream bos = new BufferedOutputStream(outstream);

                if (mStart != -1) {
                    is.skip(mStart);
                    Log.d(LOG_TAG, "Skipping input stream to: " + mStart);
                }

                while ((bytesRead = is.read(tmp)) != -1) {
                    bos.write(tmp, 0, bytesRead);

                    totalBytesUploaded += bytesRead; //total over all resumes

                    sendStatusMessage(totalBytesUploaded, currentFileSize);

                    if (totalBytesUploaded >= currentFileSize) {
                        break;
                    }

                }

                Log.d(LOG_TAG, "done writing data: " + totalBytesUploaded);

                bos.flush();
                bos.close();

            } finally {
                is.close();
            }
        }

        private void sendStatusMessage(double totalBytesUploaded, double currentFileSize) {
            double percent = (totalBytesUploaded / currentFileSize) * 100;

            String status = String.format("%,d/%,d bytes transfered", Math.round(totalBytesUploaded),
                    Math.round(currentFileSize));

            Message msg = handler.obtainMessage(888);

            String title = mContext.getString(R.string.uploading);

            msg.getData().putString("statusTitle", title);
            msg.getData().putString("status", status);
            msg.getData().putInt("progress", (int) percent);
            handler.sendMessage(msg);

        }

        public boolean isStreaming() {
            return totalBytesUploaded < mContentLength;
        }

        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }

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

    private ResumeInfo resumeFileUpload(String uploadUrl, File file)
            throws IOException, ParserConfigurationException, SAXException, Internal500ResumeException {

        HttpPost hPost = getGDataHttpPost(new URL(uploadUrl).getHost(), uploadUrl, file.getName());

        hPost.setHeader("Content-Range", "bytes */*");
        hPost.setHeader("X-HTTP-Method-Override", "PUT");

        HttpResponse hResp = httpClient.execute(hPost);

        int respCode = hResp.getStatusLine().getStatusCode();
        String respMessage = hResp.getStatusLine().getReasonPhrase();

        Log.d(LOG_TAG, "responseCode=" + respCode);
        Log.d(LOG_TAG, "responseMessage=" + hResp.getStatusLine().getReasonPhrase());

        InputStream isResp = hResp.getEntity().getContent();

        if (respCode >= 300 && respCode < 400) {
            int nextByteToUpload;
            String range = hResp.getFirstHeader("Range").getValue();
            if (range == null) {
                Log.d(LOG_TAG, String.format("PUT to %s did not return 'Range' header.", uploadUrl, Locale.US));
                nextByteToUpload = 0;
            } else {
                Log.d(LOG_TAG, String.format("Range header is '%s'.", range, Locale.US));
                String[] parts = range.split("-");
                if (parts.length > 1) {
                    nextByteToUpload = Integer.parseInt(parts[1]) + 1;
                } else {
                    nextByteToUpload = 0;
                }
            }
            return new ResumeInfo(nextByteToUpload);
        } else if (respCode >= 200 && respCode < 300) {
            return new ResumeInfo(parseVideoId(isResp));
        } else if (respCode == 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, respMessage,
                            respCode, Locale.US));
        } else {
            throw new IOException(String.format("Unexpected response for PUT to %s: %s " + "(code %d)", uploadUrl,
                    respMessage, respCode, Locale.US));
        }
    }

    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, Locale.US));
         Thread.currentThread().sleep(sleepSeconds * 1000);
         Log.d(LOG_TAG,String.format("Zzzzz for : %d sec done.", sleepSeconds, Locale.US));      
        } 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 HttpPost getGDataHttpPost(String host, String urlString, String slug) throws IOException {

        HttpPost request = new HttpPost(urlString);

        request.setHeader("Host", host);
        request.setHeader("Content-Type", CONTENT_TYPE);

        request.setHeader("GData-Version", "2");

        String formattedDevKeyHeader = String.format(Locale.US, "key=%s", mDevKey);
        Log.d(AppConstants.TAG, "dev key header=" + formattedDevKeyHeader);
        request.setHeader("X-GData-Key", formattedDevKeyHeader);

        if (clientLoginToken != null) //should this ever be null?
        {
            if (mAuthMode.equals("Bearer")) {
                request.setHeader("Authorization", mAuthMode + " " + clientLoginToken);
            } else {
                String authHeader = String.format(Locale.US, "%s auth=%s", mAuthMode, clientLoginToken);

                request.setHeader("Authorization", authHeader);
            }
        }

        if (slug != null)
            request.setHeader("Slug", slug);

        return request;

    }

    public void setClientLoginToken(String token) {
        this.clientLoginToken = token;
    }

    public Account setYouTubeAccount(String accountName) {

        return (accountYouTube = ((GlsAuthorizer) authorizer).getAccount(accountName));

    }

    public void getAuthTokenWithPermission(AuthorizationListener listener) {

        if (authorizer != null && accountYouTube != null)
            authorizer.fetchAuthToken(accountYouTube.name, activity, listener);
    }

    public void upload(String urlEndPoint) {
        upload(videoFile, videoContentType, urlEndPoint);

    }

    /*
      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) {
      YouTubeSubmitActivity.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(Locale.US,"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 = "https://" + ytdDomain + "/jsonrpc";
      String json = Util.makeJsonRpcCall(jsonRpcUrl, payload, activity);
        
      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);
        }
    }
}