Merge "Once restored, the wallpaper needs to actually draw" into nyc-dev
diff --git a/api/system-current.txt b/api/system-current.txt
index c9176ec..0743b91 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -23829,6 +23829,7 @@
public static class AudioMix.Builder {
ctor public AudioMix.Builder(android.media.audiopolicy.AudioMixingRule) throws java.lang.IllegalArgumentException;
method public android.media.audiopolicy.AudioMix build() throws java.lang.IllegalArgumentException;
+ method public android.media.audiopolicy.AudioMix.Builder setDevice(android.media.AudioDeviceInfo) throws java.lang.IllegalArgumentException;
method public android.media.audiopolicy.AudioMix.Builder setFormat(android.media.AudioFormat) throws java.lang.IllegalArgumentException;
method public android.media.audiopolicy.AudioMix.Builder setRouteFlags(int) throws java.lang.IllegalArgumentException;
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 2f6907e..2d33a2c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3329,6 +3329,23 @@
}
}
+ /**
+ * Logs out current current foreground user by switching to the system user and stopping the
+ * user being switched from.
+ * @hide
+ */
+ public static void logoutCurrentUser() {
+ int currentUser = ActivityManager.getCurrentUser();
+ if (currentUser != UserHandle.USER_SYSTEM) {
+ try {
+ ActivityManagerNative.getDefault().switchUser(UserHandle.USER_SYSTEM);
+ ActivityManagerNative.getDefault().stopUser(currentUser, /* force= */ false, null);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
/** {@hide} */
public static final int FLAG_OR_STOPPED = 1 << 0;
/** {@hide} */
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 82c4c51..64586a6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1267,8 +1267,15 @@
/** @hide */
public void setUserRestriction(int code, boolean restricted, IBinder token) {
+ setUserRestriction(code, restricted, token, /*exceptionPackages*/null);
+ }
+
+ /** @hide */
+ public void setUserRestriction(int code, boolean restricted, IBinder token,
+ String[] exceptionPackages) {
try {
- mService.setUserRestriction(code, restricted, token, mContext.getUserId());
+ mService.setUserRestriction(code, restricted, token, mContext.getUserId(),
+ exceptionPackages);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index d73deb6..ea8ba2f 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -70,6 +70,9 @@
private static final boolean CHECK_PARCEL_SIZE = false;
static final String TAG = "Binder";
+ /** @hide */
+ public static boolean LOG_RUNTIME_EXCEPTION = false; // DO NOT SUBMIT WITH TRUE
+
/**
* Control whether dump() calls are allowed.
*/
@@ -560,17 +563,16 @@
// If the call was FLAG_ONEWAY then these exceptions disappear into the ether.
try {
res = onTransact(code, data, reply, flags);
- } catch (RemoteException e) {
- if ((flags & FLAG_ONEWAY) != 0) {
- Log.w(TAG, "Binder call failed.", e);
- } else {
- reply.setDataPosition(0);
- reply.writeException(e);
- }
- res = true;
- } catch (RuntimeException e) {
- if ((flags & FLAG_ONEWAY) != 0) {
+ } catch (RemoteException|RuntimeException e) {
+ if (LOG_RUNTIME_EXCEPTION) {
Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
+ }
+ if ((flags & FLAG_ONEWAY) != 0) {
+ if (e instanceof RemoteException) {
+ Log.w(TAG, "Binder call failed.", e);
+ } else {
+ Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
+ }
} else {
reply.setDataPosition(0);
reply.writeException(e);
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index a747f16..d201ade 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -154,11 +154,11 @@
public static final int DENSITY_DEVICE_STABLE = getDeviceDensity();
/**
- * The absolute width of the display in pixels.
+ * The absolute width of the available display size in pixels.
*/
public int widthPixels;
/**
- * The absolute height of the display in pixels.
+ * The absolute height of the available display size in pixels.
*/
public int heightPixels;
/**
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 60c7270..dcf987b 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -84,8 +84,19 @@
if (fileSize > Integer.MAX_VALUE) {
return false;
}
- MappedByteBuffer apkContents =
- apk.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
+ MappedByteBuffer apkContents;
+ try {
+ apkContents = apk.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
+ } catch (IOException e) {
+ if (e.getCause() instanceof OutOfMemoryError) {
+ // TODO: Remove this temporary workaround once verifying large APKs is
+ // supported. Very large APKs cannot be memory-mapped. This verification code
+ // needs to change to use a different approach for verifying such APKs.
+ return false; // Pretend that this APK does not have a v2 signature.
+ } else {
+ throw new IOException("Failed to memory-map APK", e);
+ }
+ }
// ZipUtils and APK Signature Scheme v2 verifier expect little-endian byte order.
apkContents.order(ByteOrder.LITTLE_ENDIAN);
@@ -134,11 +145,26 @@
if (fileSize > Integer.MAX_VALUE) {
throw new IOException("File too large: " + apk.length() + " bytes");
}
- MappedByteBuffer apkContents =
- apk.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
- // Attempt to preload the contents into memory for faster overall verification (v2 and
- // older) at the expense of somewhat increased latency for rejecting malformed APKs.
- apkContents.load();
+ MappedByteBuffer apkContents;
+ try {
+ apkContents = apk.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
+ // Attempt to preload the contents into memory for faster overall verification (v2 and
+ // older) at the expense of somewhat increased latency for rejecting malformed APKs.
+ apkContents.load();
+ } catch (IOException e) {
+ if (e.getCause() instanceof OutOfMemoryError) {
+ // TODO: Remove this temporary workaround once verifying large APKs is supported.
+ // Very large APKs cannot be memory-mapped. This verification code needs to change
+ // to use a different approach for verifying such APKs.
+ // This workaround pretends that this APK does not have a v2 signature. This works
+ // fine provided the APK is not actually v2-signed. If the APK is v2 signed, v2
+ // signature stripping protection inside v1 signature verification code will reject
+ // this APK.
+ throw new SignatureNotFoundException("Failed to memory-map APK", e);
+ } else {
+ throw new IOException("Failed to memory-map APK", e);
+ }
+ }
return verify(apkContents);
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d29bc21..56ee478 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1382,6 +1382,13 @@
if (mIsInterestedInDrag) {
retval = true;
}
+
+ if (!retval) {
+ // Neither us nor any of our children are interested in this drag, so stop tracking
+ // the current drag event.
+ mCurrentDragStartEvent.recycle();
+ mCurrentDragStartEvent = null;
+ }
} break;
case DragEvent.ACTION_DRAG_ENDED: {
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index b13be97..3a31b37 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -45,6 +45,6 @@
void setAudioRestriction(int code, int usage, int uid, int mode, in String[] exceptionPackages);
void setUserRestrictions(in Bundle restrictions, IBinder token, int userHandle);
- void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle);
+ void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle, in String[] exceptionPackages);
void removeUser(int userHandle);
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ce5d07c..51cd029 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -805,6 +805,13 @@
-->
<integer name="config_longPressOnPowerBehavior">1</integer>
+ <!-- Control the behavior when the user long presses the back button. Non-zero values are only
+ valid for watches as part of CDD/CTS.
+ 0 - Nothing
+ 1 - Go to voice assist
+ -->
+ <integer name="config_longPressOnBackBehavior">0</integer>
+
<!-- Control the behavior when the user short presses the power button.
0 - Nothing
1 - Go to sleep (doze)
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 70f4f54..1470741 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -382,6 +382,7 @@
<java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
<java-symbol type="integer" name="config_immersive_mode_confirmation_panic" />
<java-symbol type="integer" name="config_longPressOnPowerBehavior" />
+ <java-symbol type="integer" name="config_longPressOnBackBehavior" />
<java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAdjust" />
<java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAbsolute" />
<java-symbol type="integer" name="config_max_pan_devices" />
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 936c7e8..f6e3b50 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -240,6 +240,7 @@
tests/unit/DamageAccumulatorTests.cpp \
tests/unit/DeviceInfoTests.cpp \
tests/unit/FatVectorTests.cpp \
+ tests/unit/GlopBuilderTests.cpp \
tests/unit/GpuMemoryTrackerTests.cpp \
tests/unit/LayerUpdateQueueTests.cpp \
tests/unit/LinearAllocatorTests.cpp \
diff --git a/libs/hwui/FloatColor.h b/libs/hwui/FloatColor.h
index 97dec88..9a39ec2 100644
--- a/libs/hwui/FloatColor.h
+++ b/libs/hwui/FloatColor.h
@@ -17,6 +17,7 @@
#define FLOATCOLOR_H
#include "utils/Macros.h"
+#include "utils/MathUtils.h"
#include <stdint.h>
@@ -38,6 +39,17 @@
|| b > 0.0f;
}
+ bool operator==(const FloatColor& other) const {
+ return MathUtils::areEqual(r, other.r)
+ && MathUtils::areEqual(g, other.g)
+ && MathUtils::areEqual(b, other.b)
+ && MathUtils::areEqual(a, other.a);
+ }
+
+ bool operator!=(const FloatColor& other) const {
+ return !(*this == other);
+ }
+
float r;
float g;
float b;
diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h
index e72f396..704bd69 100644
--- a/libs/hwui/Glop.h
+++ b/libs/hwui/Glop.h
@@ -81,8 +81,10 @@
* vertex/index/Texture/RoundRectClipState pointers prevent this from
* being safe.
*/
-// TODO: PREVENT_COPY_AND_ASSIGN(...) or similar
struct Glop {
+ PREVENT_COPY_AND_ASSIGN(Glop);
+public:
+ Glop() { }
struct Mesh {
GLuint primitiveMode; // GL_TRIANGLES and GL_TRIANGLE_STRIP supported
@@ -149,7 +151,7 @@
}
} transform;
- const RoundRectClipState* roundRectClipState;
+ const RoundRectClipState* roundRectClipState = nullptr;
/**
* Blending to be used by this draw - both GL_NONE if blending is disabled.
@@ -165,7 +167,7 @@
* Bounds of the drawing command in layer space. Only mapped into layer
* space once GlopBuilder::build() is called.
*/
- Rect bounds;
+ Rect bounds; // TODO: remove for HWUI_NEW_OPS
/**
* Additional render state to enumerate:
diff --git a/libs/hwui/tests/unit/GlopBuilderTests.cpp b/libs/hwui/tests/unit/GlopBuilderTests.cpp
new file mode 100644
index 0000000..949c541
--- /dev/null
+++ b/libs/hwui/tests/unit/GlopBuilderTests.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "BakedOpRenderer.h"
+#include "Glop.h"
+#include "GlopBuilder.h"
+#include "Rect.h"
+#include "tests/common/TestUtils.h"
+#include "utils/Color.h"
+
+#include <SkPaint.h>
+
+using namespace android::uirenderer;
+
+static void expectFillEq(Glop::Fill& expectedFill, Glop::Fill& builtFill) {
+ EXPECT_EQ(expectedFill.colorEnabled, builtFill.colorEnabled);
+ if (expectedFill.colorEnabled)
+ EXPECT_EQ(expectedFill.color, builtFill.color);
+
+ EXPECT_EQ(expectedFill.filterMode, builtFill.filterMode);
+ if (expectedFill.filterMode == ProgramDescription::ColorFilterMode::Blend) {
+ EXPECT_EQ(expectedFill.filter.color, builtFill.filter.color);
+ } else if (expectedFill.filterMode == ProgramDescription::ColorFilterMode::Matrix) {
+ Glop::Fill::Filter::Matrix& expectedMatrix = expectedFill.filter.matrix;
+ Glop::Fill::Filter::Matrix& builtMatrix = expectedFill.filter.matrix;
+ EXPECT_TRUE(std::memcmp(expectedMatrix.matrix, builtMatrix.matrix,
+ sizeof(Glop::Fill::Filter::Matrix::matrix)));
+ EXPECT_TRUE(std::memcmp(expectedMatrix.vector, builtMatrix.vector,
+ sizeof(Glop::Fill::Filter::Matrix::vector)));
+ }
+ EXPECT_EQ(expectedFill.skiaShaderData.skiaShaderType, builtFill.skiaShaderData.skiaShaderType);
+ EXPECT_EQ(expectedFill.texture.clamp, builtFill.texture.clamp);
+ EXPECT_EQ(expectedFill.texture.filter, builtFill.texture.filter);
+ EXPECT_EQ(expectedFill.texture.target, builtFill.texture.target);
+ EXPECT_EQ(expectedFill.texture.textureTransform, builtFill.texture.textureTransform);
+}
+
+static void expectBlendEq(Glop::Blend& expectedBlend, Glop::Blend& builtBlend) {
+ EXPECT_EQ(expectedBlend.src, builtBlend.src);
+ EXPECT_EQ(expectedBlend.dst, builtBlend.dst);
+}
+
+static void expectMeshEq(Glop::Mesh& expectedMesh, Glop::Mesh& builtMesh) {
+ EXPECT_EQ(expectedMesh.elementCount, builtMesh.elementCount);
+ EXPECT_EQ(expectedMesh.primitiveMode, builtMesh.primitiveMode);
+ EXPECT_EQ(expectedMesh.indices.indices, builtMesh.indices.indices);
+ EXPECT_EQ(expectedMesh.indices.bufferObject, builtMesh.indices.bufferObject);
+ EXPECT_EQ(expectedMesh.vertices.attribFlags, builtMesh.vertices.attribFlags);
+ EXPECT_EQ(expectedMesh.vertices.bufferObject, builtMesh.vertices.bufferObject);
+ EXPECT_EQ(expectedMesh.vertices.color, builtMesh.vertices.color);
+ EXPECT_EQ(expectedMesh.vertices.position, builtMesh.vertices.position);
+ EXPECT_EQ(expectedMesh.vertices.stride, builtMesh.vertices.stride);
+ EXPECT_EQ(expectedMesh.vertices.texCoord, builtMesh.vertices.texCoord);
+
+ if (builtMesh.vertices.position) {
+ for (int i = 0; i < 4; i++) {
+ TextureVertex& expectedVertex = expectedMesh.mappedVertices[i];
+ TextureVertex& builtVertex = builtMesh.mappedVertices[i];
+ EXPECT_EQ(expectedVertex.u, builtVertex.u);
+ EXPECT_EQ(expectedVertex.v, builtVertex.v);
+ EXPECT_EQ(expectedVertex.x, builtVertex.x);
+ EXPECT_EQ(expectedVertex.y, builtVertex.y);
+ }
+ }
+}
+
+static void expectTransformEq(Glop::Transform& expectedTransform, Glop::Transform& builtTransform) {
+ EXPECT_EQ(expectedTransform.canvas, builtTransform.canvas);
+ EXPECT_EQ(expectedTransform.modelView, builtTransform.modelView);
+ EXPECT_EQ(expectedTransform.transformFlags, expectedTransform.transformFlags);
+}
+
+static void expectGlopEq(Glop& expectedGlop, Glop& builtGlop) {
+ EXPECT_EQ(expectedGlop.bounds, builtGlop.bounds);
+ expectBlendEq(expectedGlop.blend, builtGlop.blend);
+ expectFillEq(expectedGlop.fill, builtGlop.fill);
+ expectMeshEq(expectedGlop.mesh, builtGlop.mesh);
+ expectTransformEq(expectedGlop.transform, builtGlop.transform);
+}
+
+static std::unique_ptr<Glop> blackUnitQuadGlop(RenderState& renderState) {
+ std::unique_ptr<Glop> glop(new Glop());
+ glop->blend = { GL_ZERO, GL_ZERO };
+ glop->mesh.elementCount = 4;
+ glop->mesh.primitiveMode = GL_TRIANGLE_STRIP;
+ glop->mesh.indices.indices = nullptr;
+ glop->mesh.indices.bufferObject = GL_ZERO;
+ glop->mesh.vertices = {
+ renderState.meshState().getUnitQuadVBO(),
+ VertexAttribFlags::None,
+ nullptr, nullptr, nullptr,
+ kTextureVertexStride };
+ glop->transform.modelView.loadIdentity();
+ glop->fill.colorEnabled = true;
+ glop->fill.color.set(Color::Black);
+ glop->fill.skiaShaderData.skiaShaderType = kNone_SkiaShaderType;
+ glop->fill.filterMode = ProgramDescription::ColorFilterMode::None;
+ glop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
+ return glop;
+}
+
+RENDERTHREAD_TEST(GlopBuilder, rectSnapTest) {
+ RenderState& renderState = renderThread.renderState();
+ Caches& caches = Caches::getInstance();
+ SkPaint paint;
+ Rect dest(1, 1, 100, 100);
+ Matrix4 simpleTranslate;
+ simpleTranslate.loadTranslate(0.7, 0.7, 0);
+ Glop glop;
+ GlopBuilder(renderState, caches, &glop)
+ .setRoundRectClipState(nullptr)
+ .setMeshUnitQuad()
+ .setFillPaint(paint, 1.0f)
+ .setTransform(simpleTranslate, TransformFlags::None)
+ .setModelViewMapUnitToRectSnap(dest)
+ .build();
+
+ std::unique_ptr<Glop> goldenGlop(blackUnitQuadGlop(renderState));
+ // Rect(1,1,100,100) is the set destination,
+ // so unit quad should be translated by (1,1) and scaled by (99, 99)
+ // Tricky part: because translate (0.7, 0.7) and snapping were set in glopBuilder,
+ // unit quad also should be translate by additional (0.3, 0.3) to snap to exact pixels.
+ goldenGlop->transform.modelView.loadTranslate(1.3, 1.3, 0);
+ goldenGlop->transform.modelView.scale(99, 99, 1);
+ goldenGlop->bounds = android::uirenderer::Rect(1.70, 1.70, 100.70, 100.70);
+ goldenGlop->transform.canvas = simpleTranslate;
+ goldenGlop->fill.texture.filter = GL_NEAREST;
+ expectGlopEq(*goldenGlop, glop);
+}
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index 55fb82b..56d3c99 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -17,7 +17,9 @@
package android.media.audiopolicy;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioSystem;
@@ -36,19 +38,28 @@
private int mRouteFlags;
private String mRegistrationId;
private int mMixType = MIX_TYPE_INVALID;
+
+ // written by AudioPolicy
int mMixState = MIX_STATE_DISABLED;
int mCallbackFlags;
+ // initialized in constructor, read by AudioPolicyConfig
+ final int mDeviceId;
+ final String mDeviceAddress;
+
/**
* All parameters are guaranteed valid through the Builder.
*/
- private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags, int callbackFlags) {
+ private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags, int callbackFlags,
+ int deviceId, String deviceAddress) {
mRule = rule;
mFormat = format;
mRouteFlags = routeFlags;
mRegistrationId = null;
mMixType = rule.getTargetMixType();
mCallbackFlags = callbackFlags;
+ mDeviceId = deviceId;
+ mDeviceAddress = deviceAddress;
}
// CALLBACK_FLAG_* values: keep in sync with AudioMix::kCbFlag* values defined
@@ -74,6 +85,8 @@
@SystemApi
public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1;
+ private static final int ROUTE_FLAG_SUPPORTED = ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK;
+
// MIX_TYPE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h
/**
* @hide
@@ -172,6 +185,8 @@
private AudioFormat mFormat = null;
private int mRouteFlags = 0;
private int mCallbackFlags = 0;
+ private int mDeviceId = -1;
+ private String mDeviceAddress = null;
/**
* @hide
@@ -200,7 +215,7 @@
* @return the same Builder instance.
* @throws IllegalArgumentException
*/
- public Builder setMixingRule(AudioMixingRule rule)
+ Builder setMixingRule(AudioMixingRule rule)
throws IllegalArgumentException {
if (rule == null) {
throw new IllegalArgumentException("Illegal null AudioMixingRule argument");
@@ -216,7 +231,7 @@
* @return the same Builder instance.
* @throws IllegalArgumentException
*/
- public Builder setCallbackFlags(int flags) throws IllegalArgumentException {
+ Builder setCallbackFlags(int flags) throws IllegalArgumentException {
if ((flags != 0) && ((flags & CALLBACK_FLAGS_ALL) == 0)) {
throw new IllegalArgumentException("Illegal callback flags 0x"
+ Integer.toHexString(flags).toUpperCase());
@@ -226,6 +241,19 @@
}
/**
+ * @hide
+ * Only used by AudioPolicyConfig, not a public API.
+ * @param deviceId
+ * @param address
+ * @return the same Builder instance.
+ */
+ Builder setDevice(int deviceId, String address) {
+ mDeviceId = deviceId;
+ mDeviceAddress = address;
+ return this;
+ }
+
+ /**
* Sets the {@link AudioFormat} for the mix.
* @param format a non-null {@link AudioFormat} instance.
* @return the same Builder instance.
@@ -242,7 +270,8 @@
}
/**
- * Sets the routing behavior for the mix.
+ * Sets the routing behavior for the mix. If not set, routing behavior will default to
+ * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}.
* @param routeFlags one of {@link AudioMix#ROUTE_FLAG_LOOP_BACK},
* {@link AudioMix#ROUTE_FLAG_RENDER}
* @return the same Builder instance.
@@ -254,15 +283,41 @@
if (routeFlags == 0) {
throw new IllegalArgumentException("Illegal empty route flags");
}
- if ((routeFlags & (ROUTE_FLAG_LOOP_BACK | ROUTE_FLAG_RENDER)) == 0) {
+ if ((routeFlags & ROUTE_FLAG_SUPPORTED) == 0) {
throw new IllegalArgumentException("Invalid route flags 0x"
- + Integer.toHexString(routeFlags) + "when creating an AudioMix");
+ + Integer.toHexString(routeFlags) + "when configuring an AudioMix");
+ }
+ if ((routeFlags & ~ROUTE_FLAG_SUPPORTED) != 0) {
+ throw new IllegalArgumentException("Unknown route flags 0x"
+ + Integer.toHexString(routeFlags) + "when configuring an AudioMix");
}
mRouteFlags = routeFlags;
return this;
}
/**
+ * Sets the audio device used for playback. Cannot be used in the context of an audio
+ * policy used to inject audio to be recorded, or in a mix whose route flags doesn't
+ * specify {@link AudioMix#ROUTE_FLAG_RENDER}.
+ * @param device a non-null AudioDeviceInfo describing the audio device to play the output
+ * of this mix.
+ * @return the same Builder instance
+ * @throws IllegalArgumentException
+ */
+ @SystemApi
+ public Builder setDevice(@NonNull AudioDeviceInfo device) throws IllegalArgumentException {
+ if (device == null) {
+ throw new IllegalArgumentException("Illegal null AudioDeviceInfo argument");
+ }
+ if (!device.isSink()) {
+ throw new IllegalArgumentException("Unsupported device type on mix, not a sink");
+ }
+ mDeviceId = device.getId();
+ mDeviceAddress = device.getAddress();
+ return this;
+ }
+
+ /**
* Combines all of the settings and return a new {@link AudioMix} object.
* @return a new {@link AudioMix} object
* @throws IllegalArgumentException if no {@link AudioMixingRule} has been set.
@@ -273,8 +328,13 @@
throw new IllegalArgumentException("Illegal null AudioMixingRule");
}
if (mRouteFlags == 0) {
- // no route flags set, use default
- mRouteFlags = ROUTE_FLAG_RENDER;
+ // no route flags set, use default as described in Builder.setRouteFlags(int)
+ mRouteFlags = ROUTE_FLAG_LOOP_BACK;
+ }
+ // can't do loop back AND render at same time in this implementation
+ if (mRouteFlags == (ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK)) {
+ throw new IllegalArgumentException("Unsupported route behavior combination 0x" +
+ Integer.toHexString(mRouteFlags));
}
if (mFormat == null) {
// FIXME Can we eliminate this? Will AudioMix work with an unspecified sample rate?
@@ -284,7 +344,22 @@
}
mFormat = new AudioFormat.Builder().setSampleRate(rate).build();
}
- return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags);
+ if (mDeviceId != -1) {
+ if ((mRouteFlags & ROUTE_FLAG_RENDER) == 0) {
+ throw new IllegalArgumentException(
+ "Can't have audio device without flag ROUTE_FLAG_RENDER");
+ }
+ if (mRule.getTargetMixType() != AudioMix.MIX_TYPE_PLAYERS) {
+ throw new IllegalArgumentException("Unsupported device on non-playback mix");
+ }
+ } else {
+ if ((mRouteFlags & ROUTE_FLAG_RENDER) == ROUTE_FLAG_RENDER) {
+ throw new IllegalArgumentException(
+ "Can't have flag ROUTE_FLAG_RENDER without an audio device");
+ }
+ }
+ return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceId,
+ mDeviceAddress);
}
}
}
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index 5d2bac0..3af3ae7 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -17,6 +17,8 @@
package android.media.audiopolicy;
import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioPatch;
import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
import android.os.Parcel;
import android.os.Parcelable;
@@ -81,6 +83,9 @@
dest.writeInt(mix.getRouteFlags());
// write callback flags
dest.writeInt(mix.mCallbackFlags);
+ // write device information
+ dest.writeInt(mix.mDeviceId);
+ dest.writeString(mix.mDeviceAddress);
// write mix format
dest.writeInt(mix.getFormat().getSampleRate());
dest.writeInt(mix.getFormat().getEncoding());
@@ -104,6 +109,8 @@
mixBuilder.setRouteFlags(routeFlags);
// read callback flags
mixBuilder.setCallbackFlags(in.readInt());
+ // read device information
+ mixBuilder.setDevice(in.readInt(), in.readString());
// read mix format
int sampleRate = in.readInt();
int encoding = in.readInt();
@@ -197,8 +204,14 @@
int mixIndex = 0;
for (AudioMix mix : mMixes) {
if (!mRegistrationId.isEmpty()) {
- mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":"
- + mixIndex++);
+ if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) ==
+ AudioMix.ROUTE_FLAG_LOOP_BACK) {
+ mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":"
+ + mixIndex++);
+ } else if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_RENDER) ==
+ AudioMix.ROUTE_FLAG_RENDER) {
+ mix.setRegistration(mix.mDeviceAddress);
+ }
} else {
mix.setRegistration("");
}
diff --git a/packages/DocumentsUI/res/animator-ldrtl/dir_enter.xml b/packages/DocumentsUI/res/animator-ldrtl/dir_enter.xml
deleted file mode 100644
index 6c7e224..0000000
--- a/packages/DocumentsUI/res/animator-ldrtl/dir_enter.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- Copyright (C) 2013 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.
--->
-
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:valueFrom="-1"
- android:valueTo="0"
- android:propertyName="position"
- android:valueType="floatType"
- android:duration="@android:integer/config_mediumAnimTime"
- android:interpolator="@android:interpolator/decelerate_quad" />
diff --git a/packages/DocumentsUI/res/animator-ldrtl/dir_leave.xml b/packages/DocumentsUI/res/animator-ldrtl/dir_leave.xml
deleted file mode 100644
index 8e2925c..0000000
--- a/packages/DocumentsUI/res/animator-ldrtl/dir_leave.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- Copyright (C) 2013 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.
--->
-
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:valueFrom="0"
- android:valueTo="-1"
- android:propertyName="position"
- android:valueType="floatType"
- android:duration="@android:integer/config_mediumAnimTime"
- android:interpolator="@android:interpolator/accelerate_quad" />
diff --git a/packages/DocumentsUI/res/animator/dir_frozen.xml b/packages/DocumentsUI/res/animator/dir_frozen.xml
deleted file mode 100644
index b541d13..0000000
--- a/packages/DocumentsUI/res/animator/dir_frozen.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<!-- Copyright (C) 2013 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.
--->
-
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:valueFrom="0"
- android:valueTo="0"
- android:propertyName="position"
- android:valueType="floatType"
- android:duration="@android:integer/config_mediumAnimTime" />
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index 0af8aa2..99f306a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -283,10 +283,16 @@
*/
private void openDocument(DocumentInfo doc, Model model) {
- // Provide specialized handling of downloaded APKs This sends the APK
- // details off to get extra security information added, and finally
- // to be handled by the package manager.
- if (MimePredicate.isApkType(doc.mimeType)) {
+ // Anything on downloads goes through the back through downloads manager
+ // (that's the MANAGE_DOCUMENT bit).
+ // This is done for two reasons:
+ // 1) The file in question might be a failed/queued or otherwise have some
+ // specialized download handling.
+ // 2) For APKs, the download manager will add on some important security stuff
+ // like origin URL.
+ // All other files not on downloads, event APKs, would get no benefit from this
+ // treatment, thusly the "isDownloads" check.
+ if (getCurrentRoot().isDownloads()) {
// First try managing the document; we expect manager to filter
// based on authority, so we don't grant.
final Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/AnimationView.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/AnimationView.java
index 4f076f1..a666456 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/AnimationView.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/AnimationView.java
@@ -77,6 +77,8 @@
public void setPosition(float position) {
mPosition = position;
+ // Warning! If we ever decide to switch this to setX (slide left/right)
+ // please remember to add RLT variations of the animations under res/animator-ldrtl.
setY((mSpan > 0) ? (mPosition * mSpan) : 0);
if (mPosition != 0) {
diff --git a/packages/DocumentsUI/tests/AndroidManifest.xml b/packages/DocumentsUI/tests/AndroidManifest.xml
index a312427..b986285 100644
--- a/packages/DocumentsUI/tests/AndroidManifest.xml
+++ b/packages/DocumentsUI/tests/AndroidManifest.xml
@@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.documentsui.tests">
+ <uses-permission android:name="android.permission.INTERNET" />
+
<application>
<uses-library android:name="android.test.runner" />
<provider
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 056e6ed..59dc232 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -19,10 +19,17 @@
import static com.android.documentsui.StubProvider.ROOT_0_ID;
import static com.android.documentsui.StubProvider.ROOT_1_ID;
+import android.app.DownloadManager;
+import android.app.DownloadManager.Request;
+import android.content.Context;
+import android.net.Uri;
import android.os.RemoteException;
+import android.support.test.uiautomator.Configurator;
+import android.support.test.uiautomator.UiObject;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.Suppress;
import android.view.KeyEvent;
+import android.view.MotionEvent;
import com.android.documentsui.model.RootInfo;
@@ -173,4 +180,37 @@
bots.roots.assertHasFocus();
}
}
+
+ // We don't really need to test the entirety of download support
+ // since downloads is (almost) just another provider.
+ public void testDownload_Queued() throws Exception {
+ DownloadManager dm = (DownloadManager) context.getSystemService(
+ Context.DOWNLOAD_SERVICE);
+ // This downloads ends up being queued (because DNS can't be resolved).
+ // We'll still see an entry in the downloads UI with a "Queued" label.
+ dm.enqueue(new Request(Uri.parse("http://hammychamp.toodles")));
+
+ bots.roots.openRoot("Downloads");
+ bots.directory.assertDocumentsPresent("Queued");
+ }
+
+ public void testDownload_RetryUnsuccessful() throws Exception {
+ DownloadManager dm = (DownloadManager) context.getSystemService(
+ Context.DOWNLOAD_SERVICE);
+ // This downloads fails! But it'll still show up.
+ dm.enqueue(new Request(Uri.parse("http://www.google.com/hamfancy")));
+
+ bots.roots.openRoot("Downloads");
+ UiObject doc = bots.directory.findDocument("Unsuccessful");
+ doc.waitForExists(TIMEOUT);
+
+ int toolType = Configurator.getInstance().getToolType();
+ Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER);
+ doc.click();
+ Configurator.getInstance().setToolType(toolType);
+
+ assertTrue(bots.main.findDownloadRetryDialog().exists());
+
+ device.pressBack(); // to clear the dialog.
+ }
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/bots/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/UiBot.java
index 11f5194..5b53caf 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/bots/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/UiBot.java
@@ -173,6 +173,13 @@
return findObject("android:id/content", "android:id/text1");
}
+ public UiObject findDownloadRetryDialog() {
+ UiSelector selector = new UiSelector().text("Couldn't download");
+ UiObject title = mDevice.findObject(selector);
+ title.waitForExists(mTimeout);
+ return title;
+ }
+
public UiObject findDialogOkButton() {
UiObject object = findObject("android:id/content", "android:id/button1");
object.waitForExists(mTimeout);
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index d212d53..10c5416 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -620,7 +620,11 @@
* upon receiving a {@link #INTENT_BUGREPORT_STARTED}.
*/
private void takeScreenshot(int id, boolean delayed) {
- MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SCREENSHOT);
+ if (delayed) {
+ // Only logs screenshots requested from the notification action.
+ MetricsLogger.action(this,
+ MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SCREENSHOT);
+ }
if (getInfo(id) == null) {
// Most likely am killed Shell before user tapped the notification. Since system might
// be too busy anwyays, it's better to ignore the notification and switch back to the
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index 051921a..358674c 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -78,6 +78,7 @@
private float mOldHeight;
private float mNaturalHeight;
private float mInitialTouchFocusY;
+ private float mInitialTouchX;
private float mInitialTouchY;
private float mInitialTouchSpan;
private float mLastFocusY;
@@ -291,7 +292,8 @@
}
if (mWatchingForPull) {
final float yDiff = ev.getRawY() - mInitialTouchY;
- if (yDiff > mTouchSlop) {
+ final float xDiff = ev.getRawX() - mInitialTouchX;
+ if (yDiff > mTouchSlop && yDiff > Math.abs(xDiff)) {
if (DEBUG) Log.v(TAG, "got venetian gesture (dy=" + yDiff + "px)");
mWatchingForPull = false;
if (mResizedView != null && !isFullyExpanded(mResizedView)) {
@@ -316,6 +318,7 @@
mWatchingForPull = false;
}
mInitialTouchY = ev.getY();
+ mInitialTouchX = ev.getX();
break;
case MotionEvent.ACTION_CANCEL:
@@ -409,12 +412,14 @@
mWatchingForPull = mScrollAdapter != null &&
isInside(mScrollAdapter.getHostView(), x, y);
mResizedView = findView(x, y);
+ mInitialTouchX = ev.getX();
mInitialTouchY = ev.getY();
break;
case MotionEvent.ACTION_MOVE: {
if (mWatchingForPull) {
final float yDiff = ev.getRawY() - mInitialTouchY;
- if (yDiff > mTouchSlop) {
+ final float xDiff = ev.getRawX() - mInitialTouchX;
+ if (yDiff > mTouchSlop && yDiff > Math.abs(xDiff)) {
if (DEBUG) Log.v(TAG, "got venetian gesture (dy=" + yDiff + "px)");
mWatchingForPull = false;
if (mResizedView != null && !isFullyExpanded(mResizedView)) {
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 81ba23f..1fe218a 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -517,16 +517,35 @@
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
- if (mCurrView == null) {
- break;
- }
- mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity());
- float velocity = getVelocity(mVelocityTracker);
+ if (mCurrView != null) {
+ float maxVelocity = MAX_DISMISS_VELOCITY * mDensityScale;
+ mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, maxVelocity);
+ float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale;
+ float velocity = getVelocity(mVelocityTracker);
+ float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker);
- if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) {
- if (isDismissGesture(ev)) {
+ float translation = getTranslation(mCurrView);
+ // Decide whether to dismiss the current view
+ boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH &&
+ Math.abs(translation) > 0.4 * getSize(mCurrView);
+ boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) &&
+ (Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
+ (velocity > 0) == (translation > 0);
+ boolean falsingDetected = mCallback.isAntiFalsingNeeded();
+
+ if (mFalsingManager.isClassiferEnabled()) {
+ falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
+ } else {
+ falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold;
+ }
+
+ boolean dismissChild = mCallback.canChildBeDismissed(mCurrView)
+ && !falsingDetected && (childSwipedFastEnough || childSwipedFarEnough)
+ && ev.getActionMasked() == MotionEvent.ACTION_UP;
+
+ if (dismissChild) {
// flingadingy
- dismissChild(mCurrView, swipedFastEnough() ? velocity : 0f);
+ dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f);
} else {
// snappity
mCallback.onDragCancelled(mCurrView);
@@ -543,46 +562,6 @@
return (int) (mFalsingThreshold * factor);
}
- private float getMaxVelocity() {
- return MAX_DISMISS_VELOCITY * mDensityScale;
- }
-
- protected float getEscapeVelocity() {
- return SWIPE_ESCAPE_VELOCITY * mDensityScale;
- }
-
- protected boolean swipedFarEnough() {
- float translation = getTranslation(mCurrView);
- return DISMISS_IF_SWIPED_FAR_ENOUGH && Math.abs(translation) > 0.4 * getSize(mCurrView);
- }
-
- protected boolean isDismissGesture(MotionEvent ev) {
- boolean falsingDetected = mCallback.isAntiFalsingNeeded();
- if (mFalsingManager.isClassiferEnabled()) {
- falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
- } else {
- falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold;
- }
- return !falsingDetected && (swipedFastEnough() || swipedFarEnough())
- && ev.getActionMasked() == MotionEvent.ACTION_UP
- && mCallback.canChildBeDismissed(mCurrView);
- }
-
- protected boolean swipedFastEnough() {
- float velocity = getVelocity(mVelocityTracker);
- float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker);
- float translation = getTranslation(mCurrView);
- boolean ret = (Math.abs(velocity) > getEscapeVelocity()) &&
- (Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
- (velocity > 0) == (translation > 0);
- return ret;
- }
-
- protected boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
- float translation) {
- return false;
- }
-
public interface Callback {
View getChildAtPosition(MotionEvent ev);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 73ce26f..2b6ed44 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -45,6 +45,7 @@
import com.android.systemui.RecentsComponent;
import com.android.systemui.SystemUI;
import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
@@ -619,6 +620,12 @@
}
}
+ public final void onBusEvent(ConfigurationChangedEvent event) {
+ // Update the configuration for the Recents component when the activity configuration
+ // changes as well
+ mImpl.onConfigurationChanged();
+ }
+
/**
* Attempts to register with the system user.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 4e11bca..d864df8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -759,6 +759,7 @@
TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
TaskStackViewScroller stackScroller = stackView.getScroller();
+ stackView.updateLayoutAlgorithm(true /* boundScroll */);
stackView.updateToInitialState();
for (int i = tasks.size() - 1; i >= 0; i--) {
@@ -825,6 +826,7 @@
}
// Get the transform for the running task
+ stackView.updateLayoutAlgorithm(true /* boundScroll */);
stackView.updateToInitialState();
mTmpTransform = stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
stackView.getScroller().getStackScroll(), mTmpTransform, null);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index e2830a1..6c410c3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -813,7 +813,7 @@
*
* @see #updateLayoutAlgorithm(boolean, ArraySet<Task.TaskKey>)
*/
- void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) {
+ public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) {
updateLayoutAlgorithm(boundScrollToNewMinMax, mIgnoreTasks);
}
@@ -822,7 +822,7 @@
*
* @param ignoreTasksSet the set of tasks to ignore in the relayout
*/
- void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
+ public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
ArraySet<Task.TaskKey> ignoreTasksSet) {
// Compute the min and max scroll values
mLayoutAlgorithm.update(mStack, ignoreTasksSet);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 51553be..93cb952 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -241,18 +241,22 @@
}
private void updateLimits() {
- boolean customView = getPrivateLayout().getContractedChild().getId()
+ updateLimitsForView(mPrivateLayout);
+ updateLimitsForView(mPublicLayout);
+ }
+
+ private void updateLimitsForView(NotificationContentView layout) {
+ boolean customView = layout.getContractedChild().getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
int minHeight = customView && beforeN && !mIsSummaryWithChildren ?
mNotificationMinHeightLegacy : mNotificationMinHeight;
- boolean headsUpCustom = getPrivateLayout().getHeadsUpChild() != null &&
- getPrivateLayout().getHeadsUpChild().getId()
- != com.android.internal.R.id.status_bar_latest_event_content;
+ boolean headsUpCustom = layout.getHeadsUpChild() != null &&
+ layout.getHeadsUpChild().getId()
+ != com.android.internal.R.id.status_bar_latest_event_content;
int headsUpheight = headsUpCustom && beforeN ? mMaxHeadsUpHeightLegacy
: mMaxHeadsUpHeight;
- mPrivateLayout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight);
- mPublicLayout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight);
+ layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight);
}
public StatusBarNotification getStatusBarNotification() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
index fcc48bf..375459f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
@@ -29,18 +29,11 @@
public class NotificationSettingsIconRow extends FrameLayout implements View.OnClickListener {
- private static final int GEAR_ALPHA_ANIM_DURATION = 200;
-
public interface SettingsIconRowListener {
/**
* Called when the gear behind a notification is touched.
*/
public void onGearTouched(ExpandableNotificationRow row, int x, int y);
-
- /**
- * Called when a notification is slid back over the gear.
- */
- public void onSettingsIconRowReset(NotificationSettingsIconRow row);
}
private ExpandableNotificationRow mParent;
@@ -52,8 +45,6 @@
private boolean mSettingsFadedIn = false;
private boolean mAnimating = false;
private boolean mOnLeft = true;
- private boolean mDismissing = false;
- private boolean mSnapping = false;
private int[] mGearLocation = new int[2];
private int[] mParentLocation = new int[2];
@@ -87,14 +78,8 @@
public void resetState() {
setGearAlpha(0f);
- mSettingsFadedIn = false;
mAnimating = false;
- mSnapping = false;
- mDismissing = false;
setIconLocation(true /* on left */);
- if (mListener != null) {
- mListener.onSettingsIconRowReset(this);
- }
}
public void setGearListener(SettingsIconRowListener listener) {
@@ -109,24 +94,20 @@
return mParent;
}
- public void setGearAlpha(float alpha) {
+ private void setGearAlpha(float alpha) {
if (alpha == 0) {
mSettingsFadedIn = false; // Can fade in again once it's gone.
setVisibility(View.INVISIBLE);
} else {
+ if (alpha == 1) {
+ mSettingsFadedIn = true;
+ }
setVisibility(View.VISIBLE);
}
mGearIcon.setAlpha(alpha);
}
/**
- * Returns whether the icon is on the left side of the view or not.
- */
- public boolean isIconOnLeft() {
- return mOnLeft;
- }
-
- /**
* Returns the horizontal space in pixels required to display the gear behind a notification.
*/
public float getSpaceForGear() {
@@ -138,7 +119,7 @@
* if entire view is visible.
*/
public boolean isVisible() {
- return mGearIcon.getAlpha() > 0;
+ return mSettingsFadedIn;
}
public void cancelFadeAnimator() {
@@ -148,18 +129,16 @@
}
public void updateSettingsIcons(final float transX, final float size) {
- if (mAnimating || !mSettingsFadedIn) {
- // Don't adjust when animating, or if the gear hasn't been shown yet.
+ if (mAnimating || (mGearIcon.getAlpha() == 0)) {
+ // Don't adjust when animating or settings aren't visible
return;
}
-
+ setIconLocation(transX > 0 /* fromLeft */);
final float fadeThreshold = size * 0.3f;
final float absTrans = Math.abs(transX);
float desiredAlpha = 0;
- if (absTrans == 0) {
- desiredAlpha = 0;
- } else if (absTrans <= fadeThreshold) {
+ if (absTrans <= fadeThreshold) {
desiredAlpha = 1;
} else {
desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold));
@@ -169,12 +148,6 @@
public void fadeInSettings(final boolean fromLeft, final float transX,
final float notiThreshold) {
- if (mDismissing || mAnimating) {
- return;
- }
- if (isIconLocationChange(transX)) {
- setGearAlpha(0f);
- }
setIconLocation(transX > 0 /* fromLeft */);
mFadeAnimator = ValueAnimator.ofFloat(mGearIcon.getAlpha(), 1);
mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@@ -191,53 +164,40 @@
});
mFadeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
+ public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
+ mAnimating = false;
+ mSettingsFadedIn = false;
+ }
+
+ @Override
public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
mAnimating = true;
}
@Override
- public void onAnimationCancel(Animator animation) {
- // TODO should animate back to 0f from current alpha
- mGearIcon.setAlpha(0f);
- }
-
- @Override
public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
mAnimating = false;
- mSettingsFadedIn = mGearIcon.getAlpha() == 1;
+ mSettingsFadedIn = true;
}
});
mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
- mFadeAnimator.setDuration(GEAR_ALPHA_ANIM_DURATION);
+ mFadeAnimator.setDuration(200);
mFadeAnimator.start();
}
- public void setIconLocation(boolean onLeft) {
- if (onLeft == mOnLeft || mSnapping) {
+ private void setIconLocation(boolean onLeft) {
+ if (onLeft == mOnLeft) {
// Same side? Do nothing.
return;
}
+
setTranslationX(onLeft ? 0 : (mParent.getWidth() - mHorizSpaceForGear));
mOnLeft = onLeft;
}
- public boolean isIconLocationChange(float translation) {
- boolean onLeft = translation > mGearIcon.getPaddingStart();
- boolean onRight = translation < -mGearIcon.getPaddingStart();
- if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
- return true;
- }
- return false;
- }
-
- public void setDismissing() {
- mDismissing = true;
- }
-
- public void setSnapping(boolean snapping) {
- mSnapping = snapping;
- }
-
@Override
public void onClick(View v) {
if (v.getId() == R.id.gear_icon) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index 66f945e..f75f3574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -188,6 +188,9 @@
@Override
public void setVisible(boolean visible) {
+ if (mViewTransformationAnimation != null) {
+ mViewTransformationAnimation.cancel();
+ }
for (Integer viewType : mTransformedViews.keySet()) {
TransformState ownState = getCurrentState(viewType);
if (ownState != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index cedc3c7..ab44b6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -315,8 +315,8 @@
public void logoutCurrentUser() {
int currentUser = ActivityManager.getCurrentUser();
if (currentUser != UserHandle.USER_SYSTEM) {
- switchToUserId(UserHandle.USER_SYSTEM);
- stopUserId(currentUser);
+ pauseRefreshUsers();
+ ActivityManager.logoutCurrentUser();
}
}
@@ -384,14 +384,6 @@
}
}
- private void stopUserId(int id) {
- try {
- ActivityManagerNative.getDefault().stopUser(id, /* force= */ false, null);
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't stop user.", e);
- }
- }
-
private void showExitGuestDialog(int id) {
if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
mExitGuestDialog.cancel();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 4cb0dea..eaa0c98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -372,11 +372,6 @@
}
@Override
- public void onSettingsIconRowReset(NotificationSettingsIconRow row) {
- mSwipeHelper.setSnappedToGear(false);
- }
-
- @Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, mBackgroundPaint);
if (DEBUG) {
@@ -722,15 +717,11 @@
mDragAnimPendingChildren.remove(animView);
}
- if (mCurrIconRow != null) {
- if (targetLeft == 0) {
- mCurrIconRow.resetState();
- mCurrIconRow = null;
- if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
- mGearExposedView = null;
- }
- } else {
- mSwipeHelper.setSnappedToGear(true);
+ if (targetLeft == 0 && mCurrIconRow != null) {
+ mCurrIconRow.resetState();
+ mCurrIconRow = null;
+ if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
+ mGearExposedView = null;
}
}
}
@@ -2739,13 +2730,13 @@
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
if (row.isUserLocked() && row != getFirstChildNotGone()) {
+ if (row.isSummaryWithChildren()) {
+ return;
+ }
// We are actually expanding this view
- float endPosition;
+ float endPosition = row.getTranslationY() + row.getActualHeight();
if (row.isChildInGroup()) {
- ExpandableNotificationRow parent = row.getNotificationParent();
- endPosition = parent.getTranslationY() + parent.getActualHeight();
- } else {
- endPosition = row.getTranslationY() + row.getActualHeight();
+ endPosition += row.getNotificationParent().getTranslationY();
}
int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize -
mBottomStackSlowDownHeight + (int) mStackTranslation;
@@ -3388,11 +3379,15 @@
}
private class NotificationSwipeHelper extends SwipeHelper {
+ private static final int MOVE_STATE_LEFT = -1;
+ private static final int MOVE_STATE_UNDEFINED = 0;
+ private static final int MOVE_STATE_RIGHT = 1;
+
private static final long GEAR_SHOW_DELAY = 60;
+
private CheckForDrag mCheckForDrag;
private Handler mHandler;
- private boolean mGearSnappedTo;
- private boolean mGearSnappedOnLeft;
+ private int mMoveState = MOVE_STATE_UNDEFINED;
public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
super(swipeDirection, callback, context);
@@ -3405,10 +3400,6 @@
mTranslatingParentView = currView;
// Reset check for drag gesture
- cancelCheckForDrag();
- if (mCurrIconRow != null) {
- mCurrIconRow.setSnapping(false);
- }
mCheckForDrag = null;
mCurrIconRow = null;
@@ -3420,32 +3411,17 @@
mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
mCurrIconRow.setGearListener(NotificationStackScrollLayout.this);
}
+ mMoveState = MOVE_STATE_UNDEFINED;
}
@Override
public void onMoveUpdate(View view, float translation, float delta) {
- if (mCurrIconRow != null) {
- mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.
-
- // If the gear is visible and the movement is towards it it's not a location change.
- boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft();
- boolean locationChange = isTowardsGear(translation, onLeft)
- ? false : mCurrIconRow.isIconLocationChange(translation);
- if (locationChange) {
- // Don't consider it "snapped" if location has changed.
- setSnappedToGear(false);
-
- // Changed directions, make sure we check to fade in icon again.
- if (!mHandler.hasCallbacks(mCheckForDrag)) {
- // No check scheduled, set null to schedule a new one.
- mCheckForDrag = null;
- } else {
- // Check scheduled, reset alpha and update location; check will fade it in
- mCurrIconRow.setGearAlpha(0f);
- mCurrIconRow.setIconLocation(translation > 0 /* onLeft */);
- }
- }
+ final int newMoveState = (delta < 0) ? MOVE_STATE_RIGHT : MOVE_STATE_LEFT;
+ if (mMoveState != MOVE_STATE_UNDEFINED && mMoveState != newMoveState) {
+ // Changed directions, make sure we check for drag again.
+ mCheckForDrag = null;
}
+ mMoveState = newMoveState;
final boolean gutsExposed = (view instanceof ExpandableNotificationRow)
&& ((ExpandableNotificationRow) view).areGutsExposed();
@@ -3458,99 +3434,35 @@
@Override
public void dismissChild(final View view, float velocity) {
- super.dismissChild(view, velocity);
cancelCheckForDrag();
- setSnappedToGear(false);
+ super.dismissChild(view, velocity);
}
@Override
public void snapChild(final View animView, final float targetLeft, float velocity) {
- super.snapChild(animView, targetLeft, velocity);
- if (targetLeft == 0) {
- cancelCheckForDrag();
- setSnappedToGear(false);
- }
- }
-
-
- @Override
- public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
- float translation) {
- if (mCurrIconRow == null) {
- cancelCheckForDrag();
- return false; // Let SwipeHelper handle it.
- }
-
- boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft());
- boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();
-
- if (mGearSnappedTo && mCurrIconRow.isVisible()) {
- if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) {
- boolean coveringGear =
- Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f;
- if (gestureTowardsGear || coveringGear) {
- // Gesture is towards or covering the gear
- snapChild(animView, 0 /* leftTarget */, velocity);
- } else if (isDismissGesture(ev)) {
- // Gesture is a dismiss that's not towards the gear
- dismissChild(animView, swipedFastEnough() ? velocity : 0f);
- } else {
- // Didn't move enough to dismiss or cover, snap to the gear
- snapToGear(animView, velocity);
- }
- } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
- || (gestureTowardsGear && !swipedFarEnough())) {
- // The gear has been snapped to previously, however, the gear is now on the
- // other side. If gesture is towards gear and not too far snap to the gear.
- snapToGear(animView, velocity);
- } else {
- dismissOrSnapBack(animView, velocity, ev);
- }
- } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
- || gestureTowardsGear) {
- // Gear has not been snapped to previously and this is gear revealing gesture
- snapToGear(animView, velocity);
- } else {
- dismissOrSnapBack(animView, velocity, ev);
- }
- return true;
- }
-
- private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) {
- if (isDismissGesture(ev)) {
- dismissChild(animView, swipedFastEnough() ? velocity : 0f);
- } else {
- snapChild(animView, 0 /* leftTarget */, velocity);
- }
- }
-
- private void snapToGear(View animView, float velocity) {
- final float snapBackThreshold = getSpaceForGear(animView);
- final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold
- : -snapBackThreshold;
- mGearExposedView = mTranslatingParentView;
- if (mGearDisplayedListener != null
- && (animView instanceof ExpandableNotificationRow)) {
- mGearDisplayedListener.onGearDisplayed((ExpandableNotificationRow) animView);
- }
- if (mCurrIconRow != null) {
- mCurrIconRow.setSnapping(true);
- setSnappedToGear(true);
- }
- super.snapChild(animView, target, velocity);
- }
-
- private boolean swipedEnoughToShowGear(View animView) {
final float snapBackThreshold = getSpaceForGear(animView);
final float translation = getTranslation(animView);
final boolean fromLeft = translation > 0;
final float absTrans = Math.abs(translation);
final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
- // If the notification can't be dismissed then how far it can move is
- // restricted -- reduce the distance it needs to move in this case.
- final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
- return absTrans >= snapBackThreshold * 0.4f && absTrans <= notiThreshold;
+ boolean pastGear = (fromLeft && translation >= snapBackThreshold * 0.4f
+ && translation <= notiThreshold) ||
+ (!fromLeft && absTrans >= snapBackThreshold * 0.4f
+ && absTrans <= notiThreshold);
+
+ if (pastGear && !isPinnedHeadsUp(animView)
+ && (animView instanceof ExpandableNotificationRow)) {
+ // bouncity
+ final float target = fromLeft ? snapBackThreshold : -snapBackThreshold;
+ mGearExposedView = mTranslatingParentView;
+ if (mGearDisplayedListener != null) {
+ mGearDisplayedListener.onGearDisplayed((ExpandableNotificationRow) animView);
+ }
+ super.snapChild(animView, target, velocity);
+ } else {
+ super.snapChild(animView, 0, velocity);
+ }
}
@Override
@@ -3587,25 +3499,6 @@
}
/**
- * Returns whether the gesture is towards the gear location or not.
- */
- private boolean isTowardsGear(float velocity, boolean onLeft) {
- if (mCurrIconRow == null) {
- return false;
- }
- return mCurrIconRow.isVisible()
- && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0));
- }
-
- /**
- * Indicates the the gear has been snapped to.
- */
- private void setSnappedToGear(boolean snapped) {
- mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false;
- mGearSnappedTo = snapped && mCurrIconRow != null;
- }
-
- /**
* Returns the horizontal space in pixels required to display the gear behind a
* notification.
*/
@@ -3617,7 +3510,7 @@
}
private void checkForDrag() {
- if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) {
+ if (mCheckForDrag == null) {
mCheckForDrag = new CheckForDrag();
mHandler.postDelayed(mCheckForDrag, GEAR_SHOW_DELAY);
}
@@ -3628,6 +3521,7 @@
mCurrIconRow.cancelFadeAnimator();
}
mHandler.removeCallbacks(mCheckForDrag);
+ mCheckForDrag = null;
}
private final class CheckForDrag implements Runnable {
@@ -3637,13 +3531,14 @@
final float absTransX = Math.abs(translation);
final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
- if ((mCurrIconRow != null && (!mCurrIconRow.isVisible()
- || mCurrIconRow.isIconLocationChange(translation)))
- && absTransX >= bounceBackToGearWidth * 0.4
+ if (mCurrIconRow != null && absTransX >= bounceBackToGearWidth * 0.4
&& absTransX < notiThreshold) {
- // Fade in the gear
+ // Show icon
mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation,
notiThreshold);
+ } else {
+ // Allow more to be posted if this wasn't a drag.
+ mCheckForDrag = null;
}
}
}
@@ -3656,7 +3551,7 @@
final View prevGearExposedView = mGearExposedView;
mGearExposedView = null;
- mGearSnappedTo = false;
+
Animator anim = getViewTranslationAnimator(prevGearExposedView,
0 /* leftTarget */, null /* updateListener */);
if (anim != null) {
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 32f2d59..bfe9e8e 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -25,6 +25,7 @@
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
@@ -57,6 +58,7 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -107,8 +109,21 @@
private final SparseArray<UidState> mUidStates = new SparseArray<>();
- /** These are app op restrictions imposed per user from various parties */
- private final ArrayMap<IBinder, SparseArray<boolean[]>> mOpUserRestrictions = new ArrayMap<>();
+ /*
+ * These are app op restrictions imposed per user from various parties.
+ *
+ * This is organized as follows:
+ *
+ * ArrayMap w/ mapping:
+ * IBinder (for client imposing restriction) --> SparseArray w/ mapping:
+ * User handle --> Pair containing:
+ * - Array w/ index = AppOp code, value = restricted status boolean
+ * - SparseArray w/ mapping:
+ * AppOp code --> Set of packages that are not restricted for this code
+ *
+ */
+ private final ArrayMap<IBinder, SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>>>
+ mOpUserRestrictions = new ArrayMap<>();
private static final class UidState {
public final int uid;
@@ -1267,11 +1282,35 @@
private boolean isOpRestricted(int uid, int code, String packageName) {
int userHandle = UserHandle.getUserId(uid);
final int restrictionSetCount = mOpUserRestrictions.size();
+
for (int i = 0; i < restrictionSetCount; i++) {
- SparseArray<boolean[]> perUserRestrictions = mOpUserRestrictions.valueAt(i);
- boolean[] opRestrictions = perUserRestrictions.get(userHandle);
- if (opRestrictions != null && opRestrictions[code]) {
+ // For each client, check that the given op is not restricted, or that the given
+ // package is exempt from the restriction.
+
+ SparseArray<Pair<boolean[],SparseArray<ArraySet<String>>>> perUserRestrictions =
+ mOpUserRestrictions.valueAt(i);
+
+ Pair<boolean[],SparseArray<ArraySet<String>>> restrictions =
+ perUserRestrictions.get(userHandle);
+ if (restrictions == null) {
+ continue; // No restrictions set by this client
+ }
+
+ boolean[] opRestrictions = restrictions.first;
+ SparseArray<ArraySet<String>> opExceptions = restrictions.second;
+
+ if (opRestrictions == null) {
+ continue; // No restrictions set by this client
+ }
+
+ if (opRestrictions[code]) {
+ if (opExceptions != null && opExceptions.get(code) != null &&
+ opExceptions.get(code).contains(packageName)) {
+ continue; // AppOps code is restricted, but this package is exempt
+ }
+
if (AppOpsManager.opAllowSystemBypassRestriction(code)) {
+ // If we are the system, bypass user restrictions for certain codes
synchronized (this) {
Ops ops = getOpsLocked(uid, packageName, true);
if ((ops != null) && ops.isPrivileged) {
@@ -1279,6 +1318,7 @@
}
}
}
+
return true;
}
}
@@ -2069,7 +2109,8 @@
}
@Override
- public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle) {
+ public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
+ String[] exceptionPackages) {
if (Binder.getCallingPid() != Process.myPid()) {
mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
Binder.getCallingPid(), Binder.getCallingUid(), null);
@@ -2085,12 +2126,37 @@
}
verifyIncomingOp(code);
Preconditions.checkNotNull(token);
- setUserRestrictionNoCheck(code, restricted, token, userHandle);
+ setUserRestrictionNoCheck(code, restricted, token, userHandle, exceptionPackages);
}
private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
int userHandle) {
+ setUserRestrictionNoCheck(code, restricted, token, userHandle, /*exceptionPackages*/null);
+ }
+
+ private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
+ int userHandle, String[] exceptionPackages) {
+
final boolean[] opRestrictions = getOrCreateUserRestrictionsForToken(token, userHandle);
+
+ if (restricted) {
+ final SparseArray<ArraySet<String>> opExceptions =
+ getUserPackageExemptionsForToken(token, userHandle);
+
+ // If exceptionPackages is not null, update the exception packages for this AppOps code
+ ArraySet<String> exceptions = opExceptions.get(code);
+ if (exceptionPackages != null) {
+ if (exceptions == null) {
+ exceptions = new ArraySet<>(exceptionPackages.length);
+ opExceptions.put(code, exceptions);
+ } else {
+ exceptions.clear();
+ }
+
+ exceptions.addAll(Arrays.asList(exceptionPackages));
+ }
+ }
+
if (opRestrictions[code] == restricted) {
return;
}
@@ -2132,7 +2198,8 @@
checkSystemUid("removeUser");
final int tokenCount = mOpUserRestrictions.size();
for (int i = tokenCount - 1; i >= 0; i--) {
- SparseArray<boolean[]> opRestrictions = mOpUserRestrictions.valueAt(i);
+ SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> opRestrictions =
+ mOpUserRestrictions.valueAt(i);
if (opRestrictions != null) {
opRestrictions.remove(userHandle);
if (opRestrictions.size() <= 0) {
@@ -2144,15 +2211,23 @@
private void pruneUserRestrictionsForToken(IBinder token, int userHandle) {
- SparseArray<boolean[]> perTokenRestrictions = mOpUserRestrictions.get(token);
+ SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> perTokenRestrictions =
+ mOpUserRestrictions.get(token);
if (perTokenRestrictions != null) {
- final boolean[] opRestrictions = perTokenRestrictions.get(userHandle);
- if (opRestrictions != null) {
- for (boolean restriction : opRestrictions) {
- if (restriction) {
- return;
+ final Pair<boolean[], SparseArray<ArraySet<String>>> restrictions =
+ perTokenRestrictions.get(userHandle);
+
+ if (restrictions != null) {
+ final boolean[] opRestrictions = restrictions.first;
+ if (opRestrictions != null) {
+ for (boolean restriction : opRestrictions) {
+ if (restriction) {
+ return;
+ }
}
}
+
+ // No restrictions set for this client
perTokenRestrictions.remove(userHandle);
if (perTokenRestrictions.size() <= 0) {
mOpUserRestrictions.remove(token);
@@ -2161,18 +2236,61 @@
}
}
+ /**
+ * Get or create the user restrictions array for a given client if it doesn't already exist.
+ *
+ * @param token the binder client creating the restriction.
+ * @param userHandle the user handle to create a restriction for.
+ *
+ * @return the array of restriction states for each AppOps code.
+ */
private boolean[] getOrCreateUserRestrictionsForToken(IBinder token, int userHandle) {
- SparseArray<boolean[]> perTokenRestrictions = mOpUserRestrictions.get(token);
+ SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> perTokenRestrictions =
+ mOpUserRestrictions.get(token);
+
if (perTokenRestrictions == null) {
- perTokenRestrictions = new SparseArray<>();
+ perTokenRestrictions =
+ new SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>>();
mOpUserRestrictions.put(token, perTokenRestrictions);
}
- boolean[] opRestrictions = perTokenRestrictions.get(userHandle);
- if (opRestrictions == null) {
- opRestrictions = new boolean[AppOpsManager._NUM_OP];
- perTokenRestrictions.put(userHandle, opRestrictions);
+
+ Pair<boolean[], SparseArray<ArraySet<String>>> restrictions =
+ perTokenRestrictions.get(userHandle);
+
+ if (restrictions == null) {
+ restrictions = new Pair<boolean[], SparseArray<ArraySet<String>>>(
+ new boolean[AppOpsManager._NUM_OP], new SparseArray<ArraySet<String>>());
+ perTokenRestrictions.put(userHandle, restrictions);
}
- return opRestrictions;
+
+ return restrictions.first;
+ }
+
+ /**
+ * Get the per-package exemptions for each AppOps code for a given client and userHandle.
+ *
+ * @param token the binder client to get the exemptions for.
+ * @param userHandle the user handle to get the exemptions for.
+ *
+ * @return a mapping from the AppOps code to a set of packages exempt for that code.
+ */
+ private SparseArray<ArraySet<String>> getUserPackageExemptionsForToken(IBinder token,
+ int userHandle) {
+ SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> perTokenRestrictions =
+ mOpUserRestrictions.get(token);
+
+ if (perTokenRestrictions == null) {
+ return null; // Don't create user restrictions accidentally
+ }
+
+ Pair<boolean[], SparseArray<ArraySet<String>>> restrictions =
+ perTokenRestrictions.get(userHandle);
+
+ if (restrictions == null) {
+ return null; // Don't create user restrictions accidentally
+ }
+
+ return restrictions.second;
}
private void checkSystemUid(String function) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 76a93d4..9af1304 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -191,6 +191,9 @@
static final int LONG_PRESS_POWER_SHUT_OFF = 2;
static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3;
+ static final int LONG_PRESS_BACK_NOTHING = 0;
+ static final int LONG_PRESS_BACK_GO_TO_VOICE_ASSIST = 1;
+
static final int MULTI_PRESS_POWER_NOTHING = 0;
static final int MULTI_PRESS_POWER_THEATER_MODE = 1;
static final int MULTI_PRESS_POWER_BRIGHTNESS_BOOST = 2;
@@ -391,6 +394,7 @@
// handler thread. We'll need to resolve this someday by teaching the input dispatcher
// to hold wakelocks during dispatch and eliminating the critical path.
volatile boolean mPowerKeyHandled;
+ volatile boolean mBackKeyHandled;
volatile boolean mBeganFromNonInteractive;
volatile int mPowerKeyPressCounter;
volatile boolean mEndCallKeyHandled;
@@ -443,6 +447,7 @@
int mLongPressOnPowerBehavior;
int mDoublePressOnPowerBehavior;
int mTriplePressOnPowerBehavior;
+ int mLongPressOnBackBehavior;
int mShortPressOnSleepBehavior;
int mShortPressWindowBehavior;
boolean mAwake;
@@ -699,6 +704,7 @@
private static final int MSG_UPDATE_DREAMING_SLEEP_TOKEN = 15;
private static final int MSG_REQUEST_TRANSIENT_BARS = 16;
private static final int MSG_REQUEST_TV_PICTURE_IN_PICTURE = 17;
+ private static final int MSG_BACK_LONG_PRESS = 18;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
@@ -763,6 +769,9 @@
case MSG_REQUEST_TV_PICTURE_IN_PICTURE:
requestTvPictureInPictureInternal();
break;
+ case MSG_BACK_LONG_PRESS:
+ backLongPress();
+ break;
}
}
}
@@ -1109,6 +1118,13 @@
}
}
+ private void cancelPendingBackKeyAction() {
+ if (!mBackKeyHandled) {
+ mBackKeyHandled = true;
+ mHandler.removeMessages(MSG_BACK_LONG_PRESS);
+ }
+ }
+
private void powerPress(long eventTime, boolean interactive, int count) {
if (mScreenOnEarly && !mScreenOnFully) {
Slog.i(TAG, "Suppressed redundant power key press while "
@@ -1216,6 +1232,19 @@
}
}
+ private void backLongPress() {
+ mBackKeyHandled = true;
+
+ switch (mLongPressOnBackBehavior) {
+ case LONG_PRESS_BACK_NOTHING:
+ break;
+ case LONG_PRESS_BACK_GO_TO_VOICE_ASSIST:
+ Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
+ startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+ break;
+ }
+ }
+
private void sleepPress(long eventTime) {
if (mShortPressOnSleepBehavior == SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME) {
launchHomeFromHotKey(false /* awakenDreams */, true /*respectKeyguard*/);
@@ -1244,6 +1273,10 @@
return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING;
}
+ private boolean hasLongPressOnBackBehavior() {
+ return mLongPressOnBackBehavior != LONG_PRESS_BACK_NOTHING;
+ }
+
private void interceptScreenshotChord() {
if (mScreenshotChordEnabled
&& mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered
@@ -1573,6 +1606,9 @@
mSupportLongPressPowerWhenNonInteractive = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_supportLongPressPowerWhenNonInteractive);
+ mLongPressOnBackBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_longPressOnBackBehavior);
+
mShortPressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_shortPressOnPowerBehavior);
mLongPressOnPowerBehavior = mContext.getResources().getInteger(
@@ -5341,6 +5377,29 @@
// Handle special keys.
switch (keyCode) {
+ case KeyEvent.KEYCODE_BACK: {
+ if (down) {
+ mBackKeyHandled = false;
+ if (hasLongPressOnBackBehavior()) {
+ Message msg = mHandler.obtainMessage(MSG_BACK_LONG_PRESS);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg,
+ ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+ }
+ } else {
+ boolean handled = mBackKeyHandled;
+
+ // Reset back key state
+ cancelPendingBackKeyAction();
+
+ // Don't pass back press to app if we've already handled it
+ if (handled) {
+ result &= ~ACTION_PASS_TO_USER;
+ }
+ }
+ break;
+ }
+
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
@@ -7390,6 +7449,8 @@
pw.print(" mLidControlsScreenLock="); pw.println(mLidControlsScreenLock);
pw.print(" mLidControlsSleep="); pw.println(mLidControlsSleep);
pw.print(prefix);
+ pw.print(" mLongPressOnBackBehavior="); pw.println(mLongPressOnBackBehavior);
+ pw.print(prefix);
pw.print("mShortPressOnPowerBehavior="); pw.print(mShortPressOnPowerBehavior);
pw.print(" mLongPressOnPowerBehavior="); pw.println(mLongPressOnPowerBehavior);
pw.print(prefix);
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index f5914faf..d0ee6e0 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -201,13 +201,16 @@
}
}
- private void updateOverlayStateLocked() {
+ private void updateOverlayStateLocked(ComponentName exemptedComponent) {
final long identity = Binder.clearCallingIdentity();
try {
AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
if (appOpsManager != null) {
+ String[] exemptions = (exemptedComponent == null) ? new String[0] :
+ new String[] { exemptedComponent.getPackageName() };
+
appOpsManager.setUserRestriction(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
- mVrModeEnabled, mOverlayToken);
+ mVrModeEnabled, mOverlayToken, exemptions);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -230,12 +233,12 @@
private boolean updateCurrentVrServiceLocked(boolean enabled,
@NonNull ComponentName component, int userId) {
- // Always send mode change events.
- changeVrModeLocked(enabled);
-
boolean validUserComponent = (mComponentObserver.isValid(component, userId) ==
EnabledComponentsObserver.NO_ERROR);
+ // Always send mode change events.
+ changeVrModeLocked(enabled, (enabled && validUserComponent) ? component : null);
+
if (!enabled || !validUserComponent) {
// Unbind whatever is running
if (mCurrentVrService != null) {
@@ -275,8 +278,9 @@
* Note: Must be called while holding {@code mLock}.
*
* @param enabled new state of the VR mode.
+ * @param exemptedComponent a component to exempt from AppOps restrictions for overlays.
*/
- private void changeVrModeLocked(boolean enabled) {
+ private void changeVrModeLocked(boolean enabled, ComponentName exemptedComponent) {
if (mVrModeEnabled != enabled) {
mVrModeEnabled = enabled;
@@ -284,7 +288,7 @@
Slog.i(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled"));
setVrModeNative(mVrModeEnabled);
- updateOverlayStateLocked();
+ updateOverlayStateLocked(exemptedComponent);
onVrModeChangedLocked();
}
}
diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs b/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs
index caa947d..0a1742e 100644
--- a/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs
+++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs
@@ -14,10 +14,14 @@
for (int x = 0; x < HEIGHT; x += REGION_SIZE) {
bool interestingRegion = false;
- int regionColor = (int) rsGetElementAt_uchar4(ideal, x, y);
+ uchar4 regionColor = rsGetElementAt_uchar4(ideal, x, y);
for (int i = 0; i < REGION_SIZE && !interestingRegion; i++) {
for (int j = 0; j < REGION_SIZE && !interestingRegion; j++) {
- interestingRegion |= ((int) rsGetElementAt_uchar4(ideal, x + j, y + i)) != regionColor;
+ uchar4 testVal = rsGetElementAt_uchar4(ideal, x + j, y + i);
+ interestingRegion |= (testVal.r != regionColor.r);
+ interestingRegion |= (testVal.g != regionColor.g);
+ interestingRegion |= (testVal.b != regionColor.b);
+ interestingRegion |= (testVal.a != regionColor.a);
}
}
if (interestingRegion) {