Java tutorial
/* ========================================================================= * * Boarder * * http://boarder.mikuz.org/ * * ========================================================================= * * Copyright (C) 2013 Boarder * * * * 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 fi.mikuz.boarder.gui; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; 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.media.AudioManager; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.Toast; import com.dropbox.client2.DropboxAPI; import com.dropbox.client2.DropboxAPI.Entry; import com.dropbox.client2.ProgressListener; import com.dropbox.client2.android.AndroidAuthSession; import com.dropbox.client2.exception.DropboxException; import com.dropbox.client2.exception.DropboxServerException; import com.dropbox.client2.session.AccessTokenPair; import com.dropbox.client2.session.AppKeyPair; import com.dropbox.client2.session.Session.AccessType; import com.dropbox.client2.session.TokenPair; import fi.mikuz.boarder.R; import fi.mikuz.boarder.app.BoarderListActivity; import fi.mikuz.boarder.component.DropboxCache; import fi.mikuz.boarder.component.DropboxCacheFile; import fi.mikuz.boarder.gui.checkboxList.ExtendedCheckBox; import fi.mikuz.boarder.gui.checkboxList.ExtendedCheckBoxListAdapter; import fi.mikuz.boarder.util.ApiKeyLoader; import fi.mikuz.boarder.util.DropboxCacheUtils; import fi.mikuz.boarder.util.ExternalIntent; public class DropboxMenu extends BoarderListActivity { private static final String TAG = "Dropbox Menu"; private static String APP_KEY; private static String APP_SECRET; final static private AccessType ACCESS_TYPE = AccessType.APP_FOLDER; final static private String ACCOUNT_PREFS_NAME = "prefs"; final static private String ACCESS_KEY_NAME = "ACCESS_KEY"; final static private String ACCESS_SECRET_NAME = "ACCESS_SECRET"; protected static DropboxAPI<AndroidAuthSession> mApi; private boolean mLoggedIn; private String mInfo = ""; private int mProgressValue = 0; final Handler mHandler = new Handler(); private ProgressDialog mWaitDialog; private Thread t; private LinearLayout mDropboxContent; private Button mAuthDropbox; private Button mDropboxDownload; private Button mDropboxUpload; private Button mSelectAll; private Button mStart; private ExtendedCheckBoxListAdapter mCbla; private static final int DOWNLOAD_OPERATION = 0; private static final int UPLOAD_OPERATION = 1; private int mOperation = UPLOAD_OPERATION; private String mToastMessage; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.dropbox_menu); setTitle("Dropbox"); this.setVolumeControlStream(AudioManager.STREAM_MUSIC); APP_KEY = ApiKeyLoader.loadDropboxApiKey(this.getApplicationContext(), TAG); APP_SECRET = ApiKeyLoader.loadDropboxApiSecret(this.getApplicationContext(), TAG); AndroidAuthSession session = buildSession(); mApi = new DropboxAPI<AndroidAuthSession>(session); try { loadCbla(); } catch (NullPointerException npe) { } mDropboxContent = (LinearLayout) findViewById(R.id.dropbox_content); mAuthDropbox = (Button) findViewById(R.id.auth_button); mDropboxDownload = (Button) findViewById(R.id.dropbox_download); mDropboxUpload = (Button) findViewById(R.id.dropbox_upload); mSelectAll = (Button) findViewById(R.id.select_all); mStart = (Button) findViewById(R.id.start); mAuthDropbox.setOnClickListener(new OnClickListener() { public void onClick(View v) { // This logs you out if you're logged in, or vice versa if (mLoggedIn) { logOut(); } else { // Start the remote authentication mApi.getSession().startAuthentication(DropboxMenu.this); } } }); mDropboxDownload.setOnClickListener(new OnClickListener() { public void onClick(View v) { mOperation = DOWNLOAD_OPERATION; try { loadCbla(); } catch (NullPointerException npe) { } } }); mDropboxUpload.setOnClickListener(new OnClickListener() { public void onClick(View v) { mOperation = UPLOAD_OPERATION; try { loadCbla(); } catch (NullPointerException npe) { } } }); mSelectAll.setOnClickListener(new OnClickListener() { public void onClick(View v) { mCbla.selectAll(); } }); mStart.setOnClickListener(new OnClickListener() { public void onClick(View v) { AlertDialog.Builder cleanerBuilder = new AlertDialog.Builder(DropboxMenu.this); cleanerBuilder.setTitle("Cleaner"); if (mOperation == UPLOAD_OPERATION) { cleanerBuilder.setMessage( "Do you want to clean files that are not found locally but only in Dropbox?"); } else if (mOperation == DOWNLOAD_OPERATION) { cleanerBuilder.setMessage( "Do you want to clean files that are not found in Dropbox but only locally?"); } cleanerBuilder.setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { boolean cleanUnusedFiles = true; initializeTransfer(mCbla.getAllSelectedTitles(), cleanUnusedFiles); } }); cleanerBuilder.setNegativeButton("No", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { boolean cleanUnusedFiles = false; initializeTransfer(mCbla.getAllSelectedTitles(), cleanUnusedFiles); } }); cleanerBuilder.show(); } }); // Display the proper UI state if logged in or not setLoggedIn(mApi.getSession().isLinked()); } private void loadCbla() throws NullPointerException { mCbla = new ExtendedCheckBoxListAdapter(this); if (mOperation == UPLOAD_OPERATION) { setTitle("Dropbox - Upload"); for (File file : SoundboardMenu.mSbDir.listFiles()) { if (file.isDirectory()) { mCbla.addItem(new ExtendedCheckBox(file.getName(), false)); } } mHandler.post(mUpdateList); } else if (mOperation == DOWNLOAD_OPERATION) { setTitle("Dropbox - Download"); mWaitDialog = ProgressDialog.show(this, "", "Please wait", true); mWaitDialog.setCancelable(true); t = new Thread() { public void run() { Looper.prepare(); try { DropboxAPI.Account account = null; try { account = mApi.accountInfo(); } catch (DropboxException e) { Log.e(TAG, "Account info missing", e); } if (account != null) { for (Entry board : mApi.metadata("/", 0, null, true, null).contents) { if (board.isDir) { mCbla.addItem(new ExtendedCheckBox(board.fileName(), false)); } } } else { mWaitDialog.dismiss(); } } catch (DropboxException e) { Log.e(TAG, "Error getting Dropbox content", e); } mHandler.post(mUpdateList); mWaitDialog.dismiss(); } }; t.start(); } } final Runnable mUpdateList = new Runnable() { public void run() { setListAdapter(mCbla); } }; @Override protected void onResume() { super.onResume(); AndroidAuthSession session = mApi.getSession(); if (session.authenticationSuccessful()) { try { session.finishAuthentication(); TokenPair tokens = session.getAccessTokenPair(); storeKeys(tokens.key, tokens.secret); setLoggedIn(true); } catch (IllegalStateException e) { Toast.makeText(this, "Couldn't authenticate with Dropbox:", Toast.LENGTH_LONG).show(); Log.i(TAG, "Error authenticating", e); } } } // @Override // public boolean onCreateOptionsMenu(Menu menu) { // MenuInflater inflater = getMenuInflater(); // inflater.inflate(R.menu.dropbox_bottom, menu); // return true; // } public void initializeTransfer(final ArrayList<String> boards, final boolean cleanUnusedFiles) { mWaitDialog = new ProgressDialog(this); mWaitDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mWaitDialog.setMessage("Please wait"); mWaitDialog.show(); t = new Thread() { public void run() { Looper.prepare(); DropboxAPI.Account account = null; try { account = mApi.accountInfo(); } catch (DropboxException e) { Log.e(TAG, "Account info missing", e); } if (account != null) { if (mOperation == UPLOAD_OPERATION) { upload(boards, cleanUnusedFiles); } else if (mOperation == DOWNLOAD_OPERATION) { download(boards, cleanUnusedFiles); } } else { mWaitDialog.dismiss(); } } }; t.start(); } public void upload(final ArrayList<String> boards, boolean cleanUnusedFiles) { int currentBoardIndex = 1; int numberOfBoards = boards.size(); DropboxCache dropboxCache = new DropboxCache(); DropboxCacheUtils.load(dropboxCache); if (numberOfBoards > 0) { try { for (String boardName : boards) { try { Entry dbBoardDir = mApi.metadata("/" + boardName, 0, null, false, null); if (!dbBoardDir.isDeleted && !dbBoardDir.isDir) { mApi.delete("/" + boardName); mApi.createFolder("/" + boardName); } } catch (DropboxException e2) { Log.v(TAG, "Creating new board directory /" + boardName + " in Dropbox"); mApi.createFolder("/" + boardName); } if (cleanUnusedFiles) { // Check for unnecessary files in Dropbox checkUnusedDropbox(mApi.metadata("/" + boardName, 0, null, true, null), dropboxCache); } // Upload necessary files uploadBoard(new File(SoundboardMenu.mSbDir, boardName), currentBoardIndex, numberOfBoards, dropboxCache); currentBoardIndex++; } } catch (DropboxException e) { Log.e(TAG, "Failed to upload", e); } } else { Toast error = Toast.makeText(DropboxMenu.this, "Please select boards", Toast.LENGTH_LONG); error.show(); } DropboxCacheUtils.save(dropboxCache); mWaitDialog.dismiss(); } private void checkUnusedDropbox(Entry dropboxDir, DropboxCache dropboxCache) throws DropboxException { String localDir = DropboxCacheUtils.getLocalPath(dropboxDir.path); for (Entry dropboxFile : dropboxDir.contents) { String dropboxPath = dropboxFile.path; String localPath = DropboxCacheUtils.getLocalPath(dropboxPath); boolean fileExists = false; for (File file : new File(localDir).listFiles()) { if (localPath.equals(file.getAbsolutePath())) { fileExists = true; break; } } if (!fileExists) { DropboxCacheUtils.removeFile(dropboxCache, dropboxPath); mApi.delete(dropboxPath); Log.v(SoundboardMenu.TAG, "Deleted unused file: " + dropboxPath); } else if (dropboxFile.isDir) { checkUnusedDropbox(mApi.metadata(dropboxPath, 0, null, true, null), dropboxCache); } } } private void uploadBoard(File uploadDir, int currentBoardIndex, int numberOfBoards, DropboxCache dropboxCache) throws DropboxException { for (File localPath : uploadDir.listFiles()) { if (localPath.isDirectory()) { uploadBoard(localPath, currentBoardIndex, numberOfBoards, dropboxCache); } else { String dropboxPath = DropboxCacheUtils.getDropboxPath(localPath.getAbsolutePath()); final String baseInfo = "Use back to put upload on background.\n\n" + "Board " + currentBoardIndex + "/" + numberOfBoards + "\n" + dropboxPath; mInfo = baseInfo; mProgressValue = 0; mHandler.post(mUpdateResults); if (DropboxCacheUtils.fileChanged(dropboxCache, mApi, dropboxPath)) { if (!restoreFile(dropboxPath, dropboxCache)) { uploadFile(localPath, dropboxPath, baseInfo); } DropboxCacheUtils.updateFile(dropboxCache, mApi, dropboxPath); } } } } public boolean restoreFile(String dropboxPath, DropboxCache dropboxCache) { try { for (DropboxCacheFile file : dropboxCache.getFiles()) { if (file.getPath().equals(dropboxPath) && file.getMd5().equals( DropboxCacheUtils.getMd5(new File(DropboxCacheUtils.getLocalPath(file.getPath()))))) { Log.v(SoundboardMenu.TAG, "Restoring " + dropboxPath); mApi.restore(dropboxPath, file.getRev()); return true; } } } catch (DropboxServerException e) { } catch (DropboxException e) { } Log.v(SoundboardMenu.TAG, "Failed to restore " + dropboxPath); return false; } public void uploadFile(File localPath, String dropboxPath, final String baseInfo) { Log.v(SoundboardMenu.TAG, "Uploading " + dropboxPath); try { ByteArrayInputStream inputStream = new ByteArrayInputStream( IOUtils.toByteArray(new FileInputStream(localPath))); mApi.putFileOverwrite(dropboxPath, inputStream, inputStream.available(), new ProgressListener() { @Override public void onProgress(long bytes, long total) { mInfo = baseInfo + "\n"; Log.v(TAG, "bytes " + bytes + " total " + total); mProgressValue = (int) (bytes * 100 / total); mHandler.post(mUpdateResults); } }); } catch (FileNotFoundException e) { Log.e(TAG, "Unable to upload " + localPath, e); } catch (IOException e) { Log.e(TAG, "Unable to upload " + localPath, e); } catch (DropboxException e) { Log.e(TAG, "Unable to upload " + localPath, e); } } public void download(final ArrayList<String> boards, boolean cleanUnusedFiles) { int currentBoardIndex = 1; int numberOfBoards = boards.size(); DropboxCache dropboxCache = new DropboxCache(); DropboxCacheUtils.load(dropboxCache); if (numberOfBoards > 0) { try { for (String boardName : boards) { if (cleanUnusedFiles) { // Clean unnecessary local files try { checkUnusedLocal(new File(SoundboardMenu.mSbDir, boardName), dropboxCache); } catch (NullPointerException npe) { Log.w(SoundboardMenu.TAG, "Failed to delete unused local files", npe); } catch (IOException e) { Log.w(SoundboardMenu.TAG, "Failed to delete unused local files", e); } } // Download necessary files List<Entry> fileEntrys = mApi.metadata("/" + boardName, 0, null, true, null).contents; downloadBoard(fileEntrys, currentBoardIndex, numberOfBoards, dropboxCache); currentBoardIndex++; } } catch (DropboxException e) { Log.e(TAG, "Failed to download", e); } catch (FileNotFoundException e) { Log.e(TAG, "Failed to download", e); } } else { Toast error = Toast.makeText(DropboxMenu.this, "Please select boards", Toast.LENGTH_LONG); error.show(); } DropboxCacheUtils.save(dropboxCache); mWaitDialog.dismiss(); } private void checkUnusedLocal(File contentDir, DropboxCache dropboxCache) throws DropboxException, IOException { List<Entry> fileEntrys = mApi.metadata(DropboxCacheUtils.getDropboxPath(contentDir.getAbsolutePath()), 0, null, true, null).contents; for (File file : contentDir.listFiles()) { String dropboxPath = DropboxCacheUtils.getDropboxPath(file.getAbsolutePath()); boolean fileExists = false; for (Entry fileEntry : fileEntrys) { if (fileEntry.path.equals(dropboxPath)) { fileExists = true; break; } } if (!fileExists) { DropboxCacheUtils.removeFile(dropboxCache, dropboxPath); if (file.isDirectory()) { FileUtils.deleteDirectory(file); } else { file.delete(); } Log.v(SoundboardMenu.TAG, "Deleted unused " + dropboxPath); } else if (file.isDirectory()) { checkUnusedLocal(file, dropboxCache); } } } private void downloadBoard(List<Entry> fileEntrys, int currentBoardIndex, int numberOfBoards, DropboxCache dropboxCache) throws FileNotFoundException, DropboxException { for (Entry fileEntry : fileEntrys) { String dropboxPath = fileEntry.path; String localPath = DropboxCacheUtils.getLocalPath(dropboxPath); File localFile = new File(localPath); if (fileEntry.isDir) { Log.v(TAG, "Creating dir " + localPath); localFile.mkdirs(); List<Entry> subFileEntrys = mApi.metadata(fileEntry.path, 0, null, true, null).contents; downloadBoard(subFileEntrys, currentBoardIndex, numberOfBoards, dropboxCache); } else { final String baseInfo = "Use back to put download on background.\n\n" + "Board " + currentBoardIndex + "/" + numberOfBoards + "\n" + dropboxPath; mInfo = baseInfo; mHandler.post(mUpdateResults); if (localFile.isDirectory()) localFile.delete(); boolean fileChanged = DropboxCacheUtils.fileChanged(dropboxCache, mApi, dropboxPath); if (fileChanged) { Log.v(SoundboardMenu.TAG, "Downloading " + dropboxPath); if (!localFile.getParentFile().exists()) localFile.getParentFile().mkdirs(); OutputStream out = new BufferedOutputStream(new FileOutputStream(localPath)); mApi.getFile(dropboxPath, null, out, new ProgressListener() { @Override public void onProgress(long bytes, long total) { mInfo = baseInfo + "\n"; mProgressValue = (int) (bytes * 100 / total); mHandler.post(mUpdateResults); } }); DropboxCacheUtils.updateFile(dropboxCache, mApi, dropboxPath); } } } } final Runnable mUpdateResults = new Runnable() { public void run() { mWaitDialog.setMessage(mInfo); mWaitDialog.setProgress(mProgressValue); } }; @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { switch (item.getItemId()) { case R.id.menu_share: Thread thread = new Thread() { public void run() { Looper.prepare(); try { final ArrayList<String> boards = mCbla.getAllSelectedTitles(); if (!(mOperation == DOWNLOAD_OPERATION)) { mToastMessage = "Select 'Download' mode"; mHandler.post(mShowToast); } else if (boards.size() < 1) { mToastMessage = "Select boards to share"; mHandler.post(mShowToast); } else { String shareString = "I want to share some cool soundboards to you!\n\n" + "To use the boards you need to have Boarder for Android:\n" + ExternalIntent.mExtLinkMarket + "\n\n" + "Here are the boards:\n"; for (String board : boards) { shareString += board + " - " + mApi.createCopyRef("/" + board).copyRef + "\n"; } shareString += "\n\n" + "Importing a board:\n" + "1. Open Boarder'\n" + "2. Open Dropbox from menu in 'Soundboard Menu'\n" + "3. Open 'Import share' from menu\n" + "4. Copy a reference from above to textfield\n"; Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); sharingIntent.setType("text/plain"); sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Sharing boards"); sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareString); startActivity(Intent.createChooser(sharingIntent, "Share via")); } } catch (DropboxException e) { Log.e(TAG, "Unable to share", e); } } }; thread.start(); return true; case R.id.menu_import_share: LayoutInflater removeInflater = (LayoutInflater) DropboxMenu.this .getSystemService(LAYOUT_INFLATER_SERVICE); View importLayout = removeInflater.inflate(R.layout.dropbox_menu_alert_import_share, (ViewGroup) findViewById(R.id.alert_remove_sound_root)); AlertDialog.Builder importBuilder = new AlertDialog.Builder(DropboxMenu.this); importBuilder.setView(importLayout); importBuilder.setTitle("Import share"); final EditText importCodeInput = (EditText) importLayout.findViewById(R.id.importCodeInput); final EditText importNameInput = (EditText) importLayout.findViewById(R.id.importNameInput); importBuilder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { t = new Thread() { public void run() { Looper.prepare(); try { mApi.addFromCopyRef(importCodeInput.getText().toString(), "/" + importNameInput.getText().toString()); mToastMessage = "Download the board from 'Download'"; mHandler.post(mShowToast); } catch (DropboxException e) { Log.e(TAG, "Unable to get shared board", e); } } }; t.start(); } }); importBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { } }); importBuilder.show(); return true; } return super.onMenuItemSelected(featureId, item); } final Runnable mShowToast = new Runnable() { public void run() { Toast.makeText(DropboxMenu.this, mToastMessage, Toast.LENGTH_LONG).show(); } }; private void logOut() { // Remove credentials from the session mApi.getSession().unlink(); // Clear our stored keys clearKeys(); // Change UI state to display logged out version setLoggedIn(false); } /** * Convenience function to change UI state based on being logged in */ private void setLoggedIn(boolean loggedIn) { mLoggedIn = loggedIn; if (loggedIn) { mDropboxContent.setVisibility(View.VISIBLE); mAuthDropbox.setText("Unlink from Dropbox"); } else { mDropboxContent.setVisibility(View.INVISIBLE); mAuthDropbox.setText("Link with Dropbox"); } } /** * Shows keeping the access keys returned from Trusted Authenticator in a local * store, rather than storing user name & password, and re-authenticating each * time (which is not to be done, ever). * * @return Array of [access_key, access_secret], or null if none stored */ private String[] getKeys() { SharedPreferences prefs = getSharedPreferences(ACCOUNT_PREFS_NAME, 0); String key = prefs.getString(ACCESS_KEY_NAME, null); String secret = prefs.getString(ACCESS_SECRET_NAME, null); if (key != null && secret != null) { String[] ret = new String[2]; ret[0] = key; ret[1] = secret; return ret; } else { return null; } } /** * Shows keeping the access keys returned from Trusted Authenticator in a local * store, rather than storing user name & password, and re-authenticating each * time (which is not to be done, ever). */ private void storeKeys(String key, String secret) { // Save the access key for later SharedPreferences prefs = getSharedPreferences(ACCOUNT_PREFS_NAME, 0); Editor edit = prefs.edit(); edit.putString(ACCESS_KEY_NAME, key); edit.putString(ACCESS_SECRET_NAME, secret); edit.commit(); } private void clearKeys() { SharedPreferences prefs = getSharedPreferences(ACCOUNT_PREFS_NAME, 0); Editor edit = prefs.edit(); edit.clear(); edit.commit(); } private AndroidAuthSession buildSession() { AppKeyPair appKeyPair = new AppKeyPair(APP_KEY, APP_SECRET); AndroidAuthSession session; String[] stored = getKeys(); if (stored != null) { AccessTokenPair accessToken = new AccessTokenPair(stored[0], stored[1]); session = new AndroidAuthSession(appKeyPair, ACCESS_TYPE, accessToken); } else { session = new AndroidAuthSession(appKeyPair, ACCESS_TYPE); } return session; } }