com.concentricsky.android.khanacademy.app.ShowProfileActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.concentricsky.android.khanacademy.app.ShowProfileActivity.java

Source

/*
Viewer for Khan Academy
Copyright (C) 2012 Concentric Sky, Inc.
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.concentricsky.android.khanacademy.app;

import static com.concentricsky.android.khanacademy.Constants.PARAM_TOPIC_ID;
import static com.concentricsky.android.khanacademy.Constants.PARAM_VIDEO_ID;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

import oauth.signpost.exception.OAuthCommunicationException;
import oauth.signpost.exception.OAuthExpectationFailedException;
import oauth.signpost.exception.OAuthMessageSignerException;

import org.apache.http.Header;
import org.apache.http.client.methods.HttpGet;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebSettings.ZoomDensity;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.widget.Toast;

import com.concentricsky.android.khan.R;
import com.concentricsky.android.khanacademy.Constants;
import com.concentricsky.android.khanacademy.data.KADataService;
import com.concentricsky.android.khanacademy.data.db.Topic;
import com.concentricsky.android.khanacademy.data.db.User;
import com.concentricsky.android.khanacademy.data.db.Video;
import com.concentricsky.android.khanacademy.data.remote.KAAPIAdapter;
import com.concentricsky.android.khanacademy.util.Log;
import com.concentricsky.android.khanacademy.util.ObjectCallback;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.stmt.QueryBuilder;

/**
 * This just displays the profile page as loaded from Khan servers.
 * 
 * @author austinlally
 *
 */
public class ShowProfileActivity extends KADataServiceProviderActivityBase {
    //TODO done button
    public static final String LOG_TAG = ShowProfileActivity.class.getSimpleName();

    private static final int WEBVIEW_LOAD_TIMEOUT = 5000;

    private View spinnerView;
    private WebView webView;
    private Handler handler = new Handler();
    private KAAPIAdapter api;
    private KADataService dataService;

    private boolean destroyed = false;

    /**
     * Get the current user, and fail if there is none.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        destroyed = false;
        this.webViewTimeoutPromptDialog = new AlertDialog.Builder(this)
                .setMessage("The page is taking a long time to respond. Stop loading?")
                .setPositiveButton("Stop", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int id) {
                        if (webView != null) {
                            webView.stopLoading();
                            finish();
                        }
                    }
                }).setNegativeButton("Wait", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        stopWebViewLoadTimeout();
                    }
                }).setOnCancelListener(new DialogInterface.OnCancelListener() {
                    @Override
                    public void onCancel(DialogInterface dialog) {
                        startWebViewLoadTimeout();
                    }
                }).create();

        getActionBar().setDisplayHomeAsUpEnabled(true);

        setContentView(R.layout.profile);
        setTitle(getString(R.string.profile_title));
        webView = (WebView) findViewById(R.id.web_view);
        webView.setMinimumWidth(800);
        enableJavascript(webView);
        webView.getSettings().setDefaultZoom(ZoomDensity.FAR);
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView webView, String url) {
                Log.d(LOG_TAG, "shouldOverrideUrlLoading: " + url);
                URL parsed = null;
                try {
                    parsed = new URL(url);
                } catch (MalformedURLException e) {
                    // Let the webview figure that one out.
                    return false;
                }

                // Only ka links will load in this webview.
                if (parsed.getHost().equals("www.khanacademy.org")) {

                    // Video urls should link into video detail. See below for another video url format.
                    if (parsed.getPath().equals("/video")) {
                        String query = parsed.getQuery();
                        if (query != null && query.length() > 0) {
                            String[] items = query.split("&");
                            String videoId = null;
                            for (String item : items) {
                                String[] parts = item.split("=", 2);
                                if (parts.length > 1) {
                                    if ("v".equals(parts[0])) {
                                        videoId = parts[1];
                                        break;
                                    }
                                }
                            }
                            if (videoId != null) {
                                String[] ids = normalizeVideoAndTopicId(videoId, "");
                                if (ids != null) {
                                    launchVideoDetailActivity(ids[0], ids[1]);
                                    return true;
                                }
                            }
                        }
                        // There was no ?v= or something weird is going on. Allow the page
                        // load, which should hit KA's nice "no video found" page.
                        showSpinner();
                        startWebViewLoadTimeout();
                        return false;
                    }

                    if (parsed.getPath().startsWith("/profile")) {
                        // navigation within the profile makes sense here.
                        showSpinner();
                        startWebViewLoadTimeout();
                        return false;
                    }

                    // Embedded logout option (in upper left menu) can be intercepted and cause the app to be logged out as well.
                    if (parsed.getPath().equals("/logout")) {
                        if (dataService != null) {
                            dataService.getAPIAdapter().logout();
                        }
                        finish();
                        return false; // try to let the webview hit logout to get the cookies cleared as we exit
                    }

                    // There is a new kind of video url now.. thanks guys..
                    // http://www.khanacademy.org/video/subtraction-2
                    //  redirects to
                    // http://www.khanacademy.org/math/arithmetic/addition-subtraction/two_dig_add_sub/v/subtraction-2
                    String[] path = parsed.getPath().split("/");
                    List<String> parts = Arrays.asList(path);
                    if (parts.contains("v")) {
                        String videoId = null;
                        String topicId = null;
                        for (int i = path.length - 1; i >= 0; --i) {
                            if (path[i].equals("v")) {
                                continue;
                            }
                            if (videoId == null) {
                                videoId = path[i];
                            } else if (topicId == null) {
                                topicId = path[i];
                            } else {
                                break;
                            }
                        }

                        if (videoId != null && topicId != null && dataService != null) {
                            // Looks like a video url. Double check that we have the topic and video before launching detail activity.
                            Log.d(LOG_TAG, "video and topic ids found; looks like a video url.");

                            String[] ids = normalizeVideoAndTopicId(videoId, topicId);
                            if (ids != null) {
                                launchVideoDetailActivity(ids[0], ids[1]);
                                return true;
                            }
                        }
                    } else if (parsed.getPath().startsWith("/video")) {
                        String videoId = path[path.length - 1];
                        String[] ids = normalizeVideoAndTopicId(videoId, "");
                        if (ids != null) {
                            launchVideoDetailActivity(ids[0], ids[1]);
                            return true;
                        }
                    }

                    //               showSpinner();
                    //               startWebViewLoadTimeout();
                    //               return false;
                }

                // All other urls should launch in the browser instead of here, except hash changes if we can distinguish.
                loadInBrowser(url);
                return true;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                Log.d(LOG_TAG, "onPageFinished");
                stopWebViewLoadTimeout();
                if (!destroyed) {
                    hideSpinner();
                }
            }

            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                Log.d(LOG_TAG, "onPageStarted");
                stopWebViewLoadTimeout();

                //            view.loadUrl("javascript:document.addEventListener( 'DOMContentLoaded',function() {AndroidApplication.jqueryReady()} );");

                //            handler.postDelayed(new Runnable() {
                //               @Override
                //               public void run() {
                //                  if (!destroyed) {
                //                     hijack();
                //                  }
                //               }
                //            }, 100);
            }
        });
        //      webView.addJavascriptInterface(new Object() {
        //         public void log(String msg) {
        //            Log.d(LOG_TAG, "JSLOG: " + msg);
        //         }
        //         public void jqueryReady() {
        //            Log.d(LOG_TAG, "javascript: jqueryReady");
        //            hijack();
        //         }
        //      }, "AndroidApplication");
        //      
        showSpinner();

        requestDataService(new ObjectCallback<KADataService>() {
            @Override
            public void call(KADataService service) {
                dataService = service;
                api = service.getAPIAdapter();

                String[] credentials = getCurrentLoginCredentials();
                loginUser(credentials[0], credentials[1]);
            }
        });
    }

    @SuppressLint("SetJavaScriptEnabled")
    private void enableJavascript(WebView webView) {
        webView.getSettings().setJavaScriptEnabled(true);
    }

    private String[] normalizeVideoAndTopicId(String videoId, String topicId) {
        Log.d(LOG_TAG, "videoAndTopicExist: " + videoId + ", " + topicId);
        if (videoId != null && topicId != null && dataService != null) {
            Dao<Video, String> videoDao;
            Dao<Topic, String> topicDao;
            try {
                videoDao = dataService.getHelper().getVideoDao();
                QueryBuilder<Video, String> q = videoDao.queryBuilder();
                q.where().eq("youtube_id", videoId).or().eq("readable_id", videoId);
                Video video = videoDao.queryForFirst(q.prepare());
                if (video != null) {
                    Log.d(LOG_TAG, " video found");
                    topicDao = dataService.getHelper().getTopicDao();
                    Topic parent = topicDao.queryForId(topicId);
                    if (parent != null) {
                        Log.d(LOG_TAG, " topic found");
                        return new String[] { video.getReadable_id(), parent.getId() };
                    } else {
                        parent = topicDao.queryRaw(
                                "select topic._id from topic, topicvideo where topicvideo.video_id=? and topicvideo.topic_id = topic._id limit 1",
                                topicDao.getRawRowMapper(), new String[] { video.getReadable_id() })
                                .getFirstResult();
                        if (parent != null) {
                            Log.d(LOG_TAG, " another topic found");
                            return new String[] { video.getReadable_id(), parent.getId() };
                        }
                    }
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private void launchVideoDetailActivity(String videoId, String topicId) {
        Intent intent = new Intent(this, VideoDetailActivity.class);
        intent.putExtra(PARAM_VIDEO_ID, videoId);
        intent.putExtra(PARAM_TOPIC_ID, topicId);
        startActivity(intent);
    }

    private void loadInBrowser(String url) {
        startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
    }

    private Runnable timeoutPromptRunnable = new Runnable() {
        @Override
        public void run() {
            promptLoadTimeout();
        }
    };

    private AlertDialog webViewTimeoutPromptDialog;

    private void startWebViewLoadTimeout() {
        stopWebViewLoadTimeout();
        handler.postDelayed(timeoutPromptRunnable, WEBVIEW_LOAD_TIMEOUT);
    }

    private void stopWebViewLoadTimeout() {
        handler.removeCallbacks(timeoutPromptRunnable);
    }

    private void promptLoadTimeout() {
        stopWebViewLoadTimeout();
        Log.d(LOG_TAG, "showing dialog");
        webViewTimeoutPromptDialog.show();
    }

    @Override
    protected void onDestroy() {
        destroyed = true;
        if (webView != null) {
            webView.destroy();
            webView = null;
        }
        super.onDestroy();
    }

    private KAAPIAdapter.UserLoginHandler userLoginHandler = new KAAPIAdapter.UserLoginHandler() {
        @Override
        public void onUserLogin(final User user) {
            if (destroyed)
                return;

            if (user == null) {
                // No user logged in, likely thanks to bad credentials.
                launchSignInActivity();

                // TODO
                // Could also be network trouble.

            } else {
                // Success.
                requestDataService(new ObjectCallback<KADataService>() {
                    @Override
                    public void call(KADataService dataService) {
                        dataService.getAPIAdapter().requestUserVideoUpdate(user);
                    }
                });
                loadProfilePage(user);
            }
        }
    };

    private void launchSignInActivity() {
        Intent intent = new Intent(this, SignInActivity.class);
        startActivityForResult(intent, Constants.REQUEST_CODE_USER_LOGIN);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        Log.d(LOG_TAG, "onActivityResult");
        switch (requestCode) {
        case Constants.REQUEST_CODE_USER_LOGIN:
            handleLoginResult(resultCode, intent);
            break;
        default:
            // ???
            break;
        }
    }

    private void handleLoginResult(int resultCode, Intent intent) {
        Log.d(LOG_TAG, "request code was user login, result code is " + resultCode);
        if (destroyed)
            return;

        // Sent by SignInActivity
        switch (resultCode) {
        case Constants.RESULT_CODE_SUCCESS:
            String token = intent == null ? null : intent.getStringExtra(Constants.PARAM_OAUTH_TOKEN);
            String secret = intent == null ? null : intent.getStringExtra(Constants.PARAM_OAUTH_SECRET);

            loginUser(token, secret);

            break;
        case Constants.RESULT_CODE_FAILURE:
            // Network error, user declines authorization, activity closed before finishing.
            Toast.makeText(this, "Login failed", Toast.LENGTH_SHORT).show();
            // fall through
        default:
            // User exits the login dialog without finishing the process.
            finish();
        }

    }

    // Begin the login process here. We try to log in with whatever token,secret we have saved.
    // We will get a callback from the API Adapter after this with a user object. If the user
    // isn't null, then we're logged in. If it is, that indicates we need to launch the 
    // SignInActivity to get a new set of credentials and try again.
    private void loginUser(final String token, final String secret) {
        requestDataService(new ObjectCallback<KADataService>() {
            @Override
            public void call(KADataService dataService) {
                dataService.getAPIAdapter().login(token, secret, userLoginHandler);
            }
        });
    }

    private String[] getCurrentLoginCredentials() {
        Log.d(LOG_TAG, "getCurrentLoginCredentials");
        // Make sure this.api is non-null before trying this.
        User user = api.getCurrentUser();
        String[] result = new String[2];
        if (user != null) {
            result[0] = user.getToken();
            result[1] = user.getSecret();
        } else {
            result[0] = "";
            result[1] = "";
        }
        Log.d(LOG_TAG, String.format(" --> [%s, %s]", result[0], result[1]));
        return result;
    }

    private void loadProfilePage(User user) {
        String url = "http://www.khanacademy.org/api/auth/token_to_session?continue=";
        try {
            url += URLEncoder.encode("/profile", "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            url += "/profile"; // Works ok as of 03/22/2013, but encoding it is of course the right way.
        }

        HashMap<String, String> headers = new HashMap<String, String>();

        HttpGet request = new HttpGet(url);
        try {
            api.getConsumer(user).sign(request);
            for (Header h : request.getAllHeaders()) {
                headers.put(h.getName(), h.getValue());
            }
        } catch (OAuthMessageSignerException e) {
            e.printStackTrace();
        } catch (OAuthExpectationFailedException e) {
            e.printStackTrace();
        } catch (OAuthCommunicationException e) {
            e.printStackTrace();
        }

        webView.loadUrl(url, headers);

    }

    /**
     * Show a loading indicator.
     */
    protected void showSpinner() {
        if (spinnerView == null) {
            spinnerView = getLayoutInflater().inflate(R.layout.spinner, null, false);
        }
        handler.post(new Runnable() {
            @Override
            public void run() {
                ViewGroup parent = (ViewGroup) spinnerView.getParent();
                if (parent != null)
                    parent.removeView(spinnerView);
                ((FrameLayout) findViewById(R.id.popover_view)).addView(spinnerView);
            }
        });
    }

    /**
     * Hide any loading indicator.
     */
    protected void hideSpinner() {
        if (spinnerView != null) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    ViewGroup parent = (ViewGroup) spinnerView.getParent();
                    if (parent != null)
                        parent.removeView(spinnerView);
                }
            });
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case android.R.id.home:
            finish();
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onBackPressed() {
        if (webView != null && webView.canGoBack()) {
            webView.goBack();
        } else {
            super.onBackPressed();
        }
    }
}