Merge "Add support for the new native Builder class on StaticLayout."
diff --git a/api/current.txt b/api/current.txt
index 612d16d..489fd05 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -24062,6 +24062,7 @@
field public static final java.lang.String CACHED_NUMBER_LABEL = "numberlabel";
field public static final java.lang.String CACHED_NUMBER_TYPE = "numbertype";
field public static final java.lang.String CACHED_PHOTO_ID = "photo_id";
+ field public static final java.lang.String CACHED_PHOTO_URI = "photo_uri";
field public static final android.net.Uri CONTENT_FILTER_URI;
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/calls";
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/calls";
diff --git a/api/system-current.txt b/api/system-current.txt
index ff9809f..7fa8d9b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -25667,6 +25667,7 @@
field public static final java.lang.String CACHED_NUMBER_LABEL = "numberlabel";
field public static final java.lang.String CACHED_NUMBER_TYPE = "numbertype";
field public static final java.lang.String CACHED_PHOTO_ID = "photo_id";
+ field public static final java.lang.String CACHED_PHOTO_URI = "photo_uri";
field public static final android.net.Uri CONTENT_FILTER_URI;
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/calls";
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/calls";
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4a3650f..e23ffe4 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3681,9 +3681,14 @@
/**
* Check whether the current user has been blocked by device policy from uninstalling a package.
* Requires the caller to be the profile owner if checking a specific admin's policy.
+ * <p>
+ * <strong>Note:</strong> Starting from {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}, the
+ * behavior of this API is changed such that passing <code>null</code> as the <code>admin</code>
+ * parameter will return if any admin has blocked the uninstallation. Before L MR1, passing
+ * <code>null</code> will cause a NullPointerException to be raised.
*
* @param admin The name of the admin component whose blocking policy will be checked, or null
- * to check if any admin has blocked the uninstallation.
+ * to check if any admin has blocked the uninstallation.
* @param packageName package to check.
* @return true if uninstallation is blocked.
*/
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 2df9dbf..6517f35 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -328,6 +328,14 @@
public static final String CACHED_PHOTO_ID = "photo_id";
/**
+ * The cached photo URI of the picture associated with the phone number, if it exists.
+ * This value may not be current if the contact information associated with this number
+ * has changed.
+ * <P>Type: TEXT (URI)</P>
+ */
+ public static final String CACHED_PHOTO_URI = "photo_uri";
+
+ /**
* The cached phone number, formatted with formatting rules based on the country the
* user was in when the call was made or received.
* This value is not guaranteed to be present, and may not be current if the contact
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/DisplayListCanvas.java
similarity index 85%
rename from core/java/android/view/GLES20Canvas.java
rename to core/java/android/view/DisplayListCanvas.java
index 06e196d..90e1f86 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/DisplayListCanvas.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.NonNull;
import android.graphics.Bitmap;
import android.graphics.CanvasProperty;
import android.graphics.NinePatch;
@@ -24,19 +25,50 @@
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.util.Pools.SynchronizedPool;
/**
- * An implementation of Canvas on top of OpenGL ES 2.0.
+ * An implementation of a GL canvas that records drawing operations.
+ * This is intended for use with a DisplayList. This class keeps a list of all the Paint and
+ * Bitmap objects that it draws, preventing the backing memory of Bitmaps from being freed while
+ * the DisplayList is still holding a native reference to the memory.
*/
-class GLES20Canvas extends HardwareCanvas {
+class DisplayListCanvas extends HardwareCanvas {
+ // The recording canvas pool should be large enough to handle a deeply nested
+ // view hierarchy because display lists are generated recursively.
+ private static final int POOL_LIMIT = 25;
+
+ private static final SynchronizedPool<DisplayListCanvas> sPool =
+ new SynchronizedPool<DisplayListCanvas>(POOL_LIMIT);
+
+ RenderNode mNode;
private int mWidth;
private int mHeight;
- private float[] mPoint;
- private float[] mLine;
- private Rect mClipBounds;
- private RectF mPathBounds;
+ static DisplayListCanvas obtain(@NonNull RenderNode node) {
+ if (node == null) throw new IllegalArgumentException("node cannot be null");
+ DisplayListCanvas canvas = sPool.acquire();
+ if (canvas == null) {
+ canvas = new DisplayListCanvas();
+ }
+ canvas.mNode = node;
+ return canvas;
+ }
+
+ void recycle() {
+ mNode = null;
+ sPool.release(this);
+ }
+
+ long finishRecording() {
+ return nFinishRecording(mNativeCanvasWrapper);
+ }
+
+ @Override
+ public boolean isRecordingFor(Object o) {
+ return o == mNode;
+ }
///////////////////////////////////////////////////////////////////////////
// JNI
@@ -53,8 +85,8 @@
// Constructors
///////////////////////////////////////////////////////////////////////////
- // TODO: Merge with GLES20RecordingCanvas
- protected GLES20Canvas() {
+
+ private DisplayListCanvas() {
super(nCreateDisplayListRenderer());
}
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
deleted file mode 100644
index 6c780c9..0000000
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.annotation.NonNull;
-import android.util.Pools.SynchronizedPool;
-
-/**
- * An implementation of a GL canvas that records drawing operations.
- * This is intended for use with a DisplayList. This class keeps a list of all the Paint and
- * Bitmap objects that it draws, preventing the backing memory of Bitmaps from being freed while
- * the DisplayList is still holding a native reference to the memory.
- */
-class GLES20RecordingCanvas extends GLES20Canvas {
- // The recording canvas pool should be large enough to handle a deeply nested
- // view hierarchy because display lists are generated recursively.
- private static final int POOL_LIMIT = 25;
-
- private static final SynchronizedPool<GLES20RecordingCanvas> sPool =
- new SynchronizedPool<GLES20RecordingCanvas>(POOL_LIMIT);
-
- RenderNode mNode;
-
- private GLES20RecordingCanvas() {
- super();
- }
-
- static GLES20RecordingCanvas obtain(@NonNull RenderNode node) {
- if (node == null) throw new IllegalArgumentException("node cannot be null");
- GLES20RecordingCanvas canvas = sPool.acquire();
- if (canvas == null) {
- canvas = new GLES20RecordingCanvas();
- }
- canvas.mNode = node;
- return canvas;
- }
-
- void recycle() {
- mNode = null;
- sPool.release(this);
- }
-
- long finishRecording() {
- return nFinishRecording(mNativeCanvasWrapper);
- }
-
- @Override
- public boolean isRecordingFor(Object o) {
- return o == mNode;
- }
-}
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index cdb350f..fc2b55b 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -119,6 +119,6 @@
CanvasProperty<Paint> paint);
public static void setProperty(String name, String value) {
- GLES20Canvas.setProperty(name, value);
+ DisplayListCanvas.setProperty(name, value);
}
}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 9921be2..afa7f51 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -205,7 +205,7 @@
* false otherwise
*/
public static boolean isAvailable() {
- return GLES20Canvas.isAvailable();
+ return DisplayListCanvas.isAvailable();
}
/**
@@ -423,7 +423,7 @@
*/
static HardwareRenderer create(Context context, boolean translucent) {
HardwareRenderer renderer = null;
- if (GLES20Canvas.isAvailable()) {
+ if (DisplayListCanvas.isAvailable()) {
renderer = new ThreadedRenderer(context, translucent);
}
return renderer;
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 09eb486..38867a8 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -225,7 +225,7 @@
* @see #isValid()
*/
public HardwareCanvas start(int width, int height) {
- HardwareCanvas canvas = GLES20RecordingCanvas.obtain(this);
+ HardwareCanvas canvas = DisplayListCanvas.obtain(this);
canvas.setViewport(width, height);
// The dirty rect should always be null for a display list
canvas.onPreDraw(null);
@@ -241,11 +241,11 @@
* @see #isValid()
*/
public void end(HardwareCanvas endCanvas) {
- if (!(endCanvas instanceof GLES20RecordingCanvas)) {
+ if (!(endCanvas instanceof DisplayListCanvas)) {
throw new IllegalArgumentException("Passed an invalid canvas to end!");
}
- GLES20RecordingCanvas canvas = (GLES20RecordingCanvas) endCanvas;
+ DisplayListCanvas canvas = (DisplayListCanvas) endCanvas;
canvas.onPostDraw();
long renderNodeData = canvas.finishRecording();
nSetDisplayListData(mNativeRenderNode, renderNodeData);
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 7b35a3b..379796d 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -283,10 +283,10 @@
}
public void setTarget(Canvas canvas) {
- if (!(canvas instanceof GLES20RecordingCanvas)) {
+ if (!(canvas instanceof DisplayListCanvas)) {
throw new IllegalArgumentException("Not a GLES20RecordingCanvas");
}
- final GLES20RecordingCanvas recordingCanvas = (GLES20RecordingCanvas) canvas;
+ final DisplayListCanvas recordingCanvas = (DisplayListCanvas) canvas;
setTarget(recordingCanvas.mNode);
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 0f99e88..cc44577 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -4887,9 +4887,10 @@
text.insert(newTextInsertAt, newText);
}
}
- // Restore the cursor position.
+ // Restore the cursor position. If there wasn't an old cursor (newCursorPos == -1) then
+ // don't explicitly set it and rely on SpannableStringBuilder to position it.
// TODO: Select all the text that was undone.
- if (newCursorPos <= text.length()) {
+ if (0 <= newCursorPos && newCursorPos <= text.length()) {
Selection.setSelection(text, newCursorPos);
}
}
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index a224f5e..d12739f 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -515,6 +515,23 @@
}
}
+ // Use the top-start-most laid out view as the baseline. RTL offsets are
+ // applied later, so we can use the left-most edge as the starting edge.
+ View baselineView = null;
+ LayoutParams baselineParams = null;
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final LayoutParams childParams = (LayoutParams) child.getLayoutParams();
+ if (baselineView == null || baselineParams == null
+ || compareLayoutPosition(childParams, baselineParams) < 0) {
+ baselineView = child;
+ baselineParams = childParams;
+ }
+ }
+ }
+ mBaselineView = baselineView;
+
if (isWrapContentWidth) {
// Width already has left padding in it since it was calculated by looking at
// the right of each child view
@@ -616,25 +633,24 @@
}
}
- // Use the bottom-most laid out view as the baseline.
- View baselineView = null;
- int baseline = 0;
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- final int childBaseline = child.getBaseline();
- if (childBaseline >= baseline) {
- baselineView = child;
- baseline = childBaseline;
- }
- }
- }
- mBaselineView = baselineView;
-
setMeasuredDimension(width, height);
}
/**
+ * @return a negative number if the top of {@code p1} is above the top of
+ * {@code p2} or if they have identical top values and the left of
+ * {@code p1} is to the left of {@code p2}, or a positive number
+ * otherwise
+ */
+ private int compareLayoutPosition(LayoutParams p1, LayoutParams p2) {
+ final int topDiff = p1.mTop - p2.mTop;
+ if (topDiff != 0) {
+ return topDiff;
+ }
+ return p1.mLeft - p2.mLeft;
+ }
+
+ /**
* Measure a child. The child should have left, top, right and bottom information
* stored in its LayoutParams. If any of these values is VALUE_NOT_SET it means
* that the view can extend up to the corresponding edge.
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index a410e45..3ceea9d 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -342,6 +342,9 @@
return;
}
+ // Do not show the profile switch message anymore.
+ mProfileSwitchMessageId = -1;
+
final Intent intent = intentForDisplayResolveInfo(dri);
onIntentSelected(dri.ri, intent, false);
finish();
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 97744ea..30a7e68 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -18,6 +18,8 @@
LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
+LOCAL_CFLAGS += -DU_USING_ICU_NAMESPACE=0
+
LOCAL_SRC_FILES:= \
AndroidRuntime.cpp \
com_android_internal_content_NativeLibraryHelper.cpp \
@@ -41,25 +43,25 @@
android_database_SQLiteDebug.cpp \
android_emoji_EmojiFactory.cpp \
android_view_DisplayEventReceiver.cpp \
- android_view_Surface.cpp \
- android_view_SurfaceControl.cpp \
- android_view_SurfaceSession.cpp \
- android_view_TextureView.cpp \
+ android_view_DisplayListCanvas.cpp \
+ android_view_GraphicBuffer.cpp \
+ android_view_HardwareLayer.cpp \
android_view_InputChannel.cpp \
android_view_InputDevice.cpp \
android_view_InputEventReceiver.cpp \
android_view_InputEventSender.cpp \
android_view_InputQueue.cpp \
- android_view_KeyEvent.cpp \
android_view_KeyCharacterMap.cpp \
- android_view_GraphicBuffer.cpp \
- android_view_GLES20Canvas.cpp \
- android_view_HardwareLayer.cpp \
- android_view_ThreadedRenderer.cpp \
+ android_view_KeyEvent.cpp \
android_view_MotionEvent.cpp \
android_view_PointerIcon.cpp \
android_view_RenderNode.cpp \
android_view_RenderNodeAnimator.cpp \
+ android_view_Surface.cpp \
+ android_view_SurfaceControl.cpp \
+ android_view_SurfaceSession.cpp \
+ android_view_TextureView.cpp \
+ android_view_ThreadedRenderer.cpp \
android_view_VelocityTracker.cpp \
android_text_AndroidCharacter.cpp \
android_text_AndroidBidi.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 77afd05..ad52e3f 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -127,16 +127,16 @@
extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
extern int register_android_view_DisplayEventReceiver(JNIEnv* env);
+extern int register_android_view_DisplayListCanvas(JNIEnv* env);
+extern int register_android_view_GraphicBuffer(JNIEnv* env);
+extern int register_android_view_HardwareLayer(JNIEnv* env);
extern int register_android_view_RenderNode(JNIEnv* env);
extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
-extern int register_android_view_GraphicBuffer(JNIEnv* env);
-extern int register_android_view_GLES20Canvas(JNIEnv* env);
-extern int register_android_view_HardwareLayer(JNIEnv* env);
-extern int register_android_view_ThreadedRenderer(JNIEnv* env);
extern int register_android_view_Surface(JNIEnv* env);
extern int register_android_view_SurfaceControl(JNIEnv* env);
extern int register_android_view_SurfaceSession(JNIEnv* env);
extern int register_android_view_TextureView(JNIEnv* env);
+extern int register_android_view_ThreadedRenderer(JNIEnv* env);
extern int register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper(JNIEnv *env);
extern int register_android_database_CursorWindow(JNIEnv* env);
extern int register_android_database_SQLiteConnection(JNIEnv* env);
@@ -1179,7 +1179,7 @@
REG_JNI(register_android_view_RenderNode),
REG_JNI(register_android_view_RenderNodeAnimator),
REG_JNI(register_android_view_GraphicBuffer),
- REG_JNI(register_android_view_GLES20Canvas),
+ REG_JNI(register_android_view_DisplayListCanvas),
REG_JNI(register_android_view_HardwareLayer),
REG_JNI(register_android_view_ThreadedRenderer),
REG_JNI(register_android_view_Surface),
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index a6f19b1..e5ae147 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -48,10 +48,10 @@
delete mBreakIterator;
}
- void setLocale(const Locale& locale) {
+ void setLocale(const icu::Locale& locale) {
delete mBreakIterator;
UErrorCode status = U_ZERO_ERROR;
- mBreakIterator = BreakIterator::createLineInstance(locale, status);
+ mBreakIterator = icu::BreakIterator::createLineInstance(locale, status);
// TODO: check status
}
@@ -77,13 +77,13 @@
}
}
- BreakIterator* breakIterator() const {
+ icu::BreakIterator* breakIterator() const {
return mBreakIterator;
}
private:
const size_t MAX_TEXT_BUF_RETAIN = 32678;
- BreakIterator* mBreakIterator = nullptr;
+ icu::BreakIterator* mBreakIterator = nullptr;
UText mUText = UTEXT_INITIALIZER;
std::vector<uint16_t>mTextBuf;
};
@@ -560,9 +560,9 @@
// TODO: this array access is pretty inefficient, but we'll replace it anyway
ScopedFloatArrayRO widthsScopedArr(env, widths);
- BreakIterator* breakIterator = b->breakIterator();
+ icu::BreakIterator* breakIterator = b->breakIterator();
int loc = breakIterator->first();
- while ((loc = breakIterator->next()) != BreakIterator::DONE) {
+ while ((loc = breakIterator->next()) != icu::BreakIterator::DONE) {
breaks.push_back(loc);
}
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp
similarity index 82%
rename from core/jni/android_view_GLES20Canvas.cpp
rename to core/jni/android_view_DisplayListCanvas.cpp
index 0bee7ae..f2e6c4b 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_DisplayListCanvas.cpp
@@ -50,43 +50,43 @@
// Setup
// ----------------------------------------------------------------------------
-static void android_view_GLES20Canvas_setViewport(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_setViewport(JNIEnv* env, jobject clazz,
jlong rendererPtr, jint width, jint height) {
DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
renderer->setViewport(width, height);
}
-static void android_view_GLES20Canvas_setHighContrastText(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_setHighContrastText(JNIEnv* env, jobject clazz,
jlong rendererPtr, jboolean highContrastText) {
DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
renderer->setHighContrastText(highContrastText);
}
-static void android_view_GLES20Canvas_insertReorderBarrier(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_insertReorderBarrier(JNIEnv* env, jobject clazz,
jlong rendererPtr, jboolean reorderEnable) {
DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
renderer->insertReorderBarrier(reorderEnable);
}
-static void android_view_GLES20Canvas_prepare(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_prepare(JNIEnv* env, jobject clazz,
jlong rendererPtr) {
DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
renderer->prepare();
}
-static void android_view_GLES20Canvas_prepareDirty(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_prepareDirty(JNIEnv* env, jobject clazz,
jlong rendererPtr, jint left, jint top, jint right, jint bottom) {
DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
renderer->prepareDirty(left, top, right, bottom);
}
-static void android_view_GLES20Canvas_finish(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_finish(JNIEnv* env, jobject clazz,
jlong rendererPtr) {
DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
renderer->finish();
}
-static void android_view_GLES20Canvas_setProperty(JNIEnv* env,
+static void android_view_DisplayListCanvas_setProperty(JNIEnv* env,
jobject clazz, jstring name, jstring value) {
if (!Caches::hasInstance()) {
ALOGW("can't set property, no Caches instance");
@@ -108,7 +108,7 @@
// Functor
// ----------------------------------------------------------------------------
-static void android_view_GLES20Canvas_callDrawGLFunction(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_callDrawGLFunction(JNIEnv* env, jobject clazz,
jlong rendererPtr, jlong functorPtr) {
DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
Functor* functor = reinterpret_cast<Functor*>(functorPtr);
@@ -120,11 +120,11 @@
// Misc
// ----------------------------------------------------------------------------
-static jint android_view_GLES20Canvas_getMaxTextureWidth(JNIEnv* env, jobject clazz) {
+static jint android_view_DisplayListCanvas_getMaxTextureWidth(JNIEnv* env, jobject clazz) {
return Caches::getInstance().maxTextureSize;
}
-static jint android_view_GLES20Canvas_getMaxTextureHeight(JNIEnv* env, jobject clazz) {
+static jint android_view_DisplayListCanvas_getMaxTextureHeight(JNIEnv* env, jobject clazz) {
return Caches::getInstance().maxTextureSize;
}
@@ -132,7 +132,7 @@
// Drawing
// ----------------------------------------------------------------------------
-static void android_view_GLES20Canvas_drawPatch(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_drawPatch(JNIEnv* env, jobject clazz,
jlong rendererPtr, jlong bitmapPtr, jlong patchPtr,
float left, float top, float right, float bottom, jlong paintPtr) {
SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapPtr);
@@ -143,7 +143,7 @@
renderer->drawPatch(bitmap, patch, left, top, right, bottom, paint);
}
-static void android_view_GLES20Canvas_drawRoundRectProps(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_drawRoundRectProps(JNIEnv* env, jobject clazz,
jlong rendererPtr, jlong leftPropPtr, jlong topPropPtr, jlong rightPropPtr,
jlong bottomPropPtr, jlong rxPropPtr, jlong ryPropPtr, jlong paintPropPtr) {
DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
@@ -157,7 +157,7 @@
renderer->drawRoundRect(leftProp, topProp, rightProp, bottomProp, rxProp, ryProp, paintProp);
}
-static void android_view_GLES20Canvas_drawCircleProps(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_drawCircleProps(JNIEnv* env, jobject clazz,
jlong rendererPtr, jlong xPropPtr, jlong yPropPtr, jlong radiusPropPtr, jlong paintPropPtr) {
DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr);
@@ -167,7 +167,7 @@
renderer->drawCircle(xProp, yProp, radiusProp, paintProp);
}
-static void android_view_GLES20Canvas_drawRegionAsRects(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_drawRegionAsRects(JNIEnv* env, jobject clazz,
jlong rendererPtr, jlong regionPtr, jlong paintPtr) {
DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
SkRegion* region = reinterpret_cast<SkRegion*>(regionPtr);
@@ -201,17 +201,17 @@
// Display lists
// ----------------------------------------------------------------------------
-static jlong android_view_GLES20Canvas_finishRecording(JNIEnv* env,
+static jlong android_view_DisplayListCanvas_finishRecording(JNIEnv* env,
jobject clazz, jlong rendererPtr) {
DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
return reinterpret_cast<jlong>(renderer->finishRecording());
}
-static jlong android_view_GLES20Canvas_createDisplayListRenderer(JNIEnv* env, jobject clazz) {
+static jlong android_view_DisplayListCanvas_createDisplayListRenderer(JNIEnv* env, jobject clazz) {
return reinterpret_cast<jlong>(new DisplayListRenderer);
}
-static void android_view_GLES20Canvas_drawRenderNode(JNIEnv* env,
+static void android_view_DisplayListCanvas_drawRenderNode(JNIEnv* env,
jobject clazz, jlong rendererPtr, jlong renderNodePtr,
jint flags) {
DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
@@ -224,7 +224,7 @@
// Layers
// ----------------------------------------------------------------------------
-static void android_view_GLES20Canvas_drawLayer(JNIEnv* env, jobject clazz,
+static void android_view_DisplayListCanvas_drawLayer(JNIEnv* env, jobject clazz,
jlong rendererPtr, jlong layerPtr, jfloat x, jfloat y) {
DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr);
@@ -235,7 +235,7 @@
// Common
// ----------------------------------------------------------------------------
-static jboolean android_view_GLES20Canvas_isAvailable(JNIEnv* env, jobject clazz) {
+static jboolean android_view_DisplayListCanvas_isAvailable(JNIEnv* env, jobject clazz) {
char prop[PROPERTY_VALUE_MAX];
if (property_get("ro.kernel.qemu", prop, NULL) == 0) {
// not in the emulator
@@ -261,36 +261,36 @@
// JNI Glue
// ----------------------------------------------------------------------------
-const char* const kClassPathName = "android/view/GLES20Canvas";
+const char* const kClassPathName = "android/view/DisplayListCanvas";
static JNINativeMethod gMethods[] = {
- { "nIsAvailable", "()Z", (void*) android_view_GLES20Canvas_isAvailable },
- { "nSetViewport", "(JII)V", (void*) android_view_GLES20Canvas_setViewport },
- { "nSetHighContrastText","(JZ)V", (void*) android_view_GLES20Canvas_setHighContrastText },
- { "nInsertReorderBarrier","(JZ)V", (void*) android_view_GLES20Canvas_insertReorderBarrier },
- { "nPrepare", "(J)V", (void*) android_view_GLES20Canvas_prepare },
- { "nPrepareDirty", "(JIIII)V", (void*) android_view_GLES20Canvas_prepareDirty },
- { "nFinish", "(J)V", (void*) android_view_GLES20Canvas_finish },
+ { "nIsAvailable", "()Z", (void*) android_view_DisplayListCanvas_isAvailable },
+ { "nSetViewport", "(JII)V", (void*) android_view_DisplayListCanvas_setViewport },
+ { "nSetHighContrastText","(JZ)V", (void*) android_view_DisplayListCanvas_setHighContrastText },
+ { "nInsertReorderBarrier","(JZ)V", (void*) android_view_DisplayListCanvas_insertReorderBarrier },
+ { "nPrepare", "(J)V", (void*) android_view_DisplayListCanvas_prepare },
+ { "nPrepareDirty", "(JIIII)V", (void*) android_view_DisplayListCanvas_prepareDirty },
+ { "nFinish", "(J)V", (void*) android_view_DisplayListCanvas_finish },
{ "nSetProperty", "(Ljava/lang/String;Ljava/lang/String;)V",
- (void*) android_view_GLES20Canvas_setProperty },
+ (void*) android_view_DisplayListCanvas_setProperty },
- { "nCallDrawGLFunction", "(JJ)V", (void*) android_view_GLES20Canvas_callDrawGLFunction },
+ { "nCallDrawGLFunction", "(JJ)V", (void*) android_view_DisplayListCanvas_callDrawGLFunction },
- { "nDrawPatch", "(JJJFFFFJ)V", (void*) android_view_GLES20Canvas_drawPatch },
+ { "nDrawPatch", "(JJJFFFFJ)V", (void*) android_view_DisplayListCanvas_drawPatch },
- { "nDrawRects", "(JJJ)V", (void*) android_view_GLES20Canvas_drawRegionAsRects },
- { "nDrawRoundRect", "(JJJJJJJJ)V", (void*) android_view_GLES20Canvas_drawRoundRectProps },
- { "nDrawCircle", "(JJJJJ)V", (void*) android_view_GLES20Canvas_drawCircleProps },
+ { "nDrawRects", "(JJJ)V", (void*) android_view_DisplayListCanvas_drawRegionAsRects },
+ { "nDrawRoundRect", "(JJJJJJJJ)V", (void*) android_view_DisplayListCanvas_drawRoundRectProps },
+ { "nDrawCircle", "(JJJJJ)V", (void*) android_view_DisplayListCanvas_drawCircleProps },
- { "nFinishRecording", "(J)J", (void*) android_view_GLES20Canvas_finishRecording },
- { "nDrawRenderNode", "(JJI)V", (void*) android_view_GLES20Canvas_drawRenderNode },
+ { "nFinishRecording", "(J)J", (void*) android_view_DisplayListCanvas_finishRecording },
+ { "nDrawRenderNode", "(JJI)V", (void*) android_view_DisplayListCanvas_drawRenderNode },
- { "nCreateDisplayListRenderer", "()J", (void*) android_view_GLES20Canvas_createDisplayListRenderer },
+ { "nCreateDisplayListRenderer", "()J", (void*) android_view_DisplayListCanvas_createDisplayListRenderer },
- { "nDrawLayer", "(JJFF)V", (void*) android_view_GLES20Canvas_drawLayer },
+ { "nDrawLayer", "(JJFF)V", (void*) android_view_DisplayListCanvas_drawLayer },
- { "nGetMaximumTextureWidth", "()I", (void*) android_view_GLES20Canvas_getMaxTextureWidth },
- { "nGetMaximumTextureHeight", "()I", (void*) android_view_GLES20Canvas_getMaxTextureHeight },
+ { "nGetMaximumTextureWidth", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureWidth },
+ { "nGetMaximumTextureHeight", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureHeight },
};
static JNINativeMethod gActivityThreadMethods[] = {
@@ -298,7 +298,7 @@
(void*) android_app_ActivityThread_dumpGraphics }
};
-int register_android_view_GLES20Canvas(JNIEnv* env) {
+int register_android_view_DisplayListCanvas(JNIEnv* env) {
jclass clazz = FindClassOrDie(env, "android/graphics/Rect");
gRectClassInfo.set = GetMethodIDOrDie(env, clazz, "set", "(IIII)V");
diff --git a/docs/html/images/training/geofence.png b/docs/html/images/training/geofence.png
new file mode 100644
index 0000000..2d5d3aa
--- /dev/null
+++ b/docs/html/images/training/geofence.png
Binary files differ
diff --git a/docs/html/images/training/geofence@2x.png b/docs/html/images/training/geofence@2x.png
new file mode 100644
index 0000000..2f83105
--- /dev/null
+++ b/docs/html/images/training/geofence@2x.png
Binary files differ
diff --git a/docs/html/training/location/geofencing.jd b/docs/html/training/location/geofencing.jd
index 748b6ec..59fc4c6 100644
--- a/docs/html/training/location/geofencing.jd
+++ b/docs/html/training/location/geofencing.jd
@@ -9,9 +9,11 @@
<h2>This lesson teaches you to</h2>
<ol>
- <li><a href="#RequestGeofences">Request Geofence Monitoring</a></li>
+ <li><a href="#RequestGeofences">Set up for Geofence Monitoring</a></li>
+ <li><a href="#CreateAdd">Create and Add Geofences</a></li>
<li><a href="#HandleGeofenceTransitions">Handle Geofence Transitions</a></li>
<li><a href="#StopGeofenceMonitoring">Stop Geofence Monitoring</a></li>
+
</ol>
<h2>You should also read</h2>
@@ -23,577 +25,148 @@
<h2>Try it out</h2>
-<div class="download-box">
- <a href="http://developer.android.com/shareables/training/GeofenceDetection.zip" class="button">Download the sample</a>
- <p class="filename">GeofenceDetection.zip</p>
-</div>
+ <ul>
+ <li>
+ <a href="https://github.com/googlesamples/android-play-location/tree/master/Geofencing"
+ class="external-link">Geofencing</a>
+ </li>
+ </ul>
</div>
</div>
<p>
- Geofencing combines awareness of the user's current location with awareness of nearby
- features, defined as the user's proximity to locations that may be of interest. To mark a
+ Geofencing combines awareness of the user's current location with awareness of the user's
+ proximity to locations that may be of interest. To mark a
location of interest, you specify its latitude and longitude. To adjust the proximity for the
- location, you add a radius. The latitude, longitude, and radius define a geofence.
- You can have multiple active geofences at one time.
+ location, you add a radius. The latitude, longitude, and radius define a geofence, creating a
+ circular area, or fence, around the location of interest.
</p>
<p>
- Location Services treats a geofences as an area rather than as a points and proximity. This
- allows it to detect when the user enters or exits a geofence. For each geofence, you can ask
- Location Services to send you entrance events or exit events or both. You can also limit the
- duration of a geofence by specifying an expiration duration in milliseconds. After the geofence
- expires, Location Services automatically removes it.
+ You can have multiple active geofences, with a limit of 100 per device user. For each geofence,
+ you can ask Location Services to send you entrance and exit events, or you can specify a
+ duration within the geofence area to wait, or <em>dwell</em>, before triggering an event. You
+ can limit the duration of any geofence by specifying an expiration duration in milliseconds.
+ After the geofence expires, Location Services automatically removes it.
</p>
-<!--
- Send geofences to Location Services
- -->
-<h2 id="RequestGeofences">Request Geofence Monitoring</h2>
+
+<img src="{@docRoot}images/training/geofence@2x.png"
+srcset="{@docRoot}images/training/geofence.png 1x, {@docRoot}images/training/geofence@2x.png 2x" alt=""
+ width="400" height="400"/>
+<p>
+ This lesson shows you how to add and remove geofences, and then listen for geofence transitions
+ using an {@link android.app.IntentService}.</p>
+
+<h2 id="RequestGeofences">Set up for Geofence Monitoring</h2>
<p>
The first step in requesting geofence monitoring is to request the necessary permission.
To use geofencing, your app must request
{@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}. To request this
permission, add the following element as a child element of the
<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code>
- element:
+ element in your app manifest:
</p>
<pre>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
</pre>
-<!-- Check for Google Play services -->
-<h3>Check for Google Play Services</h3>
-<p>
- Location Services is part of the Google Play services APK. Since it's hard to anticipate the
- state of the user's device, you should always check that the APK is installed before you attempt
- to connect to Location Services. To check that the APK is installed, call
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesUtil.html#isGooglePlayServicesAvailable(android.content.Context)">GooglePlayServicesUtil.isGooglePlayServicesAvailable()</a></code>,
- which returns one of the
- integer result codes listed in the API reference documentation. If you encounter an error,
- call
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesUtil.html#getErrorDialog(int, android.app.Activity, int)">GooglePlayServicesUtil.getErrorDialog()</a></code>
- to retrieve localized dialog that prompts users to take the correct action, then display
- the dialog in a {@link android.support.v4.app.DialogFragment}. The dialog may allow the
- user to correct the problem, in which case Google Play services may send a result back to your
- activity. To handle this result, override the method
- {@link android.support.v4.app.FragmentActivity#onActivityResult onActivityResult()}
-</p>
-<p class="note">
- <strong>Note:</strong> To make your app compatible with
- platform version 1.6 and later, the activity that displays the
- {@link android.support.v4.app.DialogFragment} must subclass
- {@link android.support.v4.app.FragmentActivity} instead of {@link android.app.Activity}. Using
- {@link android.support.v4.app.FragmentActivity} also allows you to call
- {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager
- getSupportFragmentManager()} to display the {@link android.support.v4.app.DialogFragment}.
-</p>
<p>
- Since you usually need to check for Google Play services in more than one place in your code,
- define a method that encapsulates the check, then call the method before each connection
- attempt. The following snippet contains all of the code required to check for Google
- Play services:
+ If you want to use an {@link android.app.IntentService} to listen for geofence transitions,
+ add an element specifying the service name. This element must be
+ a child of the <code><a href="{@docRoot}guide/topics/manifest/application-element.html">
+ <application></a></code> element:
+</p>
+
+<pre>
+<application
+ android:allowBackup="true">
+ ...
+ <service android:name=".GeofenceTransitionsIntentService"/>
+<application/>
+</pre>
+
+<p>To access the location APIs, you need to create an instance of the
+ Google Play services API client. To learn how to connect your client, see
+ <a href="{@docRoot}training/location/retrieve-current.html#play-services">Connect
+ to Google Play Services</a>.</p>
+
+<h2 id="CreateAdd">Create and Add Geofences</h2>
+
+<p>Your app needs to create and add geofences using the location API's builder class for
+ creating Geofence objects, and the convenience class for adding them. Also, to handle the
+ intents sent from Location Services when geofence transitions occur, you can define a
+ {@link android.app.PendingIntent} as shown in this section.
+</p>
+
+<h3>Create geofence objects</h3>
+
+<p>
+ First, use <code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.Builder.
+ html">Geofence.Builder</a></code> to create a geofence, setting the desired radius, duration, and
+ transition types for the geofence. For example, to populate a list object named
+ {@code mGeofenceList}:
+ </p>
+
+<pre>
+mGeofenceList.add(new Geofence.Builder()
+ // Set the request ID of the geofence. This is a string to identify this
+ // geofence.
+ .setRequestId(entry.getKey())
+
+ .setCircularRegion(
+ entry.getValue().latitude,
+ entry.getValue().longitude,
+ Constants.GEOFENCE_RADIUS_IN_METERS
+ )
+ .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS)
+ .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
+ Geofence.GEOFENCE_TRANSITION_EXIT)
+ .build());
+</pre>
+
+<p>This example pulls data from a constants file. In actual practice, apps might
+ dynamically create geofences based on the user's location.</p>
+
+<h3>Specify geofences and initial triggers</h3>
+
+<p>
+ The following snippet uses the <code><a href="{@docRoot}reference/com/google/android/gms/location/GeofencingRequest.html">
+ GeofencingRequest</a></code> class
+ and its nested <code><a href="{@docRoot}reference/com/google/android/gms/location/GeofencingRequest.Builder.html">
+ GeofencingRequestBuilder</a></code> class to
+ specify the geofences to monitor and to set how related geofence events are triggered:
</p>
<pre>
-public class MainActivity extends FragmentActivity {
- ...
- // Global constants
- /*
- * Define a request code to send to Google Play services
- * This code is returned in Activity.onActivityResult
- */
- private final static int
- CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000;
- ...
- // Define a DialogFragment that displays the error dialog
- public static class ErrorDialogFragment extends DialogFragment {
- // Global field to contain the error dialog
- private Dialog mDialog;
- ...
- // Default constructor. Sets the dialog field to null
- public ErrorDialogFragment() {
- super();
- mDialog = null;
- }
- ...
- // Set the dialog to display
- public void setDialog(Dialog dialog) {
- mDialog = dialog;
- }
- ...
- // Return a Dialog to the DialogFragment.
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- return mDialog;
- }
- ...
- }
- ...
- /*
- * Handle results returned to the FragmentActivity
- * by Google Play services
- */
- @Override
- protected void onActivityResult(
- int requestCode, int resultCode, Intent data) {
- // Decide what to do based on the original request code
- switch (requestCode) {
- ...
- case CONNECTION_FAILURE_RESOLUTION_REQUEST :
- /*
- * If the result code is Activity.RESULT_OK, try
- * to connect again
- */
- switch (resultCode) {
- ...
- case Activity.RESULT_OK :
- /*
- * Try the request again
- */
- ...
- break;
- }
- ...
- }
- ...
- }
- ...
- private boolean servicesConnected() {
- // Check that Google Play services is available
- int resultCode =
- GooglePlayServicesUtil.
- isGooglePlayServicesAvailable(this);
- // If Google Play services is available
- if (ConnectionResult.SUCCESS == resultCode) {
- // In debug mode, log the status
- Log.d("Geofence Detection",
- "Google Play services is available.");
- // Continue
- return true;
- // Google Play services was not available for some reason
- } else {
- // Get the error code
- int errorCode = connectionResult.getErrorCode();
- // Get the error dialog from Google Play services
- Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
- errorCode,
- this,
- CONNECTION_FAILURE_RESOLUTION_REQUEST);
-
- // If Google Play services can provide an error dialog
- if (errorDialog != null) {
- // Create a new DialogFragment for the error dialog
- ErrorDialogFragment errorFragment =
- new ErrorDialogFragment();
- // Set the dialog in the DialogFragment
- errorFragment.setDialog(errorDialog);
- // Show the error dialog in the DialogFragment
- errorFragment.show(
- getSupportFragmentManager(),
- "Geofence Detection");
- }
- }
- }
- ...
+private GeofencingRequest getGeofencingRequest() {
+ GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
+ builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
+ builder.addGeofences(mGeofenceList);
+ return builder.build();
}
</pre>
-<p>
- Snippets in the following sections call this method to verify that Google Play services is
- available.
-</p>
-<p>
- To use geofencing, start by defining the geofences you want to monitor. Although you usually
- store geofence data in a local database or download it from the network, you need to send
- a geofence to Location Services as an instance of
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>,
- which you create with
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.Builder.html">Geofence.Builder</a></code>.
- Each
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>
- object contains the following information:
-</p>
-<dl>
- <dt>Latitude, longitude, and radius</dt>
- <dd>
- Define a circular area for the geofence. Use the latitude and longitude to mark a location
- of interest, and then use the radius to adjust how close the user needs to approach the
- location before the geofence is detected. The larger the radius, the more likely the
- user will trigger a geofence transition alert by approaching the geofence. For example,
- providing a large radius for a geofencing app that turns on lights in the user's house as
- the user returns home might cause the lights to go on even if the user is simply passing by.
- </dd>
- <dt>Expiration time</dt>
- <dd>
- How long the geofence should remain active. Once the expiration time is reached, Location
- Services deletes the geofence. Most of the time, you should specify an expiration time, but
- you may want to keep permanent geofences for the user's home or place of work.
- </dd>
- <dt>Transition type</dt>
- <dd>
- Location Services can detect when the user steps within the radius of the geofence ("entry")
- and when the user steps outside the radius of the geofence ("exit"), or both.
- </dd>
- <dt>Geofence ID</dt>
- <dd>
- A string that is stored with the geofence. You should make this unique, so that you can
- use it to remove a geofence from Location Services tracking.
- </dd>
-</dl>
-<h3>Define geofence storage</h3>
-<p>
- A geofencing app needs to read and write geofence data to persistent storage. You shouldn't use
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>
- objects to do this; instead, use storage techniques such as databases that can store groups of
- related data.
-</p>
-<p>
- As an example of storing geofence data, the following snippet defines two classes that use
- the app's {@link android.content.SharedPreferences} instance for persistent storage. The class
- {@code SimpleGeofence}, analogous to a database record, stores the
- data for a single
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>
- object in a "flattened" form. The class {@code SimpleGeofenceStore}, analogous to a database,
- reads and writes {@code SimpleGeofence} data to the
- {@link android.content.SharedPreferences} instance.
-</p>
-<pre>
-public class MainActivity extends FragmentActivity {
- ...
- /**
- * A single Geofence object, defined by its center and radius.
- */
- public class SimpleGeofence {
- // Instance variables
- private final String mId;
- private final double mLatitude;
- private final double mLongitude;
- private final float mRadius;
- private long mExpirationDuration;
- private int mTransitionType;
- /**
- * @param geofenceId The Geofence's request ID
- * @param latitude Latitude of the Geofence's center.
- * @param longitude Longitude of the Geofence's center.
- * @param radius Radius of the geofence circle.
- * @param expiration Geofence expiration duration
- * @param transition Type of Geofence transition.
- */
- public SimpleGeofence(
- String geofenceId,
- double latitude,
- double longitude,
- float radius,
- long expiration,
- int transition) {
- // Set the instance fields from the constructor
- this.mId = geofenceId;
- this.mLatitude = latitude;
- this.mLongitude = longitude;
- this.mRadius = radius;
- this.mExpirationDuration = expiration;
- this.mTransitionType = transition;
- }
- // Instance field getters
- public String getId() {
- return mId;
- }
- public double getLatitude() {
- return mLatitude;
- }
- public double getLongitude() {
- return mLongitude;
- }
- public float getRadius() {
- return mRadius;
- }
- public long getExpirationDuration() {
- return mExpirationDuration;
- }
- public int getTransitionType() {
- return mTransitionType;
- }
- /**
- * Creates a Location Services Geofence object from a
- * SimpleGeofence.
- *
- * @return A Geofence object
- */
- public Geofence toGeofence() {
- // Build a new Geofence object
- return new Geofence.Builder()
- .setRequestId(getId())
- .setTransitionTypes(mTransitionType)
- .setCircularRegion(
- getLatitude(), getLongitude(), getRadius())
- .setExpirationDuration(mExpirationDuration)
- .build();
- }
- }
- ...
- /**
- * Storage for geofence values, implemented in SharedPreferences.
- */
- public class SimpleGeofenceStore {
- // Keys for flattened geofences stored in SharedPreferences
- public static final String KEY_LATITUDE =
- "com.example.android.geofence.KEY_LATITUDE";
- public static final String KEY_LONGITUDE =
- "com.example.android.geofence.KEY_LONGITUDE";
- public static final String KEY_RADIUS =
- "com.example.android.geofence.KEY_RADIUS";
- public static final String KEY_EXPIRATION_DURATION =
- "com.example.android.geofence.KEY_EXPIRATION_DURATION";
- public static final String KEY_TRANSITION_TYPE =
- "com.example.android.geofence.KEY_TRANSITION_TYPE";
- // The prefix for flattened geofence keys
- public static final String KEY_PREFIX =
- "com.example.android.geofence.KEY";
- /*
- * Invalid values, used to test geofence storage when
- * retrieving geofences
- */
- public static final long INVALID_LONG_VALUE = -999l;
- public static final float INVALID_FLOAT_VALUE = -999.0f;
- public static final int INVALID_INT_VALUE = -999;
- // The SharedPreferences object in which geofences are stored
- private final SharedPreferences mPrefs;
- // The name of the SharedPreferences
- private static final String SHARED_PREFERENCES =
- "SharedPreferences";
- // Create the SharedPreferences storage with private access only
- public SimpleGeofenceStore(Context context) {
- mPrefs =
- context.getSharedPreferences(
- SHARED_PREFERENCES,
- Context.MODE_PRIVATE);
- }
- /**
- * Returns a stored geofence by its id, or returns {@code null}
- * if it's not found.
- *
- * @param id The ID of a stored geofence
- * @return A geofence defined by its center and radius. See
- */
- public SimpleGeofence getGeofence(String id) {
- /*
- * Get the latitude for the geofence identified by id, or
- * INVALID_FLOAT_VALUE if it doesn't exist
- */
- double lat = mPrefs.getFloat(
- getGeofenceFieldKey(id, KEY_LATITUDE),
- INVALID_FLOAT_VALUE);
- /*
- * Get the longitude for the geofence identified by id, or
- * INVALID_FLOAT_VALUE if it doesn't exist
- */
- double lng = mPrefs.getFloat(
- getGeofenceFieldKey(id, KEY_LONGITUDE),
- INVALID_FLOAT_VALUE);
- /*
- * Get the radius for the geofence identified by id, or
- * INVALID_FLOAT_VALUE if it doesn't exist
- */
- float radius = mPrefs.getFloat(
- getGeofenceFieldKey(id, KEY_RADIUS),
- INVALID_FLOAT_VALUE);
- /*
- * Get the expiration duration for the geofence identified
- * by id, or INVALID_LONG_VALUE if it doesn't exist
- */
- long expirationDuration = mPrefs.getLong(
- getGeofenceFieldKey(id, KEY_EXPIRATION_DURATION),
- INVALID_LONG_VALUE);
- /*
- * Get the transition type for the geofence identified by
- * id, or INVALID_INT_VALUE if it doesn't exist
- */
- int transitionType = mPrefs.getInt(
- getGeofenceFieldKey(id, KEY_TRANSITION_TYPE),
- INVALID_INT_VALUE);
- // If none of the values is incorrect, return the object
- if (
- lat != GeofenceUtils.INVALID_FLOAT_VALUE &&
- lng != GeofenceUtils.INVALID_FLOAT_VALUE &&
- radius != GeofenceUtils.INVALID_FLOAT_VALUE &&
- expirationDuration !=
- GeofenceUtils.INVALID_LONG_VALUE &&
- transitionType != GeofenceUtils.INVALID_INT_VALUE) {
-
- // Return a true Geofence object
- return new SimpleGeofence(
- id, lat, lng, radius, expirationDuration,
- transitionType);
- // Otherwise, return null.
- } else {
- return null;
- }
- }
- /**
- * Save a geofence.
- * @param geofence The SimpleGeofence containing the
- * values you want to save in SharedPreferences
- */
- public void setGeofence(String id, SimpleGeofence geofence) {
- /*
- * Get a SharedPreferences editor instance. Among other
- * things, SharedPreferences ensures that updates are atomic
- * and non-concurrent
- */
- Editor editor = mPrefs.edit();
- // Write the Geofence values to SharedPreferences
- editor.putFloat(
- getGeofenceFieldKey(id, KEY_LATITUDE),
- (float) geofence.getLatitude());
- editor.putFloat(
- getGeofenceFieldKey(id, KEY_LONGITUDE),
- (float) geofence.getLongitude());
- editor.putFloat(
- getGeofenceFieldKey(id, KEY_RADIUS),
- geofence.getRadius());
- editor.putLong(
- getGeofenceFieldKey(id, KEY_EXPIRATION_DURATION),
- geofence.getExpirationDuration());
- editor.putInt(
- getGeofenceFieldKey(id, KEY_TRANSITION_TYPE),
- geofence.getTransitionType());
- // Commit the changes
- editor.commit();
- }
- public void clearGeofence(String id) {
- /*
- * Remove a flattened geofence object from storage by
- * removing all of its keys
- */
- Editor editor = mPrefs.edit();
- editor.remove(getGeofenceFieldKey(id, KEY_LATITUDE));
- editor.remove(getGeofenceFieldKey(id, KEY_LONGITUDE));
- editor.remove(getGeofenceFieldKey(id, KEY_RADIUS));
- editor.remove(getGeofenceFieldKey(id,
- KEY_EXPIRATION_DURATION));
- editor.remove(getGeofenceFieldKey(id, KEY_TRANSITION_TYPE));
- editor.commit();
- }
- /**
- * Given a Geofence object's ID and the name of a field
- * (for example, KEY_LATITUDE), return the key name of the
- * object's values in SharedPreferences.
- *
- * @param id The ID of a Geofence object
- * @param fieldName The field represented by the key
- * @return The full key name of a value in SharedPreferences
- */
- private String getGeofenceFieldKey(String id,
- String fieldName) {
- return KEY_PREFIX + "_" + id + "_" + fieldName;
- }
- }
- ...
-}
-</pre>
-<h3>Create Geofence objects</h3>
<p>
- The following snippet uses the {@code SimpleGeofence} and {@code SimpleGeofenceStore} classes
- gets geofence data from the UI, stores it in {@code SimpleGeofence} objects, stores these
- objects in a {@code SimpleGeofenceStore} object, and then creates
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>
- objects:
+ This example shows the use of two geofence triggers. The <code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html#GEOFENCE_TRANSITION_ENTER">
+ GEOFENCE_TRANSITION_ENTER</a></code>
+ transition triggers when a device enters a geofence, and the <code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html#GEOFENCE_TRANSITION_EXIT">
+ GEOFENCE_TRANSITION_EXIT</a></code>
+ transition triggers when a device exits a geofence. Specifying
+ <code><a href="{@docRoot}reference/com/google/android/gms/location/GeofencingRequest.html#INITIAL_TRIGGER_ENTER">
+ INITIAL_TRIGGER_ENTER</a></code> tells Location services that
+ <code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html#GEOFENCE_TRANSITION_ENTER">
+ GEOFENCE_TRANSITION_ENTER</a></code>
+ should be triggered if the the device is already inside the geofence.</p>
</p>
-<pre>
-public class MainActivity extends FragmentActivity {
- ...
- /*
- * Use to set an expiration time for a geofence. After this amount
- * of time Location Services will stop tracking the geofence.
- */
- private static final long SECONDS_PER_HOUR = 60;
- private static final long MILLISECONDS_PER_SECOND = 1000;
- private static final long GEOFENCE_EXPIRATION_IN_HOURS = 12;
- private static final long GEOFENCE_EXPIRATION_TIME =
- GEOFENCE_EXPIRATION_IN_HOURS *
- SECONDS_PER_HOUR *
- MILLISECONDS_PER_SECOND;
- ...
- /*
- * Handles to UI views containing geofence data
- */
- // Handle to geofence 1 latitude in the UI
- private EditText mLatitude1;
- // Handle to geofence 1 longitude in the UI
- private EditText mLongitude1;
- // Handle to geofence 1 radius in the UI
- private EditText mRadius1;
- // Handle to geofence 2 latitude in the UI
- private EditText mLatitude2;
- // Handle to geofence 2 longitude in the UI
- private EditText mLongitude2;
- // Handle to geofence 2 radius in the UI
- private EditText mRadius2;
- /*
- * Internal geofence objects for geofence 1 and 2
- */
- private SimpleGeofence mUIGeofence1;
- private SimpleGeofence mUIGeofence2;
- ...
- // Internal List of Geofence objects
- List<Geofence> mGeofenceList;
- // Persistent storage for geofences
- private SimpleGeofenceStore mGeofenceStorage;
- ...
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ...
- // Instantiate a new geofence storage area
- mGeofenceStorage = new SimpleGeofenceStore(this);
- // Instantiate the current List of geofences
- mCurrentGeofences = new ArrayList<Geofence>();
- }
- ...
- /**
- * Get the geofence parameters for each geofence from the UI
- * and add them to a List.
- */
- public void createGeofences() {
- /*
- * Create an internal object to store the data. Set its
- * ID to "1". This is a "flattened" object that contains
- * a set of strings
- */
- mUIGeofence1 = new SimpleGeofence(
- "1",
- Double.valueOf(mLatitude1.getText().toString()),
- Double.valueOf(mLongitude1.getText().toString()),
- Float.valueOf(mRadius1.getText().toString()),
- GEOFENCE_EXPIRATION_TIME,
- // This geofence records only entry transitions
- Geofence.GEOFENCE_TRANSITION_ENTER);
- // Store this flat version
- mGeofenceStorage.setGeofence("1", mUIGeofence1);
- // Create another internal object. Set its ID to "2"
- mUIGeofence2 = new SimpleGeofence(
- "2",
- Double.valueOf(mLatitude2.getText().toString()),
- Double.valueOf(mLongitude2.getText().toString()),
- Float.valueOf(mRadius2.getText().toString()),
- GEOFENCE_EXPIRATION_TIME,
- // This geofence records both entry and exit transitions
- Geofence.GEOFENCE_TRANSITION_ENTER |
- Geofence.GEOFENCE_TRANSITION_EXIT);
- // Store this flat version
- mGeofenceStorage.setGeofence(2, mUIGeofence2);
- mGeofenceList.add(mUIGeofence1.toGeofence());
- mGeofenceList.add(mUIGeofence2.toGeofence());
- }
- ...
-}
-</pre>
-<p>
- In addition to the {@link java.util.List} of
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>
- objects you want to monitor, you need to provide Location Services with the
- {@link android.content.Intent} that it sends to your app when it detects geofence
- transitions.
-<h4>Define a Intent for geofence transitions</h4>
+<p>In many cases, it may be preferable to use instead <code><a href="{@docRoot}reference/com/google/android/gms/location/GeofencingRequest.html#INITIAL_TRIGGER_DWELL">
+ INITIAL_TRIGGER_DWELL</a></code>,
+ which triggers events only when the user stops for a defined duration within a geofence.
+ This approach can help reduce "alert spam" resulting from large numbers notifications when a
+ device briefly enters and exits geofences. Another strategy for getting best results from your
+ geofences is to set a minimum radius of 100 meters. This helps account for the location accuracy
+ of typical WiFi networks, and also helps reduce device power consumption.
+</p>
+
+<h3>Define an Intent for geofence transitions</h3>
<p>
The {@link android.content.Intent} sent from Location Services can trigger various actions in
your app, but you should <i>not</i> have it start an activity or fragment, because components
@@ -601,807 +174,133 @@
{@link android.app.IntentService} is a good way to handle the intent. An
{@link android.app.IntentService} can post a notification, do long-running background work,
send intents to other services, or send a broadcast intent. The following snippet shows how
- how to define a {@link android.app.PendingIntent} that starts an
- {@link android.app.IntentService}:
+ to define a {@link android.app.PendingIntent} that starts an {@link android.app.IntentService}:
</p>
<pre>
public class MainActivity extends FragmentActivity {
...
- /*
- * Create a PendingIntent that triggers an IntentService in your
- * app when a geofence transition occurs.
- */
- private PendingIntent getTransitionPendingIntent() {
- // Create an explicit Intent
- Intent intent = new Intent(this,
- ReceiveTransitionsIntentService.class);
- /*
- * Return the PendingIntent
- */
- return PendingIntent.getService(
- this,
- 0,
- intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- }
- ...
-}
-</pre>
-<p>
- Now you have all the code you need to send a request to monitor geofences to Location
- Services.
-</p>
-<!-- Send the monitoring request -->
-<h3 id="requestmonitoring">Send the monitoring request</h3>
-<p>
- Sending the monitoring request requires two asynchronous operations. The first operation gets a
- location client for the request, and the second makes the request using the client. In both
- cases, Location Services invokes a callback method when it finishes the operation. The best way
- to handle these operations is to chain together the method calls. The following snippets
- demonstrate how to set up an activity, define the methods, and call them in the proper order.
-</p>
-<p>
- First, modify the activity's class definition to implement the necessary callback interfaces.
- Add the following interfaces:
-</p>
-<dl>
- <dt>
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html">ConnectionCallbacks</a></code>
- </dt>
- <dd>
- Specifies methods that Location Services calls when a location client is connected or
- disconnected.
- </dd>
- <dt>
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.OnConnectionFailedListener.html">OnConnectionFailedListener</a></code>
- </dt>
- <dd>
- Specifies a method that Location Services calls if an error occurs while attempting to
- connect the location client.
- </dd>
- <dt>
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html">OnAddGeofencesResultListener</a></code>
- </dt>
- <dd>
- Specifies a method that Location Services calls once it has added the geofences.
- </dd>
-</dl>
-<p>
- For example:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
- ConnectionCallbacks,
- OnConnectionFailedListener,
- OnAddGeofencesResultListener {
- ...
-}
-</pre>
-<h4>Start the request process</h4>
-<p>
- Next, define a method that starts the request process by connecting to Location Services.
- Mark this as a request to add a geofence by setting a global variable. This allows you to
- use the callback
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">ConnectionCallbacks.onConnected()</a></code>
- to add geofences and to remove them, as described in succeeding sections.
-</p>
-<p>
-<p>
- To guard against race conditions that might arise if your app tries to start another request
- before the first one finishes, define a boolean flag that tracks the state of the current
- request:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
- ConnectionCallbacks,
- OnConnectionFailedListener,
- OnAddGeofencesResultListener {
- ...
- // Holds the location client
- private LocationClient mLocationClient;
- // Stores the PendingIntent used to request geofence monitoring
- private PendingIntent mGeofenceRequestIntent;
- // Defines the allowable request types.
- public enum REQUEST_TYPE = {ADD}
- private REQUEST_TYPE mRequestType;
- // Flag that indicates if a request is underway.
- private boolean mInProgress;
- ...
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- ...
- // Start with the request flag set to false
- mInProgress = false;
- ...
- }
- ...
- /**
- * Start a request for geofence monitoring by calling
- * LocationClient.connect().
- */
- public void addGeofences() {
- // Start a request to add geofences
- mRequestType = ADD;
- /*
- * Test for Google Play services after setting the request type.
- * If Google Play services isn't present, the proper request
- * can be restarted.
- */
- if (!servicesConnected()) {
- return;
+ private PendingIntent getGeofencePendingIntent() {
+ // Reuse the PendingIntent if we already have it.
+ if (mGeofencePendingIntent != null) {
+ return mGeofencePendingIntent;
}
- /*
- * Create a new location client object. Since the current
- * activity class implements ConnectionCallbacks and
- * OnConnectionFailedListener, pass the current activity object
- * as the listener for both parameters
- */
- mLocationClient = new LocationClient(this, this, this)
- // If a request is not already underway
- if (!mInProgress) {
- // Indicate that a request is underway
- mInProgress = true;
- // Request a connection from the client to Location Services
- mLocationClient.connect();
- } else {
- /*
- * A request is already underway. You can handle
- * this situation by disconnecting the client,
- * re-setting the flag, and then re-trying the
- * request.
- */
- }
+ Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
+ // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when
+ // calling addGeofences() and removeGeofences().
+ return PendingIntent.getService(this, 0, intent, PendingIntent.
+ FLAG_UPDATE_CURRENT);
}
- ...
-}
</pre>
-<h4>Send a request to add the geofences</h4>
+
+<h3>Add geofences</h3>
+
<p>
- In your implementation of
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">ConnectionCallbacks.onConnected()</a></code>,
- call
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#addGeofences(java.util.List<com.google.android.gms.location.Geofence>, android.app.PendingIntent, com.google.android.gms.location.LocationClient.OnAddGeofencesResultListener)">LocationClient.addGeofences()</a></code>.
- Notice that if the connection fails,
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">onConnected()</a></code>
- isn't called, and the request stops.
+ To add geofences, use the <code><a href="{@docRoot}reference/com/google/android/gms/location/GeofencingApi.html#addGeofences(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.GeofencingRequest, android.app.PendingIntent)">{@code GeoencingApi.addGeofences()}</a></code> method.
+ Provide the Google API client, the <code><a href="{@docRoot}reference/com/google/android/gms/location/GeofencingRequest">
+ GeofencingRequest</a></code> object, and the {@link android.app.PendingIntent}.
+ The following snippet, which processes the results in <code><a href="{@docRoot}reference/com/google/android/gms/common/api/ResultCallback.html#onResult(R)">
+ onResult()</a></code>, assumes that the main activity implements <code><a href="{@docRoot}reference/com/google/android/gms/common/api/ResultCallback.html">
+ ResultCallback</a></code>:
</p>
<pre>
-public class MainActivity extends FragmentActivity implements
- ConnectionCallbacks,
- OnConnectionFailedListener,
- OnAddGeofencesResultListener {
+public class MainActivity extends FragmentActivity {
...
- /*
- * Provide the implementation of ConnectionCallbacks.onConnected()
- * Once the connection is available, send a request to add the
- * Geofences
- */
- @Override
- private void onConnected(Bundle dataBundle) {
- ...
- switch (mRequestType) {
- case ADD :
- // Get the PendingIntent for the request
- mTransitionPendingIntent =
- getTransitionPendingIntent();
- // Send a request to add the current geofences
- mLocationClient.addGeofences(
- mCurrentGeofences, pendingIntent, this);
- ...
- }
- }
- ...
-}
+ LocationServices.GeofencingApi.addGeofences(
+ mGoogleApiClient,
+ getGeofencingRequest(),
+ getGeofencePendingIntent()
+ ).setResultCallback(this);
</pre>
-<p>
- Notice that
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#addGeofences(java.util.List<com.google.android.gms.location.Geofence>, android.app.PendingIntent, com.google.android.gms.location.LocationClient.OnAddGeofencesResultListener)">addGeofences()</a></code>
- returns immediately, but the status of the request is indeterminate until Location Services
- calls
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html#onAddGeofencesResult(int, java.lang.String[])">onAddGeofencesResult()</a></code>
- Once this method is called, you can determine if the request was successful or not.
-</p>
-<h4>Check the result returned by Location Services</h4>
-<p>
- When Location Services invokes your implementation of the callback method
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnAddGeofencesResultListener.html#onAddGeofencesResult(int, java.lang.String[])">onAddGeofencesResult()</a></code>,
- indicating that the request is complete, examine the incoming status code. If the request
- was successful, the geofences you requested are active. If the request was unsuccessful,
- the geofences aren't active, and you need to re-try the request or report an error. For example:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
- ConnectionCallbacks,
- OnConnectionFailedListener,
- OnAddGeofencesResultListener {
- ...
- /*
- * Provide the implementation of
- * OnAddGeofencesResultListener.onAddGeofencesResult.
- * Handle the result of adding the geofences
- *
- */
- @Override
- public void onAddGeofencesResult(
- int statusCode, String[] geofenceRequestIds) {
- // If adding the geofences was successful
- if (LocationStatusCodes.SUCCESS == statusCode) {
- /*
- * Handle successful addition of geofences here.
- * You can send out a broadcast intent or update the UI.
- * geofences into the Intent's extended data.
- */
- } else {
- // If adding the geofences failed
- /*
- * Report errors here.
- * You can log the error using Log.e() or update
- * the UI.
- */
- }
- // Turn off the in progress flag and disconnect the client
- mInProgress = false;
- mLocationClient.disconnect();
- }
- ...
-}
-</pre>
-<!-- Handle disconnections -->
-<h3>Handle disconnections</h3>
-<p>
- In some cases, Location Services may disconnect from the activity recognition client before
- you call
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#disconnect()">disconnect()</a></code>.
- To handle this situation, implement <code>
-<a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onDisconnected()">onDisconnected()</a></code>.
- In this method, set the request flag to indicate that a request is not in progress, and
- delete the client:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
- ConnectionCallbacks,
- OnConnectionFailedListener,
- OnAddGeofencesResultListener {
- ...
- /*
- * Implement ConnectionCallbacks.onDisconnected()
- * Called by Location Services once the location client is
- * disconnected.
- */
- @Override
- public void onDisconnected() {
- // Turn off the request flag
- mInProgress = false;
- // Destroy the current location client
- mLocationClient = null;
- }
- ...
-}
-</pre>
-<!-- Handle connection errors -->
-<h3>Handle connection errors</h3>
-<p>
- Besides handling the normal callbacks from Location Services, you have to provide a callback
- method that Location Services calls if a connection error occurs. This callback method
- can re-use the {@link android.support.v4.app.DialogFragment} class that you defined to
- handle the check for Google Play services. It can also re-use the override you defined
- for {@link android.support.v4.app.FragmentActivity#onActivityResult onActivityResult()} that
- receives any Google Play services results that occur when the user interacts with the
- error dialog. The following snippet shows you a sample implementation of the callback method:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
- ConnectionCallbacks,
- OnConnectionFailedListener,
- OnAddGeofencesResultListener {
- ...
- // Implementation of OnConnectionFailedListener.onConnectionFailed
- @Override
- public void onConnectionFailed(ConnectionResult connectionResult) {
- // Turn off the request flag
- mInProgress = false;
- /*
- * If the error has a resolution, start a Google Play services
- * activity to resolve it.
- */
- if (connectionResult.hasResolution()) {
- try {
- connectionResult.startResolutionForResult(
- this,
- CONNECTION_FAILURE_RESOLUTION_REQUEST);
- } catch (SendIntentException e) {
- // Log the error
- e.printStackTrace();
- }
- // If no resolution is available, display an error dialog
- } else {
- // Get the error code
- int errorCode = connectionResult.getErrorCode();
- // Get the error dialog from Google Play services
- Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
- errorCode,
- this,
- CONNECTION_FAILURE_RESOLUTION_REQUEST);
- // If Google Play services can provide an error dialog
- if (errorDialog != null) {
- // Create a new DialogFragment for the error dialog
- ErrorDialogFragment errorFragment =
- new ErrorDialogFragment();
- // Set the dialog in the DialogFragment
- errorFragment.setDialog(errorDialog);
- // Show the error dialog in the DialogFragment
- errorFragment.show(
- getSupportFragmentManager(),
- "Geofence Detection");
- }
- }
- }
- ...
-}
-</pre>
-<!--
- Handle Geofence Transitions
- -->
+
+
<h2 id="HandleGeofenceTransitions">Handle Geofence Transitions</h2>
<p>
When Location Services detects that the user has entered or exited a geofence, it
sends out the {@link android.content.Intent} contained in the {@link android.app.PendingIntent}
- you included in the request to add geofences. This {@link android.content.Intent} is
+ you included in the request to add geofences. This {@link android.content.Intent} is received
+ by a service like <code>GeofenceTransitionsIntentService</code>,
+ which obtains the geofencing event from the intent, determines the type of Geofence transition(s),
+ and determines which of the defined geofences was triggered. It then sends a notification as
+ the output.
</p>
-<h3>Define an IntentService</h3>
<p>
The following snippet shows how to define an {@link android.app.IntentService} that posts a
notification when a geofence transition occurs. When the user clicks the notification, the
app's main activity appears:
</p>
<pre>
-public class ReceiveTransitionsIntentService extends IntentService {
- ...
- /**
- * Sets an identifier for the service
- */
- public ReceiveTransitionsIntentService() {
- super("ReceiveTransitionsIntentService");
- }
- /**
- * Handles incoming intents
- *@param intent The Intent sent by Location Services. This
- * Intent is provided
- * to Location Services (inside a PendingIntent) when you call
- * addGeofences()
- */
- @Override
+public class GeofenceTransitionsIntentService extends IntentService {
+ ...
protected void onHandleIntent(Intent intent) {
- // First check for errors
- if (LocationClient.hasError(intent)) {
- // Get the error code with a static method
- int errorCode = LocationClient.getErrorCode(intent);
- // Log the error
- Log.e("ReceiveTransitionsIntentService",
- "Location Services error: " +
- Integer.toString(errorCode));
- /*
- * You can also send the error code to an Activity or
- * Fragment with a broadcast Intent
- */
- /*
- * If there's no error, get the transition type and the IDs
- * of the geofence or geofences that triggered the transition
- */
- } else {
- // Get the type of transition (entry or exit)
- int transitionType =
- LocationClient.getGeofenceTransition(intent);
- // Test that a valid transition was reported
- if (
- (transitionType == Geofence.GEOFENCE_TRANSITION_ENTER)
- ||
- (transitionType == Geofence.GEOFENCE_TRANSITION_EXIT)
- ) {
- List <Geofence> triggerList =
- getTriggeringGeofences(intent);
+ GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
+ if (geofencingEvent.hasError()) {
+ String errorMessage = GeofenceErrorMessages.getErrorString(this,
+ geofencingEvent.getErrorCode());
+ Log.e(TAG, errorMessage);
+ return;
+ }
- String[] triggerIds = new String[geofenceList.size()];
+ // Get the transition type.
+ int geofenceTransition = geofencingEvent.getGeofenceTransition();
- for (int i = 0; i < triggerIds.length; i++) {
- // Store the Id of each geofence
- triggerIds[i] = triggerList.get(i).getRequestId();
- }
- /*
- * At this point, you can store the IDs for further use
- * display them, or display the details associated with
- * them.
- */
- }
- // An invalid transition was reported
+ // Test that the reported transition was of interest.
+ if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
+ geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
+
+ // Get the geofences that were triggered. A single event can trigger
+ // multiple geofences.
+ List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
+
+ // Get the transition details as a String.
+ String geofenceTransitionDetails = getGeofenceTransitionDetails(
+ this,
+ geofenceTransition,
+ triggeringGeofences
+ );
+
+ // Send notification and log the transition details.
+ sendNotification(geofenceTransitionDetails);
+ Log.i(TAG, geofenceTransitionDetails);
} else {
- Log.e("ReceiveTransitionsIntentService",
- "Geofence transition error: " +
- Integer.toString()transitionType));
+ // Log the error.
+ Log.e(TAG, getString(R.string.geofence_transition_invalid_type,
+ geofenceTransition));
}
}
- ...
-}
</pre>
-<!-- Specify the IntentService in the manifest -->
-<h3>Specify the IntentService in the manifest</h3>
-<p>
- To identify the {@link android.app.IntentService} to the system, add a
- <code><a href="{@docRoot}guide/topics/manifest/service-element.html"><service></a></code>
- element to the app manifest. For example:
-</p>
-<pre>
-<service
- android:name="com.example.android.location.ReceiveTransitionsIntentService"
- android:label="@string/app_name"
- android:exported="false">
-</service>
-</pre>
-<p>
- Notice that you don't have to specify intent filters for the service, because it only receives
- explicit intents. How the incoming geofence transition intents are created is described in the
- section <a href="#requestmonitoring">Send the monitoring request</a>.
-</p>
+
+<p>After detecting the transition event via the {@link android.app.PendingIntent},
+ this {@link android.app.IntentService} gets the geofence transition type and tests whether
+ it is one of the events the app uses to trigger notifications -- either
+ <code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html#GEOFENCE_TRANSITION_ENTER">GEOFENCE_TRANSITION_ENTER</a></code>
+ or <code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html#GEOFENCE_TRANSITION_EXIT">GEOFENCE_TRANSITION_EXIT</a></code>
+ in this case. The service then sends a notification and logs the transition details.</p>
<!--
Remove Geofences
-->
<h2 id="StopGeofenceMonitoring">Stop Geofence Monitoring</h2>
-<p>
- To stop geofence monitoring, you remove the geofences themselves. You can remove a specific
- set of geofences or all the geofences associated with a {@link android.app.PendingIntent}. The
- procedure is similar to adding geofences. The first operation gets a location
- client for the removal request, and the second makes the request using the client.
+
+<p>Stopping geofence monitoring when it is no longer needed or desired can help save battery
+ power and CPU cycles on the device. You can stop geofence monitoring
+ in the main activity used to add and remove geofences; removing a geofence stops it
+ immediately. The API provides methods to
+ remove geofences either by request IDs, or by removing geofences associated with a given
+ {@link android.app.PendingIntent}.
</p>
<p>
- The callback methods that Location Services invokes when it has finished removing geofences
- are defined in the interface
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html">LocationClient.OnRemoveGeofencesResultListener</a></code>. Declare
- this interface as part of your class definition, and then add definitions for its two methods:
-</p>
-<dl>
- <dt>
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html#onRemoveGeofencesByPendingIntentResult(int, android.app.PendingIntent)">onRemoveGeofencesByPendingIntentResult()</a></code>
- </dt>
- <dd>
- Callback invoked when Location Services finishes a request to remove all geofences made
- by the method
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#removeGeofences(android.app.PendingIntent, com.google.android.gms.location.LocationClient.OnRemoveGeofencesResultListener)">removeGeofences(PendingIntent, LocationClient.OnRemoveGeofencesResultListener)</a></code>.
- </dd>
- <dt>
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html#onRemoveGeofencesByRequestIdsResult(int, java.lang.String[])">onRemoveGeofencesByRequestIdsResult(List<String>, LocationClient.OnRemoveGeofencesResultListener)</a></code>
- </dt>
- <dd>
- Callback invoked when Location Services finished a request to remove a set of geofences,
- specified by their geofence IDs, by the method
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#removeGeofences(java.util.List<java.lang.String>, com.google.android.gms.location.LocationClient.OnRemoveGeofencesResultListener)">removeGeofences(List<String>, LocationClient.OnRemoveGeofencesResultListener)</a></code>.
- </dd>
-</dl>
-<p>
- Examples of implementing these methods are shown in the next snippets.
-</p>
-<h3>Remove all geofences</h3>
-<p>
- Since removing geofences uses some of the methods you use to add geofences, start by defining
- another request type:
+ The following snippet removes geofences by {@link android.app.PendingIntent}, stopping all
+ further notification when the device enters or exits previously added geofences:
</p>
<pre>
-public class MainActivity extends FragmentActivity implements
- ConnectionCallbacks,
- OnConnectionFailedListener,
- OnAddGeofencesResultListener {
- ...
- // Enum type for controlling the type of removal requested
- public enum REQUEST_TYPE = {ADD, REMOVE_INTENT}
- ...
+LocationServices.GeofencingApi.removeGeofences(
+ mGoogleApiClient,
+ // This is the same pending intent that was used in addGeofences().
+ getGeofencePendingIntent()
+ ).setResultCallback(this); // Result processed in onResult().
}
</pre>
+
<p>
- Start the removal request by getting a connection to Location Services. If the connection fails,
-<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)">onConnected()</a></code> isn't called,
- and the request stops. The following snippet shows how to start the request:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
- ConnectionCallbacks,
- OnConnectionFailedListener,
- OnAddGeofencesResultListener {
- ...
- /**
- * Start a request to remove geofences by calling
- * LocationClient.connect()
- */
- public void removeGeofences(PendingIntent requestIntent) {
- // Record the type of removal request
- mRequestType = REMOVE_INTENT;
- /*
- * Test for Google Play services after setting the request type.
- * If Google Play services isn't present, the request can be
- * restarted.
- */
- if (!servicesConnected()) {
- return;
- }
- // Store the PendingIntent
- mGeofenceRequestIntent = requestIntent;
- /*
- * Create a new location client object. Since the current
- * activity class implements ConnectionCallbacks and
- * OnConnectionFailedListener, pass the current activity object
- * as the listener for both parameters
- */
- mLocationClient = new LocationClient(this, this, this);
- // If a request is not already underway
- if (!mInProgress) {
- // Indicate that a request is underway
- mInProgress = true;
- // Request a connection from the client to Location Services
- mLocationClient.connect();
- } else {
- /*
- * A request is already underway. You can handle
- * this situation by disconnecting the client,
- * re-setting the flag, and then re-trying the
- * request.
- */
- }
- }
- ...
-}
-</pre>
-<p>
- When Location Services invokes the callback method indicating that the connection is open,
- make the request to remove all geofences. Disconnect the client after making the request.
- For example:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
- ConnectionCallbacks,
- OnConnectionFailedListener,
- OnAddGeofencesResultListener {
- ...
- /**
- * Once the connection is available, send a request to remove the
- * Geofences. The method signature used depends on which type of
- * remove request was originally received.
- */
- private void onConnected(Bundle dataBundle) {
- /*
- * Choose what to do based on the request type set in
- * removeGeofences
- */
- switch (mRequestType) {
- ...
- case REMOVE_INTENT :
- mLocationClient.removeGeofences(
- mGeofenceRequestIntent, this);
- break;
- ...
- }
- }
- ...
-}
-</pre>
-<p>
- Although the call to
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#removeGeofences(android.app.PendingIntent, com.google.android.gms.location.LocationClient.OnRemoveGeofencesResultListener)">removeGeofences(PendingIntent, LocationClient.OnRemoveGeofencesResultListener)</a></code> Services calls
- returns immediately, the result of the removal request is indeterminate until Location Services
- calls
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html#onRemoveGeofencesByPendingIntentResult(int, android.app.PendingIntent)">onRemoveGeofencesByPendingIntentResult()</a></code>.
- The following snippet shows how to define this method:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
- ConnectionCallbacks,
- OnConnectionFailedListener,
- OnAddGeofencesResultListener {
- ...
- /**
- * When the request to remove geofences by PendingIntent returns,
- * handle the result.
- *
- *@param statusCode the code returned by Location Services
- *@param requestIntent The Intent used to request the removal.
- */
- @Override
- public void onRemoveGeofencesByPendingIntentResult(int statusCode,
- PendingIntent requestIntent) {
- // If removing the geofences was successful
- if (statusCode == LocationStatusCodes.SUCCESS) {
- /*
- * Handle successful removal of geofences here.
- * You can send out a broadcast intent or update the UI.
- * geofences into the Intent's extended data.
- */
- } else {
- // If adding the geocodes failed
- /*
- * Report errors here.
- * You can log the error using Log.e() or update
- * the UI.
- */
- }
- /*
- * Disconnect the location client regardless of the
- * request status, and indicate that a request is no
- * longer in progress
- */
- mInProgress = false;
- mLocationClient.disconnect();
- }
- ...
-}
-</pre>
-<h3>Remove individual geofences</h3>
-<p>
- The procedure for removing an individual geofence or set of geofences is similar to the
- removal of all geofences. To specify the geofences you want remove, add their geofence ID
- values to a {@link java.util.List} of String objects. Pass this {@link java.util.List} to a
- different definition of {@code removeGeofences} with the appropriate signature. This method
- then starts the removal process.
-</p>
-<p>
- Start by adding a request type for removing geofences by a list, and also add a global variable
- for storing the list of geofences:
-</p>
-<pre>
- ...
- // Enum type for controlling the type of removal requested
- public enum REQUEST_TYPE = {ADD, REMOVE_INTENT, REMOVE_LIST}
- // Store the list of geofence Ids to remove
- String<List> mGeofencesToRemove;
-</pre>
-<p>
- Next, define a list of geofences you want to remove. For example, this snippet removes the
-<code><a href="{@docRoot}reference/com/google/android/gms/location/Geofence.html">Geofence</a></code>
- defined by the geofence ID "1":
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
- ConnectionCallbacks,
- OnConnectionFailedListener,
- OnAddGeofencesResultListener {
- ...
- List<String> listOfGeofences =
- Collections.singletonList("1");
- removeGeofences(listOfGeofences);
- ...
-}
-</pre>
-<p>
- The following snippet defines the {@code removeGeofences()} method:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
- ConnectionCallbacks,
- OnConnectionFailedListener,
- OnAddGeofencesResultListener {
- ...
- /**
- * Start a request to remove monitoring by
- * calling LocationClient.connect()
- *
- */
- public void removeGeofences(List<String> geofenceIds) {
- // If Google Play services is unavailable, exit
- // Record the type of removal request
- mRequestType = REMOVE_LIST;
- /*
- * Test for Google Play services after setting the request type.
- * If Google Play services isn't present, the request can be
- * restarted.
- */
- if (!servicesConnected()) {
- return;
- }
- // Store the list of geofences to remove
- mGeofencesToRemove = geofenceIds;
- /*
- * Create a new location client object. Since the current
- * activity class implements ConnectionCallbacks and
- * OnConnectionFailedListener, pass the current activity object
- * as the listener for both parameters
- */
- mLocationClient = new LocationClient(this, this, this);
- // If a request is not already underway
- if (!mInProgress) {
- // Indicate that a request is underway
- mInProgress = true;
- // Request a connection from the client to Location Services
- mLocationClient.connect();
- } else {
- /*
- * A request is already underway. You can handle
- * this situation by disconnecting the client,
- * re-setting the flag, and then re-trying the
- * request.
- */
- }
- }
- ...
-}
-</pre>
-<p>
- When Location Services invokes the callback method indicating that the connection is open,
- make the request to remove the list of geofences. Disconnect the client after making the request.
- For example:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
- ConnectionCallbacks,
- OnConnectionFailedListener,
- OnAddGeofencesResultListener {
- ...
- private void onConnected(Bundle dataBundle) {
- ...
- switch (mRequestType) {
- ...
- // If removeGeofencesById was called
- case REMOVE_LIST :
- mLocationClient.removeGeofences(
- mGeofencesToRemove, this);
- break;
- ...
- }
- ...
- }
- ...
-}
-</pre>
-<p>
- Define an implementation of
-<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.OnRemoveGeofencesResultListener.html#onRemoveGeofencesByRequestIdsResult(int, java.lang.String[])">onRemoveGeofencesByRequestIdsResult()</a></code>.
- Location Services invokes this callback method to indicate that the request to remove a list of
- geofences is complete. In this method, examine the incoming status code and take the
- appropriate action:
-</p>
-<pre>
-public class MainActivity extends FragmentActivity implements
- ConnectionCallbacks,
- OnConnectionFailedListener,
- OnAddGeofencesResultListener {
- ...
- /**
- * When the request to remove geofences by IDs returns, handle the
- * result.
- *
- * @param statusCode The code returned by Location Services
- * @param geofenceRequestIds The IDs removed
- */
- @Override
- public void onRemoveGeofencesByRequestIdsResult(
- int statusCode, String[] geofenceRequestIds) {
- // If removing the geocodes was successful
- if (LocationStatusCodes.SUCCESS == statusCode) {
- /*
- * Handle successful removal of geofences here.
- * You can send out a broadcast intent or update the UI.
- * geofences into the Intent's extended data.
- */
- } else {
- // If removing the geofences failed
- /*
- * Report errors here.
- * You can log the error using Log.e() or update
- * the UI.
- */
- }
- // Indicate that a request is no longer in progress
- mInProgress = false;
- // Disconnect the location client
- mLocationClient.disconnect();
- }
- ...
-}
-</pre>
-<p>
- You can combine geofencing with other location-aware features, such as periodic location updates
- or activity recognition, which are described in other lessons in this class.
-</p>
-<p>
- The next lesson,
- <a href="activity-recognition.html">Recognizing the User's Current Activity</a>, shows you how
- to request and receive activity updates. At regular intervals, Location Services can send you
- information about the user's current physical activity. Based on this information, you can
- change your app's behavior; for example, you can switch to a longer update interval if you
- detect that the user is walking instead of driving.
+ You can combine geofencing with other location-aware features, such as periodic location updates.
+ For more information, see the other lessons in this class.
</p>
diff --git a/docs/html/training/location/index.jd b/docs/html/training/location/index.jd
index 35e177f..c4dec99 100644
--- a/docs/html/training/location/index.jd
+++ b/docs/html/training/location/index.jd
@@ -81,4 +81,10 @@
Learn how to convert a location's latitude and longitude into an address
(reverse geocoding).
</dd>
+ <dt>
+ <b><a href="geofencing.html">Creating and Monitoring Geofences</a></b>
+ </dt> <dd>
+ Learn how to define one or more geographic areas as locations of interest,
+ called geofences, and detect when the user is close to or inside a geofence.
+ </dd>
</dl>
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index 89e72f1..11ae1a6 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -736,6 +736,10 @@
Displaying a Location Address
</a>
</li>
+ <li><a href="<?cs var:toroot ?>training/location/geofencing.html">
+ Creating and Monitoring Geofences
+ </a>
+ </li>
</ul>
</li>
</ul>
diff --git a/docs/html/wear/images/partners/acer.png b/docs/html/wear/images/partners/acer.png
new file mode 100644
index 0000000..439de80
--- /dev/null
+++ b/docs/html/wear/images/partners/acer.png
Binary files differ
diff --git a/docs/html/wear/images/partners/arm.png b/docs/html/wear/images/partners/arm.png
new file mode 100644
index 0000000..3e2f642
--- /dev/null
+++ b/docs/html/wear/images/partners/arm.png
Binary files differ
diff --git a/docs/html/wear/images/partners/huawei.png b/docs/html/wear/images/partners/huawei.png
new file mode 100644
index 0000000..9099ed4
--- /dev/null
+++ b/docs/html/wear/images/partners/huawei.png
Binary files differ
diff --git a/docs/html/wear/index.jd b/docs/html/wear/index.jd
index 27e8098..316f5ca 100644
--- a/docs/html/wear/index.jd
+++ b/docs/html/wear/index.jd
@@ -202,6 +202,12 @@
<div class="landing-partners cols">
<div class="col-4">
+ <img src="/wear/images/partners/acer.png" alt="Acer">
+ </div>
+ <div class="col-4">
+ <img src="/wear/images/partners/arm.png" alt="ARM">
+ </div>
+ <div class="col-4">
<img src="/wear/images/partners/asus.png" alt="Asus">
</div>
<div class="col-4">
@@ -214,6 +220,9 @@
<img src="/wear/images/partners/htc.png" alt="HTC">
</div>
<div class="col-4">
+ <img src="/wear/images/partners/huawei.png" alt="Huawei">
+ </div>
+ <div class="col-4">
<img src="/wear/images/partners/intel.png" alt="Intel">
</div>
<div class="col-4">
diff --git a/graphics/java/android/graphics/drawable/RippleComponent.java b/graphics/java/android/graphics/drawable/RippleComponent.java
index fd3e06c..79407f7 100644
--- a/graphics/java/android/graphics/drawable/RippleComponent.java
+++ b/graphics/java/android/graphics/drawable/RippleComponent.java
@@ -69,7 +69,6 @@
mDensity = density;
- onSetup();
onTargetRadiusChanged(mTargetRadius);
}
@@ -82,7 +81,10 @@
cancel();
mSoftwareAnimator = createSoftwareEnter(fast);
- mSoftwareAnimator.start();
+
+ if (mSoftwareAnimator != null) {
+ mSoftwareAnimator.start();
+ }
}
/**
@@ -250,14 +252,6 @@
// Stub.
}
- /**
- * Called during ripple setup, which occurs before the first enter
- * animation.
- */
- protected void onSetup() {
- // Stub.
- }
-
protected abstract Animator createSoftwareEnter(boolean fast);
protected abstract Animator createSoftwareExit();
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index bc8c7d2..66160c0 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -564,7 +564,9 @@
x = mHotspotBounds.exactCenterX();
y = mHotspotBounds.exactCenterY();
}
- mRipple = new RippleForeground(this, mHotspotBounds, x, y);
+
+ final boolean isBounded = !isProjected();
+ mRipple = new RippleForeground(this, mHotspotBounds, x, y, isBounded);
}
mRipple.setup(mState.mMaxRadius, mDensity);
diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java
index 2023f04..334122d 100644
--- a/graphics/java/android/graphics/drawable/RippleForeground.java
+++ b/graphics/java/android/graphics/drawable/RippleForeground.java
@@ -44,9 +44,16 @@
private static final float WAVE_TOUCH_UP_ACCELERATION = 3400;
private static final float WAVE_OPACITY_DECAY_VELOCITY = 3;
+ // Bounded ripple animation properties.
+ private static final int BOUNDED_ORIGIN_EXIT_DURATION = 300;
+ private static final int BOUNDED_RADIUS_EXIT_DURATION = 800;
+ private static final int BOUNDED_OPACITY_EXIT_DURATION = 400;
+ private static final float MAX_BOUNDED_RADIUS = 350;
+
private static final int RIPPLE_ENTER_DELAY = 80;
private static final int OPACITY_ENTER_DURATION_FAST = 120;
+ // Parent-relative values for starting position.
private float mStartingX;
private float mStartingY;
private float mClampedStartingX;
@@ -58,30 +65,41 @@
private CanvasProperty<Float> mPropX;
private CanvasProperty<Float> mPropY;
+ // Target values for tween animations.
+ private float mTargetX = 0;
+ private float mTargetY = 0;
+
+ /** Ripple target radius used when bounded. Not used for clamping. */
+ private float mBoundedRadius = 0;
+
// Software rendering properties.
private float mOpacity = 1;
- private float mOuterX;
- private float mOuterY;
// Values used to tween between the start and end positions.
private float mTweenRadius = 0;
private float mTweenX = 0;
private float mTweenY = 0;
+ /** Whether this ripple is bounded. */
+ private boolean mIsBounded;
+
/** Whether this ripple has finished its exit animation. */
private boolean mHasFinishedExit;
- public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY) {
+ public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY,
+ boolean isBounded) {
super(owner, bounds);
+ mIsBounded = isBounded;
mStartingX = startingX;
mStartingY = startingY;
- }
- @Override
- public void onSetup() {
- mOuterX = 0;
- mOuterY = 0;
+ if (isBounded) {
+ mBoundedRadius = MAX_BOUNDED_RADIUS * 0.9f
+ + (float) (MAX_BOUNDED_RADIUS * Math.random() * 0.1);
+ } else {
+ mBoundedRadius = 0;
+ }
}
@Override
@@ -95,12 +113,10 @@
final int origAlpha = p.getAlpha();
final int alpha = (int) (origAlpha * mOpacity + 0.5f);
- final float radius = MathUtils.lerp(0, mTargetRadius, mTweenRadius);
+ final float radius = getCurrentRadius();
if (alpha > 0 && radius > 0) {
- final float x = MathUtils.lerp(
- mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
- final float y = MathUtils.lerp(
- mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
+ final float x = getCurrentX();
+ final float y = getCurrentY();
p.setAlpha(alpha);
c.drawCircle(x, y, radius, p);
p.setAlpha(origAlpha);
@@ -120,8 +136,8 @@
* Returns the maximum bounds of the ripple relative to the ripple center.
*/
public void getBounds(Rect bounds) {
- final int outerX = (int) mOuterX;
- final int outerY = (int) mOuterY;
+ final int outerX = (int) mTargetX;
+ final int outerY = (int) mTargetY;
final int r = (int) mTargetRadius + 1;
bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
}
@@ -146,14 +162,25 @@
@Override
protected Animator createSoftwareEnter(boolean fast) {
+ // Bounded ripples don't have enter animations.
+ if (mIsBounded) {
+ return null;
+ }
+
final int duration = (int)
(1000 * Math.sqrt(mTargetRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
- final ObjectAnimator tweenAll = ObjectAnimator.ofFloat(this, TWEEN_ALL, 1);
- tweenAll.setAutoCancel(true);
- tweenAll.setDuration(duration);
- tweenAll.setInterpolator(LINEAR_INTERPOLATOR);
- tweenAll.setStartDelay(RIPPLE_ENTER_DELAY);
+ final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
+ tweenRadius.setAutoCancel(true);
+ tweenRadius.setDuration(duration);
+ tweenRadius.setInterpolator(LINEAR_INTERPOLATOR);
+ tweenRadius.setStartDelay(RIPPLE_ENTER_DELAY);
+
+ final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
+ tweenOrigin.setAutoCancel(true);
+ tweenOrigin.setDuration(duration);
+ tweenOrigin.setInterpolator(LINEAR_INTERPOLATOR);
+ tweenOrigin.setStartDelay(RIPPLE_ENTER_DELAY);
final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
opacity.setAutoCancel(true);
@@ -161,31 +188,68 @@
opacity.setInterpolator(LINEAR_INTERPOLATOR);
final AnimatorSet set = new AnimatorSet();
- set.play(tweenAll).with(opacity);
+ set.play(tweenOrigin).with(tweenRadius).with(opacity);
return set;
}
+ private float getCurrentX() {
+ return MathUtils.lerp(mClampedStartingX - mBounds.exactCenterX(), mTargetX, mTweenX);
+ }
+
+ private float getCurrentY() {
+ return MathUtils.lerp(mClampedStartingY - mBounds.exactCenterY(), mTargetY, mTweenY);
+ }
+
private int getRadiusExitDuration() {
- final float radius = MathUtils.lerp(0, mTargetRadius, mTweenRadius);
- final float remaining = mTargetRadius - radius;
- return (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION
+ final float remainingRadius = mTargetRadius - getCurrentRadius();
+ return (int) (1000 * Math.sqrt(remainingRadius / (WAVE_TOUCH_UP_ACCELERATION
+ WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
}
+ private float getCurrentRadius() {
+ return MathUtils.lerp(0, mTargetRadius, mTweenRadius);
+ }
+
private int getOpacityExitDuration() {
return (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
}
+ /**
+ * Compute target values that are dependent on bounding.
+ */
+ private void computeBoundedTargetValues() {
+ mTargetX = (mClampedStartingX - mBounds.exactCenterX()) * .7f;
+ mTargetY = (mClampedStartingY - mBounds.exactCenterY()) * .7f;
+ mTargetRadius = mBoundedRadius;
+ }
+
@Override
protected Animator createSoftwareExit() {
- final int radiusDuration = getRadiusExitDuration();
- final int opacityDuration = getOpacityExitDuration();
+ final int radiusDuration;
+ final int originDuration;
+ final int opacityDuration;
+ if (mIsBounded) {
+ computeBoundedTargetValues();
- final ObjectAnimator tweenAll = ObjectAnimator.ofFloat(this, TWEEN_ALL, 1);
- tweenAll.setAutoCancel(true);
- tweenAll.setDuration(radiusDuration);
- tweenAll.setInterpolator(DECELERATE_INTERPOLATOR);
+ radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
+ originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
+ opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
+ } else {
+ radiusDuration = getRadiusExitDuration();
+ originDuration = radiusDuration;
+ opacityDuration = getOpacityExitDuration();
+ }
+
+ final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
+ tweenRadius.setAutoCancel(true);
+ tweenRadius.setDuration(radiusDuration);
+ tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);
+
+ final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
+ tweenOrigin.setAutoCancel(true);
+ tweenOrigin.setDuration(originDuration);
+ tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);
final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 0);
opacity.setAutoCancel(true);
@@ -193,7 +257,7 @@
opacity.setInterpolator(LINEAR_INTERPOLATOR);
final AnimatorSet set = new AnimatorSet();
- set.play(tweenAll).with(opacity);
+ set.play(tweenOrigin).with(tweenRadius).with(opacity);
set.addListener(mAnimationListener);
return set;
@@ -201,15 +265,25 @@
@Override
protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
- final int radiusDuration = getRadiusExitDuration();
- final int opacityDuration = getOpacityExitDuration();
+ final int radiusDuration;
+ final int originDuration;
+ final int opacityDuration;
+ if (mIsBounded) {
+ computeBoundedTargetValues();
- final float startX = MathUtils.lerp(
- mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
- final float startY = MathUtils.lerp(
- mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
+ radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
+ originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
+ opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
+ } else {
+ radiusDuration = getRadiusExitDuration();
+ originDuration = radiusDuration;
+ opacityDuration = getOpacityExitDuration();
+ }
- final float startRadius = MathUtils.lerp(0, mTargetRadius, mTweenRadius);
+ final float startX = getCurrentX();
+ final float startY = getCurrentY();
+ final float startRadius = getCurrentRadius();
+
p.setAlpha((int) (p.getAlpha() * mOpacity + 0.5f));
mPropPaint = CanvasProperty.createPaint(p);
@@ -221,12 +295,12 @@
radius.setDuration(radiusDuration);
radius.setInterpolator(DECELERATE_INTERPOLATOR);
- final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mOuterX);
- x.setDuration(radiusDuration);
+ final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mTargetX);
+ x.setDuration(originDuration);
x.setInterpolator(DECELERATE_INTERPOLATOR);
- final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mOuterY);
- y.setDuration(radiusDuration);
+ final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mTargetY);
+ y.setDuration(originDuration);
y.setInterpolator(DECELERATE_INTERPOLATOR);
final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
@@ -307,16 +381,13 @@
}
/**
- * Property for animating radius, center X, and center Y between their
- * initial and target values.
+ * Property for animating radius between its initial and target values.
*/
- private static final FloatProperty<RippleForeground> TWEEN_ALL =
- new FloatProperty<RippleForeground>("tweenAll") {
+ private static final FloatProperty<RippleForeground> TWEEN_RADIUS =
+ new FloatProperty<RippleForeground>("tweenRadius") {
@Override
public void setValue(RippleForeground object, float value) {
object.mTweenRadius = value;
- object.mTweenX = value;
- object.mTweenY = value;
object.invalidateSelf();
}
@@ -327,6 +398,24 @@
};
/**
+ * Property for animating origin between its initial and target values.
+ */
+ private static final FloatProperty<RippleForeground> TWEEN_ORIGIN =
+ new FloatProperty<RippleForeground>("tweenOrigin") {
+ @Override
+ public void setValue(RippleForeground object, float value) {
+ object.mTweenX = value;
+ object.mTweenY = value;
+ object.invalidateSelf();
+ }
+
+ @Override
+ public Float get(RippleForeground object) {
+ return object.mTweenX;
+ }
+ };
+
+ /**
* Property for animating opacity between 0 and its target value.
*/
private static final FloatProperty<RippleForeground> OPACITY =
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index 1d07951..a9ce7f4 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -208,7 +208,8 @@
// Fill
////////////////////////////////////////////////////////////////////////////////
-void GlopBuilder::setFill(int color, float alphaScale, SkXfermode::Mode mode,
+void GlopBuilder::setFill(int color, float alphaScale,
+ SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage,
const SkShader* shader, const SkColorFilter* colorFilter) {
if (mode != SkXfermode::kClear_Mode) {
float alpha = (SkColorGetA(color) / 255.0f) * alphaScale;
@@ -226,7 +227,6 @@
} else {
mOutGlop->fill.color = { 0, 0, 0, 1 };
}
- const bool SWAP_SRC_DST = false;
mOutGlop->blend = { GL_ZERO, GL_ZERO };
if (mOutGlop->fill.color.a < 1.0f
@@ -237,7 +237,7 @@
|| PaintUtils::isBlendedColorFilter(colorFilter)
|| mode != SkXfermode::kSrcOver_Mode) {
if (CC_LIKELY(mode <= SkXfermode::kScreen_Mode)) {
- Blend::getFactors(mode, SWAP_SRC_DST,
+ Blend::getFactors(mode, modeUsage,
&mOutGlop->blend.src, &mOutGlop->blend.dst);
} else {
// These blend modes are not supported by OpenGL directly and have
@@ -247,11 +247,11 @@
// back to the default SrcOver blend mode instead
if (CC_UNLIKELY(mCaches.extensions().hasFramebufferFetch())) {
mDescription.framebufferMode = mode;
- mDescription.swapSrcDst = SWAP_SRC_DST;
+ mDescription.swapSrcDst = (modeUsage == Blend::ModeOrderSwap::Swap);
// blending in shader, don't enable
} else {
// unsupported
- Blend::getFactors(SkXfermode::kSrcOver_Mode, SWAP_SRC_DST,
+ Blend::getFactors(SkXfermode::kSrcOver_Mode, modeUsage,
&mOutGlop->blend.src, &mOutGlop->blend.dst);
}
}
@@ -317,17 +317,17 @@
color |= 0x00FFFFFF;
shader = nullptr;
}
- setFill(color, alphaScale, PaintUtils::getXfermode(paint->getXfermode()),
+ setFill(color, alphaScale,
+ PaintUtils::getXfermode(paint->getXfermode()), Blend::ModeOrderSwap::NoSwap,
shader, paint->getColorFilter());
} else {
mOutGlop->fill.color = { alphaScale, alphaScale, alphaScale, alphaScale };
- const bool SWAP_SRC_DST = false;
if (alphaScale < 1.0f
|| (mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::kAlpha)
|| texture.blend
|| mOutGlop->roundRectClipState) {
- Blend::getFactors(SkXfermode::kSrcOver_Mode, SWAP_SRC_DST,
+ Blend::getFactors(SkXfermode::kSrcOver_Mode, Blend::ModeOrderSwap::NoSwap,
&mOutGlop->blend.src, &mOutGlop->blend.dst);
} else {
mOutGlop->blend = { GL_ZERO, GL_ZERO };
@@ -349,7 +349,8 @@
mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
- setFill(paint.getColor(), alphaScale, PaintUtils::getXfermode(paint.getXfermode()),
+ setFill(paint.getColor(), alphaScale,
+ PaintUtils::getXfermode(paint.getXfermode()), Blend::ModeOrderSwap::NoSwap,
paint.getShader(), paint.getColorFilter());
mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
return *this;
@@ -363,7 +364,8 @@
//specify invalid filter/clamp, since these are always static for PathTextures
mOutGlop->fill.texture = { &texture, GL_TEXTURE_2D, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
- setFill(paint.getColor(), alphaScale, PaintUtils::getXfermode(paint.getXfermode()),
+ setFill(paint.getColor(), alphaScale,
+ PaintUtils::getXfermode(paint.getXfermode()), Blend::ModeOrderSwap::NoSwap,
paint.getShader(), paint.getColorFilter());
mDescription.hasAlpha8Texture = true;
@@ -386,7 +388,8 @@
shadowColor &= paint.getColor() | COLOR_BITMASK;
}
- setFill(shadowColor, alphaScale, PaintUtils::getXfermode(paint.getXfermode()),
+ setFill(shadowColor, alphaScale,
+ PaintUtils::getXfermode(paint.getXfermode()), Blend::ModeOrderSwap::NoSwap,
paint.getShader(), paint.getColorFilter());
mDescription.hasAlpha8Texture = true;
@@ -399,7 +402,8 @@
REQUIRE_STAGES(kMeshStage);
mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
- setFill(SK_ColorBLACK, 1.0f, SkXfermode::kSrcOver_Mode, nullptr, nullptr);
+ setFill(SK_ColorBLACK, 1.0f, SkXfermode::kSrcOver_Mode, Blend::ModeOrderSwap::NoSwap,
+ nullptr, nullptr);
return *this;
}
@@ -408,12 +412,13 @@
REQUIRE_STAGES(kMeshStage);
mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
- setFill(SK_ColorBLACK, 1.0f, SkXfermode::kClear_Mode, nullptr, nullptr);
+ setFill(SK_ColorBLACK, 1.0f, SkXfermode::kClear_Mode, Blend::ModeOrderSwap::NoSwap,
+ nullptr, nullptr);
return *this;
}
GlopBuilder& GlopBuilder::setFillLayer(Texture& texture, const SkColorFilter* colorFilter,
- float alpha, SkXfermode::Mode mode) {
+ float alpha, SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage) {
TRIGGER_STAGE(kFillStage);
REQUIRE_STAGES(kMeshStage);
@@ -421,7 +426,7 @@
GL_TEXTURE_2D, GL_LINEAR, GL_CLAMP_TO_EDGE, nullptr };
mOutGlop->fill.color = { alpha, alpha, alpha, alpha };
- setFill(SK_ColorWHITE, alpha, mode, nullptr, colorFilter);
+ setFill(SK_ColorWHITE, alpha, mode, modeUsage, nullptr, colorFilter);
mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
return *this;
@@ -435,7 +440,8 @@
layer.getRenderTarget(), GL_LINEAR, GL_CLAMP_TO_EDGE, &layer.getTexTransform() };
mOutGlop->fill.color = { alpha, alpha, alpha, alpha };
- setFill(SK_ColorWHITE, alpha, layer.getMode(), nullptr, layer.getColorFilter());
+ setFill(SK_ColorWHITE, alpha, layer.getMode(), Blend::ModeOrderSwap::NoSwap,
+ nullptr, layer.getColorFilter());
mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
mDescription.hasTextureTransform = true;
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index 74d4889..b335a2c 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -18,6 +18,7 @@
#include "OpenGLRenderer.h"
#include "Program.h"
+#include "renderstate/Blend.h"
#include "utils/Macros.h"
class SkPaint;
@@ -65,7 +66,7 @@
GlopBuilder& setFillBlack();
GlopBuilder& setFillClear();
GlopBuilder& setFillLayer(Texture& texture, const SkColorFilter* colorFilter,
- float alpha, SkXfermode::Mode mode);
+ float alpha, SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage);
GlopBuilder& setFillTextureLayer(Layer& layer, float alpha);
GlopBuilder& setTransform(const Matrix4& ortho, const Matrix4& transform, bool fudgingOffset);
@@ -94,7 +95,8 @@
void build();
private:
- void setFill(int color, float alphaScale, SkXfermode::Mode mode,
+ void setFill(int color, float alphaScale,
+ SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage,
const SkShader* shader, const SkColorFilter* colorFilter);
enum StageFlags {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index a6f6e18..622b570 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -908,15 +908,35 @@
resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
} else {
EVENT_LOGD("composeHardwareLayerRect");
+
+ if (USE_GLOPS) {
+ Blend::ModeOrderSwap modeUsage = swap ?
+ Blend::ModeOrderSwap::Swap : Blend::ModeOrderSwap::NoSwap;
+ const Matrix4& transform = swap ? Matrix4::identity() : *currentTransform();
+ bool snap = !swap
+ && layer->getWidth() == static_cast<uint32_t>(rect.getWidth())
+ && layer->getHeight() == static_cast<uint32_t>(rect.getHeight());
+ Glop glop;
+ GlopBuilder(mRenderState, mCaches, &glop)
+ .setMeshTexturedUvQuad(nullptr, layer->texCoords)
+ .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode(), modeUsage)
+ .setTransform(currentSnapshot()->getOrthoMatrix(), transform, false)
+ .setModelViewMapUnitToRectOptionalSnap(snap, rect)
+ .setRoundRectClipState(currentSnapshot()->roundRectClipState)
+ .build();
+ renderGlop(glop);
+ return;
+ }
+
const Rect& texCoords = layer->texCoords;
resetDrawTextureTexCoords(texCoords.left, texCoords.top,
texCoords.right, texCoords.bottom);
float x = rect.left;
float y = rect.top;
- bool simpleTransform = currentTransform()->isPureTranslate() &&
- layer->getWidth() == (uint32_t) rect.getWidth() &&
- layer->getHeight() == (uint32_t) rect.getHeight();
+ bool simpleTransform = currentTransform()->isPureTranslate()
+ && layer->getWidth() == (uint32_t) rect.getWidth()
+ && layer->getHeight() == (uint32_t) rect.getHeight();
if (simpleTransform) {
// When we're swapping, the layer is already in screen coordinates
@@ -1053,11 +1073,42 @@
rects = safeRegion.getArray(&count);
}
- const float alpha = getLayerAlpha(layer);
const float texX = 1.0f / float(layer->getWidth());
const float texY = 1.0f / float(layer->getHeight());
const float height = rect.getHeight();
+ if (USE_GLOPS) {
+ TextureVertex quadVertices[count * 4];
+ //std::unique_ptr<TextureVertex[]> quadVertices(new TextureVertex[count * 4]);
+ TextureVertex* mesh = &quadVertices[0];
+ for (size_t i = 0; i < count; i++) {
+ const android::Rect* r = &rects[i];
+
+ const float u1 = r->left * texX;
+ const float v1 = (height - r->top) * texY;
+ const float u2 = r->right * texX;
+ const float v2 = (height - r->bottom) * texY;
+
+ // TODO: Reject quads outside of the clip
+ TextureVertex::set(mesh++, r->left, r->top, u1, v1);
+ TextureVertex::set(mesh++, r->right, r->top, u2, v1);
+ TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
+ TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
+ }
+ Glop glop;
+ GlopBuilder(mRenderState, mCaches, &glop)
+ .setMeshTexturedIndexedQuads(&quadVertices[0], count * 6)
+ .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode(), Blend::ModeOrderSwap::NoSwap)
+ .setTransform(currentSnapshot()->getOrthoMatrix(), *currentTransform(), false)
+ .setModelViewOffsetRectSnap(0, 0, rect)
+ .setRoundRectClipState(currentSnapshot()->roundRectClipState)
+ .build();
+ DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate, renderGlop(glop));
+ return;
+ }
+
+ const float alpha = getLayerAlpha(layer);
+
setupDraw();
// We must get (and therefore bind) the region mesh buffer
@@ -3110,7 +3161,7 @@
Glop glop;
GlopBuilder(mRenderState, mCaches, &glop)
.setMeshTexturedIndexedQuads(layer->mesh, layer->meshElementCount)
- .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode())
+ .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode(), Blend::ModeOrderSwap::NoSwap)
.setTransform(currentSnapshot()->getOrthoMatrix(), *currentTransform(), false)
.setModelViewOffsetRectSnap(x, y, Rect(0, 0, layer->layer.getWidth(), layer->layer.getHeight()))
.setRoundRectClipState(currentSnapshot()->roundRectClipState)
@@ -3596,7 +3647,8 @@
mode = SkXfermode::kSrcOver_Mode;
}
}
- mRenderState.blend().enable(mode, swapSrcDst);
+ mRenderState.blend().enable(mode,
+ swapSrcDst ? Blend::ModeOrderSwap::Swap : Blend::ModeOrderSwap::NoSwap);
} else {
mRenderState.blend().disable();
}
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index c6fdd3f..e009451 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -139,7 +139,7 @@
void RenderNode::prepareLayer(TreeInfo& info, uint32_t dirtyMask) {
LayerType layerType = properties().layerProperties().type();
- if (CC_UNLIKELY(layerType == kLayerTypeRenderLayer)) {
+ if (CC_UNLIKELY(layerType == LayerType::RenderLayer)) {
// Damage applied so far needs to affect our parent, but does not require
// the layer to be updated. So we pop/push here to clear out the current
// damage and get a clean state for display list or children updates to
@@ -156,7 +156,7 @@
LayerType layerType = properties().layerProperties().type();
// If we are not a layer OR we cannot be rendered (eg, view was detached)
// we need to destroy any Layers we may have had previously
- if (CC_LIKELY(layerType != kLayerTypeRenderLayer) || CC_UNLIKELY(!isRenderable())) {
+ if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable())) {
if (CC_UNLIKELY(mLayer)) {
LayerRenderer::destroyLayer(mLayer);
mLayer = nullptr;
@@ -384,7 +384,7 @@
renderer.concatMatrix(*properties().getTransformMatrix());
}
}
- const bool isLayer = properties().layerProperties().type() != kLayerTypeNone;
+ const bool isLayer = properties().layerProperties().type() != LayerType::None;
int clipFlags = properties().getClippingFlags();
if (properties().getAlpha() < 1) {
if (isLayer) {
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index bb6d087..9f1ceed 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -33,14 +33,12 @@
namespace android {
namespace uirenderer {
-LayerProperties::LayerProperties()
- : mType(kLayerTypeNone)
- , mColorFilter(nullptr) {
+LayerProperties::LayerProperties() {
reset();
}
LayerProperties::~LayerProperties() {
- setType(kLayerTypeNone);
+ setType(LayerType::None);
}
void LayerProperties::reset() {
@@ -146,7 +144,7 @@
}
}
- const bool isLayer = layerProperties().type() != kLayerTypeNone;
+ const bool isLayer = layerProperties().type() != LayerType::None;
int clipFlags = getClippingFlags();
if (mPrimitiveFields.mAlpha < 1) {
if (isLayer) {
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index f0e22d6..61e98d2 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -49,12 +49,12 @@
#define RP_SET_AND_DIRTY(a, b) RP_SET(a, b, mPrimitiveFields.mMatrixOrPivotDirty = true)
// Keep in sync with View.java:LAYER_TYPE_*
-enum LayerType {
- kLayerTypeNone = 0,
+enum class LayerType {
+ None = 0,
// Although we cannot build the software layer directly (must be done at
// record time), this information is used when applying alpha.
- kLayerTypeSoftware = 1,
- kLayerTypeRenderLayer = 2,
+ Software = 1,
+ RenderLayer = 2,
// TODO: LayerTypeSurfaceTexture? Maybe?
};
@@ -124,12 +124,12 @@
friend class RenderProperties;
- LayerType mType;
+ LayerType mType = LayerType::None;
// Whether or not that Layer's content is opaque, doesn't include alpha
bool mOpaque;
uint8_t mAlpha;
SkXfermode::Mode mMode;
- SkColorFilter* mColorFilter;
+ SkColorFilter* mColorFilter = nullptr;
};
/*
diff --git a/libs/hwui/renderstate/Blend.cpp b/libs/hwui/renderstate/Blend.cpp
index 789f6cc..29927ed 100644
--- a/libs/hwui/renderstate/Blend.cpp
+++ b/libs/hwui/renderstate/Blend.cpp
@@ -78,10 +78,10 @@
// gl blending off by default
}
-void Blend::enable(SkXfermode::Mode mode, bool swapSrcDst) {
+void Blend::enable(SkXfermode::Mode mode, ModeOrderSwap modeUsage) {
GLenum srcMode;
GLenum dstMode;
- getFactors(mode, swapSrcDst, &srcMode, &dstMode);
+ getFactors(mode, modeUsage, &srcMode, &dstMode);
setFactors(srcMode, dstMode);
}
@@ -105,9 +105,9 @@
}
}
-void Blend::getFactors(SkXfermode::Mode mode, bool swapSrcDst, GLenum* outSrc, GLenum* outDst) {
- *outSrc = swapSrcDst ? kBlendsSwap[mode].src : kBlends[mode].src;
- *outDst = swapSrcDst ? kBlendsSwap[mode].dst : kBlends[mode].dst;
+void Blend::getFactors(SkXfermode::Mode mode, ModeOrderSwap modeUsage, GLenum* outSrc, GLenum* outDst) {
+ *outSrc = (modeUsage == ModeOrderSwap::Swap) ? kBlendsSwap[mode].src : kBlends[mode].src;
+ *outDst = (modeUsage == ModeOrderSwap::Swap) ? kBlendsSwap[mode].dst : kBlends[mode].dst;
}
void Blend::setFactors(GLenum srcMode, GLenum dstMode) {
diff --git a/libs/hwui/renderstate/Blend.h b/libs/hwui/renderstate/Blend.h
index 6d0c115c..dcc681d 100644
--- a/libs/hwui/renderstate/Blend.h
+++ b/libs/hwui/renderstate/Blend.h
@@ -29,11 +29,18 @@
class Blend {
friend class RenderState;
public:
- void enable(SkXfermode::Mode mode, bool swapSrcDst);
+ // dictates whether to swap src/dst
+ enum class ModeOrderSwap {
+ NoSwap,
+ Swap,
+ };
+
+ void enable(SkXfermode::Mode mode, ModeOrderSwap modeUsage);
void disable();
void syncEnabled();
- static void getFactors(SkXfermode::Mode mode, bool swapSrcDst, GLenum* outSrc, GLenum* outDst);
+ static void getFactors(SkXfermode::Mode mode, ModeOrderSwap modeUsage,
+ GLenum* outSrc, GLenum* outDst);
void setFactors(GLenum src, GLenum dst);
void dump();
diff --git a/libs/hwui/renderstate/MeshState.cpp b/libs/hwui/renderstate/MeshState.cpp
index 6b00020..0521f65 100644
--- a/libs/hwui/renderstate/MeshState.cpp
+++ b/libs/hwui/renderstate/MeshState.cpp
@@ -37,7 +37,7 @@
mCurrentBuffer = mUnitQuadBuffer;
- std::unique_ptr<uint16_t[]> regionIndices(new uint16_t[kMaxNumberOfQuads * 6]);
+ uint16_t regionIndices[kMaxNumberOfQuads * 6];
for (uint32_t i = 0; i < kMaxNumberOfQuads; i++) {
uint16_t quad = i * 4;
int index = i * 6;
@@ -50,8 +50,7 @@
}
glGenBuffers(1, &mQuadListIndices);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mQuadListIndices);
- glBufferData(GL_ELEMENT_ARRAY_BUFFER, kMaxNumberOfQuads * 6 * sizeof(uint16_t),
- regionIndices.get(), GL_STATIC_DRAW);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(regionIndices), regionIndices, GL_STATIC_DRAW);
mCurrentIndicesBuffer = mQuadListIndices;
// position attribute always enabled
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index 925d17e..84544ff 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -33,7 +33,7 @@
private static Method sPropertyMethod;
static {
try {
- Class<?> c = Class.forName("android.view.GLES20Canvas");
+ Class<?> c = Class.forName("android.view.DisplayListCanvas");
sPropertyMethod = c.getDeclaredMethod("setProperty", String.class, String.class);
if (!sPropertyMethod.isAccessible()) sPropertyMethod.setAccessible(true);
} catch (ClassNotFoundException e) {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 5363968..b5036db 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -808,6 +808,7 @@
}
if (typeChanged) {
mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
+ mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
}
}
@@ -822,6 +823,7 @@
}
if (typeChanged) {
mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
+ mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7e69e87..a4f3f23 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -496,7 +496,12 @@
int mLastDisplayFreezeDuration = 0;
Object mLastFinishedFreezeSource = null;
boolean mWaitingForConfig = false;
- boolean mWindowsFreezingScreen = false;
+
+ final static int WINDOWS_FREEZING_SCREENS_NONE = 0;
+ final static int WINDOWS_FREEZING_SCREENS_ACTIVE = 1;
+ final static int WINDOWS_FREEZING_SCREENS_TIMEOUT = 2;
+ private int mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
+
boolean mClientFreezingScreen = false;
int mAppsFreezingScreen = 0;
int mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -4699,7 +4704,7 @@
if (mAppsFreezingScreen == 1) {
startFreezingDisplayLocked(false, 0, 0);
mH.removeMessages(H.APP_FREEZE_TIMEOUT);
- mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 5000);
+ mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000);
}
}
final int N = wtoken.allAppWindows.size();
@@ -6415,7 +6420,7 @@
mAltOrientation = altOrientation;
mPolicy.setRotationLw(mRotation);
- mWindowsFreezingScreen = true;
+ mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, WINDOW_FREEZE_TIMEOUT_DURATION);
mWaitingForConfig = true;
@@ -7784,6 +7789,7 @@
// TODO(multidisplay): Can non-default displays rotate?
synchronized (mWindowMap) {
Slog.w(TAG, "Window freeze timeout expired.");
+ mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
final WindowList windows = getDefaultWindowListLocked();
int i = windows.size();
while (i > 0) {
@@ -7851,6 +7857,7 @@
case APP_FREEZE_TIMEOUT: {
synchronized (mWindowMap) {
Slog.w(TAG, "App freeze timeout expired.");
+ mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
final int numStacks = mStackIdToStack.size();
for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
final TaskStack stack = mStackIdToStack.valueAt(stackNdx);
@@ -8914,8 +8921,8 @@
w.mOrientationChanging = true;
w.mLastFreezeDuration = 0;
mInnerFields.mOrientationChangeComplete = false;
- if (!mWindowsFreezingScreen) {
- mWindowsFreezingScreen = true;
+ if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
+ mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
// XXX should probably keep timeout from
// when we first froze the display.
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
@@ -9905,8 +9912,8 @@
"With display frozen, orientationChangeComplete="
+ mInnerFields.mOrientationChangeComplete);
if (mInnerFields.mOrientationChangeComplete) {
- if (mWindowsFreezingScreen) {
- mWindowsFreezingScreen = false;
+ if (mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
+ mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
mLastFinishedFreezeSource = mInnerFields.mLastWindowFreezeSource;
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
}
@@ -10188,7 +10195,7 @@
} else {
mInnerFields.mOrientationChangeComplete = true;
mInnerFields.mLastWindowFreezeSource = mAnimator.mLastWindowFreezeSource;
- if (mWindowsFreezingScreen) {
+ if (mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
doRequest = true;
}
}
@@ -10524,7 +10531,8 @@
return;
}
- if (mWaitingForConfig || mAppsFreezingScreen > 0 || mWindowsFreezingScreen
+ if (mWaitingForConfig || mAppsFreezingScreen > 0
+ || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
|| mClientFreezingScreen) {
if (DEBUG_ORIENTATION) Slog.d(TAG,
"stopFreezingDisplayLocked: Returning mWaitingForConfig=" + mWaitingForConfig