├── .gitignore
├── LICENSE
├── README.md
├── api
├── build.gradle
├── javadoc-scripts
│ ├── .gitignore
│ ├── generate_javadoc.sh
│ ├── index.html
│ ├── javadoc_stylesheet.css
│ ├── prettify.css
│ ├── prettify.js
│ └── tweak_javadoc_html.py
└── src
│ └── main
│ └── java
│ └── com
│ └── actionlauncher
│ └── api
│ ├── LiveWallpaperInfo.java
│ ├── LiveWallpaperSource.java
│ ├── actionpalette
│ ├── ActionPalette.java
│ ├── AsyncTaskCompat.java
│ ├── AsyncTaskCompatHoneycomb.java
│ ├── ColorCutQuantizer.java
│ ├── ColorHistogram.java
│ ├── ColorUtils.java
│ └── DefaultGenerator.java
│ └── internal
│ ├── ProtocolConstants.java
│ └── SourceState.java
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── keystore.properties.example
├── local.properties.example
├── main
├── build.gradle
├── proguard-project.txt
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── assets
│ │ ├── Alegreya-BlackItalic.ttf
│ │ ├── Alegreya-Italic.ttf
│ │ ├── AlegreyaModifiedReadme
│ │ ├── kepler-01.jpg
│ │ ├── kepler-02.jpg
│ │ ├── kepler-03.jpg
│ │ └── starrynight.jpg
│ │ ├── java
│ │ ├── com
│ │ │ └── google
│ │ │ │ └── android
│ │ │ │ └── apps
│ │ │ │ └── muzei
│ │ │ │ ├── ArtDetailViewport.java
│ │ │ │ ├── LockScreenVisibleReceiver.java
│ │ │ │ ├── MuzeiActivity.java
│ │ │ │ ├── MuzeiWallpaperService.java
│ │ │ │ ├── event
│ │ │ │ ├── ArtDetailOpenedClosedEvent.java
│ │ │ │ ├── ArtworkSizeChangedEvent.java
│ │ │ │ ├── LockScreenVisibleChangedEvent.java
│ │ │ │ ├── SwitchingPhotosStateChangedEvent.java
│ │ │ │ ├── WallpaperActiveStateChangedEvent.java
│ │ │ │ └── WallpaperSizeChangedEvent.java
│ │ │ │ ├── render
│ │ │ │ ├── BitmapRegionLoader.java
│ │ │ │ ├── GLColorOverlay.java
│ │ │ │ ├── GLPicture.java
│ │ │ │ ├── GLTextureView.java
│ │ │ │ ├── GLUtil.java
│ │ │ │ ├── ImageUtil.java
│ │ │ │ ├── LocalRenderController.java
│ │ │ │ ├── MuzeiBlurRenderer.java
│ │ │ │ ├── MuzeiRendererFragment.java
│ │ │ │ └── RenderController.java
│ │ │ │ ├── settings
│ │ │ │ └── Prefs.java
│ │ │ │ └── util
│ │ │ │ ├── CheatSheet.java
│ │ │ │ ├── DrawInsetsFrameLayout.java
│ │ │ │ ├── IOUtil.java
│ │ │ │ ├── ImageBlurrer.java
│ │ │ │ ├── LogUtil.java
│ │ │ │ ├── LogoPaths.java
│ │ │ │ ├── MathUtil.java
│ │ │ │ ├── MultiSelectionController.java
│ │ │ │ ├── ObservableHorizontalScrollView.java
│ │ │ │ ├── PanScaleProxyView.java
│ │ │ │ ├── ScrimUtil.java
│ │ │ │ ├── Scrollbar.java
│ │ │ │ ├── SelectionBuilder.java
│ │ │ │ ├── ShadowDipsTextView.java
│ │ │ │ ├── SvgPathParser.java
│ │ │ │ ├── TickingFloatAnimator.java
│ │ │ │ ├── TypefaceUtil.java
│ │ │ │ └── Zoomer.java
│ │ └── net
│ │ │ └── rbgrn
│ │ │ └── android
│ │ │ └── glwallpaperservice
│ │ │ ├── BaseConfigChooser.java
│ │ │ └── GLWallpaperService.java
│ │ └── res
│ │ ├── animator
│ │ ├── fade_in.xml
│ │ └── fade_out.xml
│ │ ├── drawable-hdpi
│ │ ├── ic_notif_full_info.png
│ │ ├── ic_notif_full_next_artwork.png
│ │ └── ic_notif_full_user_command.png
│ │ ├── drawable-nodpi
│ │ ├── ic_source_featured.png
│ │ ├── ic_source_gallery.png
│ │ ├── ic_source_selected.png
│ │ └── thumb.png
│ │ ├── drawable-v21
│ │ ├── grey_selectable_item_background_circle.xml
│ │ ├── settings_source_item_image_overlay.xml
│ │ └── white_selectable_item_background.xml
│ │ ├── drawable-xhdpi
│ │ └── ic_stat_muzei.png
│ │ ├── drawable-xxhdpi
│ │ ├── gallery_settings_chosen_photo_selected.png
│ │ ├── grumpy_mcpuzzles.png
│ │ ├── ic_ab_done.png
│ │ ├── ic_ab_up.png
│ │ ├── ic_action_play.png
│ │ ├── ic_action_remove.png
│ │ ├── ic_action_rotate_interval.png
│ │ ├── ic_add_photos.png
│ │ ├── ic_notif_info.png
│ │ ├── ic_notif_next_artwork.png
│ │ ├── ic_overflow.png
│ │ ├── ic_skip.png
│ │ ├── ic_source_settings.png
│ │ ├── ic_stat_muzei.png
│ │ ├── logo_subtitle.png
│ │ ├── scrubber_control_disabled.png
│ │ ├── scrubber_control_focused.png
│ │ ├── scrubber_control_normal.png
│ │ ├── scrubber_control_pressed.png
│ │ ├── scrubber_primary.9.png
│ │ ├── scrubber_secondary.9.png
│ │ ├── scrubber_track.9.png
│ │ ├── scrubber_track_blur_amount.9.png
│ │ ├── scrubber_track_dim_amount.9.png
│ │ ├── scrubber_track_grey_amount.9.png
│ │ ├── spinner_triangle.png
│ │ ├── tutorial_icon_off.png
│ │ ├── tutorial_icon_on.png
│ │ ├── white_item_focused.9.png
│ │ └── white_item_pressed.9.png
│ │ ├── drawable
│ │ ├── grey_selectable_item_background_circle.xml
│ │ ├── intro_background_protection.xml
│ │ ├── metadata_scrim.xml
│ │ ├── popup_background.xml
│ │ ├── scrubber_control_selector.xml
│ │ ├── scrubber_progress_blur_amount.xml
│ │ ├── scrubber_progress_dim_amount.xml
│ │ ├── scrubber_progress_grey_amount.xml
│ │ ├── scrubber_progress_horizontal.xml
│ │ ├── settings_source_item_image_overlay.xml
│ │ ├── statusbar_scrim.xml
│ │ ├── tutorial_icon.xml
│ │ ├── white_circle_button.xml
│ │ └── white_selectable_item_background.xml
│ │ ├── layout
│ │ └── muzei_activity.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-sw360dp
│ │ └── dimens.xml
│ │ ├── values-sw600dp
│ │ └── dimens.xml
│ │ ├── values-v21
│ │ └── styles.xml
│ │ ├── values
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── ids.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ └── wallpaper.xml
└── version.properties
├── screenshot.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/
13 | gen/
14 | out/
15 | build/
16 | .gradle/
17 |
18 | # Project files
19 | *.iml
20 | .idea
21 | # .idea/workspace.xml
22 |
23 | # Local configuration file (sdk path, etc)
24 | local.properties
25 | keystore.properties
26 |
27 | # Windows thumbnail db
28 | .DS_Store
29 |
30 | # Idea non-crucial project fileS
31 | *.iws
32 |
33 | # Sandbox stuff
34 | _sandbox
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Action Launcher API
2 | ==================================
3 |
4 | A simple API live wallpaper developers can use to allow [Action Launcher 3](2) to theme itself based on the current colors of your wallpaper.
5 |
6 |
7 |
8 | Note: use of this API is only necessary for **live wallpapers** (not status wallpapers). This is due to Android not providing any APIs for apps to fetch Bitmap data about the current live wallpaper.
9 |
10 |
11 |
12 | Usage
13 | =====
14 |
15 | 1. Integrate the Action 3 API code into the `dependencies` section of your `build.gradle` file:
16 |
17 | `compile 'com.actionlauncher:action3-api:1.+'`
18 |
If you're not using Android Studio/gradle, you can add the [`action3-api.jar`][5] to your `/libs` folder, or copy the API [code][3] directly into your project.
19 |
20 |
21 | 2. Add this code to your `AndroidManifest.xml` (inside the `Application` entry):
22 |
23 | ```
24 |
To test it all works:
54 |
55 | * Load Action Launcher 3 (you *must* be using version 3.3 or later).
56 | * Ensure your wallpaper is set as the live wallpaper.
57 | * Ensure Action Launcher's wallpaper extraction mode is enabled (Settings -> Quicktheme -> Theme -> Wallpaper).
58 | * As you're integrating the API, be sure to turn on Settings -> Help -> Advanced -> Live wallpaper API debug in Action Launcher 3. By doing so, you will enable a debug mode where pressing the voice search button on the search bar will trigger a request to your app for the latest `LiveWallpaperInfo` data.
59 |
60 |
61 | Demo
62 | ====
63 | The `main` app in this repository demonstrates the live wallpaper functionality. It is basically the main app from the [Android Live Wallpaper Hello World project](1). If you double-tap empty space on Action Launcher 3's home screen, the wallpaper image will change, and you items such as the search bar will have their colors updated as per the current wallpaper image in Action Launcher 3.
64 |
65 | Check out the `LiveWallpaperSource.with()` call in `MuzeiBlurRenderer.java`.
66 |
67 | Notes
68 | =====
69 |
70 | * Keep in mind that each time you call `LiveWallpaperSource.setBitmapSynchronous()`, a new palette will be generated. In order to not waste battery, you only want to make this call when you know there has been a meaninful visual change in your wallpaper app and Action Launcher's Quicktheme feature should be updated.
71 | * This API includes a copy of API 22's Palette library from Support Library named `ActionPalette`[4]. It has been integrated directly into the ActionLauncherApi rather than as a dependency because:
72 | * Many live-wallpaper developers are still using Eclipse, which has seemingly isn't well set up to use AARs.
73 | * Makes the dependencies easier.
74 | * It doesn't take much code size, so there's little harm in it.
75 |
76 |
77 | 3rd party examples
78 | ==================
79 | The following Android apps make use of this API:
80 |
81 | * [Minima Pro Live Wallpaper](https://play.google.com/store/apps/details?id=com.joko.minimapro)
82 | * [TapDeck - Wallpaper Discovery](https://play.google.com/store/apps/details?id=io.tapdeck.android)
83 |
84 |
85 | License
86 | =======
87 |
88 | Copyright 2015 Chris Lacy
89 |
90 | Licensed under the Apache License, Version 2.0 (the "License");
91 | you may not use this file except in compliance with the License.
92 | You may obtain a copy of the License at
93 |
94 | http://www.apache.org/licenses/LICENSE-2.0
95 |
96 | Unless required by applicable law or agreed to in writing, software
97 | distributed under the License is distributed on an "AS IS" BASIS,
98 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
99 | See the License for the specific language governing permissions and
100 | limitations under the License.
101 |
102 | [1]: https://github.com/chrislacy/AndroidLiveWallpaperHelloWorld
103 | [2]: https://play.google.com/store/apps/details?id=com.actionlauncher.playstore
104 | [3]: https://github.com/chrislacy/ActionLauncherApi/tree/master/api/src/main/java
105 | [4]: https://github.com/chrislacy/ActionLauncherApi/tree/master/api/src/main/java/com/actionlauncher/api/actionpalette
106 | [5]: https://oss.sonatype.org/content/repositories/releases/com/actionlauncher/action3-api/1.1.0/action3-api-1.1.0.jar
107 |
--------------------------------------------------------------------------------
/api/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Chris Lacy
3 | * Copyright 2014 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | apply plugin: 'maven'
18 | apply plugin: 'signing'
19 | apply plugin: 'java'
20 | sourceCompatibility = 1.7
21 | targetCompatibility = 1.7
22 |
23 | jar.baseName = 'action3-api'
24 | group = 'com.actionlauncher'
25 | version = '1.1.0'
26 |
27 | def Properties props = new Properties()
28 | props.load(new FileInputStream(file('../local.properties')))
29 |
30 | def android = [
31 | sdk: props["sdk.dir"],
32 | target: 'android-21'
33 | ]
34 |
35 | allprojects { ext."signing.keyId" = props["signing.keyId"] }
36 | allprojects { ext."signing.password" = props["signing.password"] }
37 | allprojects { ext."signing.secretKeyRingFile" = props["signing.secretKeyRingFile"] }
38 |
39 |
40 | sourceSets {
41 | main {
42 | java {
43 | srcDir 'build/source/aidl/debug'
44 | }
45 | }
46 | }
47 |
48 | task javadoc(type: Exec, overwrite: true, dependsOn: 'jar') {
49 | commandLine './javadoc-scripts/generate_javadoc.sh'
50 | }
51 |
52 | task javadocJar(type: Jar, dependsOn: javadoc) {
53 | classifier = 'javadoc'
54 | baseName = jar.baseName
55 | from 'build/javadoc'
56 | }
57 |
58 | task sourcesJar(type: Jar, dependsOn: classes) {
59 | classifier = 'sources'
60 | baseName = jar.baseName
61 | from sourceSets.main.allSource
62 | }
63 |
64 | artifacts {
65 | archives jar
66 | archives sourcesJar
67 | archives javadocJar
68 | }
69 |
70 | dependencies {
71 | compile files("${android.sdk}/platforms/${android.target}/android.jar")
72 | }
73 |
74 | // http://jedicoder.blogspot.com/2011/11/automated-gradle-project-deployment-to.html
75 | // TODO: switch to http://www.gradle.org/docs/current/userguide/publishing_maven.html
76 |
77 | signing {
78 | sign configurations.archives
79 | }
80 |
81 | uploadArchives {
82 | repositories {
83 | mavenDeployer {
84 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
85 |
86 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
87 | authentication(userName: props["sonatypeUsername"], password: props["sonatypePassword"])
88 | }
89 |
90 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
91 | authentication(userName: props["sonatypeUsername"], password: props["sonatypePassword"])
92 | }
93 |
94 | pom.project {
95 | name 'ActionLauncherApi'
96 | packaging 'jar'
97 | description 'The Action Launcher API allows live wallpaper app developers to provide palette details about your wallpaper so Action Launcher might theme items on a user\'s Home screen with the current colors from their wallpaper.'
98 | url 'https://github.com/chrislacy/ActionLauncherApi'
99 |
100 | scm {
101 | url 'https://github.com/chrislacy/ActionLauncherApi.git'
102 | connection 'scm:git:https://github.com/chrislacy/ActionLauncherApi'
103 | developerConnection 'scm:git:https://github.com/chrislacy/ActionLauncherApi'
104 | }
105 |
106 | licenses {
107 | license {
108 | name 'The Apache Software License, Version 2.0'
109 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
110 | distribution 'repo'
111 | }
112 | }
113 |
114 | developers {
115 | developer {
116 | id 'chrislacy'
117 | name 'Chris Lacy'
118 | }
119 | }
120 | }
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/api/javadoc-scripts/.gitignore:
--------------------------------------------------------------------------------
1 | _locals.sh
2 |
--------------------------------------------------------------------------------
/api/javadoc-scripts/generate_javadoc.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Copyright 2014 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | PLATFORM=android-17
17 | OUT_PATH=../build/javadoc
18 |
19 | cd `dirname $0`
20 |
21 | #source _locals.sh
22 | javadoc -linkoffline http://developer.android.com/reference ${ANDROID_SDK}/docs/reference \
23 | -sourcepath ../src/main/java:../build/source/aidl/debug \
24 | -classpath ${ANDROID_SDK}/platforms/${PLATFORM}/android.jar:${ANDROID_SDK}/tools/support/annotations.jar \
25 | -d ${OUT_PATH} \
26 | -notree -nonavbar -noindex -notree -nohelp -nodeprecated \
27 | -stylesheetfile javadoc_stylesheet.css \
28 | -windowtitle "Action Launcher API" \
29 | -doctitle "Action Launcher API" \
30 | com.actionlauncher.api
31 |
32 | cp prettify* ${OUT_PATH}/resources/
33 |
34 | python tweak_javadoc_html.py ${OUT_PATH}/
35 |
--------------------------------------------------------------------------------
/api/javadoc-scripts/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
inBitmap
is given, a sub-bitmap might be returned.
79 | */
80 | public synchronized Bitmap decodeRegion(Rect rect, Options options) {
81 | int unsampledInBitmapWidth = -1;
82 | int unsampledInBitmapHeight = -1;
83 | int sampleSize = Math.max(1, options != null ? options.inSampleSize : 1);
84 | if (options != null && options.inBitmap != null) {
85 | unsampledInBitmapWidth = options.inBitmap.getWidth() * sampleSize;
86 | unsampledInBitmapHeight = options.inBitmap.getHeight() * sampleSize;
87 | }
88 |
89 | // Decode with rotation
90 | switch (mRotation) {
91 | case 90:
92 | mTempRect.set(
93 | rect.top, mOriginalHeight - rect.right,
94 | rect.bottom, mOriginalHeight - rect.left);
95 | break;
96 |
97 | case 180:
98 | mTempRect.set(
99 | mOriginalWidth - rect.right, mOriginalHeight - rect.bottom,
100 | mOriginalWidth - rect.left, mOriginalHeight - rect.top);
101 | break;
102 |
103 | case 270:
104 | mTempRect.set(
105 | mOriginalWidth - rect.bottom, rect.left,
106 | mOriginalWidth - rect.top, rect.right);
107 | break;
108 |
109 | default:
110 | mTempRect.set(rect);
111 | }
112 |
113 | Bitmap bitmap = mBitmapRegionDecoder.decodeRegion(mTempRect, options);
114 | if (bitmap == null) {
115 | return null;
116 | }
117 |
118 | if (options != null && options.inBitmap != null &&
119 | ((mTempRect.width() != unsampledInBitmapWidth
120 | || mTempRect.height() != unsampledInBitmapHeight))) {
121 | // Need to extract the sub-bitmap
122 | Bitmap subBitmap = Bitmap.createBitmap(
123 | bitmap, 0, 0,
124 | mTempRect.width() / sampleSize,
125 | mTempRect.height() / sampleSize);
126 | if (bitmap != options.inBitmap && bitmap != subBitmap) {
127 | bitmap.recycle();
128 | }
129 | bitmap = subBitmap;
130 | }
131 |
132 | if (mRotateMatrix != null) {
133 | // Rotate decoded bitmap
134 | Bitmap rotatedBitmap = Bitmap.createBitmap(
135 | bitmap, 0, 0,
136 | bitmap.getWidth(), bitmap.getHeight(),
137 | mRotateMatrix, true);
138 | if ((options == null || bitmap != options.inBitmap) && bitmap != rotatedBitmap) {
139 | bitmap.recycle();
140 | }
141 | bitmap = rotatedBitmap;
142 | }
143 |
144 | return bitmap;
145 | }
146 |
147 | public synchronized int getWidth() {
148 | return (mRotation == 90 || mRotation == 270) ? mOriginalHeight : mOriginalWidth;
149 | }
150 |
151 | public synchronized int getHeight() {
152 | return (mRotation == 90 || mRotation == 270) ? mOriginalWidth : mOriginalHeight;
153 | }
154 |
155 | public synchronized void destroy() {
156 | mBitmapRegionDecoder.recycle();
157 | mBitmapRegionDecoder = null;
158 | try {
159 | mInputStream.close();
160 | } catch (IOException ignored) {
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/main/src/main/java/com/google/android/apps/muzei/render/GLColorOverlay.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Chris Lacy
3 | * Copyright 2014 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package com.google.android.apps.muzei.render;
19 |
20 | import android.graphics.Color;
21 | import android.opengl.GLES20;
22 |
23 | import java.nio.FloatBuffer;
24 |
25 | class GLColorOverlay {
26 | private static final String VERTEX_SHADER_CODE = "" +
27 | // This matrix member variable provides a hook to manipulate
28 | // the coordinates of the objects that use this vertex shader
29 | "uniform mat4 uMVPMatrix;" +
30 | "attribute vec4 aPosition;" +
31 | "void main(){" +
32 | " gl_Position = uMVPMatrix * aPosition;" +
33 | "}";
34 |
35 | private static final String FRAGMENT_SHADER_CODE = "" +
36 | "precision mediump float;" +
37 | "uniform sampler2D uTexture;" +
38 | "uniform vec4 uColor;" +
39 | "void main(){" +
40 | " gl_FragColor = uColor;" +
41 | "}";
42 |
43 | // number of coordinates per vertex in this array
44 | private static final int COORDS_PER_VERTEX = 3;
45 | private static final int VERTEX_STRIDE_BYTES = COORDS_PER_VERTEX * GLUtil.BYTES_PER_FLOAT;
46 |
47 | private float mVertices[] = {
48 | -1, 1, 0, // top left
49 | -1, -1, 0, // bottom left
50 | 1, -1, 0, // bottom right
51 |
52 | -1, 1, 0, // top left
53 | 1, -1, 0, // bottom right
54 | 1, 1, 0, // top right
55 | };
56 |
57 | private int mColor;
58 |
59 | private FloatBuffer mVertexBuffer;
60 |
61 | private static int sProgramHandle;
62 | private static int sAttribPositionHandle;
63 | private static int sUniformColorHandle;
64 | private static int sUniformMVPMatrixHandle;
65 |
66 | public GLColorOverlay(int color) {
67 | mColor = color;
68 |
69 | mVertexBuffer = GLUtil.asFloatBuffer(mVertices);
70 | }
71 |
72 | public static void initGl() {
73 | // Initialize shaders and create/link program
74 | int vertexShaderHandle = GLUtil.loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_CODE);
75 | int fragShaderHandle = GLUtil.loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_CODE);
76 |
77 | sProgramHandle = GLUtil.createAndLinkProgram(vertexShaderHandle, fragShaderHandle, null);
78 | sAttribPositionHandle = GLES20.glGetAttribLocation(sProgramHandle, "aPosition");
79 | sUniformMVPMatrixHandle = GLES20.glGetUniformLocation(sProgramHandle, "uMVPMatrix");
80 | sUniformColorHandle = GLES20.glGetUniformLocation(sProgramHandle, "uColor");
81 | }
82 |
83 | public void draw(float[] mvpMatrix) {
84 | // Add program to OpenGL ES environment
85 | GLES20.glUseProgram(sProgramHandle);
86 |
87 | // Pass in the vertex information
88 | GLES20.glEnableVertexAttribArray(sAttribPositionHandle);
89 | GLES20.glVertexAttribPointer(sAttribPositionHandle,
90 | COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
91 | VERTEX_STRIDE_BYTES, mVertexBuffer);
92 |
93 | // Apply the projection and view transformation
94 | GLES20.glUniformMatrix4fv(sUniformMVPMatrixHandle, 1, false, mvpMatrix, 0);
95 | GLUtil.checkGlError("glUniformMatrix4fv");
96 |
97 | // Set the alpha
98 | float r = Color.red(mColor) * 1f / 255;
99 | float g = Color.green(mColor) * 1f / 255;
100 | float b = Color.blue(mColor) * 1f / 255;
101 | float a = Color.alpha(mColor) * 1f / 255;
102 | GLES20.glUniform4f(sUniformColorHandle, r, g, b, a);
103 |
104 | // Draw the triangle
105 | GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mVertices.length / COORDS_PER_VERTEX);
106 |
107 | GLES20.glDisableVertexAttribArray(sAttribPositionHandle);
108 | }
109 |
110 | public void setColor(int color) {
111 | mColor = color;
112 | }
113 |
114 | public void destroy() {
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/main/src/main/java/com/google/android/apps/muzei/render/GLUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Chris Lacy
3 | * Copyright 2014 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package com.google.android.apps.muzei.render;
19 |
20 | import android.graphics.Bitmap;
21 | import android.opengl.GLES20;
22 | import android.opengl.GLUtils;
23 |
24 | import com.google.android.apps.muzei.util.LogUtil;
25 |
26 | import net.nurik.roman.muzei.BuildConfig;
27 |
28 | import java.nio.ByteBuffer;
29 | import java.nio.ByteOrder;
30 | import java.nio.FloatBuffer;
31 |
32 | import static com.google.android.apps.muzei.util.LogUtil.LOGE;
33 |
34 | public class GLUtil {
35 | private static final String TAG = LogUtil.makeLogTag(GLUtil.class);
36 |
37 | public static final int BYTES_PER_FLOAT = 4;
38 |
39 | public static int loadShader(int type, String shaderCode) {
40 | // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
41 | // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
42 | int shaderHandle = GLES20.glCreateShader(type);
43 |
44 | // add the source code to the shader and compile it
45 | GLES20.glShaderSource(shaderHandle, shaderCode);
46 | GLES20.glCompileShader(shaderHandle);
47 | checkGlError("glCompileShader");
48 | return shaderHandle;
49 | }
50 |
51 | public static int createAndLinkProgram(int vertexShaderHandle, int fragShaderHandle,
52 | String[] attributes) {
53 | int programHandle = GLES20.glCreateProgram();
54 | GLUtil.checkGlError("glCreateProgram");
55 | GLES20.glAttachShader(programHandle, vertexShaderHandle);
56 | GLES20.glAttachShader(programHandle, fragShaderHandle);
57 | if (attributes != null) {
58 | final int size = attributes.length;
59 | for (int i = 0; i < size; i++) {
60 | GLES20.glBindAttribLocation(programHandle, i, attributes[i]);
61 | }
62 | }
63 | GLES20.glLinkProgram(programHandle);
64 | GLUtil.checkGlError("glLinkProgram");
65 | GLES20.glDeleteShader(vertexShaderHandle);
66 | GLES20.glDeleteShader(fragShaderHandle);
67 | return programHandle;
68 | }
69 |
70 | public static int loadTexture(Bitmap bitmap) {
71 | final int[] textureHandle = new int[1];
72 |
73 | GLES20.glGenTextures(1, textureHandle, 0);
74 | GLUtil.checkGlError("glGenTextures");
75 |
76 | if (textureHandle[0] != 0) {
77 | // Bind to the texture in OpenGL
78 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);
79 |
80 | // Set filtering
81 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
82 | GLES20.GL_CLAMP_TO_EDGE);
83 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
84 | GLES20.GL_CLAMP_TO_EDGE);
85 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
86 | GLES20.GL_LINEAR);
87 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
88 | GLES20.GL_LINEAR);
89 |
90 | // Load the bitmap into the bound texture.
91 | GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
92 | GLUtil.checkGlError("texImage2D");
93 | }
94 |
95 | if (textureHandle[0] == 0) {
96 | LOGE(TAG, "Error loading texture (empty texture handle)");
97 | if (BuildConfig.DEBUG) {
98 | throw new RuntimeException("Error loading texture (empty texture handle).");
99 | }
100 | }
101 |
102 | return textureHandle[0];
103 | }
104 |
105 | public static void checkGlError(String glOperation) {
106 | int error;
107 | while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
108 | LOGE(TAG, glOperation + ": glError " + error);
109 | if (BuildConfig.DEBUG) {
110 | throw new RuntimeException(glOperation + ": glError " + error);
111 | }
112 | }
113 | }
114 |
115 | public static FloatBuffer asFloatBuffer(float[] array) {
116 | FloatBuffer buffer = newFloatBuffer(array.length);
117 | buffer.put(array);
118 | buffer.position(0);
119 | return buffer;
120 | }
121 |
122 | public static FloatBuffer newFloatBuffer(int size) {
123 | FloatBuffer buffer = ByteBuffer.allocateDirect(size * BYTES_PER_FLOAT)
124 | .order(ByteOrder.nativeOrder())
125 | .asFloatBuffer();
126 | buffer.position(0);
127 | return buffer;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/main/src/main/java/com/google/android/apps/muzei/render/ImageUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Chris Lacy
3 | * Copyright 2014 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package com.google.android.apps.muzei.render;
19 |
20 | import android.graphics.Bitmap;
21 | import android.graphics.Color;
22 |
23 | public class ImageUtil {
24 | // Make sure input images are very small!
25 | public static float calculateDarkness(Bitmap bitmap) {
26 | if (bitmap == null) {
27 | return 0;
28 | }
29 |
30 | int width = bitmap.getWidth();
31 | int height = bitmap.getHeight();
32 |
33 | int totalLum = 0;
34 | int n = 0;
35 | int x, y, color;
36 | for (y = 0; y < height; y++) {
37 | for (x = 0; x < width; x++) {
38 | ++n;
39 | color = bitmap.getPixel(x, y);
40 | totalLum += (0.21f * Color.red(color)
41 | + 0.71f * Color.green(color)
42 | + 0.07f * Color.blue(color));
43 | }
44 | }
45 |
46 | return (totalLum / n) / 256f;
47 | }
48 |
49 | private ImageUtil() {
50 | }
51 |
52 | public static int calculateSampleSize(int rawSize, int targetSize) {
53 | int sampleSize = 1;
54 | while (rawSize / (sampleSize << 1) > targetSize) {
55 | sampleSize <<= 1;
56 | }
57 | return sampleSize;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/main/src/main/java/com/google/android/apps/muzei/render/LocalRenderController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Chris Lacy
3 | * Copyright 2014 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package com.google.android.apps.muzei.render;
19 |
20 | import android.content.Context;
21 | import android.content.SharedPreferences;
22 | import android.os.Handler;
23 | import android.preference.PreferenceManager;
24 |
25 | import com.google.android.apps.muzei.util.LogUtil;
26 |
27 | import java.io.IOException;
28 |
29 | import static com.google.android.apps.muzei.util.LogUtil.LOGE;
30 |
31 | public class LocalRenderController extends RenderController {
32 | private static final String TAG = LogUtil.makeLogTag(LocalRenderController.class);
33 |
34 | private static final String PREF_LOCAL_IMAGE_INDEX = "local_image_index";
35 |
36 | static final String LOCAL_IMAGES[] = {
37 | "kepler-01.jpg",
38 | "kepler-02.jpg",
39 | "kepler-03.jpg",
40 | };
41 |
42 | private final Handler mHandler = new Handler();
43 |
44 | public LocalRenderController(Context context, MuzeiBlurRenderer renderer,
45 | Callbacks callbacks) {
46 | super(context, renderer, callbacks);
47 | }
48 |
49 | @Override
50 | public void destroy() {
51 | super.destroy();
52 | mHandler.removeCallbacksAndMessages(null);
53 | }
54 |
55 | @Override
56 | protected BitmapRegionLoader openDownloadedCurrentArtwork(boolean forceReload) {
57 | try {
58 | final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
59 | int index = sp.getInt(PREF_LOCAL_IMAGE_INDEX, 0);
60 | int nextIndex = index+1;
61 | if (nextIndex >= LOCAL_IMAGES.length) {
62 | nextIndex = 0;
63 | }
64 | sp.edit().putInt(PREF_LOCAL_IMAGE_INDEX, nextIndex).apply();
65 |
66 | return BitmapRegionLoader.newInstance(mContext.getAssets().open(LOCAL_IMAGES[index]));
67 | } catch (IOException e) {
68 | LOGE(TAG, "Error opening demo image.", e);
69 | return null;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/main/src/main/java/com/google/android/apps/muzei/render/RenderController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Chris Lacy
3 | * Copyright 2014 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package com.google.android.apps.muzei.render;
19 |
20 | import android.content.Context;
21 | import android.os.AsyncTask;
22 | import android.os.Handler;
23 | import android.os.Message;
24 |
25 |
26 | public abstract class RenderController {
27 | protected Context mContext;
28 | protected MuzeiBlurRenderer mRenderer;
29 | protected Callbacks mCallbacks;
30 | protected boolean mVisible;
31 | private BitmapRegionLoader mQueuedBitmapRegionLoader;
32 |
33 | public RenderController(Context context, MuzeiBlurRenderer renderer, Callbacks callbacks) {
34 | mRenderer = renderer;
35 | mContext = context;
36 | mCallbacks = callbacks;
37 | }
38 |
39 | public void destroy() {
40 | if (mQueuedBitmapRegionLoader != null) {
41 | mQueuedBitmapRegionLoader.destroy();
42 | }
43 | }
44 |
45 | private void throttledForceReloadCurrentArtwork() {
46 | mThrottledForceReloadHandler.removeMessages(0);
47 | mThrottledForceReloadHandler.sendEmptyMessageDelayed(0, 250);
48 | }
49 |
50 | private Handler mThrottledForceReloadHandler = new Handler() {
51 | @Override
52 | public void handleMessage(Message msg) {
53 | reloadCurrentArtwork(true);
54 | }
55 | };
56 |
57 | protected abstract BitmapRegionLoader openDownloadedCurrentArtwork(boolean forceReload);
58 |
59 | public void reloadCurrentArtwork(final boolean forceReload) {
60 | new AsyncTaskBased on the original action bar implementation in
33 | * ActionMenuItemView.java.
34 | */
35 | public class CheatSheet {
36 | /**
37 | * The estimated height of a toast, in dips (density-independent pixels). This is used to
38 | * determine whether or not the toast should appear above or below the UI element.
39 | */
40 | private static final int ESTIMATED_TOAST_HEIGHT_DIPS = 48;
41 |
42 | /**
43 | * Sets up a cheat sheet (tooltip) for the given view by setting its {@link
44 | * android.view.View.OnLongClickListener}. When the view is long-pressed, a {@link Toast} with
45 | * the view's {@link android.view.View#getContentDescription() content description} will be
46 | * shown either above (default) or below the view (if there isn't room above it).
47 | *
48 | * @param view The view to add a cheat sheet for.
49 | */
50 | public static void setup(View view) {
51 | view.setOnLongClickListener(new View.OnLongClickListener() {
52 | @Override
53 | public boolean onLongClick(View view) {
54 | return showCheatSheet(view, view.getContentDescription());
55 | }
56 | });
57 | }
58 |
59 | /**
60 | * Sets up a cheat sheet (tooltip) for the given view by setting its {@link
61 | * android.view.View.OnLongClickListener}. When the view is long-pressed, a {@link Toast} with
62 | * the given text will be shown either above (default) or below the view (if there isn't room
63 | * above it).
64 | *
65 | * @param view The view to add a cheat sheet for.
66 | * @param textResId The string resource containing the text to show on long-press.
67 | */
68 | public static void setup(View view, final int textResId) {
69 | view.setOnLongClickListener(new View.OnLongClickListener() {
70 | @Override
71 | public boolean onLongClick(View view) {
72 | return showCheatSheet(view, view.getContext().getString(textResId));
73 | }
74 | });
75 | }
76 |
77 | /**
78 | * Sets up a cheat sheet (tooltip) for the given view by setting its {@link
79 | * android.view.View.OnLongClickListener}. When the view is long-pressed, a {@link Toast} with
80 | * the given text will be shown either above (default) or below the view (if there isn't room
81 | * above it).
82 | *
83 | * @param view The view to add a cheat sheet for.
84 | * @param text The text to show on long-press.
85 | */
86 | public static void setup(View view, final CharSequence text) {
87 | view.setOnLongClickListener(new View.OnLongClickListener() {
88 | @Override
89 | public boolean onLongClick(View view) {
90 | return showCheatSheet(view, text);
91 | }
92 | });
93 | }
94 |
95 | /**
96 | * Removes the cheat sheet for the given view by removing the view's {@link
97 | * android.view.View.OnLongClickListener}.
98 | *
99 | * @param view The view whose cheat sheet should be removed.
100 | */
101 | public static void remove(final View view) {
102 | view.setOnLongClickListener(null);
103 | }
104 |
105 | /**
106 | * Internal helper method to show the cheat sheet toast.
107 | */
108 | private static boolean showCheatSheet(View view, CharSequence text) {
109 | if (TextUtils.isEmpty(text)) {
110 | return false;
111 | }
112 |
113 | final int[] screenPos = new int[2]; // origin is device display
114 | final Rect displayFrame = new Rect(); // includes decorations (e.g. status bar)
115 | view.getLocationOnScreen(screenPos);
116 | view.getWindowVisibleDisplayFrame(displayFrame);
117 |
118 | final Context context = view.getContext();
119 | final int viewWidth = view.getWidth();
120 | final int viewHeight = view.getHeight();
121 | final int viewCenterX = screenPos[0] + viewWidth / 2;
122 | final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
123 | final int estimatedToastHeight = (int) (ESTIMATED_TOAST_HEIGHT_DIPS
124 | * context.getResources().getDisplayMetrics().density);
125 |
126 | Toast cheatSheet = Toast.makeText(context, text, Toast.LENGTH_SHORT);
127 | boolean showBelow = screenPos[1] < estimatedToastHeight;
128 | if (showBelow) {
129 | // Show below
130 | // Offsets are after decorations (e.g. status bar) are factored in
131 | cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
132 | viewCenterX - screenWidth / 2,
133 | screenPos[1] - displayFrame.top + viewHeight);
134 | } else {
135 | // Show above
136 | // Offsets are after decorations (e.g. status bar) are factored in
137 | // NOTE: We can't use Gravity.BOTTOM because when the keyboard is up
138 | // its height isn't factored in.
139 | cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
140 | viewCenterX - screenWidth / 2,
141 | screenPos[1] - displayFrame.top - estimatedToastHeight);
142 | }
143 |
144 | cheatSheet.show();
145 | return true;
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/main/src/main/java/com/google/android/apps/muzei/util/DrawInsetsFrameLayout.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Chris Lacy
3 | * Copyright 2014 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package com.google.android.apps.muzei.util;
19 |
20 | import android.content.Context;
21 | import android.content.res.TypedArray;
22 | import android.graphics.Canvas;
23 | import android.graphics.Rect;
24 | import android.graphics.drawable.Drawable;
25 | import android.util.AttributeSet;
26 | import android.widget.FrameLayout;
27 |
28 | import net.nurik.roman.muzei.R;
29 |
30 | public class DrawInsetsFrameLayout extends FrameLayout {
31 | private Drawable mInsetBackground;
32 | private Drawable mTopInsetBackground;
33 | private Drawable mBottomInsetBackground;
34 | private Drawable mSideInsetBackground;
35 |
36 | private Rect mInsets;
37 | private Rect mTempRect = new Rect();
38 | private OnInsetsCallback mOnInsetsCallback;
39 |
40 | public DrawInsetsFrameLayout(Context context) {
41 | super(context);
42 | init(context, null, 0);
43 | }
44 |
45 | public DrawInsetsFrameLayout(Context context, AttributeSet attrs) {
46 | super(context, attrs);
47 | init(context, attrs, 0);
48 | }
49 |
50 | public DrawInsetsFrameLayout(Context context, AttributeSet attrs, int defStyle) {
51 | super(context, attrs, defStyle);
52 | init(context, attrs, defStyle);
53 | }
54 |
55 | private void init(Context context, AttributeSet attrs, int defStyle) {
56 | final TypedArray a = context.obtainStyledAttributes(attrs,
57 | R.styleable.DrawInsetsFrameLayout, defStyle, 0);
58 | assert a != null;
59 |
60 | mInsetBackground = a.getDrawable(R.styleable.DrawInsetsFrameLayout_insetBackground);
61 | mTopInsetBackground = a.getDrawable(R.styleable.DrawInsetsFrameLayout_topInsetBackground);
62 | mBottomInsetBackground =
63 | a.getDrawable(R.styleable.DrawInsetsFrameLayout_bottomInsetBackground);
64 | mSideInsetBackground = a.getDrawable(R.styleable.DrawInsetsFrameLayout_sideInsetBackground);
65 |
66 | a.recycle();
67 | }
68 |
69 | @Override
70 | protected void onAttachedToWindow() {
71 | super.onAttachedToWindow();
72 | if (mInsetBackground != null) {
73 | mInsetBackground.setCallback(this);
74 | }
75 | if (mTopInsetBackground != null) {
76 | mTopInsetBackground.setCallback(this);
77 | }
78 | if (mBottomInsetBackground != null) {
79 | mBottomInsetBackground.setCallback(this);
80 | }
81 | if (mSideInsetBackground != null) {
82 | mSideInsetBackground.setCallback(this);
83 | }
84 | }
85 |
86 | @Override
87 | protected void onDetachedFromWindow() {
88 | super.onDetachedFromWindow();
89 | if (mInsetBackground != null) {
90 | mInsetBackground.setCallback(null);
91 | }
92 | if (mTopInsetBackground != null) {
93 | mTopInsetBackground.setCallback(null);
94 | }
95 | if (mBottomInsetBackground != null) {
96 | mBottomInsetBackground.setCallback(null);
97 | }
98 | if (mSideInsetBackground != null) {
99 | mSideInsetBackground.setCallback(null);
100 | }
101 | }
102 |
103 | public void setOnInsetsCallback(OnInsetsCallback onInsetsCallback) {
104 | mOnInsetsCallback = onInsetsCallback;
105 | }
106 |
107 | @Override
108 | protected boolean fitSystemWindows(Rect insets) {
109 | mInsets = new Rect(insets);
110 | setWillNotDraw(false);
111 | postInvalidateOnAnimation();
112 | if (mOnInsetsCallback != null) {
113 | mOnInsetsCallback.onInsetsChanged(insets);
114 | }
115 | return true;
116 | }
117 |
118 | @Override
119 | protected void onDraw(Canvas canvas) {
120 | super.onDraw(canvas);
121 | int width = getWidth();
122 | int height = getHeight();
123 |
124 | if (mInsets != null) {
125 | // Top
126 | mTempRect.set(0, 0, width, mInsets.top);
127 | if (mInsetBackground != null) {
128 | mInsetBackground.setBounds(mTempRect);
129 | mInsetBackground.draw(canvas);
130 | }
131 | if (mTopInsetBackground != null) {
132 | mTopInsetBackground.setBounds(mTempRect);
133 | mTopInsetBackground.draw(canvas);
134 | }
135 |
136 | // Bottom
137 | mTempRect.set(0, height - mInsets.bottom, width, height);
138 | if (mInsetBackground != null) {
139 | mInsetBackground.setBounds(mTempRect);
140 | mInsetBackground.draw(canvas);
141 | }
142 | if (mTopInsetBackground != null) {
143 | mBottomInsetBackground.setBounds(mTempRect);
144 | mBottomInsetBackground.draw(canvas);
145 | }
146 |
147 | // Left
148 | mTempRect.set(0, mInsets.top, mInsets.left, height - mInsets.bottom);
149 | if (mInsetBackground != null) {
150 | mInsetBackground.setBounds(mTempRect);
151 | mInsetBackground.draw(canvas);
152 | }
153 | if (mSideInsetBackground != null) {
154 | mSideInsetBackground.setBounds(mTempRect);
155 | mSideInsetBackground.draw(canvas);
156 | }
157 |
158 | // Right
159 | mTempRect.set(width - mInsets.right, mInsets.top, width, height - mInsets.bottom);
160 | if (mInsetBackground != null) {
161 | mInsetBackground.setBounds(mTempRect);
162 | mInsetBackground.draw(canvas);
163 | }
164 | if (mSideInsetBackground != null) {
165 | mSideInsetBackground.setBounds(mTempRect);
166 | mSideInsetBackground.draw(canvas);
167 | }
168 | }
169 | }
170 |
171 | public static interface OnInsetsCallback {
172 | public void onInsetsChanged(Rect insets);
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/main/src/main/java/com/google/android/apps/muzei/util/ImageBlurrer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Chris Lacy
3 | * Copyright 2014 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package com.google.android.apps.muzei.util;
19 |
20 | import android.content.Context;
21 | import android.graphics.Bitmap;
22 | import android.support.v8.renderscript.Allocation;
23 | import android.support.v8.renderscript.Element;
24 | import android.support.v8.renderscript.Matrix3f;
25 | import android.support.v8.renderscript.RSInvalidStateException;
26 | import android.support.v8.renderscript.RenderScript;
27 | import android.support.v8.renderscript.ScriptIntrinsicBlur;
28 | import android.support.v8.renderscript.ScriptIntrinsicColorMatrix;
29 |
30 | public class ImageBlurrer {
31 | public static final int MAX_SUPPORTED_BLUR_PIXELS = 25;
32 | private RenderScript mRS;
33 |
34 | private ScriptIntrinsicBlur mSIBlur;
35 | private ScriptIntrinsicColorMatrix mSIGrey;
36 | private Allocation mTmp1;
37 | private Allocation mTmp2;
38 |
39 | public ImageBlurrer(Context context) {
40 | mRS = RenderScript.create(context);
41 | mSIBlur = ScriptIntrinsicBlur.create(mRS, Element.U8_4(mRS));
42 | mSIGrey = ScriptIntrinsicColorMatrix.create(mRS, Element.U8_4(mRS));
43 | }
44 |
45 | public Bitmap blurBitmap(Bitmap src, float radius, float desaturateAmount) {
46 | if (src == null) {
47 | return null;
48 | }
49 |
50 | Bitmap dest = Bitmap.createBitmap(src);
51 | if (radius == 0f && desaturateAmount == 0f) {
52 | return dest;
53 | }
54 |
55 | if (mTmp1 != null) {
56 | mTmp1.destroy();
57 | }
58 | if (mTmp2 != null) {
59 | try {
60 | mTmp2.destroy();
61 | } catch (RSInvalidStateException e) {
62 | // Ignore 'Object already destroyed' exceptions
63 | }
64 | }
65 |
66 | mTmp1 = Allocation.createFromBitmap(mRS, src);
67 | mTmp2 = Allocation.createFromBitmap(mRS, dest);
68 |
69 | if (radius > 0f && desaturateAmount > 0f) {
70 | doBlur(radius, mTmp1, mTmp2);
71 | doDesaturate(MathUtil.constrain(0, 1, desaturateAmount), mTmp2, mTmp1);
72 | mTmp1.copyTo(dest);
73 | } else if (radius > 0f) {
74 | doBlur(radius, mTmp1, mTmp2);
75 | mTmp2.copyTo(dest);
76 | } else {
77 | doDesaturate(MathUtil.constrain(0, 1, desaturateAmount), mTmp1, mTmp2);
78 | mTmp2.copyTo(dest);
79 | }
80 | return dest;
81 | }
82 |
83 | private void doBlur(float amount, Allocation input, Allocation output) {
84 | mSIBlur.setRadius(amount);
85 | mSIBlur.setInput(input);
86 | mSIBlur.forEach(output);
87 | }
88 |
89 | private void doDesaturate(float normalizedAmount, Allocation input, Allocation output) {
90 | Matrix3f m = new Matrix3f(new float[]{
91 | MathUtil.interpolate(1, 0.299f, normalizedAmount),
92 | MathUtil.interpolate(0, 0.299f, normalizedAmount),
93 | MathUtil.interpolate(0, 0.299f, normalizedAmount),
94 |
95 | MathUtil.interpolate(0, 0.587f, normalizedAmount),
96 | MathUtil.interpolate(1, 0.587f, normalizedAmount),
97 | MathUtil.interpolate(0, 0.587f, normalizedAmount),
98 |
99 | MathUtil.interpolate(0, 0.114f, normalizedAmount),
100 | MathUtil.interpolate(0, 0.114f, normalizedAmount),
101 | MathUtil.interpolate(1, 0.114f, normalizedAmount),
102 | });
103 | mSIGrey.setColorMatrix(m);
104 | mSIGrey.forEach(input, output);
105 | }
106 |
107 | public void destroy() {
108 | mSIBlur.destroy();
109 | if (mTmp1 != null) {
110 | mTmp1.destroy();
111 | }
112 | if (mTmp2 != null) {
113 | mTmp2.destroy();
114 | }
115 | mRS.destroy();
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/main/src/main/java/com/google/android/apps/muzei/util/LogUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Chris Lacy
3 | * Copyright 2014 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package com.google.android.apps.muzei.util;
19 |
20 | import android.util.Log;
21 |
22 | import net.nurik.roman.muzei.BuildConfig;
23 |
24 | /**
25 | * Helper methods that make logging more consistent throughout the app.
26 | */
27 | public class LogUtil {
28 | private static final String TAG = makeLogTag(LogUtil.class);
29 |
30 | private static final String LOG_PREFIX = "muzei_";
31 | private static final int LOG_PREFIX_LENGTH = LOG_PREFIX.length();
32 | private static final int MAX_LOG_TAG_LENGTH = 23;
33 |
34 | private LogUtil() {
35 | }
36 |
37 | public static String makeLogTag(String str) {
38 | if (str.length() > MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH) {
39 | return LOG_PREFIX + str.substring(0, MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH - 1);
40 | }
41 |
42 | return LOG_PREFIX + str;
43 | }
44 |
45 | /**
46 | * WARNING: Don't use this when obfuscating class names with Proguard!
47 | */
48 | public static String makeLogTag(Class cls) {
49 | return makeLogTag(cls.getSimpleName());
50 | }
51 |
52 | public static void LOGD(final String tag, String message) {
53 | //noinspection PointlessBooleanExpression,ConstantConditions
54 | if (BuildConfig.DEBUG || Log.isLoggable(tag, Log.DEBUG)) {
55 | Log.d(tag, message);
56 | }
57 | }
58 |
59 | public static void LOGD(final String tag, String message, Throwable cause) {
60 | //noinspection PointlessBooleanExpression,ConstantConditions
61 | if (BuildConfig.DEBUG || Log.isLoggable(tag, Log.DEBUG)) {
62 | Log.d(tag, message, cause);
63 | }
64 | }
65 |
66 | public static void LOGV(final String tag, String message) {
67 | //noinspection PointlessBooleanExpression,ConstantConditions
68 | if (BuildConfig.DEBUG && Log.isLoggable(tag, Log.VERBOSE)) {
69 | Log.v(tag, message);
70 | }
71 | }
72 |
73 | public static void LOGV(final String tag, String message, Throwable cause) {
74 | //noinspection PointlessBooleanExpression,ConstantConditions
75 | if (BuildConfig.DEBUG && Log.isLoggable(tag, Log.VERBOSE)) {
76 | Log.v(tag, message, cause);
77 | }
78 | }
79 |
80 | public static void LOGI(final String tag, String message) {
81 | Log.i(tag, message);
82 | }
83 |
84 | public static void LOGI(final String tag, String message, Throwable cause) {
85 | Log.i(tag, message, cause);
86 | }
87 |
88 | public static void LOGW(final String tag, String message) {
89 | if (BuildConfig.DEBUG) {
90 | Log.w(tag, message, new Throwable()); // create a stacktrace
91 | } else {
92 | Log.w(tag, message);
93 | }
94 | }
95 |
96 | public static void LOGW(final String tag, String message, Throwable cause) {
97 | Log.w(tag, message, cause);
98 | }
99 |
100 | public static void LOGE(final String tag, String message) {
101 | if (BuildConfig.DEBUG) {
102 | Log.e(tag, message, new Throwable()); // create a stacktrace
103 | } else {
104 | Log.e(tag, message);
105 | }
106 | }
107 |
108 | public static void LOGE(final String tag, String message, Throwable cause) {
109 | Log.e(tag, message, cause);
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/main/src/main/java/com/google/android/apps/muzei/util/MathUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Chris Lacy
3 | * Copyright 2014 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package com.google.android.apps.muzei.util;
19 |
20 | import android.util.FloatMath;
21 |
22 | public class MathUtil {
23 | public static float constrain(float min, float max, float v) {
24 | return Math.max(min, Math.min(max, v));
25 | }
26 |
27 | public static float interpolate(float x1, float x2, float f) {
28 | return x1 + (x2 - x1) * f;
29 | }
30 |
31 | public static float uninterpolate(float x1, float x2, float v) {
32 | if (x2 - x1 == 0) {
33 | throw new IllegalArgumentException("Can't reverse interpolate with domain size of 0");
34 | }
35 | return (v - x1) / (x2 - x1);
36 | }
37 |
38 | public static float dist(float x, float y) {
39 | return FloatMath.sqrt(x * x + y * y);
40 | }
41 |
42 | public static int floorEven(int num) {
43 | return num & ~0x01;
44 | }
45 |
46 | public static int roundMult4(int num) {
47 | return (num + 2) & ~0x03;
48 | }
49 |
50 | public static boolean isEven(int num) {
51 | return num % 2 == 0;
52 | }
53 |
54 | // divide two integers but round up
55 | // see http://stackoverflow.com/a/7446742/102703
56 | public static int intDivideRoundUp(int num, int divisor) {
57 | int sign = (num > 0 ? 1 : -1) * (divisor > 0 ? 1 : -1);
58 | return sign * (Math.abs(num) + Math.abs(divisor) - 1) / Math.abs(divisor);
59 | }
60 |
61 | private MathUtil() {
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/main/src/main/java/com/google/android/apps/muzei/util/MultiSelectionController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Chris Lacy
3 | * Copyright 2014 Google Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package com.google.android.apps.muzei.util;
19 |
20 | import android.os.Bundle;
21 | import android.os.Parcelable;
22 |
23 | import java.util.HashSet;
24 | import java.util.Set;
25 |
26 | /**
27 | * Utilities for storing multiple selection information in collection views.
28 | */
29 | public class MultiSelectionController