├── sample
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── dimens.xml
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ ├── layout
│ │ │ │ ├── activity_provider.xml
│ │ │ │ ├── activity_normal.xml
│ │ │ │ └── activity_main.xml
│ │ │ └── menu
│ │ │ │ └── menu_actionprovideractivity.xml
│ │ ├── java
│ │ │ └── me
│ │ │ │ └── kentin
│ │ │ │ └── sample
│ │ │ │ ├── MainActivity.java
│ │ │ │ ├── NormalShareActivity.java
│ │ │ │ └── ActionProviderActivity.java
│ │ └── AndroidManifest.xml
│ └── androidTest
│ │ └── java
│ │ └── me
│ │ └── kentin
│ │ └── sample
│ │ └── ApplicationTest.java
├── build.gradle
└── proguard-rules.pro
├── yeti
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable
│ │ │ │ └── selectable_background.xml
│ │ │ ├── drawable-v21
│ │ │ │ └── selectable_background.xml
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ ├── attrs.xml
│ │ │ │ └── styles.xml
│ │ │ └── layout
│ │ │ │ ├── r_application.xml
│ │ │ │ └── yeti_resolver_list.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── me
│ │ │ └── kentin
│ │ │ └── yeti
│ │ │ ├── Yeti.java
│ │ │ ├── listener
│ │ │ └── OnShareListener.java
│ │ │ ├── activity
│ │ │ └── YetiShareActivity.java
│ │ │ ├── utils
│ │ │ ├── ChooserHistory.java
│ │ │ └── SharePackageHelper.java
│ │ │ ├── adapter
│ │ │ └── ResolverAdapter.java
│ │ │ ├── YetiActionProvider.java
│ │ │ └── view
│ │ │ └── ResolverDrawerLayout.java
│ └── androidTest
│ │ └── java
│ │ └── me
│ │ └── kentin
│ │ └── yeti
│ │ └── ApplicationTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .drone.yml
├── .travis.yml
├── .gitignore
├── gradlew.bat
├── README.md
└── gradlew
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/yeti/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':sample', ':yeti'
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quentin23soleil/Yeti/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quentin23soleil/Yeti/HEAD/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quentin23soleil/Yeti/HEAD/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quentin23soleil/Yeti/HEAD/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quentin23soleil/Yeti/HEAD/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/.drone.yml:
--------------------------------------------------------------------------------
1 | image: bradrydzewski/android:r23
2 | env:
3 | script:
4 | - ./gradlew clean build
5 | notify:
6 | email:
7 | recipients:
8 | - dommer.q@gmail.com
--------------------------------------------------------------------------------
/yeti/src/main/res/drawable/selectable_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | YetiSample
3 |
4 | Hello world!
5 | Settings
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/yeti/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 |
3 | jdk: oraclejdk7
4 |
5 | notifications:
6 | email: true
7 |
8 | android:
9 | components:
10 | - build-tools-22.0.1
11 | - android-22
12 | - extra-android-m2repository
13 |
14 | script: ./gradlew clean build
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
7 |
--------------------------------------------------------------------------------
/yeti/src/main/res/drawable-v21/selectable_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/yeti/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 600dp
4 | 36dp
5 | 16dp
6 | 48dp
7 |
--------------------------------------------------------------------------------
/yeti/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @android:color/white
4 | #ffeeeeee
5 | #ff303030
6 | #ff808080
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/yeti/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Yeti
3 | toto
4 | titi
5 | See all
6 | No application
7 | Share with
8 |
9 |
--------------------------------------------------------------------------------
/yeti/src/androidTest/java/me/kentin/yeti/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package me.kentin.yeti;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/sample/src/androidTest/java/me/kentin/sample/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package me.kentin.sample;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_provider.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
--------------------------------------------------------------------------------
/sample/src/main/res/menu/menu_actionprovideractivity.xml:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 22
5 | buildToolsVersion "22.0.1"
6 |
7 | defaultConfig {
8 | applicationId "me.kentin.yetisample"
9 | minSdkVersion 15
10 | targetSdkVersion 22
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile 'com.android.support:appcompat-v7:22.0.0'
25 | compile project(':yeti')
26 | }
27 |
--------------------------------------------------------------------------------
/sample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/kentin/Desktop/adt-bundle-mac-x86_64-20130522/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/yeti/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/kentin/Desktop/adt-bundle-mac-x86_64-20130522/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/yeti/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_normal.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
20 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
13 |
14 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/yeti/src/main/res/layout/r_application.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
24 |
--------------------------------------------------------------------------------
/yeti/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
16 |
17 |
18 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/sample/src/main/java/me/kentin/sample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package me.kentin.sample;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v7.app.ActionBarActivity;
6 | import android.view.View;
7 | import android.widget.Button;
8 |
9 | public class MainActivity extends ActionBarActivity {
10 |
11 | @Override
12 | protected void onCreate(Bundle savedInstanceState) {
13 | super.onCreate(savedInstanceState);
14 | setContentView(R.layout.activity_main);
15 |
16 | Button buttonActionProviderActivity = (Button) findViewById(R.id.main_actionprovider_share);
17 | buttonActionProviderActivity.setOnClickListener(new View.OnClickListener() {
18 | @Override
19 | public void onClick(View v) {
20 | Intent intent = new Intent(MainActivity.this, ActionProviderActivity.class);
21 | startActivity(intent);
22 | }
23 | });
24 |
25 | Button buttonNormalShareActivity = (Button) findViewById(R.id.main_share_button_normal);
26 | buttonNormalShareActivity.setOnClickListener(new View.OnClickListener() {
27 | @Override
28 | public void onClick(View v) {
29 | Intent intent = new Intent(MainActivity.this, NormalShareActivity.class);
30 | startActivity(intent);
31 | }
32 | });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io
2 |
3 | ### Gradle ###
4 | .gradle
5 | build/
6 |
7 | # Ignore Gradle GUI config
8 | gradle-app.setting
9 |
10 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
11 | !gradle-wrapper.jar
12 |
13 |
14 | ### Android ###
15 | # Built application files
16 | *.apk
17 | *.ap_
18 |
19 | # Files for the Dalvik VM
20 | *.dex
21 |
22 | # Java class files
23 | *.class
24 |
25 | # Generated files
26 | bin/
27 | gen/
28 |
29 | # Gradle files
30 | .gradle/
31 | build/
32 | /*/build/
33 |
34 | # Local configuration file (sdk path, etc)
35 | local.properties
36 |
37 | # Proguard folder generated by Eclipse
38 | proguard/
39 |
40 | # Log Files
41 | *.log
42 |
43 |
44 | ### Intellij ###
45 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
46 |
47 | *.iml
48 |
49 | ## Directory-based project format:
50 | .idea/
51 | # if you remove the above rule, at least ignore the following:
52 |
53 | # User-specific stuff:
54 | # .idea/workspace.xml
55 | # .idea/tasks.xml
56 | # .idea/dictionaries
57 |
58 | # Sensitive or high-churn files:
59 | # .idea/dataSources.ids
60 | # .idea/dataSources.xml
61 | # .idea/sqlDataSources.xml
62 | # .idea/dynamic.xml
63 | # .idea/uiDesigner.xml
64 |
65 | # Gradle:
66 | # .idea/gradle.xml
67 | # .idea/libraries
68 |
69 | # Mongo Explorer plugin:
70 | # .idea/mongoSettings.xml
71 |
72 | ## File-based project format:
73 | *.ipr
74 | *.iws
75 |
76 | ## Plugin-specific files:
77 |
78 | # IntelliJ
79 | out/
80 |
81 | # mpeltonen/sbt-idea plugin
82 | .idea_modules/
83 |
84 | # JIRA plugin
85 | atlassian-ide-plugin.xml
86 |
87 | # Crashlytics plugin (for Android Studio and IntelliJ)
88 | com_crashlytics_export_strings.xml
89 | crashlytics.properties
90 | crashlytics-build.properties
91 |
92 |
93 | ### OSX ###
94 | .DS_Store
95 | .AppleDouble
96 | .LSOverride
97 |
98 | # Icon must end with two \r
99 | Icon
100 |
101 |
102 | # Thumbnails
103 | ._*
104 |
105 | # Files that might appear on external disk
106 | .Spotlight-V100
107 | .Trashes
108 |
109 | # Directories potentially created on remote AFP share
110 | .AppleDB
111 | .AppleDesktop
112 | Network Trash Folder
113 | Temporary Items
114 | .apdisk
115 |
116 |
--------------------------------------------------------------------------------
/yeti/src/main/java/me/kentin/yeti/Yeti.java:
--------------------------------------------------------------------------------
1 | package me.kentin.yeti;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 |
6 | import java.util.Arrays;
7 | import java.util.List;
8 |
9 | import me.kentin.yeti.activity.YetiShareActivity;
10 | import me.kentin.yeti.listener.OnShareListener;
11 | import me.kentin.yeti.utils.SharePackageHelper;
12 |
13 | public class Yeti {
14 |
15 | private Context context;
16 |
17 | private List acceptableTypes;
18 |
19 | Yeti(Context context) {
20 | this.context = context;
21 | }
22 |
23 | public static Yeti with(Context context) {
24 | return new Yeti(context);
25 | }
26 |
27 | public Yeti only(SharePackageHelper.ApplicationType... types) {
28 | this.acceptableTypes = Arrays.asList(types);
29 | return this;
30 | }
31 |
32 | public Intent share(Intent shareIntent) {
33 | return YetiShareActivity.createIntent(context, shareIntent, acceptableTypes);
34 | }
35 |
36 | public void result(Intent intent, OnShareListener onShareListener) {
37 | if (onShareListener != null) {
38 |
39 | SharePackageHelper.ApplicationType applicationType = new SharePackageHelper().getApplicationType(intent);
40 |
41 | if (applicationType == SharePackageHelper.ApplicationType.Twitter) {
42 | if (!onShareListener.shareWithTwitter(intent)) {
43 | context.startActivity(intent);
44 | }
45 | } else if (applicationType == SharePackageHelper.ApplicationType.Facebook) {
46 | if (!onShareListener.shareWithFacebook(intent)) {
47 | context.startActivity(intent);
48 | }
49 | } else if (applicationType == SharePackageHelper.ApplicationType.GooglePlus) {
50 | if (!onShareListener.shareWithGooglePlus(intent)) {
51 | context.startActivity(intent);
52 | }
53 | } else if (applicationType == SharePackageHelper.ApplicationType.Email) {
54 | if (!onShareListener.shareWithEmail(intent)) {
55 | context.startActivity(intent);
56 | }
57 | } else if (applicationType == SharePackageHelper.ApplicationType.Sms) {
58 | if (!onShareListener.shareWithSms(intent)) {
59 | context.startActivity(intent);
60 | }
61 | } else {
62 | if (!onShareListener.shareWithOther(intent)) {
63 | context.startActivity(intent);
64 | }
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/yeti/src/main/java/me/kentin/yeti/listener/OnShareListener.java:
--------------------------------------------------------------------------------
1 | package me.kentin.yeti.listener;
2 |
3 | import android.content.Intent;
4 |
5 | public abstract class OnShareListener {
6 |
7 | /**
8 | * Called when a share target has been selected. The client can
9 | * decide whether to perform some action before the sharing is
10 | * actually performed OR handle the action itself.
11 | *
12 | * @param intent The intent for launching the chosen share target.
13 | * @return Return true if you have handled the intent.
14 | */
15 | public abstract boolean shareWithFacebook(Intent intent);
16 |
17 | /**
18 | * Called when a share target has been selected. The client can
19 | * decide whether to perform some action before the sharing is
20 | * actually performed OR handle the action itself.
21 | *
22 | * @param intent The intent for launching the chosen share target.
23 | * @return Return true if you have handled the intent.
24 | */
25 | public abstract boolean shareWithTwitter(Intent intent);
26 |
27 | /**
28 | * Called when a share target has been selected. The client can
29 | * decide whether to perform some action before the sharing is
30 | * actually performed OR handle the action itself.
31 | *
32 | * @param intent The intent for launching the chosen share target.
33 | * @return Return true if you have handled the intent.
34 | */
35 | public abstract boolean shareWithGooglePlus(Intent intent);
36 |
37 | /**
38 | * Called when a share target has been selected. The client can
39 | * decide whether to perform some action before the sharing is
40 | * actually performed OR handle the action itself.
41 | *
42 | * @param intent The intent for launching the chosen share target.
43 | * @return Return true if you have handled the intent.
44 | */
45 | public abstract boolean shareWithEmail(Intent intent);
46 |
47 | /**
48 | * Called when a share target has been selected. The client can
49 | * decide whether to perform some action before the sharing is
50 | * actually performed OR handle the action itself.
51 | *
52 | * @param intent The intent for launching the chosen share target.
53 | * @return Return true if you have handled the intent.
54 | */
55 | public abstract boolean shareWithSms(Intent intent);
56 |
57 | /**
58 | * Called when a share target has been selected. The client can
59 | * decide whether to perform some action before the sharing is
60 | * actually performed OR handle the action itself.
61 | *
62 | * @param intent The intent for launching the chosen share target.
63 | * @return Return true if you have handled the intent.
64 | */
65 | public abstract boolean shareWithOther(Intent intent);
66 | }
67 |
--------------------------------------------------------------------------------
/yeti/src/main/java/me/kentin/yeti/activity/YetiShareActivity.java:
--------------------------------------------------------------------------------
1 | package me.kentin.yeti.activity;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.pm.ResolveInfo;
7 | import android.os.Bundle;
8 | import android.support.v7.widget.LinearLayoutManager;
9 | import android.support.v7.widget.RecyclerView;
10 | import android.view.View;
11 | import android.widget.TextView;
12 |
13 | import java.util.List;
14 |
15 | import me.kentin.yeti.R;
16 | import me.kentin.yeti.adapter.ResolverAdapter;
17 | import me.kentin.yeti.view.ResolverDrawerLayout;
18 |
19 | import static me.kentin.yeti.utils.SharePackageHelper.ApplicationType;
20 |
21 | public class YetiShareActivity extends Activity implements ResolverAdapter.OnItemClickedListener {
22 |
23 | public static final String EXTRA_SHARE_INTENT = "shareIntent";
24 |
25 | private static List acceptableTypes;
26 |
27 | private Intent shareIntent;
28 |
29 | public static Intent createIntent(Context context, Intent shareIntent, List acceptableTypes) {
30 | YetiShareActivity.acceptableTypes = acceptableTypes;
31 | Intent intent = new Intent(context, YetiShareActivity.class);
32 | intent.putExtra(EXTRA_SHARE_INTENT, shareIntent);
33 | return intent;
34 | }
35 |
36 | @Override
37 | protected void onCreate(Bundle savedInstanceState) {
38 | super.onCreate(savedInstanceState);
39 | setContentView(R.layout.yeti_resolver_list);
40 |
41 | ResolverDrawerLayout resolverDrawerLayout = (ResolverDrawerLayout) findViewById(R.id.resolver_contentPanel);
42 | TextView title = (TextView) findViewById(R.id.resolver_title);
43 | resolverDrawerLayout.setOnClickOutsideListener(new View.OnClickListener() {
44 | @Override
45 | public void onClick(View v) {
46 | finish();
47 | }
48 | });
49 |
50 | RecyclerView recyclerview = (RecyclerView) findViewById(R.id.resolver_recyclerview);
51 | recyclerview.setHasFixedSize(true);
52 | recyclerview.setLayoutManager(new LinearLayoutManager(this));
53 |
54 | ResolverAdapter adapter = createAdapter();
55 |
56 | recyclerview.setAdapter(adapter);
57 |
58 | resolverDrawerLayout.setScrollableChildView(recyclerview);
59 |
60 | title.setText(getString(R.string.whichSendApplication));
61 | }
62 |
63 | private ResolverAdapter createAdapter() {
64 | Intent intent = getIntent();
65 | shareIntent = intent.getParcelableExtra(EXTRA_SHARE_INTENT);
66 | return new ResolverAdapter(this, shareIntent, this, acceptableTypes);
67 | }
68 |
69 | @Override
70 | public void onItemClicked(Intent intent, ResolveInfo resolveInfo) {
71 | setResult(RESULT_OK, intent);
72 | finish();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/yeti/src/main/res/layout/yeti_resolver_list.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
17 |
18 |
23 |
24 |
35 |
36 |
48 |
49 |
50 |
54 |
55 |
56 |
63 |
64 |
--------------------------------------------------------------------------------
/sample/src/main/java/me/kentin/sample/NormalShareActivity.java:
--------------------------------------------------------------------------------
1 | package me.kentin.sample;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v7.app.ActionBarActivity;
6 | import android.view.View;
7 | import android.widget.Button;
8 |
9 | import me.kentin.yeti.Yeti;
10 | import me.kentin.yeti.listener.OnShareListener;
11 |
12 | public class NormalShareActivity extends ActionBarActivity {
13 |
14 | private static final int REQUEST_CODE_YETI = 0;
15 | private Yeti yeti;
16 | private Intent shareIntent;
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | setContentView(R.layout.activity_normal);
22 |
23 | shareIntent = new Intent();
24 | shareIntent.setAction(Intent.ACTION_SEND);
25 | shareIntent.setType("text/plain");
26 | shareIntent.putExtra(Intent.EXTRA_TEXT, "This is the text BEFORE you change it bro. (if you want huh)");
27 |
28 | yeti = Yeti.with(this);
29 |
30 |
31 | Button button = (Button) findViewById(R.id.normal_share_button);
32 | button.setOnClickListener(new View.OnClickListener() {
33 | @Override
34 | public void onClick(View v) {
35 | startActivityForResult(yeti.share(shareIntent), REQUEST_CODE_YETI);
36 | }
37 | });
38 |
39 | }
40 |
41 | @Override
42 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
43 | super.onActivityResult(requestCode, resultCode, data);
44 |
45 | if (resultCode == RESULT_OK && requestCode == REQUEST_CODE_YETI) {
46 | yeti.result(data, shareListener);
47 | }
48 | }
49 |
50 | OnShareListener shareListener = new OnShareListener() {
51 | @Override
52 | public boolean shareWithFacebook(Intent intent) {
53 | return false;
54 | }
55 |
56 | @Override
57 | public boolean shareWithTwitter(Intent intent) {
58 |
59 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TASK);
60 | intent.removeExtra(Intent.EXTRA_TEXT);
61 | intent.putExtra(Intent.EXTRA_TEXT, "BOOM this is what's actually going to be shared.");
62 |
63 | startActivity(intent);
64 | return true; // true = you have changed the intent, prevent the system from firing the old intent
65 | }
66 |
67 | @Override
68 | public boolean shareWithGooglePlus(Intent intent) {
69 | return false; // false = let the system handle it the usual way
70 | }
71 |
72 | @Override
73 | public boolean shareWithEmail(Intent intent) {
74 | return false;
75 | }
76 |
77 | @Override
78 | public boolean shareWithSms(Intent intent) {
79 | return false;
80 | }
81 |
82 | @Override
83 | public boolean shareWithOther(Intent intent) {
84 | return false;
85 | }
86 | };
87 | }
88 |
--------------------------------------------------------------------------------
/yeti/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 | apply plugin: "com.jfrog.bintray"
4 |
5 | version = "1.1.0"
6 |
7 | android {
8 | compileSdkVersion 22
9 | buildToolsVersion "22.0.1"
10 |
11 | defaultConfig {
12 | minSdkVersion 15
13 | targetSdkVersion 22
14 | versionCode 1
15 | versionName "1.0"
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | compile fileTree(dir: 'libs', include: ['*.jar'])
27 | compile 'com.android.support:appcompat-v7:22.1.1'
28 | compile 'com.android.support:recyclerview-v7:22.1.1'
29 | }
30 |
31 | // jcenter & maven config
32 |
33 | def siteUrl = 'https://github.com/dommerq/Yeti'
34 | def gitUrl = 'https://github.com/dommerq/Yeti.git'
35 | group = "me.kentin" // Maven Group ID for the artifact
36 |
37 |
38 | install {
39 | repositories.mavenInstaller {
40 | // This generates POM.xml with proper parameters
41 | pom {
42 | project {
43 | packaging 'aar'
44 |
45 | name = "yeti"
46 | // Add your description here
47 | description = 'So fresh. Yeti helps you share to others.'
48 | url siteUrl
49 |
50 | // Set your license
51 | licenses {
52 | license {
53 | name 'The Apache Software License, Version 2.0'
54 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
55 | }
56 | }
57 |
58 | developers {
59 | developer {
60 | id 'dommerq'
61 | name 'Quentin Dommerc'
62 | email 'dommer.q@gmail.com'
63 | }
64 | }
65 | scm {
66 | connection gitUrl
67 | developerConnection gitUrl
68 | url siteUrl
69 |
70 | }
71 | }
72 | }
73 | }
74 | }
75 |
76 | task sourcesJar(type: Jar) {
77 | from android.sourceSets.main.java.srcDirs
78 | classifier = 'sources'
79 | }
80 |
81 | artifacts {
82 | archives sourcesJar
83 | }
84 |
85 | bintray {
86 | Properties properties = new Properties()
87 | File file = new File("local.properties")
88 | if (file.exists()) {
89 | properties.load(project.rootProject.file('local.properties').newDataInputStream())
90 |
91 | user = properties.getProperty("bintray.user")
92 | key = properties.getProperty("bintray.apikey")
93 |
94 | configurations = ['archives']
95 | pkg {
96 | repo = "Maven"
97 | name = "Yeti"
98 | websiteUrl = siteUrl
99 | vcsUrl = gitUrl
100 | licenses = ["Apache-2.0"]
101 | publish = true
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/yeti/src/main/java/me/kentin/yeti/utils/ChooserHistory.java:
--------------------------------------------------------------------------------
1 | package me.kentin.yeti.utils;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | public class ChooserHistory {
10 | private static final String KEY_HISTORY = "history";
11 |
12 | private static final char SEPARATOR_KEY_VALUE = '|';
13 |
14 | private static final char SEPARATOR_ITEMS = '#';
15 |
16 | HashMap mHistoryMap;
17 |
18 | public static ChooserHistory fromSettings(Context context) {
19 | return fromSettings(getPreferences(context).getString(KEY_HISTORY, ""));
20 | }
21 |
22 | public static ChooserHistory fromSettings(String saveString) {
23 | ChooserHistory history = new ChooserHistory();
24 | if (saveString == null || saveString.length() == 0) {
25 | return history;
26 | }
27 | StringBuilder packageName = new StringBuilder();
28 | StringBuilder number = new StringBuilder();
29 | int i = 0;
30 | while (i < saveString.length()) {
31 | char character = saveString.charAt(i);
32 | if (character == SEPARATOR_KEY_VALUE) {
33 | // skip separator icon
34 | while (++i < saveString.length()) {
35 | character = saveString.charAt(i);
36 | if (character == SEPARATOR_ITEMS) {
37 | break;
38 | }
39 | number.append(character);
40 | }
41 | history.mHistoryMap.put(packageName.toString(), Integer.valueOf(number.toString()));
42 | packageName.delete(0, packageName.length());
43 | number.delete(0, number.length());
44 | } else {
45 | packageName.append(character);
46 | }
47 | ++i;
48 | }
49 | return history;
50 | }
51 |
52 | public ChooserHistory() {
53 | mHistoryMap = new HashMap<>();
54 | }
55 |
56 | public void add(String packageName) {
57 | Integer currentCount = mHistoryMap.get(packageName);
58 | if (currentCount == null) {
59 | mHistoryMap.put(packageName, 1);
60 | } else {
61 | mHistoryMap.put(packageName, currentCount + 1);
62 | }
63 | }
64 |
65 | public void save(Context context) {
66 | SharedPreferences prefs = getPreferences(context);
67 | prefs.edit().putString(KEY_HISTORY, getAsSaveString()).apply();
68 | }
69 |
70 | private String getAsSaveString() {
71 | StringBuilder saveString = new StringBuilder();
72 | for (Map.Entry entry : mHistoryMap.entrySet()) {
73 | saveString.append(entry.getKey());
74 | saveString.append(SEPARATOR_KEY_VALUE);
75 | saveString.append(entry.getValue().toString());
76 | saveString.append(SEPARATOR_ITEMS);
77 | }
78 | if (saveString.length() > 0) {
79 | saveString.deleteCharAt(saveString.length() - 1);
80 | }
81 | return saveString.toString();
82 | }
83 |
84 | private static SharedPreferences getPreferences(Context context) {
85 | return context.getSharedPreferences("bs_chooser", Context.MODE_PRIVATE);
86 | }
87 |
88 | public int get(String packageName) {
89 | Integer count = mHistoryMap.get(packageName);
90 | return count != null ? count : 0;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/sample/src/main/java/me/kentin/sample/ActionProviderActivity.java:
--------------------------------------------------------------------------------
1 | package me.kentin.sample;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v4.view.MenuItemCompat;
6 | import android.support.v7.app.ActionBarActivity;
7 | import android.view.Menu;
8 | import android.view.MenuItem;
9 |
10 | import me.kentin.yeti.YetiActionProvider;
11 | import me.kentin.yeti.listener.OnShareListener;
12 |
13 | public class ActionProviderActivity extends ActionBarActivity {
14 |
15 | private YetiActionProvider yetiActionProvider;
16 | private Intent shareIntent;
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | setContentView(R.layout.activity_provider);
22 |
23 | shareIntent = new Intent();
24 | shareIntent.setAction(Intent.ACTION_SEND);
25 | shareIntent.setType("text/plain");
26 | shareIntent.putExtra(Intent.EXTRA_TEXT, "This is the text BEFORE you change it bro. (if you want huh)");
27 | setShareIntent(shareIntent);
28 | }
29 |
30 | @Override
31 | public boolean onCreateOptionsMenu(Menu menu) {
32 | // Inflate the menu; this adds items to the action bar/toolbar if it is present.
33 | getMenuInflater().inflate(R.menu.menu_actionprovideractivity, menu);
34 |
35 | // Locate MenuItem with YetiActionProvider
36 | MenuItem item = menu.findItem(R.id.menu_item_share);
37 |
38 | // Fetch and store ShareActionProvider
39 | yetiActionProvider = (YetiActionProvider) MenuItemCompat.getActionProvider(item);
40 |
41 | // You don't have to Override everything though, just what you want
42 | yetiActionProvider.setOnShareListener(shareListener);
43 | yetiActionProvider.setShareIntent(shareIntent);
44 |
45 | return true;
46 | }
47 |
48 | private void setShareIntent(Intent shareIntent) {
49 | if (yetiActionProvider != null) {
50 | yetiActionProvider.setShareIntent(shareIntent);
51 | }
52 | }
53 |
54 | @Override
55 | public boolean onOptionsItemSelected(MenuItem item) {
56 |
57 | return super.onOptionsItemSelected(item);
58 | }
59 |
60 | OnShareListener shareListener = new OnShareListener() {
61 | @Override
62 | public boolean shareWithFacebook(Intent intent) {
63 | return false;
64 | }
65 |
66 | @Override
67 | public boolean shareWithTwitter(Intent intent) {
68 |
69 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TASK);
70 | intent.removeExtra(Intent.EXTRA_TEXT);
71 | intent.putExtra(Intent.EXTRA_TEXT, "BOOM this is what's actually going to be shared.");
72 |
73 | startActivity(intent);
74 | return true; // true = you have changed the intent, prevent the system from firing the old intent
75 | }
76 |
77 | @Override
78 | public boolean shareWithGooglePlus(Intent intent) {
79 | return false; // false = let the system handle it the usual way
80 | }
81 |
82 | @Override
83 | public boolean shareWithEmail(Intent intent) {
84 | return false;
85 | }
86 |
87 | @Override
88 | public boolean shareWithSms(Intent intent) {
89 | return false;
90 | }
91 |
92 | @Override
93 | public boolean shareWithOther(Intent intent) {
94 | return false;
95 | }
96 | };
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Yeti - Control what you share [](https://travis-ci.org/dommerq/Yeti) [](https://bintray.com/dommerq/Maven/Yeti/_latestVersion)
2 |
3 | Do you want to customize what you share, depending on what the user wants to share it with?
4 |
5 | Let's say you want to add a "via @theTwitterAccountOfYourProduct" if the content is being shared with a Twitter client.
6 |
7 |
8 | ###**BOOM! That's what Yeti does.**
9 |
10 |
11 | ## Add to your project
12 |
13 | **Gradle (jcenter)** [](https://bintray.com/dommerq/Maven/Yeti/_latestVersion)
14 |
15 | ```groovy
16 | compile 'me.kentin:yeti:1.1.0'
17 | ```
18 | **Maven**
19 |
20 | ```
21 | Who uses that? Seriously.
22 | ```
23 |
24 | Or you can clone it and import `yeti` as a module.
25 |
26 |
27 | ## Usage
28 |
29 | Two options. Because there are 2 ways to share on Android.
30 |
31 | - using a shareActionProvider (the menu thing)
32 | - using a normal intent, with ACTION_SEND.
33 |
34 | For both you're going to need an Intent anyway.
35 |
36 |
37 | ```java
38 | shareIntent = new Intent();
39 | shareIntent.setAction(Intent.ACTION_SEND);
40 | shareIntent.setType("text/plain");
41 | shareIntent.putExtra(Intent.EXTRA_TEXT, "This is the text BEFORE you change it bro. (if you want huh)");
42 | ```
43 |
44 |
45 | ### ShareActionProvider
46 |
47 | Add the action provider to your menu xml.
48 | ```xml
49 |
55 |
56 | ```
57 |
58 | And set the intent in your activity:
59 |
60 | ```java
61 |
62 | @Override
63 | public boolean onCreateOptionsMenu(Menu menu) {
64 | // Inflate the menu; this adds items to the action bar/toolbar if it is present.
65 | getMenuInflater().inflate(R.menu.menu_actionprovideractivity, menu);
66 |
67 | // Locate MenuItem with YetiActionProvider
68 | MenuItem item = menu.findItem(R.id.menu_item_share);
69 |
70 | // Fetch and store ShareActionProvider
71 | yetiActionProvider = (YetiActionProvider) MenuItemCompat.getActionProvider(item);
72 |
73 | // You don't have to Override everything though, just what you want
74 | yetiActionProvider.setOnShareListener(shareListener);
75 | yetiActionProvider.setShareIntent(shareIntent);
76 |
77 | return true;
78 | }
79 |
80 | ```
81 |
82 | ### Normal way
83 |
84 | Create the **Yeti intent** and then, `startActivityForResult` with it.
85 |
86 | ```java
87 | Yeti yeti = Yeti.with(activity);
88 | Intent intent = yeti.share(yourShareIntent)
89 | startActivityForResult(yeti.share(shareIntent), REQUEST_CODE_YETI);
90 | ```
91 |
92 | Override `onActivityResult` and use **THE SAME INSTANCE OF YETI** with the data.
93 |
94 |
95 | ```java
96 |
97 | @Override
98 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
99 | super.onActivityResult(requestCode, resultCode, data);
100 | if (resultCode == RESULT_OK && requestCode == REQUEST_CODE_YETI) {
101 | yeti.result(data, shareListener);
102 | }
103 | }
104 | ```
105 | `shareListener` here is just a [OnShareListener](https://github.com/dommerq/Yeti/blob/master/library/src/main/java/me/kentin/yeti/listener/OnShareListener.java).
106 |
107 |
108 | ## Todo
109 | - "I want to share with Twitter, and Facebook only. Filter the apps pretty please." ---> Done in 1.1.0
110 | - Want something? Tell me. :)
111 |
112 | ## Contributors
113 | - [geralt-encore](https://github.com/geralt-encore)
114 |
115 | ## License
116 |
117 | Yeti is licensed under the Apache 2 license (you can use it in commercial and open source projects).
118 |
119 | ```
120 | Copyright 2015 Quentin Dommerc
121 |
122 | Licensed under the Apache License, Version 2.0 (the "License");
123 | you may not use this file except in compliance with the License.
124 | You may obtain a copy of the License at
125 |
126 | http://www.apache.org/licenses/LICENSE-2.0
127 |
128 | Unless required by applicable law or agreed to in writing, software
129 | distributed under the License is distributed on an "AS IS" BASIS,
130 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131 | Anyway no one read this, so why would I care having meaningful License.
132 | See the License for the specific language governing permissions and
133 | limitations under the License.
134 | ```
135 |
--------------------------------------------------------------------------------
/yeti/src/main/java/me/kentin/yeti/utils/SharePackageHelper.java:
--------------------------------------------------------------------------------
1 | package me.kentin.yeti.utils;
2 |
3 | import android.content.Intent;
4 | import android.content.pm.ResolveInfo;
5 |
6 | import java.util.List;
7 |
8 | public class SharePackageHelper {
9 |
10 | String[] twitter = new String[] {
11 | "com.klinker.android.twitter_l",
12 | "org.mariotaku.twidere",
13 | "com.levelup.touiteur",
14 | "jp.r246.twicca",
15 | "com.jv.materialfalcon",
16 | "com.twitter.android",
17 | "com.handmark.tweetcaster",
18 | "it.mvilla.android.fenix",
19 | "com.handmark.tweetcaster.premium",
20 | "com.twidroid",
21 | "com.echofon",
22 | "com.dwdesign.tweetings",
23 | "com.levelup.touiteurpremium",
24 | "com.jv.falcon",
25 | "com.saulmm.tweetwear",
26 | "com.seesmic",
27 | "net.sinproject.android.tweecha",
28 | "de.vanita5.twittnuker",
29 | "com.happydroid.tweetline.donate",
30 | "com.happydroid.tweetline",
31 | "com.handlerexploit.tweedle",
32 | "me.kentin.sharetest"
33 | };
34 |
35 | String[] facebook = new String[] {
36 | "com.facebook.katana",
37 | "com.facebook.lite",
38 | "app.fastfacebook.com",
39 | "com.androdb.fastlitefb",
40 | "com.danvelazco.fbwrapper"
41 | };
42 |
43 | String[] email = new String[] {
44 | "com.my.mail",
45 | "com.mail.emails",
46 | "com.yahoo.mobile.client.android.mail",
47 | "com.trtf.blue",
48 | "com.cloudmagic.mail",
49 | "com.email.email",
50 | "com.sonyericsson.extras.liveware.extension.mail",
51 | "com.google.android.gm",
52 | "com.mailboxapp",
53 | "com.fsck.k9",
54 | "com.google.android.apps.inbox",
55 | "com.boxer.email",
56 | "com.clearhub.wl",
57 | "com.google.android.email"
58 | };
59 |
60 | String[] googlePlus = new String[] {
61 | "com.google.android.apps.plus"
62 | };
63 |
64 | String[] sms = new String[] {
65 | "com.textra",
66 | "com.jb.gosms",
67 | "com.handcent.nextsms",
68 | "com.hellotext.hello",
69 | "com.p1.chompsms",
70 | "com.google.android.apps.messaging",
71 | "com.klinker.android.evolve_sms",
72 | "fr.slvn.mms",
73 | "com.android.mms",
74 | "com.google.android.talk",
75 | "com.facebook.orca",
76 | "com.whatsapp",
77 | "com.tencent.mm",
78 | "com.viber.voip"
79 | };
80 |
81 | public ApplicationType getApplicationType(Intent intent) {
82 | String packageName = intent.getComponent().getPackageName();
83 |
84 | for (String twitterApp : twitter) {
85 | if (twitterApp.equals(packageName)) {
86 | return ApplicationType.Twitter;
87 | }
88 | }
89 | for (String facebookApp : facebook) {
90 | if (facebookApp.equals(packageName)) {
91 | return ApplicationType.Facebook;
92 | }
93 | }
94 | for (String emailApp : email) {
95 | if (emailApp.equals(packageName)) {
96 | return ApplicationType.Email;
97 | }
98 | }
99 | for (String googlePlusApp : googlePlus) {
100 | if (googlePlusApp.equals(packageName)) {
101 | return ApplicationType.GooglePlus;
102 | }
103 | }
104 | for (String smsApp : sms) {
105 | if (smsApp.equals(packageName)) {
106 | return ApplicationType.Sms;
107 | }
108 | }
109 | return ApplicationType.Other;
110 | }
111 |
112 | public boolean isIntentAcceptable(ResolveInfo resolveInfo, List acceptableTypes) {
113 | if (acceptableTypes == null) {
114 | return true;
115 | }
116 | for (ApplicationType type : acceptableTypes) {
117 | String[] packageNames = getPackageNamesByType(type);
118 | if (packageNames != null) {
119 | for (String packageName : packageNames) {
120 | if (resolveInfo.activityInfo.packageName.equals(packageName)) {
121 | return true;
122 | }
123 | }
124 | }
125 | }
126 |
127 | return false;
128 | }
129 |
130 | private String[] getPackageNamesByType(ApplicationType type) {
131 | switch (type) {
132 | case Twitter:
133 | return twitter;
134 | case Facebook:
135 | return facebook;
136 | case Email:
137 | return email;
138 | case GooglePlus:
139 | return googlePlus;
140 | case Sms:
141 | return sms;
142 | default:
143 | return null;
144 | }
145 | }
146 |
147 | public enum ApplicationType {
148 | Twitter,
149 | Facebook,
150 | Email,
151 | GooglePlus,
152 | Sms,
153 | Other
154 |
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/yeti/src/main/java/me/kentin/yeti/adapter/ResolverAdapter.java:
--------------------------------------------------------------------------------
1 | package me.kentin.yeti.adapter;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.content.pm.ActivityInfo;
6 | import android.content.pm.ApplicationInfo;
7 | import android.content.pm.LabeledIntent;
8 | import android.content.pm.PackageManager;
9 | import android.content.pm.ResolveInfo;
10 | import android.graphics.drawable.Drawable;
11 | import android.os.AsyncTask;
12 | import android.support.v7.widget.RecyclerView;
13 | import android.util.Log;
14 | import android.view.LayoutInflater;
15 | import android.view.View;
16 | import android.view.ViewGroup;
17 | import android.widget.TextView;
18 |
19 | import java.lang.ref.WeakReference;
20 | import java.text.Collator;
21 | import java.util.ArrayList;
22 | import java.util.Collections;
23 | import java.util.Comparator;
24 | import java.util.HashMap;
25 | import java.util.List;
26 |
27 | import me.kentin.yeti.R;
28 | import me.kentin.yeti.utils.ChooserHistory;
29 | import me.kentin.yeti.utils.SharePackageHelper;
30 |
31 | public class ResolverAdapter extends RecyclerView.Adapter {
32 |
33 | private static final String TAG = ResolverAdapter.class.getSimpleName();
34 |
35 | private final ResolverComparator comparator;
36 |
37 | private Intent shareIntent;
38 |
39 | private final PackageManager packageManager;
40 |
41 | private final int iconSize;
42 |
43 | private ArrayList intents;
44 |
45 | private List items;
46 |
47 | private OnItemClickedListener onItemClickedListener;
48 |
49 | private HashMap priorities;
50 |
51 | private ChooserHistory history;
52 |
53 | private SharePackageHelper sharePackageHelper;
54 |
55 | private List acceptableTypes;
56 |
57 | public ResolverAdapter(Context context, Intent shareIntent, OnItemClickedListener listener, List acceptableTypes) {
58 | packageManager = context.getPackageManager();
59 | iconSize = context.getResources().getDimensionPixelSize(R.dimen.item_share_icon);
60 | this.shareIntent = shareIntent;
61 | this.acceptableTypes = acceptableTypes;
62 | comparator = new ResolverComparator(context);
63 | sharePackageHelper = new SharePackageHelper();
64 | setOnItemClickedListener(listener);
65 | }
66 |
67 | @Override
68 | public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
69 | View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.r_application, viewGroup, false);
70 | return new ViewHolder(view);
71 | }
72 |
73 | @Override
74 | public void onBindViewHolder(ViewHolder holder, final int position) {
75 | ensureListBuilt();
76 |
77 | final DisplayResolveInfoWithIntent item = items.get(position);
78 |
79 | holder.text.setText(item.getLabel(packageManager));
80 | holder.text.setCompoundDrawables(null, null, null, null);
81 |
82 | new IconLoaderTask(item, holder.text).execute();
83 |
84 | holder.itemView.setOnClickListener(new View.OnClickListener() {
85 | @Override
86 | public void onClick(View v) {
87 | onItemClickedListener.onItemClicked(item.intent, item.resolveInfo);
88 | }
89 | });
90 | }
91 |
92 | @Override
93 | public int getItemCount() {
94 | ensureListBuilt();
95 | return items.size();
96 | }
97 |
98 | public void setOnItemClickedListener(OnItemClickedListener listener) {
99 | onItemClickedListener = listener;
100 | }
101 |
102 | private void rebuildItems() {
103 | items = null;
104 | notifyDataSetChanged();
105 | }
106 |
107 | private void ensureListBuilt() {
108 | if (items == null) {
109 | buildList();
110 | }
111 | }
112 |
113 | private void buildList() {
114 | if (intents != null) {
115 | items = new ArrayList<>(intents.size());
116 | for (Intent intent : intents) {
117 | // new Intent to get a normal intent in case of a LabeledIntent
118 | ActivityInfo ai = new Intent(intent)
119 | .resolveActivityInfo(packageManager, PackageManager.GET_ACTIVITIES);
120 | if (ai == null) {
121 | Log.w(TAG, "No activity found for " + intent);
122 | continue;
123 | }
124 | ResolveInfo resolveInfo = new ResolveInfo();
125 | resolveInfo.activityInfo = ai;
126 | if (intent instanceof LabeledIntent) {
127 | LabeledIntent labeledIntent = (LabeledIntent) intent;
128 | resolveInfo.resolvePackageName = labeledIntent.getSourcePackage();
129 | resolveInfo.labelRes = labeledIntent.getLabelResource();
130 | resolveInfo.nonLocalizedLabel = labeledIntent.getNonLocalizedLabel();
131 | resolveInfo.icon = labeledIntent.getIconResource();
132 | }
133 |
134 | if (resolveInfo.icon == 0 || resolveInfo.labelRes == 0) {
135 | try {
136 | ApplicationInfo info = packageManager.getApplicationInfo(
137 | intent.getPackage(), 0);
138 | if (resolveInfo.icon == 0) {
139 | resolveInfo.icon = info.icon;
140 | }
141 | if (resolveInfo.labelRes == 0) {
142 | resolveInfo.labelRes = info.labelRes;
143 | }
144 | } catch (PackageManager.NameNotFoundException ignored) {
145 | }
146 | }
147 | if (sharePackageHelper.isIntentAcceptable(resolveInfo, acceptableTypes)) {
148 | items.add(new DisplayResolveInfoWithIntent(resolveInfo, intent));
149 | }
150 | }
151 | } else {
152 | List resolveInfos = packageManager
153 | .queryIntentActivities(shareIntent, PackageManager.GET_ACTIVITIES);
154 | items = new ArrayList<>(resolveInfos.size());
155 | for (ResolveInfo resolveInfo : resolveInfos) {
156 | Intent intent = new Intent(shareIntent);
157 | intent.setClassName(resolveInfo.activityInfo.packageName,
158 | resolveInfo.activityInfo.name);
159 | if (sharePackageHelper.isIntentAcceptable(resolveInfo, acceptableTypes)) {
160 | items.add(new DisplayResolveInfoWithIntent(resolveInfo, intent));
161 | }
162 | }
163 | }
164 |
165 | Collections.sort(items, comparator);
166 | }
167 |
168 | private class DisplayResolveInfoWithIntent {
169 |
170 | private final Intent intent;
171 |
172 | private ResolveInfo resolveInfo;
173 |
174 | private CharSequence label;
175 |
176 | private Drawable icon;
177 |
178 | public DisplayResolveInfoWithIntent(ResolveInfo resolveInfo, Intent intent) {
179 | this.resolveInfo = resolveInfo;
180 | this.intent = intent;
181 | }
182 |
183 | public CharSequence getLabel(PackageManager pm) {
184 | if (label == null) {
185 | label = resolveInfo.loadLabel(pm);
186 | }
187 | return label;
188 | }
189 |
190 | public Drawable getIcon(PackageManager pm) {
191 | if (icon == null) {
192 | icon = resolveInfo.loadIcon(pm);
193 | }
194 | return icon;
195 | }
196 | }
197 |
198 | class ResolverComparator implements Comparator {
199 |
200 | private final Collator collator;
201 |
202 | public ResolverComparator(Context context) {
203 | collator = Collator.getInstance(context.getResources().getConfiguration().locale);
204 | }
205 |
206 | @Override
207 | public int compare(DisplayResolveInfoWithIntent lhs, DisplayResolveInfoWithIntent rhs) {
208 | if (history != null) {
209 | int leftCount = history.get(lhs.resolveInfo.activityInfo.packageName);
210 | int rightCount = history.get(rhs.resolveInfo.activityInfo.packageName);
211 | if (leftCount != rightCount) {
212 | return rightCount - leftCount;
213 | }
214 | }
215 |
216 | if (priorities != null) {
217 | int leftPriority = getPriority(lhs);
218 | int rightPriority = getPriority(rhs);
219 | if (leftPriority != rightPriority) {
220 | return rightPriority - leftPriority;
221 | }
222 | }
223 |
224 | CharSequence sa = lhs.getLabel(packageManager);
225 | if (sa == null) {
226 | sa = lhs.resolveInfo.activityInfo.name;
227 | }
228 | CharSequence sb = rhs.getLabel(packageManager);
229 | if (sb == null) {
230 | sb = rhs.resolveInfo.activityInfo.name;
231 | }
232 |
233 | return collator.compare(sa.toString(), sb.toString());
234 | }
235 |
236 | private int getPriority(DisplayResolveInfoWithIntent lhs) {
237 | Integer integer = priorities.get(lhs.resolveInfo.activityInfo.packageName);
238 | return integer != null ? integer : 0;
239 | }
240 | }
241 |
242 | public static class ViewHolder extends RecyclerView.ViewHolder {
243 |
244 | final TextView text;
245 |
246 | public ViewHolder(View itemView) {
247 | super(itemView);
248 | text = (TextView) itemView.findViewById(R.id.title);
249 | }
250 | }
251 |
252 | public interface OnItemClickedListener {
253 |
254 | void onItemClicked(Intent intent, ResolveInfo resolveInfo);
255 | }
256 |
257 | private class IconLoaderTask extends AsyncTask {
258 |
259 | private final WeakReference textView;
260 |
261 | private final DisplayResolveInfoWithIntent info;
262 |
263 | public IconLoaderTask(DisplayResolveInfoWithIntent info, TextView textView) {
264 | textView.setTag(R.id.resolver_icon, this);
265 | this.textView = new WeakReference<>(textView);
266 | this.info = info;
267 | }
268 |
269 | @Override
270 | protected Drawable doInBackground(Void... params) {
271 | Drawable icon = info.getIcon(packageManager);
272 | icon.setBounds(0, 0, iconSize,
273 | (int) (((float) icon.getIntrinsicHeight()) / icon.getIntrinsicWidth()
274 | * iconSize));
275 | return icon;
276 | }
277 |
278 | @Override
279 | protected void onPostExecute(Drawable icon) {
280 | super.onPostExecute(icon);
281 | TextView textView = this.textView.get();
282 | if (textView != null && textView.getTag(R.id.resolver_icon) == this) {
283 | textView.setCompoundDrawables(icon, null, null, null);
284 | }
285 | }
286 | }}
287 |
--------------------------------------------------------------------------------
/yeti/src/main/java/me/kentin/yeti/YetiActionProvider.java:
--------------------------------------------------------------------------------
1 | package me.kentin.yeti;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.content.pm.PackageManager;
6 | import android.content.pm.ResolveInfo;
7 | import android.graphics.drawable.Drawable;
8 | import android.support.v4.view.ActionProvider;
9 | import android.support.v7.internal.widget.ActivityChooserModel;
10 | import android.support.v7.internal.widget.ActivityChooserModel.OnChooseActivityListener;
11 | import android.support.v7.internal.widget.ActivityChooserView;
12 | import android.support.v7.internal.widget.TintManager;
13 | import android.util.TypedValue;
14 | import android.view.Menu;
15 | import android.view.MenuItem;
16 | import android.view.MenuItem.OnMenuItemClickListener;
17 | import android.view.SubMenu;
18 | import android.view.View;
19 |
20 | import me.kentin.yeti.listener.OnShareListener;
21 | import me.kentin.yeti.utils.SharePackageHelper;
22 |
23 | public class YetiActionProvider extends ActionProvider {
24 |
25 | public YetiActionProvider(Context context) {
26 | super(context);
27 | this.context = context;
28 |
29 | }
30 |
31 | /**
32 | * The default for the maximal number of activities shown in the sub-menu.
33 | */
34 | private static final int DEFAULT_INITIAL_ACTIVITY_COUNT = 4;
35 |
36 | /**
37 | * The the maximum number activities shown in the sub-menu.
38 | */
39 | private int maxShownActivityCount = DEFAULT_INITIAL_ACTIVITY_COUNT;
40 |
41 | /**
42 | * Listener for handling menu item clicks.
43 | */
44 | private final ShareMenuItemOnMenuItemClickListener shareMenuItemOnMenuItemClickListener =
45 | new ShareMenuItemOnMenuItemClickListener();
46 |
47 | /**
48 | * The default name for storing share history.
49 | */
50 | public static final String DEFAULT_SHARE_HISTORY_FILE_NAME = "share_history.xml";
51 |
52 | /**
53 | * Context for accessing resources.
54 | */
55 | private final Context context;
56 |
57 | /**
58 | * The name of the file with share history data.
59 | */
60 | private String shareHistoryFileName = DEFAULT_SHARE_HISTORY_FILE_NAME;
61 |
62 | private OnChooseActivityListener onChooseActivityListener;
63 |
64 | /**
65 | * {@inheritDoc}
66 | */
67 | @Override
68 | public View onCreateActionView() {
69 | // Create the view and set its data model.
70 | ActivityChooserModel dataModel = ActivityChooserModel.get(context, shareHistoryFileName);
71 | ActivityChooserView activityChooserView = new ActivityChooserView(context);
72 | activityChooserView.setActivityChooserModel(dataModel);
73 |
74 | // Lookup and set the expand action icon.
75 | TypedValue outTypedValue = new TypedValue();
76 | context.getTheme().resolveAttribute(R.attr.actionModeShareDrawable, outTypedValue, true);
77 | Drawable drawable = TintManager.getDrawable(context, outTypedValue.resourceId);
78 | activityChooserView.setExpandActivityOverflowButtonDrawable(drawable);
79 | activityChooserView.setProvider(this);
80 |
81 | // Set content description.
82 | activityChooserView.setDefaultActionButtonContentDescription(
83 | R.string.abc_shareactionprovider_share_with_application);
84 | activityChooserView.setExpandActivityOverflowButtonContentDescription(
85 | R.string.abc_shareactionprovider_share_with);
86 |
87 | return activityChooserView;
88 | }
89 |
90 | /**
91 | * {@inheritDoc}
92 | */
93 | @Override
94 | public boolean hasSubMenu() {
95 | return true;
96 | }
97 |
98 | /**
99 | * {@inheritDoc}
100 | */
101 | @Override
102 | public void onPrepareSubMenu(SubMenu subMenu) {
103 | // Clear since the order of items may change.
104 | subMenu.clear();
105 |
106 | ActivityChooserModel dataModel = ActivityChooserModel.get(context, shareHistoryFileName);
107 | PackageManager packageManager = context.getPackageManager();
108 |
109 | final int expandedActivityCount = dataModel.getActivityCount();
110 | final int collapsedActivityCount = Math.min(expandedActivityCount, maxShownActivityCount);
111 |
112 | // Populate the sub-menu with a sub set of the activities.
113 | for (int i = 0; i < collapsedActivityCount; i++) {
114 | ResolveInfo activity = dataModel.getActivity(i);
115 | subMenu.add(0, i, i, activity.loadLabel(packageManager))
116 | .setIcon(activity.loadIcon(packageManager))
117 | .setOnMenuItemClickListener(shareMenuItemOnMenuItemClickListener);
118 | }
119 |
120 | if (collapsedActivityCount < expandedActivityCount) {
121 | // Add a sub-menu for showing all activities as a list item.
122 | SubMenu expandedSubMenu = subMenu.addSubMenu(Menu.NONE, collapsedActivityCount,
123 | collapsedActivityCount,
124 | context.getString(R.string.abc_activity_chooser_view_see_all));
125 | for (int i = 0; i < expandedActivityCount; i++) {
126 | ResolveInfo activity = dataModel.getActivity(i);
127 | expandedSubMenu.add(0, i, i, activity.loadLabel(packageManager))
128 | .setIcon(activity.loadIcon(packageManager))
129 | .setOnMenuItemClickListener(shareMenuItemOnMenuItemClickListener);
130 | }
131 | }
132 | }
133 |
134 | /**
135 | * Sets the file name of a file for persisting the share history which
136 | * history will be used for ordering share targets. This file will be used
137 | * for all view created by {@link #onCreateActionView()}. Defaults to
138 | * {@link #DEFAULT_SHARE_HISTORY_FILE_NAME}. Set to null
139 | * if share history should not be persisted between sessions.
140 | *
141 | * Note: The history file name can be set any time, however
142 | * only the action views created by {@link #onCreateActionView()} after setting
143 | * the file name will be backed by the provided file. Therefore, if you want to
144 | * use different history files for sharing specific types of content, every time
145 | * you change the history file {@link #setShareHistoryFileName(String)} you must
146 | * call {@link android.app.Activity#invalidateOptionsMenu()} to recreate the
147 | * action view. You should not call
148 | * {@link android.app.Activity#invalidateOptionsMenu()} from
149 | * {@link android.app.Activity#onCreateOptionsMenu(Menu)}."
150 | *
151 | *
152 | * private void doShare(Intent intent) {
153 | * if (IMAGE.equals(intent.getMimeType())) {
154 | * mShareActionProvider.setHistoryFileName(SHARE_IMAGE_HISTORY_FILE_NAME);
155 | * } else if (TEXT.equals(intent.getMimeType())) {
156 | * mShareActionProvider.setHistoryFileName(SHARE_TEXT_HISTORY_FILE_NAME);
157 | * }
158 | * mShareActionProvider.setIntent(intent);
159 | * invalidateOptionsMenu();
160 | * }
161 | *
162 | *
163 | * @param shareHistoryFile The share history file name.
164 | */
165 | public void setShareHistoryFileName(String shareHistoryFile) {
166 | shareHistoryFileName = shareHistoryFile;
167 | setActivityChooserPolicyIfNeeded();
168 | }
169 |
170 | /**
171 | * Sets an intent with information about the share action. Here is a
172 | * sample for constructing a share intent:
173 | *
174 | *
175 | *
176 | * Intent shareIntent = new Intent(Intent.ACTION_SEND);
177 | * shareIntent.setType("image/*");
178 | * Uri uri = Uri.fromFile(new File(getFilesDir(), "foo.jpg"));
179 | * shareIntent.putExtra(Intent.EXTRA_STREAM, uri.toString());
180 | *
181 | *
182 | *
183 | *
184 | * @param shareIntent The share intent.
185 | * @see Intent#ACTION_SEND
186 | * @see Intent#ACTION_SEND_MULTIPLE
187 | */
188 | public void setShareIntent(Intent shareIntent) {
189 | ActivityChooserModel dataModel = ActivityChooserModel.get(context,
190 | shareHistoryFileName);
191 | dataModel.setIntent(shareIntent);
192 | }
193 |
194 | /**
195 | * Reusable listener for handling share item clicks.
196 | */
197 | private class ShareMenuItemOnMenuItemClickListener implements OnMenuItemClickListener {
198 | @Override
199 | public boolean onMenuItemClick(MenuItem item) {
200 | ActivityChooserModel dataModel = ActivityChooserModel.get(context,
201 | shareHistoryFileName);
202 | final int itemId = item.getItemId();
203 | Intent launchIntent = dataModel.chooseActivity(itemId);
204 | if (launchIntent != null) {
205 | launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
206 | context.startActivity(launchIntent);
207 | }
208 | return true;
209 | }
210 | }
211 |
212 | /**
213 | * Set the activity chooser policy of the model backed by the current
214 | * share history file if needed which is if there is a registered callback.
215 | */
216 | private void setActivityChooserPolicyIfNeeded() {
217 | if (onShareListener == null) {
218 | return;
219 | }
220 | if (onChooseActivityListener == null) {
221 | onChooseActivityListener = new ShareActivityChooserModelPolicy();
222 | }
223 | ActivityChooserModel dataModel = ActivityChooserModel.get(context, shareHistoryFileName);
224 | dataModel.setOnChooseActivityListener(onChooseActivityListener);
225 | }
226 |
227 | private class ShareActivityChooserModelPolicy implements ActivityChooserModel.OnChooseActivityListener {
228 | @Override
229 | public boolean onChooseActivity(ActivityChooserModel host, Intent intent) {
230 |
231 | if (onShareListener != null) {
232 | SharePackageHelper.ApplicationType applicationType = new SharePackageHelper().getApplicationType(intent);
233 |
234 | if (applicationType == SharePackageHelper.ApplicationType.Twitter) {
235 | return onShareListener.shareWithTwitter(intent);
236 | } else if (applicationType == SharePackageHelper.ApplicationType.Facebook) {
237 | return onShareListener.shareWithFacebook(intent);
238 | } else if (applicationType == SharePackageHelper.ApplicationType.GooglePlus) {
239 | return onShareListener.shareWithGooglePlus(intent);
240 | } else if (applicationType == SharePackageHelper.ApplicationType.Email) {
241 | return onShareListener.shareWithEmail(intent);
242 | } else if (applicationType == SharePackageHelper.ApplicationType.Sms) {
243 | return onShareListener.shareWithSms(intent);
244 | } else {
245 | return onShareListener.shareWithOther(intent);
246 | }
247 | }
248 | return false;
249 | }
250 | }
251 |
252 | private OnShareListener onShareListener; //also need to add getter and setter
253 |
254 | public OnShareListener getOnShareListener() {
255 | return onShareListener;
256 | }
257 |
258 | public void setOnShareListener(OnShareListener onShareListener) {
259 | this.onShareListener = onShareListener;
260 | setActivityChooserPolicyIfNeeded();
261 | }
262 |
263 | }
264 |
--------------------------------------------------------------------------------
/yeti/src/main/java/me/kentin/yeti/view/ResolverDrawerLayout.java:
--------------------------------------------------------------------------------
1 | package me.kentin.yeti.view;
2 |
3 | /*
4 | * Copyright (C) 2014 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | import android.annotation.TargetApi;
20 | import android.content.Context;
21 | import android.content.res.TypedArray;
22 | import android.graphics.Rect;
23 | import android.os.Build;
24 | import android.os.Parcel;
25 | import android.os.Parcelable;
26 | import android.support.v4.view.ViewCompat;
27 | import android.support.v7.widget.RecyclerView;
28 | import android.util.AttributeSet;
29 | import android.util.Log;
30 | import android.view.MotionEvent;
31 | import android.view.VelocityTracker;
32 | import android.view.View;
33 | import android.view.ViewConfiguration;
34 | import android.view.ViewGroup;
35 | import android.view.ViewParent;
36 | import android.view.ViewTreeObserver;
37 | import android.view.animation.AnimationUtils;
38 | import android.widget.AbsListView;
39 | import android.widget.OverScroller;
40 |
41 | import me.kentin.yeti.R;
42 |
43 |
44 | public class ResolverDrawerLayout extends ViewGroup {
45 |
46 | private static final String TAG = "ResolverDrawerLayout";
47 |
48 | private static final String RECYCLERVIEW_CLASS_NAME = "android.support.v7.widget.RecyclerView";
49 |
50 | /**
51 | * Max width of the whole drawer layout
52 | */
53 | private int mMaxWidth;
54 |
55 | /**
56 | * Max total visible height of views not marked always-show when in the closed/initial state
57 | */
58 | private int mMaxCollapsedHeight;
59 |
60 | /**
61 | * Max total visible height of views not marked always-show when in the closed/initial state
62 | * when a default option is present
63 | */
64 | private int mMaxCollapsedHeightSmall;
65 |
66 | private boolean mSmallCollapsed;
67 |
68 | /**
69 | * Move views down from the top by this much in px
70 | */
71 | private float mCollapseOffset;
72 |
73 | private int mCollapsibleHeight;
74 |
75 | private int mTopOffset;
76 |
77 | private boolean mIsDragging;
78 |
79 | private boolean mOpenOnClick;
80 |
81 | private boolean mOpenOnLayout;
82 |
83 | private final int mTouchSlop;
84 |
85 | private final float mMinFlingVelocity;
86 |
87 | private final OverScroller mScroller;
88 |
89 | private final VelocityTracker mVelocityTracker;
90 |
91 | private OnClickListener mClickOutsideListener;
92 |
93 | private float mInitialTouchX;
94 |
95 | private float mInitialTouchY;
96 |
97 | private float mLastTouchY;
98 |
99 | private int mActivePointerId = MotionEvent.INVALID_POINTER_ID;
100 |
101 | private View mScrollableChildView;
102 |
103 | private final Rect mTempRect = new Rect();
104 |
105 | private final ViewTreeObserver.OnTouchModeChangeListener mTouchModeChangeListener =
106 | new ViewTreeObserver.OnTouchModeChangeListener() {
107 | @Override
108 | public void onTouchModeChanged(boolean isInTouchMode) {
109 | if (!isInTouchMode && hasFocus() && isDescendantClipped(getFocusedChild())) {
110 | smoothScrollTo(0, 0);
111 | }
112 | }
113 | };
114 |
115 | private boolean mIsLaidOut;
116 |
117 | public ResolverDrawerLayout(Context context) {
118 | this(context, null);
119 | }
120 |
121 | public ResolverDrawerLayout(Context context, AttributeSet attrs) {
122 | this(context, attrs, 0);
123 | }
124 |
125 | public ResolverDrawerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
126 | super(context, attrs, defStyleAttr);
127 |
128 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ResolverDrawerLayout,
129 | defStyleAttr, 0);
130 | mMaxWidth = a.getDimensionPixelSize(R.styleable.ResolverDrawerLayout_android_maxWidth, -1);
131 | mMaxCollapsedHeight = a.getDimensionPixelSize(
132 | R.styleable.ResolverDrawerLayout_maxCollapsedHeight, 0);
133 | mMaxCollapsedHeightSmall = a.getDimensionPixelSize(
134 | R.styleable.ResolverDrawerLayout_maxCollapsedHeightSmall,
135 | mMaxCollapsedHeight);
136 | a.recycle();
137 |
138 | mScroller = new OverScroller(context, AnimationUtils.loadInterpolator(context,
139 | android.R.interpolator.decelerate_quint));
140 | mVelocityTracker = VelocityTracker.obtain();
141 |
142 | final ViewConfiguration vc = ViewConfiguration.get(context);
143 | mTouchSlop = vc.getScaledTouchSlop();
144 | mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
145 | }
146 |
147 | public void setSmallCollapsed(boolean smallCollapsed) {
148 | mSmallCollapsed = smallCollapsed;
149 | requestLayout();
150 | }
151 |
152 | public boolean isSmallCollapsed() {
153 | return mSmallCollapsed;
154 | }
155 |
156 | public boolean isCollapsed() {
157 | return mCollapseOffset > 0;
158 | }
159 |
160 | private boolean isMoving() {
161 | return mIsDragging || !mScroller.isFinished();
162 | }
163 |
164 | private int getMaxCollapsedHeight() {
165 | return isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight;
166 | }
167 |
168 | public void setOnClickOutsideListener(OnClickListener listener) {
169 | mClickOutsideListener = listener;
170 | }
171 |
172 | @Override
173 | public boolean onInterceptTouchEvent(MotionEvent ev) {
174 | final int action = ev.getActionMasked();
175 |
176 | if (canChildScrollUp()) {
177 | // Fail fast if we're not in a state where a swipe is possible
178 | return super.onInterceptTouchEvent(ev);
179 | }
180 |
181 | if (action == MotionEvent.ACTION_DOWN) {
182 | mVelocityTracker.clear();
183 | }
184 |
185 | mVelocityTracker.addMovement(ev);
186 |
187 | switch (action) {
188 | case MotionEvent.ACTION_DOWN: {
189 | final float x = ev.getX();
190 | final float y = ev.getY();
191 | mInitialTouchX = x;
192 | mInitialTouchY = mLastTouchY = y;
193 | mOpenOnClick = isListChildUnderClipped(x, y) && mCollapsibleHeight > 0;
194 | }
195 | break;
196 |
197 | case MotionEvent.ACTION_MOVE: {
198 | final float x = ev.getX();
199 | final float y = ev.getY();
200 | final float dy = y - mInitialTouchY;
201 | boolean isSlidingUp = Math.abs(dy) > mTouchSlop
202 | && findChildUnder(x, y) != null
203 | && mCollapseOffset > 0;
204 | boolean isSlidingDown = mCollapseOffset == 0 && dy > mTouchSlop;
205 | if (isSlidingUp || isSlidingDown) {
206 | mActivePointerId = ev.getPointerId(0);
207 | mIsDragging = true;
208 | mLastTouchY = Math.max(mLastTouchY - mTouchSlop,
209 | Math.min(mLastTouchY + dy, mLastTouchY + mTouchSlop));
210 | }
211 | }
212 | break;
213 |
214 | case MotionEvent.ACTION_POINTER_UP: {
215 | onSecondaryPointerUp(ev);
216 | }
217 | break;
218 |
219 | case MotionEvent.ACTION_CANCEL:
220 | case MotionEvent.ACTION_UP: {
221 | resetTouch();
222 | }
223 | break;
224 | }
225 |
226 | if (mIsDragging) {
227 | mScroller.abortAnimation();
228 | }
229 | return mIsDragging || mOpenOnClick;
230 | }
231 |
232 | @Override
233 | public boolean onTouchEvent(MotionEvent ev) {
234 | final int action = ev.getActionMasked();
235 |
236 | if (canChildScrollUp()) {
237 | // Fail fast if we're not in a state where a swipe is possible
238 | return super.onTouchEvent(ev);
239 | }
240 |
241 | mVelocityTracker.addMovement(ev);
242 |
243 | boolean handled = false;
244 | switch (action) {
245 | case MotionEvent.ACTION_DOWN: {
246 | final float x = ev.getX();
247 | final float y = ev.getY();
248 | mInitialTouchX = x;
249 | mInitialTouchY = mLastTouchY = y;
250 | mActivePointerId = ev.getPointerId(0);
251 | if (findChildUnder(mInitialTouchX, mInitialTouchY) == null &&
252 | mClickOutsideListener != null) {
253 | mIsDragging = handled = true;
254 | }
255 | handled |= mCollapsibleHeight > 0;
256 | mScroller.abortAnimation();
257 | }
258 | break;
259 |
260 | case MotionEvent.ACTION_MOVE: {
261 | int index = ev.findPointerIndex(mActivePointerId);
262 | if (index < 0) {
263 | Log.e(TAG, "Bad pointer id " + mActivePointerId + ", resetting");
264 | index = 0;
265 | mActivePointerId = ev.getPointerId(0);
266 | mInitialTouchX = ev.getX();
267 | mInitialTouchY = mLastTouchY = ev.getY();
268 | }
269 | final float x = ev.getX(index);
270 | final float y = ev.getY(index);
271 | if (!mIsDragging) {
272 | final float dy = y - mInitialTouchY;
273 | if (Math.abs(dy) > mTouchSlop && findChildUnder(x, y) != null) {
274 | handled = mIsDragging = true;
275 | mLastTouchY = Math.max(mLastTouchY - mTouchSlop,
276 | Math.min(mLastTouchY + dy, mLastTouchY + mTouchSlop));
277 | }
278 | }
279 | if (mIsDragging) {
280 | final float dy = y - mLastTouchY;
281 | performDrag(dy);
282 | }
283 | mLastTouchY = y;
284 | }
285 | break;
286 |
287 | case MotionEvent.ACTION_POINTER_DOWN: {
288 | final int pointerIndex = ev.getActionIndex();
289 | final int pointerId = ev.getPointerId(pointerIndex);
290 | mActivePointerId = pointerId;
291 | mInitialTouchX = ev.getX(pointerIndex);
292 | mInitialTouchY = mLastTouchY = ev.getY(pointerIndex);
293 | }
294 | break;
295 |
296 | case MotionEvent.ACTION_POINTER_UP: {
297 | onSecondaryPointerUp(ev);
298 | }
299 | break;
300 |
301 | case MotionEvent.ACTION_UP: {
302 | mIsDragging = false;
303 | if (!mIsDragging && findChildUnder(mInitialTouchX, mInitialTouchY) == null &&
304 | findChildUnder(ev.getX(), ev.getY()) == null) {
305 | if (mClickOutsideListener != null) {
306 | mClickOutsideListener.onClick(this);
307 | resetTouch();
308 | return true;
309 | }
310 | }
311 | if (mOpenOnClick && Math.abs(ev.getX() - mInitialTouchX) < mTouchSlop &&
312 | Math.abs(ev.getY() - mInitialTouchY) < mTouchSlop) {
313 | smoothScrollTo(0, 0);
314 | return true;
315 | }
316 | mVelocityTracker.computeCurrentVelocity(1000);
317 | final float yvel = mVelocityTracker.getYVelocity(mActivePointerId);
318 | if (Math.abs(yvel) > mMinFlingVelocity) {
319 | smoothScrollTo(yvel < 0 ? 0 : mCollapsibleHeight, yvel);
320 | } else {
321 | smoothScrollTo(
322 | mCollapseOffset < mCollapsibleHeight / 2 ? 0 : mCollapsibleHeight, 0);
323 | }
324 | resetTouch();
325 | }
326 | break;
327 |
328 | case MotionEvent.ACTION_CANCEL: {
329 | if (mIsDragging) {
330 | smoothScrollTo(
331 | mCollapseOffset < mCollapsibleHeight / 2 ? 0 : mCollapsibleHeight, 0);
332 | }
333 | resetTouch();
334 | return true;
335 | }
336 | }
337 |
338 | return handled
339 | || (action == MotionEvent.ACTION_MOVE && mCollapseOffset > 0);
340 | }
341 |
342 | private void onSecondaryPointerUp(MotionEvent ev) {
343 | final int pointerIndex = ev.getActionIndex();
344 | final int pointerId = ev.getPointerId(pointerIndex);
345 | if (pointerId == mActivePointerId) {
346 | // This was our active pointer going up. Choose a new
347 | // active pointer and adjust accordingly.
348 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
349 | mInitialTouchX = ev.getX(newPointerIndex);
350 | mInitialTouchY = mLastTouchY = ev.getY(newPointerIndex);
351 | mActivePointerId = ev.getPointerId(newPointerIndex);
352 | }
353 | }
354 |
355 | private void resetTouch() {
356 | mActivePointerId = MotionEvent.INVALID_POINTER_ID;
357 | mIsDragging = false;
358 | mOpenOnClick = false;
359 | mInitialTouchX = mInitialTouchY = mLastTouchY = 0;
360 | mVelocityTracker.clear();
361 | }
362 |
363 | @Override
364 | public void computeScroll() {
365 | super.computeScroll();
366 | if (!mScroller.isFinished()) {
367 | final boolean keepGoing = mScroller.computeScrollOffset();
368 | performDrag(mScroller.getCurrY() - mCollapseOffset);
369 | if (keepGoing) {
370 | ViewCompat.postInvalidateOnAnimation(this);
371 | }
372 | }
373 | }
374 |
375 | private float performDrag(float dy) {
376 | final float newPos = Math.max(0, Math.min(mCollapseOffset + dy, mCollapsibleHeight));
377 | if (newPos != mCollapseOffset) {
378 | dy = newPos - mCollapseOffset;
379 | final int childCount = getChildCount();
380 | for (int i = 0; i < childCount; i++) {
381 | final View child = getChildAt(i);
382 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
383 | if (!lp.ignoreOffset) {
384 | child.offsetTopAndBottom((int) dy);
385 | }
386 | }
387 | mCollapseOffset = newPos;
388 | mTopOffset += dy;
389 | ViewCompat.postInvalidateOnAnimation(this);
390 | return dy;
391 | }
392 | return 0;
393 | }
394 |
395 | private void smoothScrollTo(int yOffset, float velocity) {
396 | if (getMaxCollapsedHeight() == 0) {
397 | return;
398 | }
399 | mScroller.abortAnimation();
400 | final int sy = (int) mCollapseOffset;
401 | int dy = yOffset - sy;
402 | if (dy == 0) {
403 | return;
404 | }
405 |
406 | final int height = getHeight();
407 | final int halfHeight = height / 2;
408 | final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dy) / height);
409 | final float distance = halfHeight + halfHeight *
410 | distanceInfluenceForSnapDuration(distanceRatio);
411 |
412 | int duration = 0;
413 | velocity = Math.abs(velocity);
414 | if (velocity > 0) {
415 | duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
416 | } else {
417 | final float pageDelta = (float) Math.abs(dy) / height;
418 | duration = (int) ((pageDelta + 1) * 100);
419 | }
420 | duration = Math.min(duration, 300);
421 |
422 | mScroller.startScroll(0, sy, 0, dy, duration);
423 | ViewCompat.postInvalidateOnAnimation(this);
424 | }
425 |
426 | private float distanceInfluenceForSnapDuration(float f) {
427 | f -= 0.5f; // center the values about 0.
428 | f *= 0.3f * Math.PI / 2.0f;
429 | return (float) Math.sin(f);
430 | }
431 |
432 | /**
433 | * Note: this method doesn't take Z into account for overlapping views
434 | * since it is only used in contexts where this doesn't affect the outcome.
435 | */
436 | private View findChildUnder(float x, float y) {
437 | return findChildUnder(this, x, y);
438 | }
439 |
440 | private static View findChildUnder(ViewGroup parent, float x, float y) {
441 | final int childCount = parent.getChildCount();
442 | for (int i = childCount - 1; i >= 0; i--) {
443 | final View child = parent.getChildAt(i);
444 | if (isChildUnder(child, x, y)) {
445 | return child;
446 | }
447 | }
448 | return null;
449 | }
450 |
451 | private View findListChildUnder(float x, float y) {
452 | View v = findChildUnder(x, y);
453 | while (v != null) {
454 | x -= v.getX();
455 | y -= v.getY();
456 | if (v instanceof AbsListView
457 | || RECYCLERVIEW_CLASS_NAME.equals(v.getClass().getName())) {
458 | // One more after this.
459 | return findChildUnder((ViewGroup) v, x, y);
460 | }
461 | v = v instanceof ViewGroup ? findChildUnder((ViewGroup) v, x, y) : null;
462 | }
463 | return v;
464 | }
465 |
466 | /**
467 | * This only checks clipping along the bottom edge.
468 | */
469 | private boolean isListChildUnderClipped(float x, float y) {
470 | final View listChild = findListChildUnder(x, y);
471 | return listChild != null && isDescendantClipped(listChild);
472 | }
473 |
474 | private boolean isDescendantClipped(View child) {
475 | mTempRect.set(0, 0, child.getWidth(), child.getHeight());
476 | offsetDescendantRectToMyCoords(child, mTempRect);
477 | View directChild;
478 | if (child.getParent() == this) {
479 | directChild = child;
480 | } else {
481 | View v = child;
482 | ViewParent p = child.getParent();
483 | while (p != this) {
484 | v = (View) p;
485 | p = v.getParent();
486 | }
487 | directChild = v;
488 | }
489 |
490 | // ResolverDrawerLayout lays out vertically in child order;
491 | // the next view and forward is what to check against.
492 | int clipEdge = getHeight() - getPaddingBottom();
493 | final int childCount = getChildCount();
494 | for (int i = indexOfChild(directChild) + 1; i < childCount; i++) {
495 | final View nextChild = getChildAt(i);
496 | if (nextChild.getVisibility() == GONE) {
497 | continue;
498 | }
499 | clipEdge = Math.min(clipEdge, nextChild.getTop());
500 | }
501 | return mTempRect.bottom > clipEdge;
502 | }
503 |
504 | private static boolean isChildUnder(View child, float x, float y) {
505 | final float left = child.getX();
506 | final float top = child.getY();
507 | final float right = left + child.getWidth();
508 | final float bottom = top + child.getHeight();
509 | return x >= left && y >= top && x < right && y < bottom;
510 | }
511 |
512 | @Override
513 | public void requestChildFocus(View child, View focused) {
514 | super.requestChildFocus(child, focused);
515 | if (!isInTouchMode() && isDescendantClipped(focused)) {
516 | smoothScrollTo(0, 0);
517 | }
518 | }
519 |
520 | @Override
521 | protected void onAttachedToWindow() {
522 | super.onAttachedToWindow();
523 | getViewTreeObserver().addOnTouchModeChangeListener(mTouchModeChangeListener);
524 | }
525 |
526 | @Override
527 | protected void onDetachedFromWindow() {
528 | super.onDetachedFromWindow();
529 | getViewTreeObserver().removeOnTouchModeChangeListener(mTouchModeChangeListener);
530 | }
531 |
532 | @Override
533 | public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
534 | return (nestedScrollAxes & View.SCROLL_AXIS_VERTICAL) != 0;
535 | }
536 |
537 | @Override
538 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
539 | public void onStopNestedScroll(View child) {
540 | super.onStopNestedScroll(child);
541 | if (mScroller.isFinished()) {
542 | smoothScrollTo(mCollapseOffset < mCollapsibleHeight / 2 ? 0 : mCollapsibleHeight, 0);
543 | }
544 | }
545 |
546 | @Override
547 | public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
548 | int dxUnconsumed, int dyUnconsumed) {
549 | if (dyUnconsumed < 0) {
550 | performDrag(-dyUnconsumed);
551 | }
552 | }
553 |
554 | @Override
555 | public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
556 | if (dy > 0) {
557 | consumed[1] = (int) -performDrag(-dy);
558 | }
559 | }
560 |
561 | @Override
562 | public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
563 | if (velocityY > mMinFlingVelocity && mCollapseOffset != 0) {
564 | smoothScrollTo(0, velocityY);
565 | return true;
566 | }
567 | return false;
568 | }
569 |
570 | @Override
571 | public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
572 | if (!consumed && Math.abs(velocityY) > mMinFlingVelocity) {
573 | smoothScrollTo(velocityY > 0 ? 0 : mCollapsibleHeight, velocityY);
574 | return true;
575 | }
576 | return false;
577 | }
578 |
579 | @Override
580 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
581 | final int sourceWidth = MeasureSpec.getSize(widthMeasureSpec);
582 | int widthSize = sourceWidth;
583 | int heightSize = MeasureSpec.getSize(heightMeasureSpec);
584 |
585 | // Single-use layout; just ignore the mode and use available space.
586 | // Clamp to maxWidth.
587 | if (mMaxWidth >= 0) {
588 | widthSize = Math.min(widthSize, mMaxWidth);
589 | }
590 |
591 | final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
592 | final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
593 | final int widthPadding = getPaddingLeft() + getPaddingRight();
594 | int heightUsed = getPaddingTop() + getPaddingBottom();
595 |
596 | // Measure always-show children first.
597 | final int childCount = getChildCount();
598 | for (int i = 0; i < childCount; i++) {
599 | final View child = getChildAt(i);
600 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
601 | if (lp.alwaysShow && child.getVisibility() != GONE) {
602 | measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed);
603 | heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin;
604 | }
605 | }
606 |
607 | final int alwaysShowHeight = heightUsed;
608 |
609 | // And now the rest.
610 | for (int i = 0; i < childCount; i++) {
611 | final View child = getChildAt(i);
612 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
613 | if (!lp.alwaysShow && child.getVisibility() != GONE) {
614 | measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed);
615 | heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin;
616 | }
617 | }
618 |
619 | mCollapsibleHeight = Math.max(0,
620 | heightUsed - alwaysShowHeight - getMaxCollapsedHeight());
621 |
622 | if (mIsLaidOut) {
623 | mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight);
624 | } else {
625 | // Start out collapsed at first unless we restored state for otherwise
626 | mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight;
627 | }
628 |
629 | mTopOffset = Math.max(0, heightSize - heightUsed) + (int) mCollapseOffset;
630 |
631 | setMeasuredDimension(sourceWidth, heightSize);
632 | }
633 |
634 | @Override
635 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
636 | final int width = getWidth();
637 |
638 | int ypos = mTopOffset;
639 | int leftEdge = getPaddingLeft();
640 | int rightEdge = width - getPaddingRight();
641 |
642 | final int childCount = getChildCount();
643 | for (int i = 0; i < childCount; i++) {
644 | final View child = getChildAt(i);
645 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
646 |
647 | if (child.getVisibility() == GONE) {
648 | continue;
649 | }
650 |
651 | int top = ypos + lp.topMargin;
652 | if (lp.ignoreOffset) {
653 | top -= mCollapseOffset;
654 | }
655 | final int bottom = top + child.getMeasuredHeight();
656 |
657 | final int childWidth = child.getMeasuredWidth();
658 | final int widthAvailable = rightEdge - leftEdge;
659 | final int left = leftEdge + (widthAvailable - childWidth) / 2;
660 | final int right = left + childWidth;
661 |
662 | child.layout(left, top, right, bottom);
663 |
664 | ypos = bottom + lp.bottomMargin;
665 | }
666 |
667 | mIsLaidOut = true;
668 | }
669 |
670 | @Override
671 | public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
672 | return new LayoutParams(getContext(), attrs);
673 | }
674 |
675 | @Override
676 | protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
677 | if (p instanceof LayoutParams) {
678 | return new LayoutParams((LayoutParams) p);
679 | } else if (p instanceof MarginLayoutParams) {
680 | return new LayoutParams((MarginLayoutParams) p);
681 | }
682 | return new LayoutParams(p);
683 | }
684 |
685 | @Override
686 | protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
687 | return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
688 | }
689 |
690 | @Override
691 | protected Parcelable onSaveInstanceState() {
692 | final SavedState ss = new SavedState(super.onSaveInstanceState());
693 | ss.open = mCollapsibleHeight > 0 && mCollapseOffset == 0;
694 | return ss;
695 | }
696 |
697 | @Override
698 | protected void onRestoreInstanceState(Parcelable state) {
699 | final SavedState ss = (SavedState) state;
700 | super.onRestoreInstanceState(ss.getSuperState());
701 | mOpenOnLayout = ss.open;
702 | }
703 |
704 | public boolean canChildScrollUp() {
705 | if (mScrollableChildView != null) {
706 | if (android.os.Build.VERSION.SDK_INT < 14) {
707 | if (mScrollableChildView instanceof AbsListView) {
708 | final AbsListView absListView = (AbsListView) mScrollableChildView;
709 | return absListView.getChildCount() > 0
710 | && (absListView.getFirstVisiblePosition() > 0
711 | || absListView.getChildAt(0)
712 | .getTop() < absListView.getPaddingTop());
713 | } else if (
714 | RECYCLERVIEW_CLASS_NAME.equals(mScrollableChildView.getClass().getName())) {
715 | final RecyclerView recyclerView = (RecyclerView) mScrollableChildView;
716 | View firstView = recyclerView.getChildAt(0);
717 | return recyclerView.getChildCount() > 0
718 | && (recyclerView.getLayoutManager().getPosition(firstView) > 0
719 | || recyclerView.getChildAt(0).getTop() < recyclerView.getPaddingTop());
720 | } else {
721 | return mScrollableChildView.getScrollY() > 0;
722 | }
723 | } else {
724 | return ViewCompat.canScrollVertically(mScrollableChildView, -1);
725 | }
726 | }
727 | return false;
728 | }
729 |
730 | public void setScrollableChildView(View scrollableChildView) {
731 | mScrollableChildView = scrollableChildView;
732 | }
733 |
734 | public static class LayoutParams extends MarginLayoutParams {
735 |
736 | public boolean alwaysShow;
737 |
738 | public boolean ignoreOffset;
739 |
740 | public LayoutParams(Context c, AttributeSet attrs) {
741 | super(c, attrs);
742 |
743 | final TypedArray a = c.obtainStyledAttributes(attrs,
744 | R.styleable.ResolverDrawerLayout_LayoutParams);
745 | alwaysShow = a.getBoolean(
746 | R.styleable.ResolverDrawerLayout_LayoutParams_layout_alwaysShow,
747 | false);
748 | ignoreOffset = a.getBoolean(
749 | R.styleable.ResolverDrawerLayout_LayoutParams_layout_ignoreOffset,
750 | false);
751 | a.recycle();
752 | }
753 |
754 | public LayoutParams(int width, int height) {
755 | super(width, height);
756 | }
757 |
758 | public LayoutParams(LayoutParams source) {
759 | super(source);
760 | this.alwaysShow = source.alwaysShow;
761 | this.ignoreOffset = source.ignoreOffset;
762 | }
763 |
764 | public LayoutParams(MarginLayoutParams source) {
765 | super(source);
766 | }
767 |
768 | public LayoutParams(ViewGroup.LayoutParams source) {
769 | super(source);
770 | }
771 | }
772 |
773 | static class SavedState extends BaseSavedState {
774 |
775 | boolean open;
776 |
777 | SavedState(Parcelable superState) {
778 | super(superState);
779 | }
780 |
781 | private SavedState(Parcel in) {
782 | super(in);
783 | open = in.readInt() != 0;
784 | }
785 |
786 | @Override
787 | public void writeToParcel(Parcel out, int flags) {
788 | super.writeToParcel(out, flags);
789 | out.writeInt(open ? 1 : 0);
790 | }
791 |
792 | public static final Parcelable.Creator CREATOR =
793 | new Parcelable.Creator() {
794 | @Override
795 | public SavedState createFromParcel(Parcel in) {
796 | return new SavedState(in);
797 | }
798 |
799 | @Override
800 | public SavedState[] newArray(int size) {
801 | return new SavedState[size];
802 | }
803 | };
804 | }
805 | }
--------------------------------------------------------------------------------