es.uma.lcc.lockpic.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for es.uma.lcc.lockpic.MainActivity.java

Source

package es.uma.lcc.lockpic;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.cookie.BasicClientCookie;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager.LayoutParams;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import es.uma.lcc.nativejpegencoder.R;
import es.uma.lcc.tasks.DecryptionRequestTask;
import es.uma.lcc.tasks.EncryptionUploaderTask;
import es.uma.lcc.tasks.PictureDetailsTask;
import es.uma.lcc.tasks.PicturesViewTask;
import es.uma.lcc.tasks.SendUpdateTask;
import es.uma.lcc.utils.LockPicIO;
import static es.uma.lcc.utils.Constants.*;

/**
 * @author - Carlos Pars Pulido, Oct 2013
 * 
 * Activity with the initial GUI. Handles authentication, and does the
 * communication with the server, through the AsyncTasks implemented
 * in es.uma.lcc.tasks.
 * 
 * Copyright (C) 2014  Carlos Pars: carlosparespulido (at) gmail (dot) com
 * 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/>.
 */

public class MainActivity extends Activity {

    static Account sAccount;
    static String sToken;
    static Cookie mCookie = null;
    String mDirectDecryptPath = null;

    // getters (needed for several tasks)
    public Cookie getCurrentCookie() {
        return mCookie;
    }

    public String getUserEmail() {
        return sAccount.name;
    }

    /**
     * Encrypts a JPEG image and saves it to memory.
     * This is a native method, please see jni/wrapper.c
     * @param srcFilename path of file to encrypt
     * @param dstFilename path to which the encrypted image will be saved
     * @param squareNum number of regions to encrypt
     * @param horizStarts left coordinates of regions
     * @param horizEnds right coordinates of regions
     * @param vertStarts upper coordinates of regions 
     * @param vertEnds lower coordinates of regions
     * @param keys array of keys for encryption
     * @param picId picture identifier (as assigned by the key server)
     * @return true if encryption went OK, false otherwise
     */
    public static native boolean encodeWrapperRegions(String srcFilename, String dstFilename, int squareNum,
            int[] horizStarts, int[] horizEnds, int[] vertStarts, int[] vertEnds, String[] keys, String picId);

    /**
     * Decrypts a JPEG image and returns it as an array of bytes.
     * The array will contain the information in RGBA, unsigned (0-255) quadruplets.
     * 
     * This is a native method, please see jni/wrapper.c
     * @param srcFilename path of file to encrypt
     * @param squareNum number of regions to encrypt
     * @param horizStarts left coordinates of regions
     * @param horizEnds right coordinates of regions
     * @param vertStarts upper coordinates of regions 
     * @param vertEnds lower coordinates of regions
     * @param keys array of keys for encryption
     * @param picId picture identifier (as retrieved from the file comment)
     * @return an array of pixels as described above
     */
    public static native byte[] decodeWrapperRegions(String srcFilename, int squareNum, int[] horizStarts,
            int[] horizEnds, int[] vertStarts, int[] vertEnds, String[] keys, String picId);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mainactivity);
        System.loadLibrary("jpg"); // load native libraries

        // This activity is able to open images directly from a file browser.
        // If this is the case, we store the path, which will be used in onPostCreate
        Intent intent = getIntent();
        if (Intent.ACTION_VIEW.equals(intent.getAction()) && intent.getType() != null
                && (savedInstanceState == null || savedInstanceState.getBoolean("firstTime", true))) {
            if (intent.getType().startsWith("image/")) {
                mDirectDecryptPath = LockPicIO.getPathFromIntent(MainActivity.this, intent);
                if (mDirectDecryptPath == null)
                    Toast.makeText(this, R.string.unknownIntent, Toast.LENGTH_SHORT).show();
            }
        }

        // disable screen capture
        getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
    }

    @Override
    protected void onPostCreate(Bundle bundle) {
        super.onPostCreate(bundle);
        (new AuthenticationTask(MainActivity.this)).execute(); //authenticate
        if (mDirectDecryptPath != null) {
            // activity launched from file manager 
            decryptImage(mDirectDecryptPath);
            mDirectDecryptPath = null;
        }
    }

    /**
     * Overrides the back button functionality to behave in a more intuitive way.
     * When an encrypted/decrypted image is being displayed, pressing Back goes back to the menu
     * instead of closing the application.
     */
    @Override
    public void onBackPressed() {
        ImageView iv = ((ImageView) findViewById(R.id.imageView));
        if (iv.getVisibility() == ImageView.VISIBLE) {
            findViewById(R.id.imageBlock).setVisibility(View.GONE);
            findViewById(R.id.encryptBlock).setVisibility(View.VISIBLE);
            findViewById(R.id.decryptBlock).setVisibility(View.VISIBLE);
            findViewById(R.id.myPicsBlock).setVisibility(View.VISIBLE);
            findViewById(R.id.accountBlock).setVisibility(View.VISIBLE);
            findViewById(R.id.filler1).setVisibility(View.VISIBLE);
            findViewById(R.id.filler2).setVisibility(View.VISIBLE);
            findViewById(R.id.filler3).setVisibility(View.VISIBLE);
            findViewById(R.id.filler4).setVisibility(View.VISIBLE);
            iv.setVisibility(ImageView.INVISIBLE);
            (findViewById(R.id.shareButton)).setVisibility(View.INVISIBLE);
        } else
            this.finish();
    }

    @Override
    public void onSaveInstanceState(Bundle bundle) {
        bundle.putBoolean("firstTime", false);
        // avoid repeating direct decrypt in unnecesary situations
    }

    /** 
     * UI callbacks
     */

    public void buttonEncryptClick(View view) {
        // intent for launching the gallery
        Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
        startActivityForResult(i, ACTIVITY_PICK_IMAGE_ENC);
    }

    public void buttonDecryptClick(View view) {
        // intent for launching the gallery
        Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
        startActivityForResult(i, ACTIVITY_PICK_IMAGE_DEC);
    }

    public void buttonMyPicturesClick(View view) {
        (new PicturesViewTask(MainActivity.this)).execute();
    }

    public void logout() {
        Editor edit = getSharedPreferences(SETTINGS_FILENAME, MODE_PRIVATE).edit();
        edit.remove("preferred_account_name");
        edit.remove("cookie_value");
        edit.remove("cookie_expiry");
        edit.commit();
        mCookie = null;
        sAccount = null;
        (new AuthenticationTask(MainActivity.this)).execute();
    }

    public void menuLogoutClick(MenuItem item) {
        logout();
    }

    public void buttonLogoutClick(View view) {
        logout();
    }

    /**
     * reactions to results of activities
     */

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == ACTIVITY_SELECTOR && resultCode == RESULT_OK) {
            encryptImage(data);
        } else if (requestCode == ACTIVITY_PICK_IMAGE_ENC && resultCode == RESULT_OK) {
            String path = LockPicIO.getPathFromIntent(MainActivity.this, data);
            if (path != null) {
                selectRegions(path);
            } else {
                Toast.makeText(this, R.string.unreachablePathWarning, Toast.LENGTH_SHORT).show();
            }
        } else if (requestCode == ACTIVITY_PICK_IMAGE_DEC && resultCode == RESULT_OK) {
            String path = LockPicIO.getPathFromIntent(MainActivity.this, data);
            if (path != null) {
                decryptImage(path);
            } else {
                Toast.makeText(this, R.string.unreachablePathWarning, Toast.LENGTH_SHORT).show();
            }
        } else if (requestCode == ACTIVITY_VIEW_MY_PICTURES && resultCode == RESULT_OK) {
            if (data.getStringExtra("picId") != null)
                queryPermissions(data.getStringExtra("picId"));
        } else if (requestCode == ACTIVITY_PICTURE_DETAILS && resultCode == RESULT_OK) {
            if (data.getStringArrayListExtra("operations") != null
                    && data.getStringArrayListExtra("operations").size() > 0)
                sendUpdateToServer(data.getStringArrayListExtra("operations"), data.getStringExtra("picId"));
        }
    }

    private void encryptImage(Intent data) {
        String src = data.getStringExtra("path");
        int width = data.getIntExtra("width", 0);
        int height = data.getIntExtra("height", 0);
        (new EncryptionUploaderTask(src, width, height, data.getStringArrayListExtra("rectangles"),
                MainActivity.this)).execute();
    }

    private void selectRegions(String src) {
        Intent intent = new Intent(this, SelectorActivity.class);
        intent.putExtra("path", src);
        startActivityForResult(intent, ACTIVITY_SELECTOR);
    }

    private void decryptImage(String src) {
        try {
            String id = LockPicIO.getJpegComment(src);
            new DecryptionRequestTask(id, src, MainActivity.this).execute();
        } catch (IOException e) {
            Toast.makeText(this, R.string.fileCorruptedWarning, Toast.LENGTH_SHORT).show();
        }
    }

    private void queryPermissions(String pictureId) {
        (new PictureDetailsTask(MainActivity.this, pictureId)).execute();
    }

    private void sendUpdateToServer(ArrayList<String> operations, String picId) {
        (new SendUpdateTask(operations, picId, MainActivity.this)).execute();
    }

    /** 
     * Authentication functions 
     **/

    private class AuthenticationTask extends AsyncTask<Void, Integer, Boolean> {

        private MainActivity _mMainActivity;
        private ProgressDialog _mProgressDialog;
        private boolean _mIsConnectedToServer = true;

        protected AuthenticationTask(MainActivity mainActivity) {
            _mMainActivity = mainActivity;
        }

        @Override
        public void onPreExecute() {
            _mProgressDialog = new ProgressDialog(_mMainActivity, ProgressDialog.THEME_HOLO_DARK);
        }

        @Override
        protected Boolean doInBackground(Void... args) {
            return authenticate();
        }

        @Override
        protected void onPostExecute(Boolean authSuccess) {
            try {
                _mProgressDialog.dismiss();
            } catch (IllegalArgumentException illArgEx) {
                // this may be thrown if thread is destroyed before dismiss is processed.
                // In that case the dialog will be discarded anyway
            }
            if (!_mIsConnectedToServer)
                Toast.makeText(getApplicationContext(), R.string.noConnectionWarning, Toast.LENGTH_SHORT).show();
            else if (authSuccess) {
                (findViewById(R.id.imageView)).setVisibility(ImageView.INVISIBLE);
                (findViewById(R.id.buttonEncrypt)).setEnabled(true);
                (findViewById(R.id.buttonDecrypt)).setEnabled(true);
                (findViewById(R.id.buttonMyPics)).setEnabled(true);
                (findViewById(R.id.shareButton)).setVisibility(View.INVISIBLE);
                (findViewById(R.id.accountBlock)).setVisibility(View.VISIBLE);
                ((TextView) findViewById(R.id.emailTextView)).setText(sAccount.name);
                (findViewById(R.id.emailTextView)).setVisibility(View.VISIBLE);
                (findViewById(R.id.logoutButton)).setVisibility(View.VISIBLE);
            } else {
                final Account[] accounts = AccountManager.get(_mMainActivity).getAccountsByType("com.google");
                AlertDialog.Builder builder = new AlertDialog.Builder(_mMainActivity);
                builder.setTitle(R.string.emailSelectTitle);
                builder.setCancelable(false);
                final int size = accounts.length;
                String[] names = new String[size];
                for (int i = 0; i < size; i++) {
                    names[i] = accounts[i].name;
                }
                builder.setItems(names, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        sAccount = accounts[which];
                        Editor editor = getSharedPreferences(SETTINGS_FILENAME, MODE_PRIVATE).edit();
                        editor.putString("preferred_account_name", sAccount.name);
                        editor.commit();
                        (new AuthenticationTask(_mMainActivity)).execute();
                    }
                });
                builder.create();
                builder.show();
            }
        }

        private boolean authenticate() {
            SharedPreferences settings = getSharedPreferences(SETTINGS_FILENAME, MODE_PRIVATE);
            final Account[] accounts = AccountManager.get(_mMainActivity).getAccountsByType("com.google");
            String accountname = settings.getString("preferred_account_name", "--");
            if (accountname.equals("--")) { // no preferred account declared
                return false;
            } else { //we already have a preferred account declared
                for (Account a : accounts) {
                    if (a.name.equals(accountname))
                        sAccount = a;
                }
                getCookie();
                return true;
            }
        }

        private void getCookie() {
            boolean isNewCookieNeeded;
            SharedPreferences settings = getSharedPreferences(SETTINGS_FILENAME, MODE_PRIVATE);
            String cookieValue = settings.getString("cookie_value", "--");
            BasicClientCookie cookie = new BasicClientCookie("SACSID", cookieValue);
            isNewCookieNeeded = cookieValue.equals("--");
            if (!isNewCookieNeeded) {
                try {
                    String cookieExpiryDate = settings.getString("cookie_expiry", "--");
                    cookie.setExpiryDate(
                            new SimpleDateFormat("yyyy.MM.dd-HH:mm.ss", Locale.US).parse(cookieExpiryDate));
                    isNewCookieNeeded = cookie.isExpired(new Date());
                } catch (ParseException e) { // Should never happen
                    isNewCookieNeeded = true;
                }
            }
            if (!isNewCookieNeeded) { // we already had a non-expired cookie
                mCookie = cookie;
            } else { // we need to fetch a new cookie
                publishProgress(10); // show "connecting" dialog (please see onProgressUpdate)
                final AccountManager manager = AccountManager.get(getApplicationContext());

                Thread t = new TokenGetterThread(manager, MainActivity.this);
                t.start();
                try {
                    t.join();
                    t = new CookieGetterThread();
                    t.start();
                    t.join();
                } catch (InterruptedException e) {
                    Log.e(APP_TAG, "Attempt to reach the server was interrupted.");
                    e.printStackTrace();
                }

                //            if(mCookie != null)  {
                //               Editor editor = settings.edit();
                //               editor.putString("cookie_value", mCookie.getValue());
                //               editor.putString("cookie_expiry", 
                //                     new SimpleDateFormat("yyyy.MM.dd-HH:mm.ss", Locale.US).format(mCookie.getExpiryDate()));
                //               editor.commit();
                //            }  else  {
                //               _mIsConnectedToServer = false;
                //            }
                if (mCookie == null) {
                    _mIsConnectedToServer = false;
                }
            }
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            if (progress[0] == 10) {
                (findViewById(R.id.imageView)).setVisibility(ImageView.INVISIBLE);
                (findViewById(R.id.buttonEncrypt)).setEnabled(false);
                (findViewById(R.id.buttonDecrypt)).setEnabled(false);
                (findViewById(R.id.buttonMyPics)).setEnabled(false);
                (findViewById(R.id.shareButton)).setVisibility(View.INVISIBLE);
                (findViewById(R.id.emailTextView)).setVisibility(View.INVISIBLE);
                (findViewById(R.id.logoutButton)).setVisibility(View.INVISIBLE);
                _mProgressDialog.setTitle(R.string.authenticatingDialog);
                _mProgressDialog.setMessage(_mMainActivity.getString(R.string.pleaseWaitDialog));
                _mProgressDialog.setCancelable(false);
                _mProgressDialog.setIndeterminate(true);
                _mProgressDialog.show();
            }
        }
    }

    private class CookieGetterThread extends Thread {
        public void run() {
            try {
                mCookie = getAuthCookie(sToken);
                // if anything goes wrong, it will be handled by getCookie()
            } catch (ClientProtocolException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private class TokenGetterThread extends Thread {
        private AccountManager _mManager;
        private Activity _mActivity;

        public TokenGetterThread(AccountManager manager, Activity activity) {
            this._mManager = manager;
            this._mActivity = activity;
        }

        public void run() {
            try {
                AccountManagerFuture<Bundle> future = _mManager.getAuthToken(sAccount, "ah", null, _mActivity, null,
                        null);
                Bundle bundle = future.getResult();
                String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
                sToken = token;
                // This token is NOT NECESSARILY still valid. That will be determined
                // by the server; we manage that as an authentication error.
                // if anything goes wrong, it will be handled by getCookie()
            } catch (OperationCanceledException cancelEx) {
                cancelEx.printStackTrace();
            } catch (AuthenticatorException authEx) {
                authEx.printStackTrace();
            } catch (IOException ioEx) {
                ioEx.printStackTrace();
            }
        }
    }

    public static class CookieRefresherThread extends Thread {
        /** this class handles updating tokens.
         * Authentication tokens assigned to an account expire after a set amount of time,
         * and they cannot be directly queried on whether or not they are still good.
         * Thus, since asking the server for a new token can be slow, and it is thus not
         * a good idea to do this every time the token is used, we only do it whenever
         * the server states user is not authenticated.
         * This is only retried once, since the problem may be an actual authentication
         * problem, which would otherwise trigger an infinite loop (authentication failed,
         * try to get new token, retry, authentication fails again...)
         */

        private AccountManager _mManager;
        private MainActivity _mMainActivity;

        public CookieRefresherThread(AccountManager manager, MainActivity activity) {
            this._mManager = manager;
            this._mMainActivity = activity;
        }

        public void run() {
            try {
                AccountManagerFuture<Bundle> future = _mManager.getAuthToken(sAccount, "ah", null, _mMainActivity,
                        null, null);
                Bundle bundle = future.getResult();
                String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
                _mManager.invalidateAuthToken("com.google", token);

                future = _mManager.getAuthToken(sAccount, "ah", null, _mMainActivity, null, null);
                bundle = future.getResult(); //blocking
                token = bundle.getString(AccountManager.KEY_AUTHTOKEN);

                sToken = token;
                mCookie = _mMainActivity.getAuthCookie(sToken);

            } catch (OperationCanceledException cancelEx) {
                cancelEx.printStackTrace();
            } catch (AuthenticatorException authEx) {
                authEx.printStackTrace();
            } catch (IOException ioEx) {
                ioEx.printStackTrace();
            }
        }
    }

    protected Cookie getAuthCookie(String authToken) throws ClientProtocolException, IOException {
        DefaultHttpClient httpClient = new DefaultHttpClient();
        Cookie retCookie = null;
        String cookieUrl = BASEURL + "/_ah/login?continue=" + URLEncoder.encode(BASEURL, "UTF-8") + "&auth="
                + URLEncoder.encode(authToken, "UTF-8");
        HttpGet httpget = new HttpGet(cookieUrl);
        HttpResponse response = httpClient.execute(httpget);
        if (response.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK
                || response.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) {

            if (httpClient.getCookieStore().getCookies().size() > 0) {
                retCookie = httpClient.getCookieStore().getCookies().get(0);
            }
        }

        // store the cookie
        SharedPreferences settings = getSharedPreferences(SETTINGS_FILENAME, MODE_PRIVATE);
        Editor editor = settings.edit();
        editor.putString("cookie_value", retCookie.getValue());
        editor.putString("cookie_expiry",
                new SimpleDateFormat("yyyy.MM.dd-HH:mm.ss", Locale.US).format(retCookie.getExpiryDate()));
        editor.commit();

        return retCookie;
    }
}