Java tutorial
/* * Copyright (C) 2003-2015 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.shareextension; import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.http.HttpResponse; import org.exoplatform.R; import org.exoplatform.model.ExoAccount; import org.exoplatform.model.SocialPostInfo; import org.exoplatform.model.SocialSpaceInfo; import org.exoplatform.shareextension.service.ShareService; import org.exoplatform.singleton.AccountSetting; import org.exoplatform.singleton.ServerSettingHelper; import org.exoplatform.singleton.SocialServiceHelper; import org.exoplatform.social.client.api.ClientServiceFactory; import org.exoplatform.social.client.api.SocialClientContext; import org.exoplatform.social.client.api.service.VersionService; import org.exoplatform.social.client.core.ClientServiceFactoryHelper; import org.exoplatform.ui.social.SpaceSelectorActivity; import org.exoplatform.utils.ExoConnectionUtils; import org.exoplatform.utils.ExoConstants; import org.exoplatform.utils.ExoDocumentUtils; import org.exoplatform.utils.ExoDocumentUtils.DocumentInfo; import org.exoplatform.utils.Log; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentTransaction; import android.text.format.DateFormat; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.ProgressBar; import android.widget.Toast; /** * Created by The eXo Platform SAS * * @author Philippe Aristote paristote@exoplatform.com * @since 3 Jun 2015 */ public class ShareActivity extends FragmentActivity { /** * Direction of the animation to switch from one fragment to another */ public static enum Anim { NO_ANIM, FROM_LEFT, FROM_RIGHT } public static final String LOG_TAG = "____eXo____Share_Extension____"; public static final String DEFAULT_CONTENT_NAME = "TEMP_FILE_TO_SHARE"; private static final int SELECT_SHARE_DESTINATION = 11; private boolean online; private ProgressBar loadingIndicator; private Button mainButton; private SocialPostInfo postInfo; private boolean mMultiFlag = false; private List<Uri> mAttachmentUris; @Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView(R.layout.share_extension_activity); loadingIndicator = (ProgressBar) findViewById(R.id.share_progress_indicator); mainButton = (Button) findViewById(R.id.share_button); mainButton.setTag(R.attr.share_button_type_post); online = false; postInfo = new SocialPostInfo(); if (isIntentCorrect()) { Intent intent = getIntent(); String type = intent.getType(); if ("text/plain".equals(type)) { // The share does not contain an attachment // TODO extract the link info - MOB-1866 postInfo.postMessage = intent.getStringExtra(Intent.EXTRA_TEXT); } else { // The share contains an attachment if (mMultiFlag) { mAttachmentUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); } else { Uri contentUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); if (contentUri != null) { mAttachmentUris = new ArrayList<Uri>(); mAttachmentUris.add(contentUri); } if (Log.LOGD) { Log.d(LOG_TAG, "Number of files to share: ", mAttachmentUris.size()); } } prepareAttachmentsAsync(); } init(); // Create and display the composer, aka ComposeFragment ComposeFragment composer = ComposeFragment.getFragment(); openFragment(composer, ComposeFragment.COMPOSE_FRAGMENT, Anim.NO_ANIM); } else { // We're not supposed to reach this activity by anything else than an // ACTION_SEND intent finish(); } } private boolean isIntentCorrect() { Intent intent = getIntent(); String action = intent.getAction(); String type = intent.getType(); mMultiFlag = Intent.ACTION_SEND_MULTIPLE.equals(action); return ((Intent.ACTION_SEND.equals(action) || mMultiFlag) && type != null); } private void init() { // Load the list of accounts ServerSettingHelper.getInstance().getServerInfoList(this); // Init the activity with the selected account postInfo.ownerAccount = AccountSetting.getInstance().getCurrentAccount(); if (postInfo.ownerAccount == null) { List<ExoAccount> serverList = ServerSettingHelper.getInstance().getServerInfoList(this); if (serverList == null || serverList.size() == 0) { // TODO open the configuration assistant to create an account // Then come back here after the result Toast.makeText(this, R.string.ShareErrorNoAccountConfigured, Toast.LENGTH_LONG).show(); finish(); } int selectedServerIdx = Integer.parseInt(getSharedPreferences(ExoConstants.EXO_PREFERENCE, 0) .getString(ExoConstants.EXO_PRF_DOMAIN_INDEX, "-1")); AccountSetting.getInstance().setDomainIndex(String.valueOf(selectedServerIdx)); AccountSetting.getInstance() .setCurrentAccount((selectedServerIdx == -1 || selectedServerIdx >= serverList.size()) ? null : serverList.get(selectedServerIdx)); postInfo.ownerAccount = AccountSetting.getInstance().getCurrentAccount(); } ExoAccount acc = postInfo.ownerAccount; if (acc != null && acc.password != null && !"".equals(acc.password)) { // Try to login to setup the social services loginWithSelectedAccount(); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // When we come back from the space selector activity // TODO make it another fragment if (requestCode == SELECT_SHARE_DESTINATION) { if (resultCode == RESULT_OK) { SocialSpaceInfo space = (SocialSpaceInfo) data .getParcelableExtra(SpaceSelectorActivity.SELECTED_SPACE); ComposeFragment composer = ComposeFragment.getFragment(); if (space != null) { composer.setSpaceSelectorLabel(space.displayName); } else { composer.setSpaceSelectorLabel(getResources().getString(R.string.Public)); } postInfo.destinationSpace = space; } } } /** * Util method to switch from one fragment to another, with an animation * * @param toOpen The Fragment to open in this transaction * @param key the string key of the fragment * @param anim the type of animation */ public void openFragment(Fragment toOpen, String key, Anim anim) { FragmentTransaction tr = getSupportFragmentManager().beginTransaction(); switch (anim) { case FROM_LEFT: tr.setCustomAnimations(R.anim.fragment_enter_ltr, R.anim.fragment_exit_ltr); break; case FROM_RIGHT: tr.setCustomAnimations(R.anim.fragment_enter_rtl, R.anim.fragment_exit_rtl); break; default: case NO_ANIM: break; } tr.replace(R.id.share_extension_fragment, toOpen, key); tr.commit(); } @Override public void onBackPressed() { // Intercept the back button taps to display previous state with animation // If we're on the composer, call super to finish the activity ComposeFragment composer = ComposeFragment.getFragment(); if (composer.isAdded()) { if (postInfo != null) ExoDocumentUtils.deleteLocalFiles(postInfo.postAttachedFiles); super.onBackPressed(); } else if (AccountsFragment.getFragment().isAdded()) { // close the accounts fragment and reopen the composer fragment openFragment(composer, ComposeFragment.COMPOSE_FRAGMENT, Anim.FROM_LEFT); } else if (SignInFragment.getFragment().isAdded()) { // close the sign in fragment and reopen the accounts fragment openFragment(AccountsFragment.getFragment(), AccountsFragment.ACCOUNTS_FRAGMENT, Anim.FROM_LEFT); } } @Override protected void onDestroy() { Log.d(LOG_TAG, "Destroyed " + this); super.onDestroy(); } /* * GETTERS & SETTERS */ public ProgressBar getLoadingIndicator() { return loadingIndicator; } public Button getMainButton() { return mainButton; } public void setPostMessage(String message) { if (message != null) { postInfo.postMessage = message; } } public SocialPostInfo getPostInfo() { return postInfo; } public void toggleMainButtonType(int type) { if (type == R.attr.share_button_type_signin) { // switch from post => signin mainButton.setText(R.string.SignInInformation); mainButton.setTag(R.attr.share_button_type_signin); } else if (type == R.attr.share_button_type_post) { // switch from signin => post mainButton.setText(R.string.StatusUpdate); mainButton.setTag(R.attr.share_button_type_post); } } /** * Switch the main button to the given enabled state. If the main button is * the post button, we also check that the account is online. * * @param enabled */ public void enableDisableMainButton(boolean enabled) { boolean currentState = mainButton.isEnabled(); if (currentState != enabled) { int color; if (enabled) { color = getResources().getColor(android.R.color.white); } else { color = getResources().getColor(android.R.color.darker_gray); } mainButton.setTextColor(color); int buttonType = ((Integer) mainButton.getTag()).intValue(); if (buttonType == R.attr.share_button_type_post) { mainButton.setEnabled(enabled && online); } else if (buttonType == R.attr.share_button_type_signin) { mainButton.setEnabled(enabled); } } } /* * CLICK LISTENERS */ public void onMainButtonClicked(View view) { int buttonType = ((Integer) mainButton.getTag()).intValue(); // Tap on the Post button if (buttonType == R.attr.share_button_type_post) { if (postInfo.ownerAccount == null || !online) { Toast.makeText(this, R.string.ShareCannotPostBecauseOffline, Toast.LENGTH_LONG).show(); return; } String postMessage = postInfo.postMessage; if (postMessage == null || "".equals(postMessage)) return; Log.d(LOG_TAG, "Start share service..."); Intent share = new Intent(getBaseContext(), ShareService.class); share.putExtra(ShareService.POST_INFO, postInfo); startService(share); Toast.makeText(getBaseContext(), R.string.ShareOperationStarted, Toast.LENGTH_LONG).show(); // Post is in progress, our work is done here finish(); } else if (buttonType == R.attr.share_button_type_signin) { // Tap on the Sign In button postInfo.ownerAccount.password = SignInFragment.getFragment().getPassword(); openFragment(ComposeFragment.getFragment(), ComposeFragment.COMPOSE_FRAGMENT, Anim.FROM_LEFT); loginWithSelectedAccount(); } } private void hideSoftKeyboard() { InputMethodManager mgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); mgr.hideSoftInputFromWindow(ComposeFragment.getFragment().getEditText().getWindowToken(), 0); } public void onSelectAccount() { // Called when the select account field is tapped hideSoftKeyboard(); openFragment(AccountsFragment.getFragment(), AccountsFragment.ACCOUNTS_FRAGMENT, Anim.FROM_RIGHT); } public void onAccountSelected(ExoAccount account) { // Called when an account with password was selected. // If the selected account has no password, we open the SignInFragment first if (!account.equals(postInfo.ownerAccount)) { online = false; postInfo.ownerAccount = account; loginWithSelectedAccount(); } } public void onSelectSpace() { // Called when the select space field is tapped if (online) { Intent spaceSelector = new Intent(this, SpaceSelectorActivity.class); startActivityForResult(spaceSelector, SELECT_SHARE_DESTINATION); } else { Toast.makeText(this, R.string.ShareCannotSelectSpaceBecauseOffline, Toast.LENGTH_LONG).show(); } } /* * TASKS */ public void loginWithSelectedAccount() { new LoginTask().execute(postInfo.ownerAccount); } private void prepareAttachmentsAsync() { new PrepareAttachmentsTask().execute(); } /** * Performs these operations asynchronously: * <ol> * <li>Check each file URI in mAttachmentsUris</li> * <li>If the file is less than 10MB, add it to postInfo</li> * <li>Stop after 10 files</li> * <li>Generate a bitmap for the thumbnail</li> * <li>Wait until the Compose fragment is ready and display the thumbnail</li> * </ol> * * @author paristote */ private class PrepareAttachmentsTask extends AsyncTask<Void, Void, Void> { private Bitmap thumbnail = null; private String errorMessage; private Bitmap getThumbnail(File origin) { BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inSampleSize = 4; opts.inPreferredConfig = Bitmap.Config.RGB_565; Bitmap thumbnail = BitmapFactory.decodeFile(origin.getAbsolutePath(), opts); return thumbnail; } private File getFileWithRotatedBitmap(DocumentInfo info, String filename) throws IOException { FileOutputStream fos = null; try { // Decode bitmap from input stream Bitmap bm = BitmapFactory.decodeStream(info.documentData); // Turn the image in the correct orientation bm = ExoDocumentUtils.rotateBitmapByAngle(bm, info.orientationAngle); File file = new File(getFilesDir(), filename); fos = new FileOutputStream(file); bm.compress(CompressFormat.JPEG, 100, fos); fos.flush(); return file; } catch (OutOfMemoryError e) { throw new RuntimeException("Exception while decoding/rotating the bitmap", e); } finally { try { // try..catch here to not break the process if close() fails if (fos != null) fos.close(); } catch (IOException e) { } } } private File getFileWithData(DocumentInfo info, String filename) throws IOException { FileOutputStream fileOutput = null; BufferedInputStream buffInput = null; try { // create temp file fileOutput = openFileOutput(filename, MODE_PRIVATE); buffInput = new BufferedInputStream(info.documentData); byte[] buf = new byte[1024]; int len; while ((len = buffInput.read(buf)) != -1) { fileOutput.write(buf, 0, len); } File file = new File(getFilesDir(), filename); return file; } finally { try { // try..catch here to not break the process if close() fails if (buffInput != null) buffInput.close(); if (fileOutput != null) fileOutput.close(); } catch (IOException e) { } } } @Override protected Void doInBackground(Void... params) { Set<Integer> errors = new HashSet<Integer>(); if (mAttachmentUris != null && !mAttachmentUris.isEmpty()) { postInfo.postAttachedFiles = new ArrayList<String>(ExoConstants.SHARE_EXTENSION_MAX_ITEMS); for (Uri att : mAttachmentUris) { // Stop when we reach the maximum number of files if (postInfo.postAttachedFiles.size() == ExoConstants.SHARE_EXTENSION_MAX_ITEMS) { errors.add(R.string.ShareErrorTooManyFiles); break; } DocumentInfo info = ExoDocumentUtils.documentInfoFromUri(att, getApplicationContext()); // Skip if the file cannot be read if (info == null) { errors.add(R.string.ShareErrorCannotReadDoc); continue; } // Skip if the file is more than 10MB if (info.documentSizeKb > (ExoConstants.SHARE_EXTENSION_MAX_SIZE_MB * 1024)) { errors.add(R.string.ShareErrorFileTooBig); continue; } // All good, let's copy this file in our app's storage // We must do this because some sharing apps (e.g. Google Photos) // will revoke the permission on the files when the activity stops, // therefore the service won't be able to access them String cleanName = ExoDocumentUtils.cleanupFilename(info.documentName); String tempFileName = DateFormat.format("yyyy-MM-dd-HH:mm:ss", System.currentTimeMillis()) + "-" + cleanName; try { // Create temp file File tempFile = null; if ("image/jpeg".equals(info.documentMimeType) && info.orientationAngle != ExoDocumentUtils.ROTATION_0) { // For an image with an EXIF rotation information, we get the file // from the bitmap rotated back to its correct orientation tempFile = getFileWithRotatedBitmap(info, tempFileName); } else { // Otherwise we just write the data to a file tempFile = getFileWithData(info, tempFileName); } // add file to list postInfo.postAttachedFiles.add(tempFile.getAbsolutePath()); if (thumbnail == null) { thumbnail = getThumbnail(tempFile); } } catch (Exception e) { errors.add(R.string.ShareErrorCannotReadDoc); } } // Done creating the files // Create an error message (if any) to display in onPostExecute if (!errors.isEmpty()) { StringBuilder message; if (postInfo.postAttachedFiles.size() == 0) message = new StringBuilder(getString(R.string.ShareErrorAllFilesCannotShare)).append(":"); else message = new StringBuilder(getString(R.string.ShareErrorSomeFilesCannotShare)).append(":"); for (Integer errCode : errors) { switch (errCode) { case R.string.ShareErrorCannotReadDoc: case R.string.ShareErrorFileTooBig: case R.string.ShareErrorTooManyFiles: message.append("\n").append(getString(errCode)); break; } } errorMessage = message.toString(); } while (ComposeFragment.getFragment() == null) { // Wait until the compose fragment is ready } } return null; } @Override protected void onPostExecute(Void result) { ComposeFragment.getFragment().setThumbnailImage(thumbnail); if (errorMessage != null) Toast.makeText(ShareActivity.this, errorMessage, Toast.LENGTH_LONG).show(); }; } /** * Perform these operations in background: * <ol> * <li>Logout any currently logged in account</li> * <li>Login with the given account</li> * <li>If login is successful, setup the social client and services</li> * </ol> * * @author paristote */ private class LoginTask extends AsyncTask<ExoAccount, Void, Integer> { @Override protected void onPreExecute() { mainButton.setVisibility(View.INVISIBLE); loadingIndicator.setVisibility(View.VISIBLE); super.onPreExecute(); } @SuppressWarnings("unchecked") @Override protected Integer doInBackground(ExoAccount... accounts) { ExoConnectionUtils.loggingOut(); String username = accounts[0].username; String password = accounts[0].password; String url = accounts[0].serverUrl + "/rest/private/platform/info"; try { Log.d(LOG_TAG, String.format("Started login request to %s ...", url)); HttpResponse resp = ExoConnectionUtils.getPlatformResponse(username, password, url); ExoConnectionUtils.checkPLFVersion(resp, accounts[0].serverUrl, username); int result = ExoConnectionUtils.checkPlatformRespose(resp); if (ExoConnectionUtils.LOGIN_SUCCESS == result) { URL u = new URL(accounts[0].serverUrl); SocialClientContext.setProtocol(u.getProtocol()); SocialClientContext.setHost(u.getHost()); SocialClientContext.setPort(u.getPort()); SocialClientContext.setPortalContainerName(ExoConstants.ACTIVITY_PORTAL_CONTAINER); SocialClientContext.setRestContextName(ExoConstants.ACTIVITY_REST_CONTEXT); SocialClientContext.setUsername(username); SocialClientContext.setPassword(password); ClientServiceFactory clientServiceFactory = ClientServiceFactoryHelper .getClientServiceFactory(); VersionService versionService = clientServiceFactory.createVersionService(); SocialClientContext.setRestVersion(versionService.getLatest()); SocialServiceHelper.getInstance().activityService = clientServiceFactory .createActivityService(); SocialServiceHelper.getInstance().spaceService = clientServiceFactory.createSpaceService(); SocialServiceHelper.getInstance().identityService = clientServiceFactory .createIdentityService(); } return Integer.valueOf(result); } catch (Exception e) { Log.e(LOG_TAG, "Login task failed", e); } return Integer.valueOf(ExoConnectionUtils.LOGIN_FAILED); } @Override protected void onPostExecute(Integer result) { Log.d(LOG_TAG, String.format("Received login response %s", result)); if (ExoConnectionUtils.LOGIN_SUCCESS == result.intValue()) { online = true; } else { Toast.makeText(getApplicationContext(), R.string.ShareErrorSignInFailed, Toast.LENGTH_LONG).show(); postInfo.ownerAccount.password = ""; online = false; } loadingIndicator.setVisibility(View.INVISIBLE); mainButton.setVisibility(View.VISIBLE); enableDisableMainButton(online && !"".equals(ComposeFragment.getFragment().getPostMessage())); } } // TODO support for text content that contains an URL to download a file // e.g. share from dropbox @SuppressWarnings("unused") private class DownloadTask extends AsyncTask<String, Void, String> { private String extractUriFromText(String text) { int posHttp = text.indexOf("http://"); int posHttps = text.indexOf("https://"); int startOfLink = -1; if (posHttps > -1) startOfLink = posHttps; else if (posHttp > -1) startOfLink = posHttp; if (startOfLink > -1) { int endOfLink = text.indexOf(' ', startOfLink); if (endOfLink == -1) endOfLink = text.length() - startOfLink; return text.substring(startOfLink, endOfLink); } else { return null; } } private String getUriWithoutQueryString(URI uri) { StringBuffer buf = new StringBuffer(uri.getScheme()).append("://").append(uri.getHost()) .append(uri.getPort() != -1 ? ":" + uri.getPort() : "") .append(uri.getRawPath() != null ? uri.getRawPath() : ""); return buf.toString(); } private String getFileName(String decodedPath) { if (decodedPath == null) return DEFAULT_CONTENT_NAME; if (decodedPath.endsWith("/")) decodedPath = decodedPath.substring(0, decodedPath.length() - 1); int beginNamePos = decodedPath.lastIndexOf('/'); if (beginNamePos < 0) return DEFAULT_CONTENT_NAME; String name = decodedPath.substring(beginNamePos + 1); if (name == null || "".equals(name)) return DEFAULT_CONTENT_NAME; else return name; } @Override protected String doInBackground(String... params) { String str = params[0]; if (str != null) { try { URI uri = URI.create(extractUriFromText(str)); String strUri = getUriWithoutQueryString(uri); Log.d(LOG_TAG, "Started download of " + strUri); BufferedInputStream in = new BufferedInputStream(new URL(strUri).openStream()); String fileName = getFileName(uri.getPath()); FileOutputStream out = openFileOutput(fileName, 0); byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) != -1) { out.write(buf, 0, len); } out.close(); in.close(); // comment because DownloadTask is unused now, no need to update // code to match multi share case // postInfo.postAttachmentUri = new URI("file://" + // getFileStreamPath(fileName).getAbsolutePath()).toString(); // isLocalFile = true; // Log.d(LOG_TAG, "Download successful: "); } catch (Exception e) { Log.e(LOG_TAG, "Error ", e); } } return null; } } }