Java tutorial
/* * ================================================================================================= * Copyright (C) 2014 Martin Albedinsky [Wolf-ITechnologies] * ================================================================================================= * Licensed under the Apache License, Version 2.0 or later (further "License" only). * ------------------------------------------------------------------------------------------------- * You may use this file only in compliance with the License. More details and copy of this License * you may obtain at * * http://www.apache.org/licenses/LICENSE-2.0 * * You can redistribute, modify or publish any part of the code written within this file but as it * is described in the License, the software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES or CONDITIONS OF ANY KIND. * * See the License for the specific language governing permissions and limitations under the License. * ================================================================================================= */ package com.wit.android.support.content.intent; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Environment; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StringDef; import android.support.v4.app.Fragment; import android.text.TextUtils; import android.util.Log; import com.wit.android.support.content.MimeType; import java.io.File; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; /** * <h3>Class Overview</h3> * Intent builder specialized to create CONTENT specific intents. This can be intent to obtain or * preview specific type of content, like <b>image, audio, video, ...</b>. To decide which type of * intent (obtain/preview) should be started, the current set of {@link ContentIntent.ContentProviderItem} * is checked, if it isn't empty the <b>OBTAIN</b> intent will be started, so <b>chooser</b> dialog * will be showed with a list item specific for each of current providers. If there are no providers * assigned to this intent builder and there was passed valid {@link android.net.Uri} to {@link #input(android.net.Uri)}, * the <b>PREVIEW</b> intent will be started. * * @author Martin Albedinsky */ public abstract class ContentIntent extends BaseIntent<ContentIntent> { /** * Interface =================================================================================== */ /** * <h3>Annotation Overview</h3> * Defines an annotation for determining set of allowed mime types for {@link #dataType(String)} * method. */ @Retention(RetentionPolicy.SOURCE) @StringDef({ MimeType.TEXT, MimeType.TEXT_PLAIN, MimeType.TEXT_HTML, MimeType.IMAGE, MimeType.IMAGE_JPEG, MimeType.IMAGE_PNG, MimeType.IMAGE_BITMAP, MimeType.AUDIO, MimeType.AUDIO_MP3, MimeType.AUDIO_MP4, MimeType.AUDIO_MPEG, MimeType.VIDEO, MimeType.VIDEO_MP4, MimeType.VIDEO_MPEG, MimeType.VIDEO_JPEG, MimeType.VIDEO_3GP, MimeType.VIDEO_3GP2 }) public @interface DataType { } /** * Constants =================================================================================== */ /** * Log TAG. */ private static final String TAG = "ContentIntent"; /** * Flag indicating whether the output trough log-cat is enabled or not. */ // private static final boolean LOG_ENABLED = ContentConfig.LIBRARY_LOG_ENABLED; /** * Flag indicating whether the debug output trough log-cat is enabled or not. */ // private static final boolean DEBUG_ENABLED = true; /** * Name format for files created by this type of intent. * <p> * Constant Value: <b>yyyyMMdd_HHmmss</b> */ public static final String CONTENT_FILE_TIME_STAMP_FORMAT = "yyyyMMdd_HHmmss"; /** * Static members ============================================================================== */ /** * Members ===================================================================================== */ /** * Uri to content handled by this intent. */ Uri mUri; /** * Data type of content handled by this intent. */ String mDataType = MimeType.TEXT; /** * Set of simple content providers, where each of providers should provide an instance of intent * to start when that specific provider's item in the chooser dialog was clicked. */ private List<ContentProviderItem> mProviders; /** * Constructors ================================================================================ */ /** * Creates a new instance of GetContentIntent for the given <var>activity</var> context. * See {@link com.wit.android.support.content.intent.BaseIntent#BaseIntent(android.app.Activity)} * for additional info. */ public ContentIntent(@NonNull Activity activity) { super(activity); } /** * Creates a new instance of GetContentIntent for the given <var>fragment</var> context. * See {@link com.wit.android.support.content.intent.BaseIntent#BaseIntent(android.support.v4.app.Fragment)} * for additional info. */ public ContentIntent(@NonNull Fragment fragment) { super(fragment); } /** * Methods ===================================================================================== */ /** * Public -------------------------------------------------------------------------------------- */ /** */ @Override public boolean start() { final Context context = mContextWrapper.getContext(); if (context == null) { return false; } if (hasProviders()) { final int n = mProviders.size(); // Build and show dialog to allow pick one from content providers. final CharSequence[] providerNames = new CharSequence[n]; for (int i = 0; i < n; i++) { providerNames[i] = mProviders.get(i).name; } final AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(mDialogTitle); builder.setItems(providerNames, new DialogInterface.OnClickListener() { /** */ @Override public void onClick(DialogInterface dialog, int which) { final ContentProviderItem provider = mProviders.get(which); mContextWrapper.startIntentForResult(provider.intent, provider.requestCode); } }); builder.show(); return true; } else if (mUri != null) { if (mContextWrapper.getContext() == null) { throw new NullPointerException("Context is already invalid."); } final Intent intent = buildIntent(); return intent != null && onStart(intent); } Log.e(TAG, "Intent does not started. No content providers to build chooser dialog to obtain content or no Uri to preview content."); return false; } /** * Returns {@code null} if there was assigned at least one {@link com.wit.android.support.content.intent.ContentIntent.ContentProviderItem}, * otherwise returns valid instance of Intent to preview specific content. */ @Nullable @Override public Intent buildIntent() { if (!hasProviders()) { if (mUri == null) { Log.e(TAG, "Can not to create PREVIEW CONTENT intent. Missing content Uri."); return null; } if (TextUtils.isEmpty(mDataType)) { Log.e(TAG, "Can not to create PREVIEW CONTENT intent. Missing content MIME type."); return null; } final Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(mUri, mDataType); return intent; } return null; } /** * Called to populate this intent builder with default provider ({@link ContentProviderItem}) items. * <p> * Type and count of default providers may differ depending on the specific ContentIntent implementation. * * @return This intent builder instance. */ public abstract ContentIntent withDefaultProviders(); /** * Assigns the given <var>provider</var> to this content intent builder, so an item for such a * provider will be created within the <b>chooser dialog</b> list with its name ({@link ContentIntent.ContentProviderItem#getName()}) * displayed by this intent builder. * <p> * All providers can be accessed by {@link #getProviders()}. * * @param provider New provider to assign. * @return This intent builder instance. * @see #clearProviders() */ public ContentIntent withProvider(@NonNull ContentProviderItem provider) { this.initProvidersList(); if (!mProviders.contains(provider)) { mProviders.add(provider); } return this; } /** * Same as {@link #withProvider(ContentIntent.ContentProviderItem)} for the given set of <var>providers</var>. * <p> * <b>Note</b>, that this will not check for already assigned "same" providers, just appends the * given array as collection to current providers. * * @param providers The set of providers to assign. * @return This intent builder to allow methods chaining. */ public ContentIntent withProviders(@NonNull ContentProviderItem... providers) { if (providers.length > 0) { this.initProvidersList(); mProviders.addAll(Arrays.asList(providers)); } return this; } /** * Returns set of content providers for this intent builder. New providers can be added by * {@link #withProvider(ContentIntent.ContentProviderItem)}. * * @return Current set of providers. * @see #hasProviders() * @see #clearProviders() */ @Nullable public List<ContentProviderItem> getProviders() { return mProviders; } /** * Clears the current set of providers. New providers can be assigned by * {@link #withProvider(ContentIntent.ContentProviderItem)} or * {@link #withProviders(ContentIntent.ContentProviderItem...)}. */ public void clearProviders() { if (mProviders != null) { mProviders.clear(); } } /** * Getters + Setters --------------------------------------------------------------------------- */ /** * Same as {@link #input(android.net.Uri)} with <var>uri</var> created from the given <var>file</var>. * * @param file A file used to crate Uri. */ public ContentIntent input(@Nullable File file) { if (file != null) { input(Uri.fromFile(file)); } else { input((Uri) null); } return this; } /** * Sets an uri of the content which should be previewed by the activity which can handle content * of the current data type. * <p> * <b>Note</b>, that the current <b>data type</b> will be set to {@code null}, so {@link #dataType(String)} * should be called immediately after new uri was set. The specific implementations of this * ContentIntent builder can here set default data type. * * @param uri The desired uri, which should be delivered to handling activity. * @return This intent builder to allow methods chaining. */ public ContentIntent input(@Nullable Uri uri) { this.mUri = uri; this.mDataType = null; return this; } /** * Same as {@link #output(android.net.Uri)} with <var>uri</var> created from the given <var>file</var>. * * @param file A file used to crate Uri. */ public ContentIntent output(@Nullable File file) { if (file != null) { output(Uri.fromFile(file)); } else { output((Uri) null); } return this; } /** * Sets the current uri. Specific implementations of this ContentIntent builder can use this to * set here passed <var>uri</var> to the specific {@link ContentProviderItem} item which holds an * Intent with some of <b>ACTION_..._CAPTURE</b> based actions. * * @param uri The desired uri. * @return This intent builder to allow methods chaining. */ public ContentIntent output(@Nullable Uri uri) { this.mUri = uri; this.mDataType = null; return this; } /** * Returns the uri passed to {@link #input(android.net.Uri)} or {@link #output(android.net.Uri)}. * * @return Current uri or {@code null} if there was no uri set yet. */ @Nullable public Uri getUri() { return mUri; } /** * Sets the data (MIME) type for the current uri. * * @param type The desired MIME type for the current uri. * @return This intent builder to allow methods chaining. */ public ContentIntent dataType(@NonNull @DataType String type) { this.mDataType = type; return this; } /** * Returns the current data (MIME) type. * * @return Current MIME type for the current uri or {@link MimeType#TEXT} by default. */ @NonNull @DataType public String getDataType() { return mDataType; } /** * Protected ----------------------------------------------------------------------------------- */ /** * Creates time stamp in the {@link #CONTENT_FILE_TIME_STAMP_FORMAT} format for the current {@link java.util.Date}. * * @return Current time stamp string representation. */ @NonNull protected static String createContentFileTimeStamp() { return new SimpleDateFormat(CONTENT_FILE_TIME_STAMP_FORMAT).format(new Date()); } /** * Same as {@link #createContentFile(String, String, java.io.File)} with <b>.jpg</b> as <var>imageType</var> * and {@link android.os.Environment#getExternalStoragePublicDirectory(String)} with {@link android.os.Environment#DIRECTORY_PICTURES} * as <var>directory</var>. * * @param fileName Desired name for the requested file. * @param environmentDirectoryType One of {@link Environment#DIRECTORY_PICTURES}, {@link Environment#DIRECTORY_MOVIES}, .... */ @Nullable protected static File createContentFile(@NonNull String fileName, @Nullable String fileType, @NonNull String environmentDirectoryType) { return createContentFile(fileName, fileType, Environment.getExternalStoragePublicDirectory(environmentDirectoryType)); } /** * Creates a new file with the given parameters within the specified <var>directory</var>. * * @param fileName Desired name for the requested file. * @param fileType Desired suffix for the requested file. * @param directory Directory within which should be the requested file created. * @return New instance of temporary file or {@code null} if some IO error occurs.. * @see #createContentFile(String, String, String) */ @Nullable protected static File createContentFile(@NonNull String fileName, @Nullable String fileType, @Nullable File directory) { try { return File.createTempFile(fileName, fileType, directory); } catch (IOException e) { e.printStackTrace(); } return null; } /** */ @Override protected boolean onStart(@NonNull Intent intent) { startActivity(Intent.createChooser(intent, mDialogTitle)); return true; } /** * Private ------------------------------------------------------------------------------------- */ /** * Checks whether there are currently some provider items. * * @return {@code True} if this builder has some provider items, {@code false} otherwise. */ private boolean hasProviders() { return mProviders != null && !mProviders.isEmpty(); } /** * Initializes the current list of provider items if is not initialized yet. */ private void initProvidersList() { if (mProviders == null) { this.mProviders = new ArrayList<>(); } } /** * Inner classes =============================================================================== */ /** * <h3>Class Overview</h3> * Required implementation for simple "content provider", which can provide an instance of * {@link android.content.Intent} to obtain specific type of content. Each provider of {@link ContentIntent} * will have its item within <b>chooser dialog</b> list. * * @author Martin Albedinsky */ public static class ContentProviderItem { /** * Members ================================================================================= */ /** * Name of this "content provider" to be displayed within chooser dialog list. */ CharSequence name; /** * Intent to be started when an item addressed to this provider was clicked. */ Intent intent; /** * Request code used for {@link com.wit.android.support.content.SimpleContextWrapper#startIntentForResult(android.content.Intent, int) SimpleContextWrapper#startIntentForResult(android.content.Intent, int)}. */ int requestCode; /** * Methods ================================================================================= */ /** * Sets the name of this "content provider" to be displayed within <b>chooser dialog</b> list. * * @param name The desired name * @return This provider instance. */ public ContentProviderItem name(@NonNull CharSequence name) { this.name = name; return this; } /** * Returns name of this provider. * * @return This provider's name. * @see #name(java.lang.CharSequence) */ @NonNull public CharSequence getName() { return name; } /** * Sets the intent to be started when an item addressed to this provider within <b>chooser dialog</b> * was clicked. * <p> * <b>Note</b>, that the given <var>intent</var> will be started by * {@link com.wit.android.support.content.SimpleContextWrapper#startIntentForResult(android.content.Intent, int) SimpleContextWrapper#startIntentForResult(android.content.Intent, int)} * with the request code passed to {@link #requestCode(int)}. * * @param intent Valid instance of intent. * @return This provider instance. */ public ContentProviderItem intent(@NonNull Intent intent) { this.intent = intent; return this; } /** * Returns intent attached to this provider. * * @return This provider's intent. * @see #intent(android.content.Intent) */ @NonNull public Intent getIntent() { return intent; } /** * Sets the request code used for * {@link com.wit.android.support.content.SimpleContextWrapper#startIntentForResult(android.content.Intent, int) SimpleContextWrapper#startIntentForResult(android.content.Intent, int)} * when starting this provider's intent. * * @param code The desired request code. This code should be used to identify result from * started intent in {@link Activity#onActivityResult(int, int, android.content.Intent)} * or {@link android.support.v4.app.Fragment#onActivityResult(int, int, android.content.Intent)}, * depends on for which context was {@link ImageIntent} builder created. * @return This provider instance. */ public ContentProviderItem requestCode(int code) { this.requestCode = code; return this; } /** * Returns request code of this provider. * * @return This provider's request code. * @see #requestCode(int) */ public int getRequestCode() { return requestCode; } } }