Repository: tvbarthel/IntentShare Branch: master Commit: 340b9ba55515 Files: 82 Total size: 240.4 KB Directory structure: gitextract_wcsb18mc/ ├── .gitignore ├── README.md ├── build.gradle ├── config/ │ ├── quality/ │ │ ├── checkstyle/ │ │ │ ├── checkstyle.xml │ │ │ └── suppressions.xml │ │ ├── findbugs/ │ │ │ └── findbugs-filter.xml │ │ ├── lint/ │ │ │ └── lint.xml │ │ └── pmd/ │ │ └── pmd-ruleset.xml │ └── quality.gradle ├── glide-loader/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── fr/ │ └── tvbarthel/ │ └── intentshare/ │ └── loader/ │ └── glide/ │ └── GlideIconLoader.java ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── fr/ │ │ └── tvbarthel/ │ │ └── intentshare/ │ │ └── ApplicationTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── fr/ │ │ │ └── tvbarthel/ │ │ │ └── intentshare/ │ │ │ ├── AsyncIconLoader.java │ │ │ ├── BottomRecyclerView.java │ │ │ ├── IconLoader.java │ │ │ ├── IntentShare.java │ │ │ ├── IntentShareListener.java │ │ │ ├── LayoutManagerFactory.java │ │ │ ├── StyledAttributesUtils.java │ │ │ ├── TargetActivity.java │ │ │ ├── TargetActivityAdapter.java │ │ │ ├── TargetActivityComparatorProvider.java │ │ │ ├── TargetActivityHeaderView.java │ │ │ ├── TargetActivityManager.java │ │ │ ├── TargetActivityView.java │ │ │ └── TargetChooserActivity.java │ │ └── res/ │ │ ├── layout/ │ │ │ ├── isl_activity_target_chooser.xml │ │ │ └── isl_target_activity_view.xml │ │ ├── layout-v23/ │ │ │ └── isl_target_activity_view.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── public.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── values-v19/ │ │ │ └── styles.xml │ │ └── values-v23/ │ │ └── dimens.xml │ └── test/ │ └── java/ │ └── fr/ │ └── tvbarthel/ │ └── intentshare/ │ └── IntentShareTest.java ├── picasso-loader/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── fr/ │ └── tvbarthel/ │ └── intentshare/ │ └── loader/ │ └── picasso/ │ └── PicassoIconLoader.java ├── sample/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── fr/ │ │ └── tvbarthel/ │ │ └── intentsharesample/ │ │ └── ApplicationTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── fr/ │ │ │ └── tvbarthel/ │ │ │ └── intentsharesample/ │ │ │ ├── Adapter.java │ │ │ ├── ExtraProviderDialogFragment.java │ │ │ ├── ExtraProviderWrapper.java │ │ │ ├── ExtraProviderWrapperView.java │ │ │ ├── FooterView.java │ │ │ ├── HeaderView.java │ │ │ ├── MainActivity.java │ │ │ ├── SharingFileProvider.java │ │ │ └── SocialTargetActivityComparatorProvider.java │ │ └── res/ │ │ ├── anim/ │ │ │ └── wiggle.xml │ │ ├── layout/ │ │ │ ├── activity_main.xml │ │ │ ├── extra_provider_view.xml │ │ │ ├── footer_view.xml │ │ │ ├── fragment_dialog_extra_provider.xml │ │ │ └── header_view.xml │ │ ├── menu/ │ │ │ └── activity_sample_menu.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── values-w820dp/ │ │ │ └── dimens.xml │ │ └── xml/ │ │ └── file_provider_paths.xml │ └── test/ │ └── java/ │ └── fr/ │ └── tvbarthel/ │ └── intentsharesample/ │ └── ExampleUnitTest.java └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Created by https://www.gitignore.io ### Android ### # Built application files *.apk *.ap_ # Files for the Dalvik VM *.dex # Java class files *.class # Generated files bin/ gen/ # Gradle files .gradle/ build/ /*/build/ gradle.properties # Local configuration file (sdk path, etc) local.properties # Proguard folder generated by Eclipse proguard/ # Log Files *.log ### Intellij ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm *.iml ## Directory-based project format: .idea/ # if you remove the above rule, at least ignore the following: # User-specific stuff: # .idea/workspace.xml # .idea/tasks.xml # .idea/dictionaries # Sensitive or high-churn files: # .idea/dataSources.ids # .idea/dataSources.xml # .idea/sqlDataSources.xml # .idea/dynamic.xml # .idea/uiDesigner.xml # Gradle: # .idea/gradle.xml # .idea/libraries # Mongo Explorer plugin: # .idea/mongoSettings.xml ## File-based project format: *.ipr *.iws ## Plugin-specific files: # IntelliJ out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties ### Java ### *.class # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.war *.ear # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* .DS_Store # fabric fabric.properties ================================================ FILE: README.md ================================================ IntentShare ================== [![Maven Central](http://img.shields.io/maven-central/v/fr.tvbarthel.intentshare/library.svg)](http://search.maven.org/#search%7Cga%7C1%7Cintentshare) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-IntentShare-brightgreen.svg?style=flat)](http://android-arsenal.com/details/1/3551) This project is a light open-source library that improves the sharing experience on Android. * [Motivations](#motivations) * [Gradle dependency](#gradle-dependency) * [Sample app](#sample-app) * [Usage](#usage) * [Simple usage](#simple-usage) * [Dialog label](#dialog-label) * [Image](#image) * [Extra Provider](#extra-provider) * [Build-in extra provider](#build-in-extra-provider) * [Custom extra provider](#custom-extra-provider) * [Listener](#listener) * [Icon Loader](#icon-loader) * [Picasso](#picasso) * [Glide](#glide) * [Custom icon loader](#custom-icon-loader) * [Comparator Provider](#comparator-provider) * [Release Note](#release-note) * [What's next](#whats-next) * [Contributing](#contributing) * [License](#license) * [Special Thanks](#special-thanks-to-) Motivations ======= Nowadays, sharing content is part of our daily life. Unfortunately, the Android framework tools do not provide a sharing experience which reaches all of our expectations. We decided to implement our own tool, IntentShare, that might improve the user experience of a sharing action by : * providing different extras according to the chosen target activity in order to take advantages of each target's specificities. * providing an easy way to track the selected target activities in order to improve/adapt the extras for a specific target. * providing a sorted target activity list based on the context of your app in order to take advantages of how people use it. Find more about our motivations [here](http://tvbarthel.fr/IntentShare/). # Sample app [Sample app available on the PlayStore](https://play.google.com/store/apps/details?id=fr.tvbarthel.intentsharesample) # Gradle dependency available on jcenter. ```groovy compile 'fr.tvbarthel.intentshare:library:0.0.4' ``` dependencies ```groovy compile 'com.android.support:appcompat-v7:25.0.0' compile 'com.android.support:recyclerview-v7:25.0.0' ``` # Usage ## Simple usage ```java IntentShare.with(context) .text("Default text you would like to share.") .deliver(); ``` ## Dialog label ```java IntentShare.with(context) .chooserTitle("Select a sharing target : ") .text("Default text you would like to share.") .deliver(); ``` ## Image ```java IntentShare.with(context) .chooserTitle("Select a sharing target : ") .text("Default text you would like to share.") .image(Uri.parse("content://com.example.test.fileprovider/data/img.png")) .deliver(); ``` # Extra Provider An extra provider can specify shared content for a given target activity. ## Build-in extra provider ### Facebook ```java IntentShare.with(context) .chooserTitle("Select a sharing target : ") .text("Default text you would like to share.") .image(Uri.parse("content://com.example.test.fileprovider/data/img.png")) .facebookBody(Uri.parse("http://tvbarthel.fr")) .deliver(); ``` ### Twitter ```java IntentShare.with(context) .chooserTitle("Select a sharing target : ") .text("Default text you would like to share.") .image(Uri.parse("content://com.example.test.fileprovider/data/img.png")) .twitterBody("Tweet can only have 140 char!") .deliver(); ``` ### Mail clients ```java IntentShare.with(context) .chooserTitle("Select a sharing target : ") .text("Default text you would like to share.") .image(Uri.parse("content://com.example.test.fileprovider/data/img.png")) .mailSubject("Mail subject.") .mailBody("Extended text you would like to share in mail body.") .deliver(); ``` ## Custom extra provider ```java IntentShare.with(context) .chooserTitle("Select a sharing target : ") .text("Default text you would like to share.") .image(Uri.parse("content://com.example.test.fileprovider/data/img.png")) .addExtraProvider( new IntentShare.ExtraProvider("com.google.android.gm") .disableText() .overrideSubject("Gmail subject, no mail body.") .disableImage() ) .deliver(); ``` By default, an ExtraProvider will copy the default shared content. Replace default shared content : ```java /** * Provide a specific text for the linked package name. * * @param text text which will be used as {@link android.content.Intent#EXTRA_TEXT} * @return {@link ExtraProvider} for chaining. */ public ExtraProvider overrideText(String text); /** * Provide a specific subject for the linked package name. * * @param subject subject which will be used as {@link android.content.Intent#EXTRA_SUBJECT} * @return {@link ExtraProvider} for chaining. */ public ExtraProvider overrideSubject(String subject); /** * Provide a specific image for the linked package name. * * @param image image which will be used as {@link android.content.Intent#EXTRA_STREAM} * @return {@link ExtraProvider} for chaining. */ public ExtraProvider overrideImage(Uri image); ``` Avoid copying default shared content without overriding: ```java /** * Disable the extra {@link android.content.Intent#EXTRA_TEXT} for the linked package * to avoid a copy from the default {@link IntentShare} text. * * @return {@link ExtraProvider} for chaining. */ public ExtraProvider disableText(); /** * Disable the extra {@link android.content.Intent#EXTRA_SUBJECT} for the linked package * to avoid a copy from the default {@link IntentShare} subject. * * @return {@link ExtraProvider} for chaining. */ public ExtraProvider disableSubject(); /** * Disable the extra {@link android.content.Intent#EXTRA_STREAM} for the linked package * to avoid a copy from the default {@link IntentShare} image. * * @return {@link ExtraProvider} for chaining. */ public ExtraProvider disableImage(); ``` # Listener ```java intentShareListener = new IntentShareListener() { @Override public void onCompleted(String packageName) { Log.d(TAG, "onCompleted : " + packageName); } @Override public void onCanceled() { Log.d(TAG, "onCanceled"); } }; IntentShare.with(context) .chooserTitle("Select a sharing target : ") .text("Default text you would like to share.") .listener(intentShareListener) .deliver(); ``` # Icon loader Default icon loader used to load target activities icons is based on AsyncTask. ## Picasso If your are already using Picasso, you may want to consider using PicassoIconLoader: ```groovy compile 'fr.tvbarthel.intentshare:picasso-loader:0.0.4' ``` ```java IntentShare.with(context) .chooserTitle("Select a sharing target : ") .text("Default text you would like to share.") .iconLoader(new PicassoIconLoader()) .deliver(); ``` ## Glide If your are already using Glide, you may want to consider using GlideIconLoader: ```groovy compile 'fr.tvbarthel.intentshare:glide-loader:0.0.4' ``` ```java IntentShare.with(context) .chooserTitle("Select a sharing target : ") .text("Default text you would like to share.") .iconLoader(new GlideIconLoader()) .deliver(); ``` ## Custom icon loader Implement your own IconLoader: ```java IntentShare.with(context) .chooserTitle("Select a sharing target : ") .text("Default text you would like to share.") .iconLoader(new IconLoader() { @Override public void load(Uri iconUri, ImageView imageView) { } @Override public void cancel(ImageView imageView) { } }) .deliver(); ``` # Comparator provider By default, target activities are sorted based on the recency of their selection from your app. ```java /** * Comparator used to sort {@link TargetActivity} based on the recency of their previous * selection and their default order as fallback when they have never been selected. *

* The ordering imposed by this comparator on a set of {@link TargetActivity} * is not consistent with equals since c.compare(e1, e2)==0 has not the same boolean * value as e1.equals(e2). */ public RecencyComparatorProvider() { } ``` Instead of using the default comparator, you can implement your own comparator provider in order to customize the target activities order display to the user: ```java /** * ˙Interface which allow to define which comparator will be provided for sorting the * target activity inside the {@link TargetChooserActivity}. */ public interface TargetActivityComparatorProvider extends Parcelable { /** * Provide the comparator used to sort {@link TargetActivity} displayed to the user. * * @return comparator used to sort {@link TargetActivity} displayed to the user. */ Comparator provideComparator(); } ``` ```java IntentShare.with(context) .chooserTitle("Select a sharing target : ") .text("Default text you would like to share.") .comparatorProvider(customComparatorProvider) .deliver(); ``` An example from the sample can be found here : [SocialTargetActivityComparatorProvider.java](https://github.com/tvbarthel/IntentShare/blob/develop/sample/src/main/java/fr/tvbarthel/intentsharesample/SocialTargetActivityComparatorProvider.java) # Release Note * 0.0.4: min sdk set to 14. * 0.0.3: Tweet length check no longer performed (see #39). `allowbackup` disable by default. # What's next * Providing easier way to share images. * Removing dependencies on support libraries. * Sample : implementing image selection for extra provider. # Contributing Contributions are very welcome (: You can contribute through GitHub by forking the repository and sending a pull request. When submitting code, please make sure ./gradlew check succeed. # License ``` Copyright (C) 2016 tvbarthel 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. ``` # Special Thanks to ... Vincent Brison [https://github.com/vincentbrison](https://github.com/vincentbrison) , for his precious advice. Stéphane Guérin [https://github.com/guerwan](https://github.com/guerwan) , for his helpful feedback. Romain Zanon [https://github.com/romainz](https://github.com/romainz) , for his early day support. ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.2.2' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.1' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { jcenter() } } task clean(type: Delete) { delete rootProject.buildDir } ext { buildToolsVersion = "25.0.0" compileSdkVersion = 25 targetSdkVersion = 25 minSdkVersion = 14 versionCode = 4 versionName = "0.0.4" } ================================================ FILE: config/quality/checkstyle/checkstyle.xml ================================================ ================================================ FILE: config/quality/checkstyle/suppressions.xml ================================================ ================================================ FILE: config/quality/findbugs/findbugs-filter.xml ================================================ ================================================ FILE: config/quality/lint/lint.xml ================================================ ================================================ FILE: config/quality/pmd/pmd-ruleset.xml ================================================ Custom ruleset for Android application .*/R.java .*/gen/.* .*Test.java ================================================ FILE: config/quality.gradle ================================================ apply plugin: 'checkstyle' apply plugin: 'findbugs' apply plugin: 'pmd' // Add checkstyle, findbugs, pmd and lint to the check task. check.dependsOn 'checkstyle', 'findbugs', 'pmd', 'lint' task checkstyle(type: Checkstyle) { configFile file("${project.rootDir}/config/quality/checkstyle/checkstyle.xml") configProperties.checkstyleSuppressionsPath = file("${project.rootDir}/config/quality/checkstyle/suppressions.xml").absolutePath source 'src' include '**/*.java' exclude '**/gen/**' classpath = files() } task findbugs(type: FindBugs) { ignoreFailures = false effort = "max" reportLevel = "high" excludeFilter = new File("${project.rootDir}/config/quality/findbugs/findbugs-filter.xml") classes = files("${project.rootDir}/library/build/intermediates/classes") source 'src' include '**/*.java' exclude '**/gen/**' reports { xml.enabled = false html.enabled = true xml { destination "$project.buildDir/reports/findbugs/findbugs.xml" } html { destination "$project.buildDir/reports/findbugs/findbugs.html" } } classpath = files() } task pmd(type: Pmd) { ruleSetFiles = files("${project.rootDir}/config/quality/pmd/pmd-ruleset.xml") ignoreFailures = false ruleSets = [] source 'src' include '**/*.java' exclude '**/gen/**' reports { xml.enabled = false html.enabled = true xml { destination "$project.buildDir/reports/pmd/pmd.xml" } html { destination "$project.buildDir/reports/pmd/pmd.html" } } } android { lintOptions { abortOnError false lintConfig file("${project.rootDir}/config/quality/lint/lint.xml") // if true, generate an HTML report (with issue explanations, sourcecode, etc) htmlReport true // optional path to report (default will be lint-results.html in the builddir) htmlOutput file("$project.buildDir/reports/lint/lint.html") } } ================================================ FILE: glide-loader/.gitignore ================================================ /build ================================================ FILE: glide-loader/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'com.jfrog.bintray' apply from: '../config/quality.gradle' group = 'fr.tvbarthel.intentshare' version = rootProject.ext.versionName android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode rootProject.ext.versionCode versionName rootProject.ext.versionName } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile project(':library') compile 'com.github.bumptech.glide:glide:3.7.0' } Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newDataInputStream()) def bintrayUser = properties.getProperty('bintray.user') def bintrayKey = properties.getProperty('bintray.apikey') bintray { user = bintrayUser key = bintrayKey configurations = ['archives'] //When uploading configuration files dryRun = false //Whether to run this as dry-run, without deploying publish = true //If version should be auto published after an upload //Package configuration. The plugin will use the repo and name properties to check if the package already exists. In that case, there's no need to configure the other package properties (like userOrg, desc, etc). pkg { repo = 'maven' name = 'GlideLoader' desc = 'Icon loader based on Glide for IntentShare library.' websiteUrl = 'https://github.com/tvbarthel/IntentShare' issueTrackerUrl = 'https://github.com/tvbarthel/IntentShare/issues' vcsUrl = 'https://github.com/tvbarthel/IntentShare.git' licenses = ['Apache-2.0'] labels = ['android', 'intent', 'sharing', 'share', 'glide'] publicDownloadNumbers = true version { gpg { sign = true //Determines whether to GPG sign the files. The default is false } } } } install { repositories.mavenInstaller { pom { project { packaging 'aar' name 'GlideLoader' url 'https://github.com/tvbarthel/IntentShare' description 'Icon loader based on Glide for IntentShare library.' licenses { license { name 'The Apache Software License, Version 2.0' url 'http://www.apache.org/licenses/LICENSE-2.0.txt' } } developers { developer { id 'tbarthel-fr' name 'Thomas Barthelemy' email 'thomas.barthelemy.utc@gmail.com' } developer { id 'vbarthel-fr' name 'Vincent Barthelemy' email 'vincent.barthelemy.perso@gmail.com' } } scm { connection 'https://github.com/tvbarthel/IntentShare.git' developerConnection 'https://github.com/tvbarthel/IntentShare.git' url 'https://github.com/tvbarthel/IntentShare' } } } } } task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs classifier = 'sources' } task javadoc(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) failOnError = false } task javadocJar(type: Jar, dependsOn: javadoc) { classifier = 'javadoc' from javadoc.destinationDir } artifacts { archives javadocJar archives sourcesJar } ================================================ FILE: glide-loader/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/thomasbarthelemy/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: glide-loader/src/main/AndroidManifest.xml ================================================ ================================================ FILE: glide-loader/src/main/java/fr/tvbarthel/intentshare/loader/glide/GlideIconLoader.java ================================================ package fr.tvbarthel.intentshare.loader.glide; import android.net.Uri; import android.os.Parcel; import android.widget.ImageView; import com.bumptech.glide.Glide; import com.bumptech.glide.load.engine.DiskCacheStrategy; import fr.tvbarthel.intentshare.IconLoader; /** * {@link fr.tvbarthel.intentshare.IconLoader} based on {@link com.bumptech.glide.Glide} */ public class GlideIconLoader implements IconLoader { /** * Parcelable */ public static final Creator CREATOR = new Creator() { @Override public GlideIconLoader createFromParcel(Parcel source) { return new GlideIconLoader(source); } @Override public GlideIconLoader[] newArray(int size) { return new GlideIconLoader[size]; } }; /** * {@link fr.tvbarthel.intentshare.IconLoader} based on {@link com.bumptech.glide.Glide} */ public GlideIconLoader() { } /** * {@link fr.tvbarthel.intentshare.IconLoader} based on {@link com.bumptech.glide.Glide} * * @param in parcel. */ protected GlideIconLoader(Parcel in) { } @Override public void load(Uri iconUri, ImageView imageView) { Glide.with(imageView.getContext()) .load(iconUri) .fitCenter() .diskCacheStrategy(DiskCacheStrategy.RESULT) .into(imageView); } @Override public void cancel(ImageView imageView) { Glide.clear(imageView); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { } } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Nov 11 10:54:16 CET 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip ================================================ FILE: gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # For Cygwin, ensure paths are in UNIX format before anything is touched. if $cygwin ; then [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` fi # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >&- APP_HOME="`pwd -P`" cd "$SAVED" >&- CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software set CMD_LINE_ARGS=%$ :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: library/.gitignore ================================================ /build ================================================ FILE: library/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'com.jfrog.bintray' apply from: '../config/quality.gradle' group = 'fr.tvbarthel.intentshare' version = rootProject.ext.versionName android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion resourcePrefix 'isl_' defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode rootProject.ext.versionCode versionName rootProject.ext.versionName } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.9.5' testCompile 'org.robolectric:robolectric:3.0' compile 'com.android.support:appcompat-v7:25.0.0' compile 'com.android.support:recyclerview-v7:25.0.0' } Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newDataInputStream()) def bintrayUser = properties.getProperty('bintray.user') def bintrayKey = properties.getProperty('bintray.apikey') bintray { user = bintrayUser key = bintrayKey configurations = ['archives'] //When uploading configuration files dryRun = false //Whether to run this as dry-run, without deploying publish = true //If version should be auto published after an upload //Package configuration. The plugin will use the repo and name properties to check if the package already exists. In that case, there's no need to configure the other package properties (like userOrg, desc, etc). pkg { repo = 'maven' name = 'IntentShare' desc = 'IntentShare is a light open-source library that improves the sharing experience of their Android applications.' websiteUrl = 'https://github.com/tvbarthel/IntentShare' issueTrackerUrl = 'https://github.com/tvbarthel/IntentShare/issues' vcsUrl = 'https://github.com/tvbarthel/IntentShare.git' licenses = ['Apache-2.0'] labels = ['android', 'intent', 'sharing', 'share'] publicDownloadNumbers = true version { gpg { sign = true //Determines whether to GPG sign the files. The default is false } } } } install { repositories.mavenInstaller { pom { project { packaging 'aar' name 'IntentShare' url 'https://github.com/tvbarthel/IntentShare' description 'IntentShare is a light open-source library that improves the sharing experience of their Android applications.' licenses { license { name 'The Apache Software License, Version 2.0' url 'http://www.apache.org/licenses/LICENSE-2.0.txt' } } developers { developer { id 'tbarthel-fr' name 'Thomas Barthelemy' email 'thomas.barthelemy.utc@gmail.com' } developer { id 'vbarthel-fr' name 'Vincent Barthelemy' email 'vincent.barthelemy.perso@gmail.com' } } scm { connection 'https://github.com/tvbarthel/IntentShare.git' developerConnection 'https://github.com/tvbarthel/IntentShare.git' url 'https://github.com/tvbarthel/IntentShare' } } } } } task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs classifier = 'sources' } task javadoc(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) failOnError = false } task javadocJar(type: Jar, dependsOn: javadoc) { classifier = 'javadoc' from javadoc.destinationDir } artifacts { archives javadocJar archives sourcesJar } ================================================ FILE: library/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /home/thomas/Documents/android-sdk-linux/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: library/src/androidTest/java/fr/tvbarthel/intentshare/ApplicationTest.java ================================================ package fr.tvbarthel.intentshare; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: library/src/main/AndroidManifest.xml ================================================ ================================================ FILE: library/src/main/java/fr/tvbarthel/intentshare/AsyncIconLoader.java ================================================ package fr.tvbarthel.intentshare; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; import android.os.Parcel; import android.util.Log; import android.util.SparseArray; import android.widget.ImageView; import java.util.HashMap; import java.util.List; /** * Icon loader based on an {@link AsyncTask}. *

* No cashing is performed for decoded {@link Bitmap}. */ class AsyncIconLoader implements IconLoader { /** * Parcelable. */ public static final Creator CREATOR = new Creator() { @Override public AsyncIconLoader createFromParcel(Parcel source) { return new AsyncIconLoader(source); } @Override public AsyncIconLoader[] newArray(int size) { return new AsyncIconLoader[size]; } }; private SparseArray task; private HashMap cachedIcons; /** * Icon loader based on an {@link AsyncTask} *

* No cashing is performed for decoded {@link Bitmap}. * * @param in parcel. */ protected AsyncIconLoader(Parcel in) { this(); } /** * Icon loader based on an {@link AsyncTask} *

* No cashing is performed for decoded {@link Bitmap}. */ public AsyncIconLoader() { task = new SparseArray<>(); cachedIcons = new HashMap<>(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { } @Override public void load(Uri iconUri, ImageView imageView) { Bitmap bitmap = cachedIcons.get(iconUri); if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { AsyncIconLoaderTask asyncIconLoaderTask = new AsyncIconLoaderTask(iconUri, imageView, cachedIcons); task.put(imageView.hashCode(), asyncIconLoaderTask); asyncIconLoaderTask.execute(); } } @Override public void cancel(ImageView imageView) { int key = imageView.hashCode(); AsyncIconLoaderTask asyncIconLoaderTask = task.get(key); if (asyncIconLoaderTask != null) { asyncIconLoaderTask.cancel(true); task.remove(key); } } /** * {@link AsyncTask} used to load an icon off the ui thread. */ private static final class AsyncIconLoaderTask extends AsyncTask { private static final String TAG = AsyncIconLoaderTask.class.getSimpleName(); private final ImageView imageTarget; private final PackageManager packageManager; private final String targetPackage; private final HashMap cachedIcons; private final Uri uri; private int iconResId; private int targetSize; /** * {@link AsyncTask} used to load an icon off the ui thread. * * @param uri uri of the icon to load. * @param imageView image view in which the icon should be loaded. * @param cachedIcons list of bitmap to which the new decoded one will be added. */ public AsyncIconLoaderTask(Uri uri, ImageView imageView, HashMap cachedIcons) { packageManager = imageView.getContext().getPackageManager(); this.uri = uri; targetPackage = uri.getAuthority(); iconResId = 0; List pathSegments = uri.getPathSegments(); if (pathSegments.size() != 1) { Log.e(TAG, "Can't find the icon res id for : " + uri.toString()); } else { try { iconResId = Integer.parseInt(pathSegments.get(0)); } catch (NumberFormatException e) { Log.e(TAG, "Can't parse the icon res id : " + pathSegments.get(0)); } } imageTarget = imageView; targetSize = imageView.getContext().getResources() .getDimensionPixelSize(R.dimen.isl_target_activity_view_icon_size); this.cachedIcons = cachedIcons; } @Override protected Bitmap doInBackground(Void... params) { Resources resources; try { resources = packageManager.getResourcesForApplication(targetPackage); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Wrong package name, can't access to the resources : " + targetPackage); return null; } if (isCancelled()) { return null; } final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; options.inPreferredConfig = Bitmap.Config.RGB_565; BitmapFactory.decodeResource(resources, iconResId, options); if (isCancelled()) { return null; } options.inSampleSize = calculateInSampleSize(options, targetSize, targetSize); options.inJustDecodeBounds = false; if (isCancelled()) { return null; } else { return BitmapFactory.decodeResource(resources, iconResId, options); } } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); if (bitmap != null) { imageTarget.setImageBitmap(bitmap); cachedIcons.put(uri, bitmap); } else { Log.e(TAG, "Failed to load icon from uri : " + uri); } } private int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; if (reqWidth == 0 || reqHeight == 0) { return 1; } else { int heightRatio = (int) Math.floor((float) height / (float) reqHeight); int widthRatio = (int) Math.floor((float) width / (float) reqWidth); return Math.min(heightRatio, widthRatio); } } } } ================================================ FILE: library/src/main/java/fr/tvbarthel/intentshare/BottomRecyclerView.java ================================================ package fr.tvbarthel.intentshare; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; /** * Simple RecyclerView on which top padding could be assigned and enable touch on view under. */ class BottomRecyclerView extends RecyclerView { /** * Simple RecyclerView on which top padding could be assigned and enable touch on view under. * * @param context holding context. */ public BottomRecyclerView(Context context) { super(context); } /** * Simple RecyclerView on which top padding could be assigned and enable touch on view under. * * @param context holding context. * @param attrs attr from xml. */ public BottomRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); } /** * Simple RecyclerView on which top padding could be assigned and enable touch on view under. * * @param context holding context. * @param attrs attr from xml. * @param defStyleAttr style. */ public BottomRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onTouchEvent(MotionEvent ev) { boolean handled = super.onTouchEvent(ev); View child = getChildAt(0); if (child == null || ev.getY() < child.getY()) { handled = false; } return handled; } } ================================================ FILE: library/src/main/java/fr/tvbarthel/intentshare/IconLoader.java ================================================ package fr.tvbarthel.intentshare; import android.net.Uri; import android.os.Parcelable; import android.widget.ImageView; /** * Interface which define the contract of a loader used to load * {@link TargetActivity} icons. */ public interface IconLoader extends Parcelable { /** * Called when the icon should be load into the imageView. *

* See also : {@link IconLoader#cancel(ImageView)} * * @param iconUri uri of the icon to load. * @param imageView image view in which the icon should be loaded. */ void load(Uri iconUri, ImageView imageView); /** * Called when the icon doesn't need to be loaded anymore. * Should cancel any async loading started previously. *

* See also : {@link IconLoader#load(Uri, ImageView)} * * @param imageView image view for which the async loading should be canceled. */ void cancel(ImageView imageView); } ================================================ FILE: library/src/main/java/fr/tvbarthel/intentshare/IntentShare.java ================================================ package fr.tvbarthel.intentshare; import android.content.Context; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; import java.util.ArrayList; import java.util.List; /** * {@link IntentShare} is designed to enhance the sharing experience by allowing to share * different content according to the application the user will choose to share with. */ public final class IntentShare implements Parcelable { /** * Parcelable. */ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public IntentShare createFromParcel(Parcel source) { return new IntentShare(source); } public IntentShare[] newArray(int size) { return new IntentShare[size]; } }; /** * Facebook package. */ public static final String FACEBOOK = "com.facebook.katana"; /** * Twitter package. */ public static final String TWITTER = "com.twitter.android"; /** * Text which will be shared by default. */ String text; /** * Uri of the image add as {@link android.content.Intent#EXTRA_STREAM} */ Uri imageUri; /** * Text used as mail body if the targeted application is a mail one. */ String mailBody; /** * Text used as mail subject if the targeted application is a mail one. */ String mailSubject; /** * Specific extra provider for some package. */ ArrayList extraProviders; /** * Keep a track on package with a specific * {@link fr.tvbarthel.intentshare.IntentShare.ExtraProvider} in order to warn the user when * two provider are added for the same package. */ List packageWithExtraProvider; /** * Icon loader used to load icons. */ IconLoader iconLoader; /** * Provide the comparator used to sort the target activities. */ TargetActivityComparatorProvider comparatorProvider; /** * Title that will be displayed in the chooser. */ String chooserTitle; /** * Context used to start the activity used to choose a target one. */ private Context context; private IntentShareListener listener; /** * {@link IntentShare} is designed to enhance the sharing experience by allowing to share * different content according to the application the user will choose to share with. * * @param context context used send the {@link android.content.Intent} */ private IntentShare(Context context) { this.context = context; extraProviders = new ArrayList<>(); packageWithExtraProvider = new ArrayList<>(); this.listener = null; this.iconLoader = new AsyncIconLoader(); this.comparatorProvider = new TargetActivity.RecencyComparatorProvider(); this.chooserTitle = context.getString(R.string.isl_default_sharing_label); } /** * {@link IntentShare} is designed to enhance the sharing experience by allowing to share * different content according to the application the user will choose to share with. * * @param in parcel. */ protected IntentShare(Parcel in) { this.text = in.readString(); this.imageUri = in.readParcelable(Uri.class.getClassLoader()); this.mailBody = in.readString(); this.mailSubject = in.readString(); this.extraProviders = in.createTypedArrayList(ExtraProvider.CREATOR); this.iconLoader = in.readParcelable(IconLoader.class.getClassLoader()); this.comparatorProvider = in.readParcelable(TargetActivityComparatorProvider.class.getClassLoader()); this.chooserTitle = in.readString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.text); dest.writeParcelable(this.imageUri, 0); dest.writeString(this.mailBody); dest.writeString(this.mailSubject); dest.writeTypedList(this.extraProviders); dest.writeParcelable(this.iconLoader, flags); dest.writeParcelable(this.comparatorProvider, flags); dest.writeString(this.chooserTitle); } /** * {@link IntentShare} is designed to enhance the sharing experience by allowing to share * different content according to the application the user will choose to share with. * * @param context context from which the sharing is initiated. * @return new instance. */ @NonNull public static IntentShare with(@NonNull Context context) { return new IntentShare(context); } /** * Title that will be displayed in the chooser. *

* Will be displayed on a single line. * * @param title title that will be displayed in the chooser. * @return current {@link IntentShare} for method chaining. */ public IntentShare chooserTitle(@NonNull String title) { this.chooserTitle = title; return this; } /** * Set the {@link IconLoader} used to load target activities icon. * * @param iconLoader icon loader. * @return current {@link IntentShare} for method chaining. */ public IntentShare iconLoader(@NonNull IconLoader iconLoader) { this.iconLoader = iconLoader; return this; } /** * Provide a custom {@link java.util.Comparator} in order to sort the {@link TargetActivity} * displayed to the user. *

* By default, TargetActivities are sorted by the recentness of their selection. If it's the * behaviour that you are looking for, don't use this method and let the default comparator * bring the magic. * * @param comparatorProvider comparator used to sort the TargetActivities displayed to the user. * Will override the default sorting by recentness. * @return current {@link IntentShare} for method chaining. */ public IntentShare comparatorProvider(@NonNull TargetActivityComparatorProvider comparatorProvider) { if (comparatorProvider == null) { throw new NullPointerException("Custom comparator provider can't be null."); } this.comparatorProvider = comparatorProvider; return this; } /** * Text which will be shared. *

* Will be used as {@link android.content.Intent#EXTRA_TEXT} * * @param text text to share. * @return current {@link IntentShare} for method chaining. */ @NonNull public IntentShare text(@NonNull String text) { this.text = text; return this; } /** * Image which will be set as {@link android.content.Intent#EXTRA_STREAM} if the * target application can handle it. * * @param imageUri Uri of the image. * @return current {@link IntentShare} for method chaining. */ @NonNull public IntentShare image(@NonNull Uri imageUri) { String lastPathSegment = imageUri.getLastPathSegment(); if (!lastPathSegment.endsWith(".png") && !lastPathSegment.endsWith(".jpg")) { throw new IllegalArgumentException("Invalid image uri : only .png and .jpg file supported : " + imageUri); } else if (!"content".equals(imageUri.getScheme())) { throw new IllegalArgumentException("Invalid image uri : only content scheme supported : " + imageUri); } else { this.imageUri = imageUri; } return this; } /** * Text which will be shared by any application marked as mail client * (application which can handle "message/rfc822"). *

* Override {@link IntentShare#text(String)} for every mail application. * * @param mailBody text set as text body for mail application. * @return current {@link IntentShare} for method chaining. */ @NonNull public IntentShare mailBody(@NonNull String mailBody) { this.mailBody = mailBody; return this; } /** * Text which will be displayed inside the subject field of any application marked as mail * client (applcation which can handle "message/rfc822"). *

* See also : {@link IntentShare#mailBody(String)} * * @param mailSubject subject of the mail. * @return current {@link IntentShare} for method chaining. */ @NonNull public IntentShare mailSubject(@NonNull String mailSubject) { this.mailSubject = mailSubject; return this; } /** * Link which will be used as single shared content if target application is the facebook * since facebook app don't handle {@link android.content.Intent#EXTRA_TEXT} *

* Override {@link IntentShare#text(String)} for the Facebook application. * * @param link link to the content. * @return current {@link IntentShare} for method chaining. */ @NonNull public IntentShare facebookBody(@NonNull Uri link) { String scheme = link.getScheme(); if (!"http".equals(scheme) && !"https".equals(scheme)) { throw new IllegalArgumentException("Invalid facebook link : un handled scheme : " + scheme); } else if (packageWithExtraProvider.contains(IntentShare.FACEBOOK)) { throw new IllegalArgumentException("Facebook link can only be set once."); } else { packageWithExtraProvider.add(IntentShare.FACEBOOK); extraProviders.add( new ExtraProvider(IntentShare.FACEBOOK) .overrideText(link.toString()) .disableImage() .disableSubject() ); } return this; } /** * Text which will be used as tweet body if target application is the Twitter one. *

* Override {@link IntentShare#text(String)} for the Twitter application. *

* Note that length compliancy (140 char) isn't handle by the library anymore since * tweet lenght can't be easily deduced from the text length directly. *

* Please refer to the following link if you want to ensure the length check on your side: * https://blog.twitter.com/2016/doing-more-with-140-characters * * @param tweet tweet body. * @return current {@link IntentShare} for method chaining. */ @NonNull public IntentShare twitterBody(@NonNull String tweet) { if (packageWithExtraProvider.contains(IntentShare.TWITTER)) { throw new IllegalArgumentException("Twitter body can only be set once."); } else { packageWithExtraProvider.add(IntentShare.TWITTER); extraProviders.add( new ExtraProvider(TWITTER) .overrideText(tweet) ); } return this; } /** * Allow to set a listener to be notified on chosen target activity. *

* Listener will be automatically unregister once a result is delivered. * * @param listener listener to register. * @return current {@link IntentShare} for method chaining. */ public IntentShare listener(@NonNull IntentShareListener listener) { this.listener = listener; return this; } /** * Allow to add a specific intent for a given * * @param extraProvider extra provider for a given package; * @return current {@link IntentShare} for method chaining. */ public IntentShare addExtraProvider(@NonNull ExtraProvider extraProvider) { if (extraProvider == null) { throw new IllegalArgumentException("Extra provider can't be null"); } else if (packageWithExtraProvider.contains(extraProvider.packageName)) { throw new IllegalArgumentException("Extra provider already provided for the package : " + extraProvider.packageName); } extraProviders.add(extraProvider); return this; } /** * Deliver the intent to the system. *

* This will lead to the display of every applications that can handle the build intent. * Target activity field will be then filled according to the params. */ public void deliver() { if (this.listener != null) { this.listener.register(context); } TargetChooserActivity.start(context, this); } /** * Used to provide specific extras for a given package name */ public static class ExtraProvider implements Parcelable { /** * Parcelable. */ public static final Creator CREATOR = new Creator() { @Override public ExtraProvider createFromParcel(Parcel source) { return new ExtraProvider(source); } @Override public ExtraProvider[] newArray(int size) { return new ExtraProvider[size]; } }; /** * Package for which specific extras must be provided. */ String packageName; /** * Specific text to provide. */ String overriddenText; /** * Specific mail subject to provide. */ String overriddenSubject; /** * Specific image to provide. */ Uri overriddenImage; /** * Used to know if default text must be removed. */ boolean textDisabled; /** * Used to know if default subject must be removed. */ boolean subjectDisabled; /** * Used to know if default image must be removed. */ boolean imageDisabled; /** * Used to provide specific extras for a given package name. *

* By default, every extras are going to be copied from the {@link IntentShare}. *

* To override extras : * {@link ExtraProvider#overrideText(String)} * {@link ExtraProvider#overrideSubject(String)} * {@link ExtraProvider#overrideImage(Uri)} * * @param packageName package for which the extras are going to be applied; */ public ExtraProvider(String packageName) { this.packageName = packageName; this.overriddenText = null; this.overriddenSubject = null; this.overriddenImage = null; this.textDisabled = false; this.subjectDisabled = false; this.imageDisabled = false; } /** * Used to provide specific extras for a given package name. *

* By default, every extras are going to be copied from the {@link IntentShare}. *

* To override extras : * {@link ExtraProvider#overrideText(String)} * {@link ExtraProvider#overrideSubject(String)} * {@link ExtraProvider#overrideImage(Uri)} * * @param in parcel. */ protected ExtraProvider(Parcel in) { this.packageName = in.readString(); this.overriddenText = in.readString(); this.overriddenSubject = in.readString(); this.overriddenImage = in.readParcelable(Uri.class.getClassLoader()); this.textDisabled = in.readByte() != 0; this.subjectDisabled = in.readByte() != 0; this.imageDisabled = in.readByte() != 0; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ExtraProvider that = (ExtraProvider) o; if (textDisabled != that.textDisabled) { return false; } if (subjectDisabled != that.subjectDisabled) { return false; } if (imageDisabled != that.imageDisabled) { return false; } if (packageName != null ? !packageName.equals(that.packageName) : that.packageName != null) { return false; } if (overriddenText != null ? !overriddenText.equals(that.overriddenText) : that.overriddenText != null) { return false; } if (overriddenSubject != null ? !overriddenSubject.equals(that.overriddenSubject) : that.overriddenSubject != null) { return false; } return !(overriddenImage != null ? !overriddenImage.equals(that.overriddenImage) : that.overriddenImage != null); } @Override public int hashCode() { int result = packageName != null ? packageName.hashCode() : 0; result = 31 * result + (overriddenText != null ? overriddenText.hashCode() : 0); result = 31 * result + (overriddenSubject != null ? overriddenSubject.hashCode() : 0); result = 31 * result + (overriddenImage != null ? overriddenImage.hashCode() : 0); result = 31 * result + (textDisabled ? 1 : 0); result = 31 * result + (subjectDisabled ? 1 : 0); result = 31 * result + (imageDisabled ? 1 : 0); return result; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.packageName); dest.writeString(this.overriddenText); dest.writeString(this.overriddenSubject); dest.writeParcelable(this.overriddenImage, flags); dest.writeByte(textDisabled ? (byte) 1 : (byte) 0); dest.writeByte(subjectDisabled ? (byte) 1 : (byte) 0); dest.writeByte(imageDisabled ? (byte) 1 : (byte) 0); } /** * Provide a specific text for the linked package name. * * @param text text which will be used as {@link android.content.Intent#EXTRA_TEXT} * @return {@link ExtraProvider} for chaining. */ public ExtraProvider overrideText(String text) { this.overriddenText = text; return this; } /** * Provide a specific subject for the linked package name. * * @param subject subject which will be used as {@link android.content.Intent#EXTRA_SUBJECT} * @return {@link ExtraProvider} for chaining. */ public ExtraProvider overrideSubject(String subject) { this.overriddenSubject = subject; return this; } /** * Provide a specific image for the linked package name. * * @param image image which will be used as {@link android.content.Intent#EXTRA_STREAM} * @return {@link ExtraProvider} for chaining. */ public ExtraProvider overrideImage(Uri image) { this.overriddenImage = image; return this; } /** * Disable the extra {@link android.content.Intent#EXTRA_TEXT} for the linked package * to avoid a copy from the default {@link IntentShare} text. * * @return {@link ExtraProvider} for chaining. */ public ExtraProvider disableText() { this.textDisabled = true; return this; } /** * Disable the extra {@link android.content.Intent#EXTRA_SUBJECT} for the linked package * to avoid a copy from the default {@link IntentShare} subject. * * @return {@link ExtraProvider} for chaining. */ public ExtraProvider disableSubject() { this.subjectDisabled = true; return this; } /** * Disable the extra {@link android.content.Intent#EXTRA_STREAM} for the linked package * to avoid a copy from the default {@link IntentShare} image. * * @return {@link ExtraProvider} for chaining. */ public ExtraProvider disableImage() { this.imageDisabled = true; return this; } /** * Get package name of the target activity for which the extra provider has been build. * * @return package name of the target activity for which the extra provider has been build. */ public String getPackageName() { return packageName; } /** * Retrieve the specific text which is going to override the default one. * * @return the specific text which is going to override the default one. */ public String getOverriddenText() { return overriddenText; } /** * Retrieve the subject which is going to override the default one. * * @return the subject which is going to override the default one. */ public String getOverriddenSubject() { return overriddenSubject; } /** * Retrieve the {@link Uri} which is going to override the default one. * * @return the {@link Uri} which is going to override the default one. */ public Uri getOverriddenImage() { return overriddenImage; } /** * Used to know if the default text must be ignored. * * @return true if the default text must be ignored. */ public boolean isTextDisabled() { return textDisabled; } /** * Used to know if the default subject must be ignored. * * @return true if the default subject must be ignored. */ public boolean isSubjectDisabled() { return subjectDisabled; } /** * Used to know if the default image must be ignored. * * @return true if the default image must be ignored. */ public boolean isImageDisabled() { return imageDisabled; } } } ================================================ FILE: library/src/main/java/fr/tvbarthel/intentshare/IntentShareListener.java ================================================ package fr.tvbarthel.intentshare; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.support.v4.content.LocalBroadcastManager; /** * Simple listener used to catch which activity the user choose. */ public abstract class IntentShareListener { /** * Action used to inform that the user as complete the sharing. */ private static final String ACTION_ON_COMPLETED = "fr.tvbarthel.intentshare.oncompleted"; /** * Action used to inform that the user as cancel the sharing. */ private static final String ACTION_ON_CANCELED = "fr.tvbarthel.intentshare.oncanceled"; /** * Bundle key used to pass the package name. */ private static final String EXTRA_PACKAGE_NAME = "fr.tvbarthel.intentshare.package"; /** * Internal receiver. */ private final InternalReceiver internalReceiver; /** * Simple listener used to catch which activity the user chosen. */ public IntentShareListener() { internalReceiver = new InternalReceiver() { @Override public void onCanceled() { super.onCanceled(); IntentShareListener.this.onCanceled(); } @Override public void onCompleted(String packageName) { super.onCompleted(packageName); IntentShareListener.this.onCompleted(packageName); } }; } /** * Called when the user chose an activity to complete the sharing. * * @param packageName package name of the selected activity. */ public abstract void onCompleted(String packageName); /** * Called when the user cancel the sharing without selecting any target activity. */ public abstract void onCanceled(); /** * Notify all registered listener that sharing has been completed. *

* private package. * * @param context context used to send the broadcast. * @param packageName package name of the selected target activity. */ static void notifySharingCompleted(Context context, String packageName) { Intent intent = new Intent(ACTION_ON_COMPLETED); intent.putExtra(EXTRA_PACKAGE_NAME, packageName); LocalBroadcastManager.getInstance(context.getApplicationContext()).sendBroadcast(intent); } /** * Notify all registered listener that sharing has been canceled *

* private package. * * @param context context used to send the broadcast. */ static void notifySharingCanceled(Context context) { Intent intent = new Intent(ACTION_ON_CANCELED); LocalBroadcastManager.getInstance(context.getApplicationContext()).sendBroadcast(intent); } /** * Used to register the internal listener. *

* private package. * * @param context context used to register the listener. */ void register(Context context) { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_ON_COMPLETED); filter.addAction(ACTION_ON_CANCELED); LocalBroadcastManager.getInstance(context.getApplicationContext()) .registerReceiver(internalReceiver, filter); } /** * Receiver used internally to catch event. */ private static class InternalReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case ACTION_ON_COMPLETED: onCompleted(intent.getStringExtra(EXTRA_PACKAGE_NAME)); break; case ACTION_ON_CANCELED: onCanceled(); break; default: throw new IllegalArgumentException("Action unknown : " + intent.getAction()); } // unregister receiver. LocalBroadcastManager.getInstance(context.getApplicationContext()).unregisterReceiver(this); } /** * Called when the user chose an activity to complete the sharing. * * @param packageName package name of the selected activity. */ public void onCompleted(String packageName) { } /** * Called when the user cancel the sharing without selecting any target activity. */ public void onCanceled() { } } } ================================================ FILE: library/src/main/java/fr/tvbarthel/intentshare/LayoutManagerFactory.java ================================================ package fr.tvbarthel.intentshare; import android.content.Context; import android.os.Build; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; /** * Factory used to handle layout manager according to the device android version seamlessly */ final class LayoutManagerFactory { private static final int MARSHMALLOW_SPAN_COUNT = 4; /** * Non instantiable class. */ private LayoutManagerFactory() { } /** * Build the layout manager for the {@link TargetActivity} list displayed to the user * during the target activity selection. * * @param context context used to instantiate layout manager. * @return layout manager matching the native look and feel linked to the device SDK version. */ public static RecyclerView.LayoutManager buildLayoutManager(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { GridLayoutManager gridLayoutManager = new GridLayoutManager(context, MARSHMALLOW_SPAN_COUNT); gridLayoutManager.setSpanSizeLookup(new MarshmallowSpanSizeLookup()); return gridLayoutManager; } else { return new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false); } } /** * SpanSizeLookup used to fit the native look and feel provided by * {@link android.content.Intent#createChooser(android.content.Intent, CharSequence)} */ private static final class MarshmallowSpanSizeLookup extends GridLayoutManager.SpanSizeLookup { @Override public int getSpanSize(int position) { if (position == 0) { return MARSHMALLOW_SPAN_COUNT; // header taking the full width; } else { return 1; // target activity taking one unit; } } } } ================================================ FILE: library/src/main/java/fr/tvbarthel/intentshare/StyledAttributesUtils.java ================================================ package fr.tvbarthel.intentshare; import android.content.Context; import android.content.res.TypedArray; import android.util.TypedValue; /** * Util used to retrieved attributes of the current style. */ final class StyledAttributesUtils { /** * non instantiable class. */ private StyledAttributesUtils() { } /** * Retrieve the selectable background of the current style. * * @param context context used to retrieve the styled attributes. * @return selectable item background res id. */ public static int getSelectableItemBackground(Context context) { int selectableItemBackgroundResId; int[] attrs = new int[]{android.R.attr.selectableItemBackground}; TypedValue values = new TypedValue(); TypedArray array = context.obtainStyledAttributes(values.data, attrs); selectableItemBackgroundResId = array.getResourceId(0, 0); array.recycle(); return selectableItemBackgroundResId; } } ================================================ FILE: library/src/main/java/fr/tvbarthel/intentshare/TargetActivity.java ================================================ package fr.tvbarthel.intentshare; import android.content.Context; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Parcel; import java.io.File; import java.util.Comparator; /** * Plain java model for a sharing target activity. */ public class TargetActivity { private final int activityLabelResId; private final Uri iconUri; private final boolean isMail; private final long lastSelection; private ResolveInfo resolveInfo; private CharSequence label; /** * Plain java model for a sharing target activity. * * @param context context used to load target activity label. * @param resolveInfo {@link ResolveInfo} linked to the target activity. * @param lastSelection time stamp in milli of last selection. */ public TargetActivity(Context context, ResolveInfo resolveInfo, long lastSelection) { this.lastSelection = lastSelection; this.resolveInfo = resolveInfo; int icon = resolveInfo.activityInfo.icon; if (icon == 0) { icon = resolveInfo.activityInfo.applicationInfo.icon; } this.iconUri = Uri.parse( "android.resource://" + resolveInfo.activityInfo.applicationInfo.packageName + File.separator + icon ); this.activityLabelResId = resolveInfo.labelRes; this.isMail = resolveInfo.filter.hasDataType("message/rfc822"); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } TargetActivity that = (TargetActivity) o; return resolveInfo.equals(that.resolveInfo); } @Override public int hashCode() { int result = activityLabelResId; result = 31 * result + resolveInfo.hashCode(); return result; } /** * Retrieve a unique id used to identify target activity. * * @return unique id used to identify target activity. */ public String getId() { return this.resolveInfo.activityInfo.packageName + this.resolveInfo.activityInfo.name; } /** * Retrieve the package name of the target activity. * * @return package name. */ public String getPackageName() { return resolveInfo.activityInfo.packageName; } /** * Retrive the name of the target activity. * * @return activity name. */ public String getActivityName() { return resolveInfo.activityInfo.name; } /** * Retrieve the textual label res id of the Activity. * * @return textual label res id of the Activity. */ public int getActivityLabelResId() { return activityLabelResId; } /** * Retrieve the Uri used to access to the target application launcher icon. * * @return uri linking to the application launcher icon. */ public Uri getIconUri() { return iconUri; } /** * Used to know if the target activity is a mail client. *

* Basically, any activity which can handle type 'message/rfc_822'. * * @return true if the target activity is a mail client, false otherwise. */ public boolean isMailClient() { return this.isMail; } /** * Return a timestamp of the last selection inside the sharing dialog from you application. * * @return timestamp of the last selection in milliseconds since January 1, 1970 00:00:00.0 UTC * or 0 if the target activity has never been selected by the user. */ public long getLastSelection() { return lastSelection; } /** * Retrieve the label of the target activity. * * @return return target activity label or null if not yet loaded. */ CharSequence getLabel() { return label; } /** * Resolve info linked to the target activity. * * @return Resolve info linked to the target activity. */ ResolveInfo getResolveInfo() { return resolveInfo; } /** * Label of the target activity. * * @param label label of the target activity. */ void setLabel(CharSequence label) { this.label = label; } /** * Comparator used to sort {@link TargetActivity} based on the recency of their previous * selection and their default order as fallback when they have never been selected. *

* The ordering imposed by this comparator on a set of {@link TargetActivity} * is not consistent with equals since c.compare(e1, e2)==0 has not the same boolean * value as e1.equals(e2). */ public static final class RecencyComparatorProvider implements TargetActivityComparatorProvider { /** * Parcelable. */ public static final Creator CREATOR = new Creator() { @Override public RecencyComparatorProvider createFromParcel(Parcel source) { return new RecencyComparatorProvider(source); } @Override public RecencyComparatorProvider[] newArray(int size) { return new RecencyComparatorProvider[size]; } }; /** * Comparator used to sort {@link TargetActivity} based on the recency of their previous * selection and their default order as fallback when they have never been selected. *

* The ordering imposed by this comparator on a set of {@link TargetActivity} * is not consistent with equals since c.compare(e1, e2)==0 has not the same boolean * value as e1.equals(e2). */ public RecencyComparatorProvider() { } /** * Comparator used to sort {@link TargetActivity} based on the recency of their previous * selection and their default order as fallback when they have never been selected. *

* The ordering imposed by this comparator on a set of {@link TargetActivity} * is not consistent with equals since c.compare(e1, e2)==0 has not the same boolean * value as e1.equals(e2). * * @param in parcel. */ protected RecencyComparatorProvider(Parcel in) { } @Override public Comparator provideComparator() { return new Comparator() { @Override public int compare(TargetActivity lhs, TargetActivity rhs) { float lhsScore = lhs.lastSelection; float rhsScore = rhs.lastSelection; if (lhsScore > 0 && rhsScore > 0) { return (int) (rhsScore - lhsScore); } else if (lhsScore > 0) { return -1; } else if (rhsScore > 0) { return 1; } else { return 0; } } }; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { } } } ================================================ FILE: library/src/main/java/fr/tvbarthel/intentshare/TargetActivityAdapter.java ================================================ package fr.tvbarthel.intentshare; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; import java.util.List; /** * Used to adapt {@link TargetActivityView} inside a list. */ class TargetActivityAdapter extends RecyclerView.Adapter { /** * View types. */ private static final int VIEW_TYPE_HEADER = 0x00000001; private static final int VIEW_TYPE_RAW = 0x00000002; private final String label; private final IconLoader iconLoader; /** * Target activity info adapted. */ private List targetActivities; /** * Internal listener used to catch the events from the {@link TargetActivityView} adapted. */ private TargetActivityView.Listener internalTargetActivityViewListener; /** * Listener used to catch adapted views events. */ private Listener listener; /** * Used to adapt {@link TargetActivityView} inside a list. * * @param targetActivities list of target activities. * @param label label to display as an header of the list. * @param iconLoader loader used to load {@link TargetActivity} icon. */ public TargetActivityAdapter(final List targetActivities, String label, IconLoader iconLoader) { this.targetActivities = targetActivities; this.label = label; internalTargetActivityViewListener = new TargetActivityView.Listener() { @Override public void onTargetActivitySelected(TargetActivity targetActivity) { if (listener != null) { listener.onTargetActivitySelected(targetActivity); } } }; this.iconLoader = iconLoader; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { RecyclerView.LayoutParams layoutParams = new RecyclerView.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ); Context context = parent.getContext(); switch (viewType) { case VIEW_TYPE_HEADER: TargetActivityHeaderView headerView = new TargetActivityHeaderView(context); headerView.setLayoutParams(layoutParams); int extraPadding = context.getResources() .getDimensionPixelSize(R.dimen.isl_target_activity_header_extra_padding); headerView.setPadding( headerView.getPaddingLeft() + extraPadding, headerView.getPaddingTop(), headerView.getRight() + extraPadding, headerView.getPaddingBottom() ); return new ViewHolder(headerView); case VIEW_TYPE_RAW: TargetActivityView targetActivityView = new TargetActivityView(context, iconLoader); targetActivityView.setLayoutParams(layoutParams); targetActivityView.setListener(internalTargetActivityViewListener); return new ViewHolder(targetActivityView); default: throw new IllegalStateException("Can't create view " + "holder for the given view type : " + viewType); } } @Override public void onBindViewHolder(ViewHolder holder, int position) { switch (holder.getItemViewType()) { case VIEW_TYPE_HEADER: ((TargetActivityHeaderView) holder.itemView).setModel(label); break; case VIEW_TYPE_RAW: // -1 for the header. TargetActivityView targetActivityView = (TargetActivityView) holder.itemView; targetActivityView.setModel(targetActivities.get(position - 1)); targetActivityView.loadIcon(); break; default: throw new IllegalStateException("Can't bind view " + "holder for view type : " + holder.getItemViewType()); } } @Override public void onViewRecycled(ViewHolder holder) { super.onViewRecycled(holder); if (holder.itemView instanceof TargetActivityView) { ((TargetActivityView) holder.itemView).cancelIconLoading(); } } @Override public int getItemCount() { // +1 for the header return targetActivities.size() + 1; } @Override public int getItemViewType(int position) { if (position == 0) { return VIEW_TYPE_HEADER; } else { return VIEW_TYPE_RAW; } } /** * Listener used to catch events from the adapted views. * * @param listener listener to register. */ public void setListener(Listener listener) { this.listener = listener; } /** * Used to notify that a target activity changed. * * @param targetActivity target activity which have changed. */ public void notifyTargetActivityChanged(TargetActivity targetActivity) { int in = targetActivities.indexOf(targetActivity); if (in != -1) { notifyItemChanged(in + 1); // header } } /** * View holder pattern. */ public class ViewHolder extends RecyclerView.ViewHolder { /** * View holder. * * @param itemView {@link TargetActivityView} */ public ViewHolder(TargetActivityView itemView) { super(itemView); } /** * View holder. * * @param headerView {@link TargetActivityHeaderView} */ public ViewHolder(TargetActivityHeaderView headerView) { super(headerView); } } /** * Listener used to catch events from adapted views. */ public interface Listener { /** * Called when the user has chosen a target activity for the initial share intent. * * @param targetActivity chosen target activity. */ void onTargetActivitySelected(TargetActivity targetActivity); } } ================================================ FILE: library/src/main/java/fr/tvbarthel/intentshare/TargetActivityComparatorProvider.java ================================================ package fr.tvbarthel.intentshare; import android.os.Parcelable; import java.util.Comparator; /** * ˙Interface which allow to define which comparator will be provided for sorting the * target activity inside the {@link TargetChooserActivity}. */ public interface TargetActivityComparatorProvider extends Parcelable { /** * Provide the comparator used to sort {@link TargetActivity} displayed to the user. * * @return comparator used to sort {@link TargetActivity} displayed to the user. */ Comparator provideComparator(); } ================================================ FILE: library/src/main/java/fr/tvbarthel/intentshare/TargetActivityHeaderView.java ================================================ package fr.tvbarthel.intentshare; import android.content.Context; import android.content.res.Resources; import android.support.v4.content.ContextCompat; import android.text.TextUtils; import android.util.AttributeSet; import android.util.TypedValue; import android.widget.TextView; /** * Simple view used to display the label above the target activity list. */ class TargetActivityHeaderView extends TextView { /** * view height */ private int height; /** * Simple view used to display the label above the target activity list. * * @param context holding context. */ public TargetActivityHeaderView(Context context) { this(context, null); } /** * Simple view used to display the label above the target activity list. * * @param context holding context. * @param attrs attr from xml. */ public TargetActivityHeaderView(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * Simple view used to display the label above the target activity list. * * @param context holding context. * @param attrs attr from xml. * @param defStyleAttr style. */ public TargetActivityHeaderView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); if (!isInEditMode()) { initialize(context); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); } /** * Set the label which going to be displayed inside the header. * * @param label label to display inside the header. */ public void setModel(String label) { setText(label); } /** * Initialize internal component * * @param context holding context. */ private void initialize(Context context) { setBackgroundColor( ContextCompat.getColor( context, R.color.isl_target_activity_header_view_background ) ); Resources resources = context.getResources(); height = resources.getDimensionPixelSize(R.dimen.isl_target_activity_header_view_height); setTextSize( TypedValue.COMPLEX_UNIT_PX, resources.getDimensionPixelSize(R.dimen.isl_target_activity_header_view_font_size) ); setTextColor( ContextCompat.getColor( context, R.color.isl_target_activity_header_view_text_color ) ); setSingleLine(true); setEllipsize(TextUtils.TruncateAt.END); int padding = resources.getDimensionPixelSize(R.dimen.isl_default_padding); setPadding(getPaddingLeft(), padding, getPaddingRight(), padding); } } ================================================ FILE: library/src/main/java/fr/tvbarthel/intentshare/TargetActivityManager.java ================================================ package fr.tvbarthel.intentshare; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.AsyncTask; import android.support.annotation.NonNull; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * Manager used to handle all logic linked to {@link TargetActivity} */ class TargetActivityManager { /** * Shared preferences key used to store chosen activities. */ private static final String SHARED_PREF_KEY = "shared_pref_target_activities"; /** * Pattern for last selection key. * string 1 : package name * string 2 : activity name */ private static final String KEY_LAST_SELECTION = "shared_pref_last_selection_$1%s_$2%s"; /** * List of target activities. */ private ArrayList targetActivities; /** * Shared preferences used to store target activities scores. */ private SharedPreferences sharedPreferences; /** * Manager used to handle all logic linked to {@link TargetActivity} */ public TargetActivityManager() { targetActivities = new ArrayList<>(); } /** * Resolve the list of {@link android.app.Activity} which can be targeted for sharing content. *

* Basically, resolve the list of {@link android.app.Activity} which can handled * {@link Intent#ACTION_SEND}. * * @param context context used to resolves target activities. * @param listener listener used to catch resolving events. * @param comparator comparator used to sort the resolved target activities. */ public void resolveTargetActivities(Context context, ResolveListener listener, Comparator comparator) { targetActivities.clear(); sharedPreferences = context.getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE); PackageManager packageManager = context.getPackageManager(); Intent intentShare = new Intent(Intent.ACTION_SEND); intentShare.putExtra(Intent.EXTRA_TEXT, "queryText"); intentShare.setType("text/plain"); List shareActivities = packageManager.queryIntentActivities( intentShare, PackageManager.GET_RESOLVED_FILTER ); for (int i = 0; i < shareActivities.size(); i++) { ResolveInfo targetActivityInfo = shareActivities.get(i); IntentFilter filter = targetActivityInfo.filter; if (filter.hasDataType("text/plain")) { String lastSelectionKey = getLastSelectionKey( targetActivityInfo.activityInfo.packageName, targetActivityInfo.activityInfo.name ); long lastSelection = sharedPreferences.getLong(lastSelectionKey, 0); TargetActivity targetActivity = new TargetActivity(context, targetActivityInfo, lastSelection); targetActivities.add(targetActivity); } } Collections.sort(targetActivities, comparator); for (int i = 0; i < targetActivities.size(); i++) { new AsyncLabelLoader(context, targetActivities.get(i), listener).execute(); } listener.onTargetActivitiesResolved(targetActivities); } private String getLastSelectionKey(String packageName, String activityName) { return String.format(KEY_LAST_SELECTION, packageName, activityName); } /** * Start a target activity with well field params according to the given {@link IntentShare} * * @param context context used to start the activity. * @param targetActivity target activity to start. * @param intentShare data which should be send to the target activity. */ public void startTargetActivity(Context context, TargetActivity targetActivity, IntentShare intentShare) { context.startActivity( buildTargetActivityIntent( targetActivity, intentShare ) ); sharedPreferences .edit() .putLong(getLastSelectionKey( targetActivity.getPackageName(), targetActivity.getActivityName() ), System.currentTimeMillis() ) .apply(); } /** * Build the intent with {@link Intent#ACTION_SEND} action used to start the target activity * with well field params according to the given {@link IntentShare}. * * @param targetActivity target activity which should be start. * @param intentShare shared content used to field the intent params. * @return {@link Intent#ACTION_SEND} field to start the target activity. */ private Intent buildTargetActivityIntent(TargetActivity targetActivity, IntentShare intentShare) { String packageName = targetActivity.getPackageName(); String activityName = targetActivity.getActivityName(); ComponentName componentName = new ComponentName( packageName, activityName ); Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); if (targetActivity.isMailClient()) { // mail target intent.putExtra(Intent.EXTRA_TEXT, intentShare.mailBody); intent.putExtra(Intent.EXTRA_SUBJECT, intentShare.mailSubject); addImageExtras(intent, intentShare.imageUri); } else { // other target intent.putExtra(Intent.EXTRA_TEXT, intentShare.text); addImageExtras(intent, intentShare.imageUri); } applyExtraProvider(intent, packageName, intentShare.extraProviders); intent.setComponent(componentName); return intent; } /** * Apply the extra provider to the current intent if one is associated to the targeted package. * * @param intent intent send. * @param targetPackageName targeted package. * @param extraProviders list of extra providers. */ private void applyExtraProvider( Intent intent, String targetPackageName, ArrayList extraProviders) { IntentShare.ExtraProvider extraProvider = null; for (int i = 0; i < extraProviders.size(); i++) { IntentShare.ExtraProvider provider = extraProviders.get(i); if (provider.packageName.equals(targetPackageName)) { extraProvider = provider; break; } } if (extraProvider != null) { if (extraProvider.textDisabled) { intent.removeExtra(Intent.EXTRA_TEXT); } else if (extraProvider.overriddenText != null) { intent.putExtra(Intent.EXTRA_TEXT, extraProvider.overriddenText); } if (extraProvider.subjectDisabled) { intent.removeExtra(Intent.EXTRA_SUBJECT); } else if (extraProvider.overriddenSubject != null) { intent.putExtra(Intent.EXTRA_SUBJECT, extraProvider.overriddenSubject); } if (extraProvider.imageDisabled) { intent.removeExtra(Intent.EXTRA_STREAM); intent.setType("text/plain"); } else if (extraProvider.overriddenImage != null) { intent.putExtra(Intent.EXTRA_STREAM, extraProvider.overriddenImage); } } } private void addImageExtras(Intent intent, Uri imageUri) { if (imageUri != null) { intent.putExtra(Intent.EXTRA_STREAM, imageUri); intent.setType("image/jpeg"); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } } /** * Listener used to catch resolve events. */ public interface ResolveListener { /** * Called when the target activities has been resolved. *

* Note that, since resolving target activities name can take a little more time, * {@link ResolveListener#onLabelResolved(TargetActivity)} * will be called once a label has been successfully loaded. * * @param targetActivities list of resolved target activities. */ void onTargetActivitiesResolved(@NonNull ArrayList targetActivities); /** * Called when the label of a target activity has been resolved. * * @param targetActivity target for which the label has been resolved. */ void onLabelResolved(@NonNull TargetActivity targetActivity); } /** * Async task used to avoid loading the target activity label on the ui thread. */ private static final class AsyncLabelLoader extends AsyncTask { private final PackageManager packageManager; private final TargetActivity targetActivity; private final ResolveListener listener; /** * Async task used to avoid loading the target activity label on the ui thread. * * @param context context used to access to the package manager. * @param targetActivity target activity for which the label should be loaded. * @param listener to notify once the label has been loaded. */ public AsyncLabelLoader( @NonNull Context context, @NonNull TargetActivity targetActivity, @NonNull ResolveListener listener) { packageManager = context.getPackageManager(); this.targetActivity = targetActivity; this.listener = listener; } @Override protected CharSequence doInBackground(Void... params) { return targetActivity.getResolveInfo().loadLabel(packageManager); } @Override protected void onPostExecute(CharSequence s) { super.onPostExecute(s); targetActivity.setLabel(s); listener.onLabelResolved(targetActivity); } } } ================================================ FILE: library/src/main/java/fr/tvbarthel/intentshare/TargetActivityView.java ================================================ package fr.tvbarthel.intentshare; import android.content.Context; import android.content.res.Resources; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; /** * Simple view used to display a {@link TargetActivity}. */ class TargetActivityView extends FrameLayout { private ImageView icon; private TextView label; private TargetActivity model; private OnClickListener mInternalClickListener; private Listener listener; private int height; private IconLoader asyncIconLoader; /** * Simple view used to display a {@link TargetActivity}. * * @param context holding context. * @param asyncIconLoader loader used to load {@link TargetActivity} icon. */ public TargetActivityView(Context context, IconLoader asyncIconLoader) { super(context); if (!isInEditMode()) { initialize(context); } this.asyncIconLoader = asyncIconLoader; } /** * Simple view used to display a {@link TargetActivity}. * * @param context holding context. * @param attrs attr from xml. */ public TargetActivityView(Context context, AttributeSet attrs) { super(context, attrs); if (!isInEditMode()) { initialize(context); } } /** * Simple view used to display a {@link TargetActivity}. * * @param context holding context. * @param attrs attr from xml. * @param defStyleAttr style. */ public TargetActivityView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); if (!isInEditMode()) { initialize(context); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); } /** * Set the view model. * * @param model view model. */ public void setModel(TargetActivity model) { this.model = model; label.setText(model.getLabel()); } /** * Set a listener used to catch view events. * * @param listener listener to register. */ public void setListener(Listener listener) { this.listener = listener; } /** * Load the target activity icon. */ public void loadIcon() { icon.setImageDrawable(null); asyncIconLoader.load(model.getIconUri(), icon); } /** * Cancel the loading of the target activity icon. */ public void cancelIconLoading() { asyncIconLoader.cancel(icon); } /** * Initialize internal component. * * @param context holding context. */ private void initialize(Context context) { LayoutInflater.from(context).inflate(R.layout.isl_target_activity_view, this); Resources resources = context.getResources(); height = resources.getDimensionPixelSize(R.dimen.isl_target_activity_view_height); int paddingVertical = resources.getDimensionPixelSize(R.dimen.isl_target_activity_padding_vertical); int paddingHorizontal = resources.getDimensionPixelSize(R.dimen.isl_target_activity_padding_horizontal); setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical); setForeground( ContextCompat.getDrawable( context, StyledAttributesUtils.getSelectableItemBackground(context) ) ); icon = ((ImageView) findViewById(R.id.target_activity_view_icon)); label = ((TextView) findViewById(R.id.target_activity_view_label)); mInternalClickListener = new OnClickListener() { @Override public void onClick(View v) { if (listener != null) { listener.onTargetActivitySelected(model); } } }; setOnClickListener(mInternalClickListener); } /** * Listener used to catch view events. */ public interface Listener { /** * Called when the user has chosen a target activity for his share intent. * * @param targetActivity chosen target activity. */ void onTargetActivitySelected(TargetActivity targetActivity); } } ================================================ FILE: library/src/main/java/fr/tvbarthel/intentshare/TargetChooserActivity.java ================================================ package fr.tvbarthel.intentshare; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewTreeObserver; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import java.util.ArrayList; import java.util.List; /** * Simple activity used to allow the user to choose a target activity for the sharing intent. */ public class TargetChooserActivity extends AppCompatActivity implements TargetActivityAdapter.Listener, View.OnClickListener, TargetActivityManager.ResolveListener { /** * Extra key used to pass param to the activity. */ private static final String EXTRA_INTENT_SHARE = "tca_extra_intent_share"; /** * Key used to save the current scroll during rotation. */ private static final String SAVED_CURRENT_SCROLL_Y = "tca_saved_instance_key_current_scroll"; /** * Recycler view used to display the list of target application. */ private RecyclerView recyclerView; /** * Padding top applied to the recycler view. */ private int recyclerPaddingTop; /** * Adapter used to display target activity inside a list. */ private TargetActivityAdapter adapter; /** * Values to share retrieved from intent extras. */ private TargetActivityManager targetActivityManager; /** * Date which must be shared. */ private IntentShare intentShare; /** * Height in of a {@link TargetActivityView} */ private int targetActivityViewHeight; /** * Target activity selected by the user. */ private TargetActivity selectedTargetActivity; /** * Used to know if listener has been notified. */ private boolean listenerNotified; /** * Sticky header view displayed to keep an eye on the contextual action when the * header inside the list is hidden. */ private TargetActivityHeaderView stickyTitle; /** * Sticky header shadow. */ private View stickyShadow; /** * Boolean used to know if the sticky header is displayed. */ private boolean isStickyTitleDisplayed; /** * Used to keep current recycler scroll y up to date. */ private int currentRecyclerScrollY; /** * Root view of the activity */ private View rootView; /** * List of sharing target activity. */ private List targetActivities; /** * Duration in milli. */ private long animationDuration; /** * Interpolator for element which enter the screen. */ private Interpolator inInterpolator; /** * Interpolator for element which leave the screen. */ private Interpolator outInterpolator; /** * View used as background since recycler view padding can't allow to used * recycler background directly. */ private View background; /** * Used to know if the activity state has been restored after a saved instance. */ private boolean stateRestored; /** * Simple activity used to allow the user to choose a target activity for the sharing intent. * * @param context context used to start the activity. * @param intentShare data to share */ public static void start(Context context, IntentShare intentShare) { Intent intent = new Intent(context, TargetChooserActivity.class); intent.putExtra(EXTRA_INTENT_SHARE, intentShare); context.startActivity(intent); if (context instanceof Activity) { ((Activity) context).overridePendingTransition(-1, -1); } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle extras = getIntent().getExtras(); if (extras == null || !extras.containsKey(EXTRA_INTENT_SHARE)) { throw new IllegalArgumentException("Fail to start activity due to missing mandatory extras." + "Use start activity pattern."); } intentShare = extras.getParcelable(EXTRA_INTENT_SHARE); setContentView(R.layout.isl_activity_target_chooser); rootView = findViewById(R.id.activity_target_chooser_root_view); recyclerView = ((RecyclerView) findViewById(R.id.activity_target_chooser_recycler_list)); stickyTitle = ((TargetActivityHeaderView) findViewById(R.id.activity_chooser_sticky_title)); stickyShadow = findViewById(R.id.activity_chooser_sticky_title_shadow); background = findViewById(R.id.activity_target_chooser_background); targetActivities = new ArrayList<>(); selectedTargetActivity = null; listenerNotified = false; Resources resources = getResources(); animationDuration = resources.getInteger(android.R.integer.config_mediumAnimTime); rootView.setOnClickListener(this); rootView.setAlpha(0f); stateRestored = savedInstanceState != null; setUpRecyclerView(savedInstanceState); setUpStickyTitle(); targetActivityManager = new TargetActivityManager(); targetActivityManager.resolveTargetActivities(this, this, intentShare.comparatorProvider.provideComparator()); inInterpolator = new DecelerateInterpolator(); outInterpolator = new AccelerateInterpolator(); } @Override public void onBackPressed() { finishAnimated(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(SAVED_CURRENT_SCROLL_Y, currentRecyclerScrollY); } @Override protected void onDestroy() { super.onDestroy(); if (!listenerNotified && !isChangingConfigurations()) { IntentShareListener.notifySharingCanceled(this); } } @Override public void finish() { super.finish(); if (selectedTargetActivity != null) { IntentShareListener.notifySharingCompleted(this, selectedTargetActivity.getPackageName()); } else if (!isChangingConfigurations()) { IntentShareListener.notifySharingCanceled(this); } listenerNotified = true; } @Override public void onClick(View v) { finishAnimated(); } @Override public void onTargetActivitiesResolved(@NonNull ArrayList targetActivities) { this.targetActivities.addAll(targetActivities); adapter.notifyDataSetChanged(); } @Override public void onTargetActivitySelected(@NonNull TargetActivity targetActivity) { selectedTargetActivity = targetActivity; targetActivityManager.startTargetActivity(this, targetActivity, intentShare); finish(); } @Override public void onLabelResolved(TargetActivity targetActivity) { adapter.notifyTargetActivityChanged(targetActivity); } private void setUpRecyclerView(Bundle savedInstance) { recyclerView.setLayoutManager(LayoutManagerFactory.buildLayoutManager(this)); targetActivities = new ArrayList<>(); adapter = new TargetActivityAdapter( targetActivities, intentShare.chooserTitle, intentShare.iconLoader ); adapter.setListener(this); targetActivityViewHeight = getResources().getDimensionPixelSize(R.dimen.isl_target_activity_view_height); if (savedInstance != null) { currentRecyclerScrollY = savedInstance.getInt(SAVED_CURRENT_SCROLL_Y, 0); } else { currentRecyclerScrollY = 0; } recyclerView.getViewTreeObserver().addOnPreDrawListener( new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { recyclerView.getViewTreeObserver().removeOnPreDrawListener(this); int totalHeight = adapter.getItemCount() * targetActivityViewHeight; int maxStartingHeight = (int) (recyclerView.getHeight() / 2.5f); int startingHeight = Math.min(totalHeight, maxStartingHeight); recyclerPaddingTop = recyclerView.getHeight() - startingHeight; recyclerView.setPadding(recyclerView.getPaddingLeft(), recyclerPaddingTop, recyclerView.getPaddingRight(), 0); recyclerView.setTranslationY(recyclerView.getHeight()); int backgroundTranslationY = Math.max(0, recyclerPaddingTop - currentRecyclerScrollY); background.setTranslationY(recyclerView.getHeight() + backgroundTranslationY); recyclerView.setAdapter(adapter); if (stateRestored) { rootView.setAlpha(1f); recyclerView.setTranslationY(0); background.setTranslationY(backgroundTranslationY); } else { rootView.animate() .alpha(1f) .setDuration(animationDuration) .setInterpolator(inInterpolator) .setListener(null); recyclerView.animate() .translationY(0) .setDuration(animationDuration) .setInterpolator(inInterpolator) .setListener(null); background.animate() .translationY(backgroundTranslationY) .setDuration(animationDuration) .setInterpolator(inInterpolator) .setListener(null); } return false; } } ); recyclerView.addOnScrollListener( new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); currentRecyclerScrollY += dy; if (!isStickyTitleDisplayed && currentRecyclerScrollY >= recyclerPaddingTop) { isStickyTitleDisplayed = true; stickyTitle.setVisibility(View.VISIBLE); stickyShadow.setVisibility(View.VISIBLE); } else if (currentRecyclerScrollY < recyclerPaddingTop) { if (isStickyTitleDisplayed) { isStickyTitleDisplayed = false; stickyTitle.setVisibility(View.INVISIBLE); stickyShadow.setVisibility(View.INVISIBLE); } background.setTranslationY(recyclerPaddingTop - currentRecyclerScrollY); } } } ); } private void setUpStickyTitle() { stickyTitle.setVisibility(View.INVISIBLE); stickyShadow.setVisibility(View.INVISIBLE); stickyTitle.setModel(intentShare.chooserTitle); isStickyTitleDisplayed = false; } private void finishAnimated() { rootView.animate() .alpha(0) .setDuration(animationDuration) .setInterpolator(outInterpolator) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); TargetChooserActivity.this.finish(); TargetChooserActivity.this.overridePendingTransition(-1, -1); } } ); recyclerView.animate() .translationY(rootView.getHeight()) .setDuration(animationDuration) .setInterpolator(outInterpolator) .setListener(null); background.animate() .translationY(background.getTranslationY() + rootView.getHeight()) .setDuration(animationDuration) .setInterpolator(outInterpolator) .setListener(null); stickyShadow.animate() .translationY(rootView.getHeight()) .setDuration(animationDuration) .setInterpolator(outInterpolator) .setListener(null); stickyTitle.animate() .translationY(rootView.getHeight()) .setDuration(animationDuration) .setInterpolator(outInterpolator) .setListener(null); } } ================================================ FILE: library/src/main/res/layout/isl_activity_target_chooser.xml ================================================ ================================================ FILE: library/src/main/res/layout/isl_target_activity_view.xml ================================================ ================================================ FILE: library/src/main/res/layout-v23/isl_target_activity_view.xml ================================================ ================================================ FILE: library/src/main/res/values/colors.xml ================================================ #212121 @android:color/white @android:color/white #767676 #88000000 ================================================ FILE: library/src/main/res/values/dimens.xml ================================================ 16dp 16dp 16dp 24dp 10dp 54dp 19sp 60dp 60dp 19sp 0dp @dimen/isl_default_padding ================================================ FILE: library/src/main/res/values/public.xml ================================================ ================================================ FILE: library/src/main/res/values/strings.xml ================================================ Share with ================================================ FILE: library/src/main/res/values/styles.xml ================================================ ================================================ FILE: library/src/main/res/values-v19/styles.xml ================================================ ================================================ FILE: library/src/main/res/values-v23/dimens.xml ================================================ 16dp 16dp 4dp 48dp 0dp 56dp 19sp 60dp 120dp 12sp @dimen/isl_default_padding 0dp ================================================ FILE: library/src/test/java/fr/tvbarthel/intentshare/IntentShareTest.java ================================================ package fr.tvbarthel.intentshare; import android.content.Context; import android.net.Uri; import junit.framework.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import static org.mockito.Mockito.mock; /** * Test for {@link IntentShare} */ @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class IntentShareTest { @Test public void testTweetLimitEquals() throws Exception { Context context = mock(Context.class); String tweetLimitExceeded = "Tweet 140-character limit over exceeded with a " + "very very very very very very very very very very very very very" + "very ver" + "long text of 140 char"; Assert.assertEquals(140, tweetLimitExceeded.length()); IntentShare.with(context).twitterBody(tweetLimitExceeded); } @Test public void testImageUriSupported() throws Exception { Context context = mock(Context.class); Uri image = Uri.parse("content://fr.tvbarthel.test.fileprovider/test.png"); IntentShare.with(context).image(image); } @Test(expected = IllegalArgumentException.class) public void testImageUriSchemeNotSupported() throws Exception { Context context = mock(Context.class); Uri image = Uri.parse("http://tvbarthel.com/test.png"); IntentShare.with(context).image(image); } @Test(expected = IllegalArgumentException.class) public void testImageUriExtensionNotSupported() throws Exception { Context context = mock(Context.class); Uri image = Uri.parse("content://fr.tvbarthel.test.fileprovider/test.html"); IntentShare.with(context).image(image); } @Test(expected = IllegalArgumentException.class) public void testFacebookUriSchemeNotSupported() { Context context = mock(Context.class); Uri uri = Uri.parse("mailto://thomas.barthelemy.utc@gmail.com"); IntentShare.with(context).facebookBody(uri); } @Test public void testFacebookURiSchemeSupported() { Context context = mock(Context.class); Uri uri = Uri.parse("http://www.tvbarthel.com"); IntentShare.with(context).facebookBody(uri); } } ================================================ FILE: picasso-loader/.gitignore ================================================ /build ================================================ FILE: picasso-loader/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'com.jfrog.bintray' apply from: '../config/quality.gradle' group = 'fr.tvbarthel.intentshare' version = rootProject.ext.versionName android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode rootProject.ext.versionCode versionName rootProject.ext.versionName } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.squareup.picasso:picasso:2.5.2' compile project(':library') } Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newDataInputStream()) def bintrayUser = properties.getProperty('bintray.user') def bintrayKey = properties.getProperty('bintray.apikey') bintray { user = bintrayUser key = bintrayKey configurations = ['archives'] //When uploading configuration files dryRun = false //Whether to run this as dry-run, without deploying publish = true //If version should be auto published after an upload //Package configuration. The plugin will use the repo and name properties to check if the package already exists. In that case, there's no need to configure the other package properties (like userOrg, desc, etc). pkg { repo = 'maven' name = 'PicassoLoader' desc = 'Icon loader based on Picasso for IntentShare library.' websiteUrl = 'https://github.com/tvbarthel/IntentShare' issueTrackerUrl = 'https://github.com/tvbarthel/IntentShare/issues' vcsUrl = 'https://github.com/tvbarthel/IntentShare.git' licenses = ['Apache-2.0'] labels = ['android', 'intent', 'sharing', 'share', 'picasso'] publicDownloadNumbers = true version { gpg { sign = true //Determines whether to GPG sign the files. The default is false } } } } install { repositories.mavenInstaller { pom { project { packaging 'aar' name 'PicassoLoader' url 'https://github.com/tvbarthel/IntentShare' description 'Icon loader based on Picasso for IntentShare library.' licenses { license { name 'The Apache Software License, Version 2.0' url 'http://www.apache.org/licenses/LICENSE-2.0.txt' } } developers { developer { id 'tbarthel-fr' name 'Thomas Barthelemy' email 'thomas.barthelemy.utc@gmail.com' } developer { id 'vbarthel-fr' name 'Vincent Barthelemy' email 'vincent.barthelemy.perso@gmail.com' } } scm { connection 'https://github.com/tvbarthel/IntentShare.git' developerConnection 'https://github.com/tvbarthel/IntentShare.git' url 'https://github.com/tvbarthel/IntentShare' } } } } } task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs classifier = 'sources' } task javadoc(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) failOnError = false } task javadocJar(type: Jar, dependsOn: javadoc) { classifier = 'javadoc' from javadoc.destinationDir } artifacts { archives javadocJar archives sourcesJar } ================================================ FILE: picasso-loader/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/thomasbarthelemy/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: picasso-loader/src/main/AndroidManifest.xml ================================================ ================================================ FILE: picasso-loader/src/main/java/fr/tvbarthel/intentshare/loader/picasso/PicassoIconLoader.java ================================================ package fr.tvbarthel.intentshare.loader.picasso; import android.net.Uri; import android.os.Parcel; import android.widget.ImageView; import com.squareup.picasso.Picasso; import fr.tvbarthel.intentshare.IconLoader; /** * {@link IconLoader} based on {@link com.squareup.picasso.Picasso}. */ public class PicassoIconLoader implements IconLoader { /** * Parcelable. */ public static final Creator CREATOR = new Creator() { @Override public PicassoIconLoader createFromParcel(Parcel source) { return new PicassoIconLoader(source); } @Override public PicassoIconLoader[] newArray(int size) { return new PicassoIconLoader[size]; } }; /** * {@link IconLoader} based on {@link com.squareup.picasso.Picasso}. */ public PicassoIconLoader() { } /** * {@link IconLoader} based on {@link com.squareup.picasso.Picasso}. * * @param in parcel. */ protected PicassoIconLoader(Parcel in) { } @Override public void load(Uri iconUri, ImageView imageView) { Picasso.with(imageView.getContext()) .load(iconUri) .fit() .centerInside() .into(imageView); } @Override public void cancel(ImageView imageView) { Picasso.with(imageView.getContext()).cancelRequest(imageView); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { } } ================================================ FILE: sample/.gitignore ================================================ /build ================================================ FILE: sample/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion "25.0.0" defaultConfig { applicationId "fr.tvbarthel.intentsharesample" minSdkVersion 14 targetSdkVersion 25 versionCode 3 versionName "1.2" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:25.0.0' compile 'com.android.support:design:25.0.0' compile project(':library') compile project(':picasso-loader') compile project(':glide-loader') } ================================================ FILE: sample/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /home/thomas/Documents/android-sdk-linux/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: sample/src/androidTest/java/fr/tvbarthel/intentsharesample/ApplicationTest.java ================================================ package fr.tvbarthel.intentsharesample; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: sample/src/main/AndroidManifest.xml ================================================ ================================================ FILE: sample/src/main/java/fr/tvbarthel/intentsharesample/Adapter.java ================================================ package fr.tvbarthel.intentsharesample; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; /** * Simple adapter used to display the main content. */ class Adapter extends RecyclerView.Adapter { private static final int VIEW_TYPE_HEADER = 1; private static final int VIEW_TYPE_FOOTER = 2; private static final int VIEW_TYPE_EXTRA_PROVIDER = 3; private static final int HEADER_COUNT = 1; private static final int FOOTER_COUNT = 1; private final ArrayList extraProviders; private HeaderView.Listener internalHeaderListener; private FooterView.Listener internalFooterListener; private ExtraProviderWrapperView.Listener internalExtraProviderViewListener; private Listener listener; /** * Simple adapter used to display the main content. * * @param extraProviders extra providers which must be listed for the user. */ public Adapter(ArrayList extraProviders) { this.extraProviders = extraProviders; internalHeaderListener = new HeaderView.Listener() { @Override public void onLearMoreRequested() { if (listener != null) { listener.onLearMoreRequested(); } } @Override public void onDialogTitleChanged(String dialogTitle) { if (listener != null) { listener.onDialogTitleChanged(dialogTitle); } } @Override public void onSharedTextChanged(String sharedText) { if (listener != null) { listener.onSharedTextChanged(sharedText); } } @Override public void onFacebookLinkChanged(String currentFacebookLink) { if (listener != null) { listener.onFacebookLinkChanged(currentFacebookLink); } } @Override public void onTweetChanged(String currentTweet) { if (listener != null) { listener.onTweetChanged(currentTweet); } } @Override public void onMailSubjectChanged(String currentMailSubject) { if (listener != null) { listener.onMailSubjectChanged(currentMailSubject); } } @Override public void onMailBodyChanged(String currentMailBody) { if (listener != null) { listener.onMailBodyChanged(currentMailBody); } } }; internalFooterListener = new FooterView.Listener() { @Override public void onAddExtraProviderRequested() { if (listener != null) { listener.onAddExtraProviderRequested(); } } }; internalExtraProviderViewListener = new ExtraProviderWrapperView.Listener() { @Override public void onExtraProviderDetailRequested(ExtraProviderWrapper wrapper) { if (listener != null) { int wrapperPosition = Adapter.this.extraProviders.indexOf(wrapper); listener.onExtraProviderDetailRequested(wrapperPosition, wrapper); } } }; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { case VIEW_TYPE_HEADER: HeaderView headerView = new HeaderView(parent.getContext()); headerView.setListener(internalHeaderListener); headerView.setLayoutParams( new RecyclerView.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ) ); return new ViewHolder(headerView); case VIEW_TYPE_FOOTER: FooterView footerView = new FooterView(parent.getContext()); footerView.setListener(internalFooterListener); footerView.setLayoutParams( new RecyclerView.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ) ); return new ViewHolder(footerView); default: ExtraProviderWrapperView extraProviderView = new ExtraProviderWrapperView(parent.getContext()); extraProviderView.setListener(internalExtraProviderViewListener); extraProviderView.setLayoutParams( new RecyclerView.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ) ); return new ViewHolder(extraProviderView); } } @Override public void onBindViewHolder(ViewHolder holder, int position) { if (holder.getItemViewType() == VIEW_TYPE_EXTRA_PROVIDER) { ((ExtraProviderWrapperView) holder.itemView).presentData(extraProviders.get(position - HEADER_COUNT)); } } @Override public int getItemCount() { return extraProviders.size() + HEADER_COUNT + FOOTER_COUNT; } @Override public int getItemViewType(int position) { if (position == 0) { return VIEW_TYPE_HEADER; } else if (position == getItemCount() - 1) { return VIEW_TYPE_FOOTER; } else { return VIEW_TYPE_EXTRA_PROVIDER; } } /** * Listener used to catch adapted views events. * * @param listener Listener used to catch adapted views events. */ public void setListener(Listener listener) { this.listener = listener; } /** * Notify the adapter that an extra provider has been inserted at the end of the list. */ public void notifyExtraProviderInserted() { notifyItemInserted(extraProviders.size() - 1 + HEADER_COUNT); } /** * Notify the adapter that an extra provider has changed. * * @param position position of the extra provider in the list. */ public void notifyExtraProviderChanged(int position) { notifyItemChanged(position + HEADER_COUNT); } /** * View holder pattern. */ public final class ViewHolder extends RecyclerView.ViewHolder { /** * View holder pattern. * * @param itemView view to hold. */ public ViewHolder(View itemView) { super(itemView); } } /** * Listener used to catch adapted views events. */ public interface Listener { /** * Called when the user wants to access to more information about the library. */ void onLearMoreRequested(); /** * Called when the user wants to add an extra provider. */ void onAddExtraProviderRequested(); /** * Called when the user update the dialog title. */ void onDialogTitleChanged(String dialogTitle); /** * Called when the user update the text to share. * * @param sharedText text to share. */ void onSharedTextChanged(String sharedText); /** * Called when the user change the link he wants to share on facebook. * * @param currentFacebookLink facebook link. */ void onFacebookLinkChanged(String currentFacebookLink); /** * Called when the user change the content of the tweet. * * @param currentTweet new tweet body. */ void onTweetChanged(String currentTweet); /** * Called when the user change the mail subject. * * @param currentMailSubject new mail subject. */ void onMailSubjectChanged(String currentMailSubject); /** * Called when the user change the mail body. * * @param currentMailBody new mail body. */ void onMailBodyChanged(String currentMailBody); /** * Called when the user wants to access to more detail for a given extra provider. */ void onExtraProviderDetailRequested(int wrapperPosition, ExtraProviderWrapper wrapper); } } ================================================ FILE: sample/src/main/java/fr/tvbarthel/intentsharesample/ExtraProviderDialogFragment.java ================================================ package fr.tvbarthel.intentsharesample; import android.app.Activity; import android.app.Dialog; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.TextInputLayout; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentManager; import android.support.v7.app.AlertDialog; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.CheckBox; import android.widget.EditText; import android.widget.ScrollView; import fr.tvbarthel.intentshare.IntentShare; /** * Simple dialog used to edit an extra provider. */ public class ExtraProviderDialogFragment extends DialogFragment { private static final String TAG = ExtraProviderDialogFragment.class.getSimpleName(); private static final String EXTRA_PROVIDER = "args_extra_provider"; private static final String EXTRA_POSITION = "args_extra_position"; /** * Dummy callback used when fragment isn't attached. */ private static Callback dummyCallback = new Callback() { @Override public void onExtraProviderChanged(int position, ExtraProviderWrapper extraProviderWrapper) { } @Override public void onExtraProviderCreated(ExtraProviderWrapper extraProviderWrapper) { } }; /** * Current callback object. */ private Callback callback = dummyCallback; /** * Extra provider. */ private ExtraProviderWrapper extraProvider; /** * position of the extra provider. */ private int extraProviderPosition; /** * View components. */ private TextInputLayout appNameInputLayout; private TextInputLayout packageNameInputLayout; private EditText textEditText; private EditText subjectEditText; private CheckBox disableTextCheckBox; private CheckBox disableSubjectCheckBox; private CheckBox disableImageCheckBox; /** * Animation used to display emphasis on non valid field. */ private Animation wiggle; private ScrollView scrollView; /** * Retrieve a new instnace to create a new extra provider. * * @return well instantiated instance. */ public static ExtraProviderDialogFragment newInstance() { return newInstance(-1, null); } /** * Retrieve a new instance for editing a given extra provider. * * @param position position of the extra provider inside the list. * @param extraProviderWrapper extra provider to edit. * @return well instantiated instance. */ public static ExtraProviderDialogFragment newInstance( int position, ExtraProviderWrapper extraProviderWrapper) { ExtraProviderDialogFragment extraProviderDialogFragment = new ExtraProviderDialogFragment(); Bundle args = new Bundle(); args.putParcelable(EXTRA_PROVIDER, extraProviderWrapper); args.putInt(EXTRA_POSITION, position); extraProviderDialogFragment.setArguments(args); return extraProviderDialogFragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); wiggle = AnimationUtils.loadAnimation(getContext(), R.anim.wiggle); } @Override public void onAttach(Activity activity) { super.onAttach(activity); if (!(activity instanceof Callback)) { throw new IllegalStateException("Holding activity must implement the dialog callback."); } callback = ((Callback) activity); extraProviderPosition = -1; extraProvider = null; Bundle args = getArguments(); if (args == null || !args.containsKey(EXTRA_POSITION) || !args.containsKey(EXTRA_PROVIDER)) { throw new IllegalStateException("Required args missing, used new instance pattern."); } extraProviderPosition = args.getInt(EXTRA_POSITION); extraProvider = args.getParcelable(EXTRA_PROVIDER); } @Override public void onDetach() { super.onDetach(); callback = dummyCallback; } /** * Display the dialog to the user. * * @param fragmentManager fragment manager used to show the dialog. */ public void show(FragmentManager fragmentManager) { this.show(fragmentManager, TAG); } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final View dialogView = LayoutInflater.from(getContext()).inflate(R.layout.fragment_dialog_extra_provider, null); scrollView = ((ScrollView) dialogView.findViewById(R.id.fragment_dialog_extra_provider_scroll)); appNameInputLayout = ((TextInputLayout) dialogView.findViewById(R.id.fragment_dialog_extra_provider_app_name_input)); packageNameInputLayout = ((TextInputLayout) dialogView.findViewById(R.id.fragment_dialog_extra_provider_package_name_input)); textEditText = ((EditText) dialogView.findViewById(R.id.fragment_dialog_extra_provider_text)); subjectEditText = ((EditText) dialogView.findViewById(R.id.fragment_dialog_extra_provider_subject)); disableTextCheckBox = ((CheckBox) dialogView.findViewById(R.id.fragment_dialog_extra_provider_default_text_check)); disableSubjectCheckBox = ((CheckBox) dialogView.findViewById(R.id.fragment_dialog_extra_provider_default_subject_check)); disableImageCheckBox = ((CheckBox) dialogView.findViewById(R.id.fragment_dialog_extra_provider_default_image_check)); dialogView.findViewById(R.id.fragment_dialog_extra_provider_ok).setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { processForm(); } } ); initializeView(); return new AlertDialog.Builder(getContext()) .setView(dialogView) .setCancelable(true) .create(); } /** * Initialize the dialog based on the passed extra provider. */ private void initializeView() { if (extraProvider != null) { IntentShare.ExtraProvider wrappedProvider = this.extraProvider.getExtraProvider(); appNameInputLayout.getEditText().setText(extraProvider.getAppName()); packageNameInputLayout.getEditText().setText(wrappedProvider.getPackageName()); textEditText.setText(wrappedProvider.getOverriddenText()); subjectEditText.setText(wrappedProvider.getOverriddenSubject()); disableTextCheckBox.setChecked(wrappedProvider.isTextDisabled()); disableSubjectCheckBox.setChecked(wrappedProvider.isSubjectDisabled()); disableImageCheckBox.setChecked(wrappedProvider.isImageDisabled()); } } /** * Process the current form data. */ private void processForm() { if (isFormValid()) { IntentShare.ExtraProvider extraProvider = new IntentShare.ExtraProvider( packageNameInputLayout.getEditText().getText().toString() ); String text = textEditText.getText().toString(); if (!TextUtils.isEmpty(text)) { extraProvider.overrideText(text); } String subject = subjectEditText.getText().toString(); if (!TextUtils.isEmpty(subject)) { extraProvider.overrideSubject(subject); } if (disableTextCheckBox.isChecked()) { extraProvider.disableText(); } if (disableSubjectCheckBox.isChecked()) { extraProvider.disableSubject(); } if (disableImageCheckBox.isChecked()) { extraProvider.disableImage(); } if (extraProviderPosition != -1) { callback.onExtraProviderChanged( extraProviderPosition, new ExtraProviderWrapper( appNameInputLayout.getEditText().getText().toString(), extraProvider ) ); } else { callback.onExtraProviderCreated(new ExtraProviderWrapper( appNameInputLayout.getEditText().getText().toString(), extraProvider ) ); } dismiss(); } } /** * Used to check if the form is well field. * * @return true if the farm is valid, false otherwise. */ private boolean isFormValid() { return isEditTextValid(appNameInputLayout) && isEditTextValid(packageNameInputLayout); } /** * Used to check if an {@link EditText} is not empty. * * @param inputLayout layout containing an edit text which musn't be empty. * @return true if the edit text is not empty. */ private boolean isEditTextValid(@NonNull TextInputLayout inputLayout) { EditText editText = inputLayout.getEditText(); String text = editText.getText().toString(); boolean empty = TextUtils.isEmpty(text); if (empty) { inputLayout.startAnimation(wiggle); scrollView.smoothScrollTo(0, inputLayout.getTop()); } return !empty; } /** * Callback used to catch fragment events. */ public interface Callback { /** * Called when the extra provider at the given position index has changed. * * @param position position passed. * @param extraProviderWrapper extra provider wrapper. */ void onExtraProviderChanged(int position, ExtraProviderWrapper extraProviderWrapper); /** * Called when the user create a new extra provider. * * @param extraProviderWrapper new extra provider created. */ void onExtraProviderCreated(ExtraProviderWrapper extraProviderWrapper); } } ================================================ FILE: sample/src/main/java/fr/tvbarthel/intentsharesample/ExtraProviderWrapper.java ================================================ package fr.tvbarthel.intentsharesample; import android.os.Parcel; import android.os.Parcelable; import fr.tvbarthel.intentshare.IntentShare; /** * Encapsulate data around an {@link ExtraProviderWrapper} */ class ExtraProviderWrapper implements Parcelable { /** * Parcelable. */ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public ExtraProviderWrapper createFromParcel(Parcel source) { return new ExtraProviderWrapper(source); } @Override public ExtraProviderWrapper[] newArray(int size) { return new ExtraProviderWrapper[size]; } }; private String appName; private IntentShare.ExtraProvider extraProvider; /** * Encapsulate data around an {@link ExtraProviderWrapper} * * @param appName name of the app for which the extra provider has been build. */ public ExtraProviderWrapper(String appName, IntentShare.ExtraProvider extraProvider) { this.appName = appName; this.extraProvider = extraProvider; } /** * Encapsulate data around an {@link ExtraProviderWrapper} * * @param in parcel. */ protected ExtraProviderWrapper(Parcel in) { this.appName = in.readString(); this.extraProvider = in.readParcelable(IntentShare.ExtraProvider.class.getClassLoader()); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.appName); dest.writeParcelable(this.extraProvider, flags); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ExtraProviderWrapper that = (ExtraProviderWrapper) o; if (appName != null ? !appName.equals(that.appName) : that.appName != null) { return false; } return !(extraProvider != null ? !extraProvider.equals(that.extraProvider) : that.extraProvider != null); } @Override public int hashCode() { int result = appName != null ? appName.hashCode() : 0; result = 31 * result + (extraProvider != null ? extraProvider.hashCode() : 0); return result; } /** * Name of the app for which the extra provider has been build. * * @return Name of the app for which the extra provider has been build. */ public String getAppName() { return appName; } /** * Access to the wrapped extra provider. * * @return wrapped extra provider. */ public IntentShare.ExtraProvider getExtraProvider() { return extraProvider; } } ================================================ FILE: sample/src/main/java/fr/tvbarthel/intentsharesample/ExtraProviderWrapperView.java ================================================ package fr.tvbarthel.intentsharesample; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; import android.widget.TextView; /** * View used to display an {@link ExtraProviderWrapper} list entry. */ class ExtraProviderWrapperView extends FrameLayout { private TextView labelView; private Listener listener; private ExtraProviderWrapper extraProviderWrapper; /** * View used to display an extra provider list entry. * * @param context holding context. */ public ExtraProviderWrapperView(Context context) { this(context, null); } /** * View used to display an extra provider list entry. * * @param context holding context. * @param attrs attrs from xml. */ public ExtraProviderWrapperView(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * View used to display an extra provider list entry. * * @param context holding context. * @param attrs attrs from xml. * @param defStyleAttr style. */ public ExtraProviderWrapperView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initialize(context); } /** * Set the extra provider which should be presented to the user. * * @param extraProviderWrapper wrapper to display to the user. */ public void presentData(ExtraProviderWrapper extraProviderWrapper) { this.extraProviderWrapper = extraProviderWrapper; labelView.setText(extraProviderWrapper.getAppName()); } /** * Set a listener to catch view events. * * @param listener listener used to catch view events. */ public void setListener(Listener listener) { this.listener = listener; } /** * Initialize internal component. * * @param context holding context. */ private void initialize(Context context) { LayoutInflater.from(context).inflate(R.layout.extra_provider_view, this); labelView = ((TextView) findViewById(R.id.extra_provider_view_label)); labelView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (listener != null) { listener.onExtraProviderDetailRequested(extraProviderWrapper); } } }); } /** * Interface used to catch view events. */ public interface Listener { /** * Called when the user wants to access to more detail for a given extra provider. */ void onExtraProviderDetailRequested(ExtraProviderWrapper wrapper); } } ================================================ FILE: sample/src/main/java/fr/tvbarthel/intentsharesample/FooterView.java ================================================ package fr.tvbarthel.intentsharesample; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; /** * Footer view of the list. */ class FooterView extends FrameLayout { private Listener listener; /** * Footer view of the list. * * @param context holding context. */ public FooterView(Context context) { this(context, null); } /** * Footer view of the list. * * @param context holding context. * @param attrs attrs from xml. */ public FooterView(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * Footer view of the list. * * @param context holding context. * @param attrs attrs from xml. * @param defStyleAttr style. */ public FooterView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initialize(context); } /** * Listener used to catch view events. * * @param listener Listener used to catch view events. */ public void setListener(Listener listener) { this.listener = listener; } /** * Initialize internal component. * * @param context holding context. */ private void initialize(Context context) { LayoutInflater.from(context).inflate(R.layout.footer_view, this); findViewById(R.id.footer_view_add).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (listener != null) { listener.onAddExtraProviderRequested(); } } }); } /** * Listener used to catch view events. */ public interface Listener { /** * Called when the user wants to add an extra provider. */ void onAddExtraProviderRequested(); } } ================================================ FILE: sample/src/main/java/fr/tvbarthel/intentsharesample/HeaderView.java ================================================ package fr.tvbarthel.intentsharesample; import android.content.Context; import android.content.res.Resources; import android.text.Editable; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.EditText; import android.widget.LinearLayout; /** * View used to display the header. */ class HeaderView extends LinearLayout { private OnClickListener internalClickListener; private Listener listener; private String currentDialogTitle; private String currentSharedText; private String currentFacebookLink; private String currentTweet; private EditText tweetEditText; private String currentMailSubject; private String currentMailBody; /** * View used to display the header. * * @param context holding context. */ public HeaderView(Context context) { this(context, null); } /** * View used to display the header. * * @param context holding context. * @param attrs attrs from xml. */ public HeaderView(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * View used to display the header. * * @param context holding context. * @param attrs attrs from xml. * @param defStyleAttr style. */ public HeaderView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initialize(context); } /** * Listener used to catch view events. * * @param listener listener used to catch view events. */ public void setListener(Listener listener) { this.listener = listener; listener.onDialogTitleChanged(currentDialogTitle); listener.onSharedTextChanged(currentSharedText); listener.onFacebookLinkChanged(currentFacebookLink); listener.onTweetChanged(currentTweet); listener.onMailBodyChanged(currentMailBody); listener.onMailSubjectChanged(currentMailSubject); } /** * Initialize internal component. * * @param context holding context. */ private void initialize(Context context) { LayoutInflater.from(context).inflate(R.layout.header_view, this); setOrientation(VERTICAL); Resources resources = context.getResources(); int horizontalPadding = resources.getDimensionPixelSize(R.dimen.activity_horizontal_margin); int verticalPadding = resources.getDimensionPixelSize(R.dimen.activity_vertical_margin); setPadding(horizontalPadding, verticalPadding, horizontalPadding, 0); internalClickListener = new OnClickListener() { @Override public void onClick(View v) { switch (v.getId()) { case R.id.main_header_view_learn_more_button: if (listener != null) { listener.onLearMoreRequested(); } break; } } }; findViewById(R.id.main_header_view_learn_more_button).setOnClickListener(internalClickListener); initializeDialogTitle(context); initializeSharedText(context); initializeFacebookLink(context); initializeTweetLink(context); initializeMail(context); } private void initializeMail(Context context) { EditText editSubject = (EditText) findViewById(R.id.main_header_view_mail_subject); currentMailSubject = context.getString(R.string.article_title); editSubject.setText(currentMailSubject); editSubject.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { if (listener != null) { currentMailSubject = s.toString(); listener.onMailSubjectChanged(currentMailSubject); } } }); EditText editBody = (EditText) findViewById(R.id.main_header_view_mail_body); currentMailBody = context.getString(R.string.article); editBody.setText(currentMailBody); editBody.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { if (listener != null) { currentMailBody = s.toString(); listener.onMailBodyChanged(currentMailBody); } } }); } private void initializeTweetLink(Context context) { tweetEditText = (EditText) findViewById(R.id.main_header_view_twitter); currentTweet = context.getString(R.string.article_tweet); tweetEditText.setText(currentTweet); tweetEditText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { if (s.length() > 140) { tweetEditText.setText(s.subSequence(0, 139)); tweetEditText.setSelection(139); } if (listener != null) { currentTweet = s.toString(); listener.onTweetChanged(currentTweet); } } }); } private void initializeFacebookLink(Context context) { EditText editText = (EditText) findViewById(R.id.main_header_view_facebook); currentFacebookLink = context.getString(R.string.article_url); editText.setText(currentFacebookLink); editText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { if (listener != null) { currentFacebookLink = s.toString(); listener.onFacebookLinkChanged(currentFacebookLink); } } }); } private void initializeSharedText(Context context) { EditText sharedText = (EditText) findViewById(R.id.main_header_view_input_text); currentSharedText = context.getString(R.string.default_shared_text); sharedText.setText(currentSharedText); sharedText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { if (listener != null) { currentSharedText = s.toString(); listener.onSharedTextChanged(currentSharedText); } } }); } private void initializeDialogTitle(Context context) { EditText dialogTitle = (EditText) findViewById(R.id.main_header_view_input_dialog_title); currentDialogTitle = context.getString(R.string.default_dialog_title); dialogTitle.setText(currentDialogTitle); dialogTitle.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { if (listener != null) { currentDialogTitle = s.toString(); listener.onDialogTitleChanged(currentDialogTitle); } } }); } /** * Listener used to catch view events. */ public interface Listener { /** * Called when the user wants to access to more information about the library. */ void onLearMoreRequested(); /** * Called when the user update the dialog title. */ void onDialogTitleChanged(String dialogTitle); /** * Called when the user update the text to share. * * @param sharedText text to share. */ void onSharedTextChanged(String sharedText); /** * Called when the user change the link he wants to share on facebook. * * @param currentFacebookLink facebook link. */ void onFacebookLinkChanged(String currentFacebookLink); /** * Called when the user change the content of the tweet. * * @param currentTweet new tweet body. */ void onTweetChanged(String currentTweet); /** * Called when the user change the mail subject. * * @param currentMailSubject new mail subject. */ void onMailSubjectChanged(String currentMailSubject); /** * Called when the user change the mail body. * * @param currentMailBody new mail body. */ void onMailBodyChanged(String currentMailBody); } } ================================================ FILE: sample/src/main/java/fr/tvbarthel/intentsharesample/MainActivity.java ================================================ package fr.tvbarthel.intentsharesample; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; import android.support.v4.content.FileProvider; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.Toast; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import fr.tvbarthel.intentshare.IconLoader; import fr.tvbarthel.intentshare.IntentShare; import fr.tvbarthel.intentshare.IntentShareListener; import fr.tvbarthel.intentshare.TargetActivityComparatorProvider; import fr.tvbarthel.intentshare.loader.glide.GlideIconLoader; import fr.tvbarthel.intentshare.loader.picasso.PicassoIconLoader; public class MainActivity extends AppCompatActivity implements Adapter.Listener, View.OnClickListener, ExtraProviderDialogFragment.Callback { /** * Log cat. */ private static final String TAG = MainActivity.class.getSimpleName(); /** * Const used for saving temp image file for sharing purpose. */ private static final int SHARED_IMAGE_QUALITY = 100; private static final String SHARED_DIRECTORY = "sharing"; private static final String SHARED_IMAGE_FILE = "shared_img.png"; private static final String FILE_PROVIDER_AUTHORITY = "fr.tvbarthel.intentsharesample.fileprovider"; private IntentShareListener intentShareListener; private PicassoIconLoader picassoIconLoader; private IconLoader iconLoader; private TargetActivityComparatorProvider customComparatorProvider; private GlideIconLoader glideIconLoader; private String targetPackage; private String dialogTitle; private String sharedText; private ArrayList extraProviders; private Adapter adapter; private Uri facebookLink; private String tweet; private String mailSubject; private String mailBody; private Uri imageUri; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); targetPackage = null; customComparatorProvider = null; intentShareListener = new IntentShareListener() { @Override public void onCompleted(String packageName) { targetPackage = packageName; } @Override public void onCanceled() { Toast.makeText(MainActivity.this, "Sharing canceled", Toast.LENGTH_SHORT).show(); } }; setUpRecyclerView(); findViewById(R.id.activity_main_share_button).setOnClickListener(this); imageUri = getShareableUri(this, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)); } @Override protected void onResume() { super.onResume(); if (targetPackage != null) { Toast.makeText(MainActivity.this, "Shared with : " + targetPackage, Toast.LENGTH_SHORT).show(); targetPackage = null; } } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.activity_sample_menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.sample_icon_loader_default: iconLoader = null; break; case R.id.sample_icon_loader_picasso: if (picassoIconLoader == null) { picassoIconLoader = new PicassoIconLoader(); } iconLoader = picassoIconLoader; break; case R.id.sample_icon_loader_glide: if (glideIconLoader == null) { glideIconLoader = new GlideIconLoader(); } iconLoader = picassoIconLoader; break; case R.id.sorting_default: customComparatorProvider = null; break; case R.id.sorting_custom: customComparatorProvider = new SocialTargetActivityComparatorProvider(); break; default: return super.onOptionsItemSelected(item); } item.setChecked(true); return true; } @Override public void onLearMoreRequested() { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(getString(R.string.article_url))); startActivity(intent); } @Override public void onAddExtraProviderRequested() { ExtraProviderDialogFragment.newInstance().show(getSupportFragmentManager()); } @Override public void onDialogTitleChanged(String dialogTitle) { this.dialogTitle = dialogTitle; } @Override public void onSharedTextChanged(String sharedText) { this.sharedText = sharedText; } @Override public void onFacebookLinkChanged(String currentFacebookLink) { this.facebookLink = Uri.parse(currentFacebookLink); } @Override public void onTweetChanged(String currentTweet) { this.tweet = currentTweet; } @Override public void onMailSubjectChanged(String currentMailSubject) { this.mailSubject = currentMailSubject; } @Override public void onMailBodyChanged(String currentMailBody) { this.mailBody = currentMailBody; } @Override public void onExtraProviderDetailRequested(int wrapperPosition, ExtraProviderWrapper wrapper) { ExtraProviderDialogFragment.newInstance(wrapperPosition, wrapper).show(getSupportFragmentManager()); } @Override public void onExtraProviderChanged(int position, ExtraProviderWrapper extraProviderWrapper) { extraProviders.remove(position); extraProviders.add(position, extraProviderWrapper); adapter.notifyExtraProviderChanged(position); } @Override public void onExtraProviderCreated(ExtraProviderWrapper extraProviderWrapper) { extraProviders.add(extraProviderWrapper); adapter.notifyExtraProviderInserted(); } @Override public void onClick(View v) { IntentShare intentShare = IntentShare.with(MainActivity.this) .chooserTitle(dialogTitle) .text(sharedText) .mailBody(mailBody) .mailSubject(mailSubject) .image(imageUri) .facebookBody(facebookLink) .twitterBody(tweet) .listener(intentShareListener); if (customComparatorProvider != null) { intentShare.comparatorProvider(customComparatorProvider); } if (iconLoader != null) { intentShare.iconLoader(iconLoader); } for (int i = 0; i < extraProviders.size(); i++) { intentShare.addExtraProvider(extraProviders.get(i).getExtraProvider()); } intentShare.deliver(); } private void setUpRecyclerView() { RecyclerView recyclerView = (RecyclerView) findViewById(R.id.activity_main_recycler); recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); extraProviders = new ArrayList<>(); extraProviders.add(buildPocketProvider()); extraProviders.add(buildKeepProvider()); adapter = new Adapter(extraProviders); adapter.setListener(this); recyclerView.setHasFixedSize(false); recyclerView.setAdapter(adapter); } /** * Pocket only handle links. * * @return well instantiate provider. */ private ExtraProviderWrapper buildPocketProvider() { // pocket only handle link. IntentShare.ExtraProvider provider = new IntentShare.ExtraProvider("com.ideashower.readitlater.pro") .disableImage() .disableSubject() .overrideText(getString(R.string.article_url)); return new ExtraProviderWrapper("Pocket", provider); } /** * Keep can have a subject used as not title. * * @return well instantiate provider. */ private ExtraProviderWrapper buildKeepProvider() { // pocket only handle link. IntentShare.ExtraProvider provider = new IntentShare.ExtraProvider("com.google.android.keep") .overrideSubject(getString(R.string.article_title)); return new ExtraProviderWrapper("Google Keep", provider); } private Uri getShareableUri(Context context, Bitmap bitmap) { // Compress the drawingCache before saving and sharing. final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, SHARED_IMAGE_QUALITY, bytes); // Write the compressed bytes to a files final File outputDirectory = new File(context.getFilesDir(), SHARED_DIRECTORY); if (outputDirectory.isDirectory() || outputDirectory.mkdirs()) { final File shareColorFile = new File(outputDirectory, SHARED_IMAGE_FILE); try { final FileOutputStream fo = new FileOutputStream(shareColorFile); fo.write(bytes.toByteArray()); fo.close(); // Get the content uri. return FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, shareColorFile); } catch (IOException e) { Log.e(TAG, "Fail to write bitmap inside the temp file."); } } else { Log.e(TAG, "Fail to create temp file for bitmap sharing."); } return null; } } ================================================ FILE: sample/src/main/java/fr/tvbarthel/intentsharesample/SharingFileProvider.java ================================================ package fr.tvbarthel.intentsharesample; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.provider.MediaStore; import android.support.v4.content.FileProvider; /** * A simple {@link FileProvider} that solves a bug with the com.android.mms application. *

* http://androidxref.com/5.1.1_r6/xref/packages/apps/Mms/src/com/android/mms/ui/UriImage.java#546 *

*

* When sharing an images with a content Uri, the com.android.mms application * tries to get the orientation of the image from the provider. *

* This {@link FileProvider} is a very simple example that handle the query of {@link android.provider.MediaStore.Images.ImageColumns#ORIENTATION} * projection. *

* Note: this {@link FileProvider} always returns '0' for the value of the orientation. * If your images do not have the same orientation, you should build your own logic. */ public class SharingFileProvider extends FileProvider { @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { if (isMediaStoreOrientationProjection(projection)) { return queryMediaStoreOrientation(); } return super.query(uri, projection, selection, selectionArgs, sortOrder); } /** * Check if a projection corresponds to a {@link android.provider.MediaStore.Images.ImageColumns#ORIENTATION} projection. * * @param projection the projection to check. * @return Returns true is the given projection corresponds to {@link android.provider.MediaStore.Images.ImageColumns#ORIENTATION}, false otherwise. */ private boolean isMediaStoreOrientationProjection(String[] projection) { return projection != null && projection.length == 1 && MediaStore.Images.ImageColumns.ORIENTATION.equals(projection[0]); } /** * Query the {@link android.provider.MediaStore.Images.ImageColumns#ORIENTATION} * * @return Returns a {@link Cursor} with {@link android.provider.MediaStore.Images.ImageColumns#ORIENTATION} set to 0. */ private Cursor queryMediaStoreOrientation() { String[] cols = new String[]{MediaStore.Images.ImageColumns.ORIENTATION}; Object[] values = new Object[]{0}; final MatrixCursor cursor = new MatrixCursor(cols, 1); cursor.addRow(values); return cursor; } } ================================================ FILE: sample/src/main/java/fr/tvbarthel/intentsharesample/SocialTargetActivityComparatorProvider.java ================================================ package fr.tvbarthel.intentsharesample; import android.os.Parcel; import java.util.ArrayList; import java.util.Comparator; import fr.tvbarthel.intentshare.TargetActivity; import fr.tvbarthel.intentshare.TargetActivityComparatorProvider; /** * Simple custom {@link TargetActivityComparatorProvider} used to illustrate how to provide * a different sorting for the target activities displayed to the user. *

* This example simply displayed some social media apps at first and then keep the original * order returned by the system. */ class SocialTargetActivityComparatorProvider implements TargetActivityComparatorProvider { private ArrayList prioritizedPackageNames; /** * Parcelable. */ public static final Creator CREATOR = new Creator() { @Override public SocialTargetActivityComparatorProvider createFromParcel(Parcel source) { return new SocialTargetActivityComparatorProvider(source); } @Override public SocialTargetActivityComparatorProvider[] newArray(int size) { return new SocialTargetActivityComparatorProvider[size]; } }; /** * Simple custom {@link TargetActivityComparatorProvider} used to illustrate how to provide * a different sorting for the target activities displayed to the user. *

* This example simply displayed some social media apps at first and then keep the original * order returned by the system. */ public SocialTargetActivityComparatorProvider() { prioritizedPackageNames = new ArrayList<>(); prioritizedPackageNames.add("com.instagram.android"); // instagram prioritizedPackageNames.add("com.snapchat.android"); // snapchat prioritizedPackageNames.add("com.pinterest"); // pinterest prioritizedPackageNames.add("com.sgiggle.production"); // tango prioritizedPackageNames.add("jom.tencent.mm"); // wechat prioritizedPackageNames.add("jp.naver.line.android"); // line prioritizedPackageNames.add("com.whatsapp"); // what's app prioritizedPackageNames.add("com.google.android.talk"); // hangout prioritizedPackageNames.add("com.google.android.apps.plus"); // G+ prioritizedPackageNames.add("com.twitter.android"); // twitter prioritizedPackageNames.add("com.facebook.orca"); // FB prioritizedPackageNames.add("com.facebook.katana"); // FB } /** * Simple custom {@link TargetActivityComparatorProvider} used to illustrate how to provide * a different sorting for the target activities displayed to the user. *

* This example simply displayed some social media apps at first and then keep the original * order returned by the system. * * @param in parcel. */ protected SocialTargetActivityComparatorProvider(Parcel in) { prioritizedPackageNames = new ArrayList<>(); in.readStringList(prioritizedPackageNames); } @Override public Comparator provideComparator() { return new Comparator() { @Override public int compare(TargetActivity lhs, TargetActivity rhs) { int lhsPriority = prioritizedPackageNames.indexOf(lhs.getPackageName()); int rhsPriority = prioritizedPackageNames.indexOf(rhs.getPackageName()); if (lhsPriority != -1 && rhsPriority != -1) { return lhsPriority - rhsPriority; } else if (lhsPriority != -1) { return -1; } else if (rhsPriority != -1) { return 1; } else { return 0; // keep the original order if not present in the prioritized list. } } }; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeStringList(prioritizedPackageNames); } } ================================================ FILE: sample/src/main/res/anim/wiggle.xml ================================================ ================================================ FILE: sample/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: sample/src/main/res/layout/extra_provider_view.xml ================================================ ================================================ FILE: sample/src/main/res/layout/footer_view.xml ================================================ ================================================ FILE: sample/src/main/res/layout/fragment_dialog_extra_provider.xml ================================================ ================================================ FILE: sample/src/main/res/layout/header_view.xml ================================================ ================================================ FILE: sample/src/main/res/menu/activity_sample_menu.xml ================================================

================================================ FILE: sample/src/main/res/values/colors.xml ================================================ #5a69c6 #5a69c6 #5a69c6 ================================================ FILE: sample/src/main/res/values/dimens.xml ================================================ 16dp 16dp 80dp 16dp ================================================ FILE: sample/src/main/res/values/strings.xml ================================================ IntentShare Sample Default IconLoader Picasso IconLoader Glide IconLoader Default Sorting (recency) Custom Sorting (social first) Improves the sharing experience on Android with IntentShare. http://tvbarthel.github.io/IntentShare/ "IntentShare is a light open-source library that improves the sharing experience on Android applications. "IntentShare is a light open-source library that improves the sharing experience on Android applications.\n\nNowadays, sharing content is part of our daily life. Unfortunately, the Android framework tools do not provide a sharing experience which reaches all of our expectations. We decided to implement our own tool, IntentShare, that might improve the user experience of a sharing action by:\n\n\t • providing different extras according to the chosen target activity in order to take advantages of each target\'s specificities.\n\n\t • providing an easy way to track the selected target activities in order to improve/adapt the extras for a specific target.\n\n\t • providing a sorted target activity list based on the context of your app in order to take advantages of how people use it. Improves the sharing experience on Android with IntentShare. http://tvbarthel.github.io/IntentShare/ #android #sharing Learn more Dialog Title Shared Text Choose a target activity: @string/article_headline App name com.package.name overridden text overridden subject Disable Override Disable text Disable subject Disable image Overriding shared image not implemented yet in the sample. Add ExtraProvider Build-in extra providers Custom extra providers Facebook link Tweet Mail subject Mail body Default provider Target sorting Icon loading ================================================ FILE: sample/src/main/res/values/styles.xml ================================================ ================================================ FILE: sample/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: sample/src/main/res/xml/file_provider_paths.xml ================================================ ================================================ FILE: sample/src/test/java/fr/tvbarthel/intentsharesample/ExampleUnitTest.java ================================================ package fr.tvbarthel.intentsharesample; import org.junit.Test; import static org.junit.Assert.*; /** * To work on unit tests, switch the Test Artifact in the Build Variants view. */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: settings.gradle ================================================ include ':library', ':sample', ':picasso-loader', ':glide-loader'