summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/core/res/layout/test_simple_view.xml20
-rw-r--r--apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java31
-rw-r--r--apct-tests/perftests/core/src/android/view/ViewPerfTest.java47
-rw-r--r--apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java12
-rw-r--r--core/java/android/view/IPinnedStackController.aidl5
-rw-r--r--core/java/android/view/RenderNode.java47
-rw-r--r--core/java/android/view/SurfaceView.java1
-rw-r--r--core/java/android/view/View.java7
-rw-r--r--core/java/com/android/internal/policy/PipSnapAlgorithm.java10
-rw-r--r--core/jni/android_view_RenderNode.cpp5
-rw-r--r--core/res/res/values/config.xml4
-rw-r--r--libs/hwui/Android.mk1
-rw-r--r--libs/hwui/tests/microbench/RenderNodeBench.cpp33
-rw-r--r--packages/SystemUI/AndroidManifest.xml12
-rw-r--r--packages/SystemUI/res/layout/pip_menu_activity.xml36
-rw-r--r--packages/SystemUI/res/values/strings.xml19
-rw-r--r--packages/SystemUI/res/values/styles.xml18
-rw-r--r--packages/SystemUI/res/xml/tuner_prefs.xml12
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java137
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java126
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java113
-rw-r--r--services/core/java/com/android/server/location/GnssLocationProvider.java42
-rw-r--r--services/core/java/com/android/server/notification/RankingHelper.java12
-rw-r--r--services/core/java/com/android/server/webkit/SystemImpl.java18
-rw-r--r--services/core/java/com/android/server/webkit/SystemInterface.java4
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java21
-rw-r--r--services/core/java/com/android/server/wm/PinnedStackController.java7
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java13
-rw-r--r--services/tests/notification/src/com/android/server/notification/RankingHelperTest.java175
-rw-r--r--services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java124
-rw-r--r--tools/aapt2/Android.mk4
-rw-r--r--tools/aapt2/jni/ScopedUtfChars.h84
-rw-r--r--tools/aapt2/jni/aapt2_jni.cpp2
35 files changed, 1059 insertions, 154 deletions
diff --git a/apct-tests/perftests/core/res/layout/test_simple_view.xml b/apct-tests/perftests/core/res/layout/test_simple_view.xml
new file mode 100644
index 000000000000..9bc29a81eb96
--- /dev/null
+++ b/apct-tests/perftests/core/res/layout/test_simple_view.xml
@@ -0,0 +1,20 @@
+<!--
+ 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.
+-->
+
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/simple_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
index 19047d3fa165..922a47542020 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
@@ -31,11 +31,38 @@ public class RenderNodePerfTest {
@Test
public void testMeasureRenderNodeJniOverhead() {
- RenderNode node = RenderNode.create("benchmark", null);
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final RenderNode node = RenderNode.create("benchmark", null);
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
node.setTranslationX(1.0f);
}
}
+
+ @Test
+ public void testCreateRenderNodeNoName() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ RenderNode node = RenderNode.create(null, null);
+ node.destroy();
+ }
+ }
+
+ @Test
+ public void testCreateRenderNode() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ RenderNode node = RenderNode.create("LinearLayout", null);
+ node.destroy();
+ }
+ }
+
+ @Test
+ public void testIsValid() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ RenderNode node = RenderNode.create("LinearLayout", null);
+ while (state.keepRunning()) {
+ node.isValid();
+ }
+ }
}
diff --git a/apct-tests/perftests/core/src/android/view/ViewPerfTest.java b/apct-tests/perftests/core/src/android/view/ViewPerfTest.java
new file mode 100644
index 000000000000..5503ca94134e
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/view/ViewPerfTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.widget.FrameLayout;
+
+import com.android.perftests.core.R;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@LargeTest
+public class ViewPerfTest {
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void testSimpleViewInflate() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
+ FrameLayout root = new FrameLayout(context);
+ while (state.keepRunning()) {
+ inflater.inflate(R.layout.test_simple_view, root, false);
+ }
+ }
+}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java
index 519d5248a347..fd393e9d070c 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java
@@ -19,8 +19,11 @@ package android.perftests.utils;
import android.app.Activity;
import android.app.Instrumentation;
import android.os.Bundle;
+import android.os.Debug;
+import android.support.test.InstrumentationRegistry;
import android.util.Log;
+import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
@@ -45,6 +48,7 @@ import java.util.concurrent.TimeUnit;
public final class BenchmarkState {
private static final String TAG = "BenchmarkState";
+ private static final boolean ENABLE_PROFILING = false;
private static final int NOT_STARTED = 0; // The benchmark has not started yet.
private static final int WARMUP = 1; // The benchmark is warming up.
@@ -146,6 +150,11 @@ public final class BenchmarkState {
}
private void beginBenchmark(long warmupDuration, int iterations) {
+ if (ENABLE_PROFILING) {
+ File f = new File(InstrumentationRegistry.getContext().getDataDir(), "benchprof");
+ Log.d(TAG, "Tracing to: " + f.getAbsolutePath());
+ Debug.startMethodTracingSampling(f.getAbsolutePath(), 16 * 1024 * 1024, 100);
+ }
mMaxIterations = (int) (TARGET_TEST_DURATION_NS / (warmupDuration / iterations));
mMaxIterations = Math.min(MAX_TEST_ITERATIONS,
Math.max(mMaxIterations, MIN_TEST_ITERATIONS));
@@ -161,6 +170,9 @@ public final class BenchmarkState {
mResults.add((currentTime - mStartTimeNs - mPausedDurationNs) / mMaxIterations);
mRepeatCount++;
if (mRepeatCount >= REPEAT_COUNT) {
+ if (ENABLE_PROFILING) {
+ Debug.stopMethodTracing();
+ }
calculateSatistics();
mState = FINISHED;
return false;
diff --git a/core/java/android/view/IPinnedStackController.aidl b/core/java/android/view/IPinnedStackController.aidl
index 830591d5b503..a81eef831f4e 100644
--- a/core/java/android/view/IPinnedStackController.aidl
+++ b/core/java/android/view/IPinnedStackController.aidl
@@ -30,4 +30,9 @@ interface IPinnedStackController {
* Notifies the controller that the user is currently interacting with the PIP.
*/
oneway void setInInteractiveMode(boolean inInteractiveMode);
+
+ /**
+ * Notifies the controller that the desired snap mode is to the closest edge.
+ */
+ oneway void setSnapToEdge(boolean snapToEdge);
}
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 51d818b5f68d..7a3c95e004c5 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -26,8 +26,6 @@ import android.graphics.drawable.AnimatedVectorDrawable;
import dalvik.annotation.optimization.FastNative;
-import libcore.util.NativeAllocationRegistry;
-
/**
* <p>A display list records a series of graphics related operations and can replay
* them later. Display lists are usually built by recording operations on a
@@ -132,36 +130,46 @@ import libcore.util.NativeAllocationRegistry;
*/
public class RenderNode {
- // Use a Holder to allow static initialization in the boot image.
- private static class NoImagePreloadHolder {
- public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- RenderNode.class.getClassLoader(), nGetNativeFinalizer(), 1024);
- }
-
private boolean mValid;
// Do not access directly unless you are ThreadedRenderer
- final long mNativeRenderNode;
+ long mNativeRenderNode;
private final View mOwningView;
private RenderNode(String name, View owningView) {
mNativeRenderNode = nCreate(name);
- NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeRenderNode);
mOwningView = owningView;
- if (mOwningView instanceof SurfaceView) {
- nRequestPositionUpdates(mNativeRenderNode, (SurfaceView) mOwningView);
- }
}
/**
* @see RenderNode#adopt(long)
*/
private RenderNode(long nativePtr) {
- NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, nativePtr);
mNativeRenderNode = nativePtr;
mOwningView = null;
}
/**
+ * Immediately destroys the RenderNode
+ * Only suitable for testing/benchmarking where waiting for the GC/finalizer
+ * is not feasible.
+ */
+ public void destroy() {
+ if (mNativeRenderNode != 0) {
+ nFinalize(mNativeRenderNode);
+ mNativeRenderNode = 0;
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ destroy();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
* Creates a new RenderNode that can be used to record batches of
* drawing operations, and store / apply render properties when drawn.
*
@@ -183,6 +191,13 @@ public class RenderNode {
return new RenderNode(nativePtr);
}
+ /**
+ * Enable callbacks for position changes.
+ */
+ public void requestPositionUpdates(SurfaceView view) {
+ nRequestPositionUpdates(mNativeRenderNode, view);
+ }
+
/**
* Starts recording a display list for the render node. All
@@ -784,9 +799,6 @@ public class RenderNode {
*/
void onRenderNodeDetached() {
discardDisplayList();
- if (mOwningView != null) {
- mOwningView.onRenderNodeDetached(this);
- }
}
///////////////////////////////////////////////////////////////////////////
@@ -823,6 +835,7 @@ public class RenderNode {
// Intentionally not static because it acquires a reference to 'this'
private native long nCreate(String name);
+ private native void nFinalize(long renderNode);
private static native long nGetNativeFinalizer();
private static native void nSetDisplayList(long renderNode, long newData);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 5c56ebc47d27..d46910c2139c 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -207,6 +207,7 @@ public class SurfaceView extends View {
public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ mRenderNode.requestPositionUpdates(this);
setWillNotDraw(true);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ba2692674c32..441f3302994f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16185,13 +16185,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Called when the passed RenderNode is removed from the draw tree
- * @hide
- */
- public void onRenderNodeDetached(RenderNode renderNode) {
- }
-
- /**
* <p>Calling this method is equivalent to calling <code>getDrawingCache(false)</code>.</p>
*
* @return A non-scaled bitmap representing this view or null if cache is disabled.
diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
index 45b7b0183fd8..cbacf269a0f0 100644
--- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
@@ -46,7 +46,8 @@ public class PipSnapAlgorithm {
private final Context mContext;
private final ArrayList<Integer> mSnapGravities = new ArrayList<>();
- private final int mSnapMode = SNAP_MODE_CORNERS_ONLY;
+ private final int mDefaultSnapMode = SNAP_MODE_CORNERS_ONLY;
+ private int mSnapMode = mDefaultSnapMode;
private Scroller mScroller;
private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -65,6 +66,13 @@ public class PipSnapAlgorithm {
}
/**
+ * Enables snapping to the closest edge.
+ */
+ public void setSnapToEdge(boolean snapToEdge) {
+ mSnapMode = snapToEdge ? SNAP_MODE_EDGE : mDefaultSnapMode;
+ }
+
+ /**
* @return the closest absolute snap stack bounds for the given {@param stackBounds} moving at
* the given {@param velocityX} and {@param velocityY}. The {@param movementBounds} should be
* those for the given {@param stackBounds}.
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index dd2a7a98b7a0..f88de51a9a12 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -124,6 +124,10 @@ static void releaseRenderNode(RenderNode* renderNode) {
renderNode->decStrong(0);
}
+static void android_view_RenderNode_finalize(JNIEnv* env, jobject clazz, jlong renderNodePtr) {
+ releaseRenderNode(reinterpret_cast<RenderNode*>(renderNodePtr));
+}
+
static jlong android_view_RenderNode_getNativeFinalizer(JNIEnv* env,
jobject clazz) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&releaseRenderNode));
@@ -650,6 +654,7 @@ static const JNINativeMethod gMethods[] = {
// Regular JNI
// ----------------------------------------------------------------------------
{ "nCreate", "(Ljava/lang/String;)J", (void*) android_view_RenderNode_create },
+ { "nFinalize", "(J)V", (void*) android_view_RenderNode_finalize },
{ "nGetNativeFinalizer", "()J", (void*) android_view_RenderNode_getNativeFinalizer },
{ "nSetDisplayList", "(JJ)V", (void*) android_view_RenderNode_setDisplayList },
{ "nOutput", "(J)V", (void*) android_view_RenderNode_output },
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0678dfd1e759..6dc3f3eb1614 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2175,9 +2175,9 @@
-->
<string-array translatable="false" name="config_telephonyHardware">
<!-- modem -->
- <item>"0,modem,0,0,0,1,1,1"</item>
+ <item>0,modem,0,0,0,1,1,1</item>
<!-- sim -->
- <item>"1,sim,0,modem"</item>
+ <item>1,sim,0,modem</item>
</string-array>
<!-- This string array can be overriden to add an additional DRM support for WebView EME. -->
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 06eb829e22e3..461936aaa493 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -356,6 +356,7 @@ LOCAL_SRC_FILES += \
tests/microbench/FrameBuilderBench.cpp \
tests/microbench/LinearAllocatorBench.cpp \
tests/microbench/PathParserBench.cpp \
+ tests/microbench/RenderNodeBench.cpp \
tests/microbench/ShadowBench.cpp \
tests/microbench/TaskManagerBench.cpp
diff --git a/libs/hwui/tests/microbench/RenderNodeBench.cpp b/libs/hwui/tests/microbench/RenderNodeBench.cpp
new file mode 100644
index 000000000000..a5bed0026b1c
--- /dev/null
+++ b/libs/hwui/tests/microbench/RenderNodeBench.cpp
@@ -0,0 +1,33 @@
+/*
+ * 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 <benchmark/benchmark.h>
+
+#include "RenderNode.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+void BM_RenderNode_create(benchmark::State& state) {
+ while (state.KeepRunning()) {
+ auto node = new RenderNode();
+ node->incStrong(0);
+ benchmark::DoNotOptimize(node);
+ node->decStrong(0);
+ }
+}
+BENCHMARK(BM_RenderNode_create);
+
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4d59d574adc8..2adb26159622 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -415,6 +415,18 @@
android:launchMode="singleTop"
android:excludeFromRecents="true" />
+ <activity
+ android:name=".pip.phone.PipMenuActivity"
+ android:theme="@style/PipPhoneOverlayControlTheme"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:excludeFromRecents="true"
+ android:exported="false"
+ android:resizeableActivity="true"
+ android:supportsPictureInPicture="true"
+ android:stateNotNeeded="true"
+ android:taskAffinity=""
+ androidprv:alwaysFocusable="true" />
+
<!-- platform logo easter egg activity -->
<activity
android:name=".DessertCase"
diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml
new file mode 100644
index 000000000000..88e6e725c976
--- /dev/null
+++ b/packages/SystemUI/res/layout/pip_menu_activity.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#33000000">
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center">
+ <Button
+ android:id="@+id/expand_pip"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:textSize="14sp"
+ android:textColor="#ffffffff"
+ android:text="@string/pip_phone_expand"
+ android:fontFamily="sans-serif" />
+ </LinearLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d8c8b82eee4a..37a7e38eb4f9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1675,6 +1675,9 @@
not appear on production builds ever. -->
<string name="tuner_doze_always_on" translatable="false">Always on</string>
+ <!-- Making the PIP fullscreen -->
+ <string name="pip_phone_expand">Expand</string>
+
<!-- PIP section of the tuner. Non-translatable since it should
not appear on production builds ever. -->
<string name="picture_in_picture" translatable="false">Picture-in-Picture</string>
@@ -1695,4 +1698,20 @@
not appear on production builds ever. -->
<string name="pip_drag_to_dismiss_summary" translatable="false">Drag to the dismiss target at the bottom of the screen to close the PIP</string>
+ <!-- PIP tap once to break through to the activity. Non-translatable since it should
+ not appear on production builds ever. -->
+ <string name="pip_tap_through_title" translatable="false">Tap to interact</string>
+
+ <!-- PIP tap once to break through to the activity. Non-translatable since it should
+ not appear on production builds ever. -->
+ <string name="pip_tap_through_summary" translatable="false">Tap once to interact with the activity</string>
+
+ <!-- PIP snap to closest edge. Non-translatable since it should
+ not appear on production builds ever. -->
+ <string name="pip_snap_mode_edge_title" translatable="false">Snap to closest edge</string>
+
+ <!-- PIP snap to closest edge. Non-translatable since it should
+ not appear on production builds ever. -->
+ <string name="pip_snap_mode_edge_summary" translatable="false">Snap to the closest edge</string>
+
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index e5bc8b996cea..6661f076d848 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -56,6 +56,24 @@
<item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
</style>
+ <style name="PipPhoneOverlayControlTheme" parent="@android:style/Theme.Material">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowBackground">@drawable/forced_resizable_background</item>
+ <item name="android:colorBackgroundCacheHint">@null</item>
+ <item name="android:statusBarColor">@color/transparent</item>
+ <item name="android:windowAnimationStyle">@style/Animation.PipPhoneOverlayControl</item>
+ </style>
+
+ <style name="Animation.PipPhoneOverlayControl" parent="@android:style/Animation">
+ <item name="android:activityOpenEnterAnimation">@anim/forced_resizable_enter</item>
+
+ <!-- If the target stack doesn't have focus, we do a task to front animation. -->
+ <item name="android:taskToFrontEnterAnimation">@anim/forced_resizable_enter</item>
+ <item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
+ </style>
+
<style name="TextAppearance.StatusBar.HeadsUp"
parent="@*android:style/TextAppearance.StatusBar">
</style>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 942f8472b7ef..f09d6e9fe052 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -137,6 +137,18 @@
android:summary="@string/pip_drag_to_dismiss_summary"
sysui:defValue="true" />
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="pip_tap_through"
+ android:title="@string/pip_tap_through_title"
+ android:summary="@string/pip_tap_through_summary"
+ sysui:defValue="false" />
+
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="pip_snap_mode_edge"
+ android:title="@string/pip_snap_mode_edge_title"
+ android:summary="@string/pip_snap_mode_edge_summary"
+ sysui:defValue="false" />
+
</PreferenceScreen>
<PreferenceScreen
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index f9a4f7ced6cc..7b8d27eef8b4 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -34,6 +34,7 @@ public class PipManager {
private IActivityManager mActivityManager;
private IWindowManager mWindowManager;
+ private PipMenuActivityController mMenuController;
private PipTouchHandler mTouchHandler;
private PipManager() {}
@@ -46,7 +47,9 @@ public class PipManager {
mActivityManager = ActivityManagerNative.getDefault();
mWindowManager = WindowManagerGlobal.getWindowManagerService();
- mTouchHandler = new PipTouchHandler(context, mActivityManager, mWindowManager);
+ mMenuController = new PipMenuActivityController(context, mActivityManager, mWindowManager);
+ mTouchHandler = new PipTouchHandler(context, mMenuController, mActivityManager,
+ mWindowManager);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
new file mode 100644
index 000000000000..bfe5cff90a24
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -0,0 +1,137 @@
+/*
+ * 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
+ */
+
+package com.android.systemui.pip.phone;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+import com.android.systemui.R;
+
+/**
+ * Translucent activity that gets started on top of a task in PIP to allow the user to control it.
+ */
+public class PipMenuActivity extends Activity {
+
+ private static final String TAG = "PipMenuActivity";
+
+ public static final int MESSAGE_FINISH_SELF = 2;
+
+ private static final long INITIAL_DISMISS_DELAY = 2000;
+ private static final long POST_INTERACTION_DISMISS_DELAY = 1500;
+
+ private Messenger mToControllerMessenger;
+ private Messenger mMessenger = new Messenger(new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_FINISH_SELF:
+ finish();
+ break;
+ }
+ }
+ });
+
+ private final Runnable mFinishRunnable = new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ };
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent startingIntet = getIntent();
+ mToControllerMessenger = startingIntet.getParcelableExtra(
+ PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER);
+
+ setContentView(R.layout.pip_menu_activity);
+ findViewById(R.id.expand_pip).setOnClickListener((view) -> {
+ finish();
+ notifyExpandPip();
+ });
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ notifyActivityVisibility(true);
+ repostDelayedFinish(INITIAL_DISMISS_DELAY);
+ }
+
+ @Override
+ public void onUserInteraction() {
+ repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ finish();
+ }
+
+ @Override
+ public void finish() {
+ View v = getWindow().getDecorView();
+ v.removeCallbacks(mFinishRunnable);
+ notifyActivityVisibility(false);
+ super.finish();
+ overridePendingTransition(0, R.anim.forced_resizable_exit);
+ }
+
+ @Override
+ public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
+ // Do nothing
+ }
+
+ private void notifyActivityVisibility(boolean visible) {
+ Message m = Message.obtain();
+ m.what = PipMenuActivityController.MESSAGE_ACTIVITY_VISIBILITY_CHANGED;
+ m.arg1 = visible ? 1 : 0;
+ m.replyTo = visible ? mMessenger : null;
+ try {
+ mToControllerMessenger.send(m);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not notify controller of PIP menu visibility", e);
+ }
+ }
+
+ private void notifyExpandPip() {
+ Message m = Message.obtain();
+ m.what = PipMenuActivityController.MESSAGE_EXPAND_PIP;
+ try {
+ mToControllerMessenger.send(m);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not notify controller to expand PIP", e);
+ }
+ }
+
+ private void repostDelayedFinish(long delay) {
+ View v = getWindow().getDecorView();
+ v.removeCallbacks(mFinishRunnable);
+ v.postDelayed(mFinishRunnable, delay);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
new file mode 100644
index 000000000000..d1bce0c74ccd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -0,0 +1,126 @@
+package com.android.systemui.pip.phone;
+
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.view.WindowManager.INPUT_CONSUMER_PIP;
+
+import android.app.ActivityManager.StackInfo;
+import android.app.ActivityOptions;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.IWindowManager;
+
+import java.util.ArrayList;
+
+public class PipMenuActivityController {
+
+ private static final String TAG = "PipMenuActivityController";
+
+ public static final String EXTRA_CONTROLLER_MESSENGER = "messenger";
+ public static final int MESSAGE_ACTIVITY_VISIBILITY_CHANGED = 1;
+ public static final int MESSAGE_EXPAND_PIP = 3;
+
+ /**
+ * A listener interface to receive notification on changes in PIP.
+ */
+ public interface Listener {
+ /**
+ * Called when the PIP menu visibility changes.
+ */
+ void onPipMenuVisibilityChanged(boolean visible);
+ }
+
+ private Context mContext;
+ private IActivityManager mActivityManager;
+ private IWindowManager mWindowManager;
+ private ArrayList<Listener> mListeners = new ArrayList<>();
+
+ private Messenger mToActivityMessenger;
+ private Messenger mMessenger = new Messenger(new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_ACTIVITY_VISIBILITY_CHANGED: {
+ boolean visible = msg.arg1 > 0;
+ int listenerCount = mListeners.size();
+ for (int i = 0; i < listenerCount; i++) {
+ mListeners.get(i).onPipMenuVisibilityChanged(visible);
+ }
+ mToActivityMessenger = msg.replyTo;
+ break;
+ }
+ case MESSAGE_EXPAND_PIP: {
+ try {
+ mActivityManager.resizeStack(PINNED_STACK_ID, null, true, true, true, 225);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error showing PIP menu activity", e);
+ }
+ break;
+ }
+ }
+ }
+ });
+
+ public PipMenuActivityController(Context context, IActivityManager activityManager,
+ IWindowManager windowManager) {
+ mContext = context;
+ mActivityManager = activityManager;
+ mWindowManager = windowManager;
+ }
+
+ /**
+ * Adds a new menu activity listener.
+ */
+ public void addListener(Listener listener) {
+ if (!mListeners.contains(listener)) {
+ mListeners.add(listener);
+ }
+ }
+
+ /**
+ * Shows the menu activity.
+ */
+ public void showMenu() {
+ // Start the menu activity on the top task of the pinned stack
+ try {
+ StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
+ pinnedStackInfo.taskIds.length > 0) {
+ Intent intent = new Intent(mContext, PipMenuActivity.class);
+ intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger);
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchTaskId(
+ pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]);
+ options.setTaskOverlay(true);
+ mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
+ } else {
+ Log.e(TAG, "No PIP tasks found");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error showing PIP menu activity", e);
+ }
+ }
+
+ /**
+ * Hides the menu activity.
+ */
+ public void hideMenu() {
+ if (mToActivityMessenger != null) {
+ Message m = Message.obtain();
+ m.what = PipMenuActivity.MESSAGE_FINISH_SELF;
+ try {
+ mToActivityMessenger.send(m);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not notify menu activity to finish", e);
+ }
+ mToActivityMessenger = null;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index e32022abdf38..a3593806fb1f 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -32,6 +32,7 @@ import android.app.IActivityManager;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
@@ -61,6 +62,8 @@ public class PipTouchHandler implements TunerService.Tunable {
private static final String TUNER_KEY_SWIPE_TO_DISMISS = "pip_swipe_to_dismiss";
private static final String TUNER_KEY_DRAG_TO_DISMISS = "pip_drag_to_dismiss";
+ private static final String TUNER_KEY_TAP_THROUGH = "pip_tap_through";
+ private static final String TUNER_KEY_SNAP_MODE_EDGE = "pip_snap_mode_edge";
private static final int SNAP_STACK_DURATION = 225;
private static final int DISMISS_STACK_DURATION = 375;
@@ -70,17 +73,20 @@ public class PipTouchHandler implements TunerService.Tunable {
private final IActivityManager mActivityManager;
private final IWindowManager mWindowManager;
private final ViewConfiguration mViewConfig;
- private final InputChannel mInputChannel = new InputChannel();
private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
+ private final PipMenuListener mMenuListener = new PipMenuListener();
private IPinnedStackController mPinnedStackController;
- private final PipInputEventReceiver mInputEventReceiver;
+ private PipInputEventReceiver mInputEventReceiver;
+ private PipMenuActivityController mMenuController;
private PipDismissViewController mDismissViewController;
private final PipSnapAlgorithm mSnapAlgorithm;
private PipMotionHelper mMotionHelper;
private boolean mEnableSwipeToDismiss = true;
private boolean mEnableDragToDismiss = true;
+ private boolean mEnableTapThrough = false;
+ private boolean mEnableSnapToEdge = false;
private final Rect mPinnedStackBounds = new Rect();
private final Rect mBoundedPinnedStackBounds = new Rect();
@@ -97,6 +103,7 @@ public class PipTouchHandler implements TunerService.Tunable {
private final PointF mLastTouch = new PointF();
private boolean mIsDragging;
private boolean mIsSwipingToDismiss;
+ private boolean mIsTappingThrough;
private int mActivePointerId;
private final FlingAnimationUtils mFlingAnimationUtils;
@@ -120,7 +127,7 @@ public class PipTouchHandler implements TunerService.Tunable {
// To be implemented for input handling over Pip windows
if (event instanceof MotionEvent) {
MotionEvent ev = (MotionEvent) event;
- handleTouchEvent(ev);
+ handled = handleTouchEvent(ev);
}
} finally {
finishInputEvent(event, handled);
@@ -144,13 +151,26 @@ public class PipTouchHandler implements TunerService.Tunable {
}
}
- public PipTouchHandler(Context context, IActivityManager activityManager,
- IWindowManager windowManager) {
+ /**
+ * A listener for the PIP menu activity.
+ */
+ private class PipMenuListener implements PipMenuActivityController.Listener {
+ @Override
+ public void onPipMenuVisibilityChanged(boolean visible) {
+ if (!visible) {
+ mIsTappingThrough = false;
+ registerInputConsumer();
+ } else {
+ unregisterInputConsumer();
+ }
+ }
+ }
+
+ public PipTouchHandler(Context context, PipMenuActivityController menuController,
+ IActivityManager activityManager, IWindowManager windowManager) {
// Initialize the Pip input consumer
try {
- windowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
- windowManager.createInputConsumer(INPUT_CONSUMER_PIP, mInputChannel);
windowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener);
} catch (RemoteException e) {
Log.e(TAG, "Failed to create PIP input consumer", e);
@@ -159,22 +179,29 @@ public class PipTouchHandler implements TunerService.Tunable {
mActivityManager = activityManager;
mWindowManager = windowManager;
mViewConfig = ViewConfiguration.get(context);
- mInputEventReceiver = new PipInputEventReceiver(mInputChannel, Looper.myLooper());
- if (mEnableDragToDismiss) {
- mDismissViewController = new PipDismissViewController(context);
- }
+ mMenuController = menuController;
+ mMenuController.addListener(mMenuListener);
+ mDismissViewController = new PipDismissViewController(context);
mSnapAlgorithm = new PipSnapAlgorithm(mContext);
mFlingAnimationUtils = new FlingAnimationUtils(context, 2f);
mMotionHelper = new PipMotionHelper(BackgroundThread.getHandler());
+ registerInputConsumer();
// Register any tuner settings changes
TunerService.get(context).addTunable(this, TUNER_KEY_SWIPE_TO_DISMISS,
- TUNER_KEY_DRAG_TO_DISMISS);
+ TUNER_KEY_DRAG_TO_DISMISS, TUNER_KEY_TAP_THROUGH, TUNER_KEY_SNAP_MODE_EDGE);
}
@Override
public void onTuningChanged(String key, String newValue) {
if (newValue == null) {
+ // Reset back to default
+ mEnableSwipeToDismiss = true;
+ mEnableDragToDismiss = true;
+ mEnableTapThrough = false;
+ mIsTappingThrough = false;
+ mEnableSnapToEdge = false;
+ setSnapToEdge(false);
return;
}
switch (key) {
@@ -184,6 +211,14 @@ public class PipTouchHandler implements TunerService.Tunable {
case TUNER_KEY_DRAG_TO_DISMISS:
mEnableDragToDismiss = Integer.parseInt(newValue) != 0;
break;
+ case TUNER_KEY_TAP_THROUGH:
+ mEnableTapThrough = Integer.parseInt(newValue) != 0;
+ mIsTappingThrough = false;
+ break;
+ case TUNER_KEY_SNAP_MODE_EDGE:
+ mEnableSnapToEdge = Integer.parseInt(newValue) != 0;
+ setSnapToEdge(mEnableSnapToEdge);
+ break;
}
}
@@ -192,10 +227,10 @@ public class PipTouchHandler implements TunerService.Tunable {
updateBoundedPinnedStackBounds(false /* updatePinnedStackBounds */);
}
- private void handleTouchEvent(MotionEvent ev) {
+ private boolean handleTouchEvent(MotionEvent ev) {
// Skip touch handling until we are bound to the controller
if (mPinnedStackController == null) {
- return;
+ return true;
}
switch (ev.getAction()) {
@@ -239,6 +274,8 @@ public class PipTouchHandler implements TunerService.Tunable {
float movement = PointF.length(mDownTouch.x - x, mDownTouch.y - y);
if (movement > mViewConfig.getScaledTouchSlop()) {
mIsDragging = true;
+ mIsTappingThrough = false;
+ mMenuController.hideMenu();
if (mEnableSwipeToDismiss) {
// TODO: this check can have some buffer so that we only start swiping
// after a significant move out of bounds
@@ -328,7 +365,14 @@ public class PipTouchHandler implements TunerService.Tunable {
}
}
} else {
- expandPinnedStackToFullscreen();
+ if (mEnableTapThrough) {
+ if (!mIsTappingThrough) {
+ mMenuController.showMenu();
+ mIsTappingThrough = true;
+ }
+ } else {
+ expandPinnedStackToFullscreen();
+ }
}
if (mEnableDragToDismiss) {
mDismissViewController.destroyDismissTarget();
@@ -348,6 +392,7 @@ public class PipTouchHandler implements TunerService.Tunable {
break;
}
}
+ return !mIsTappingThrough;
}
private void initOrResetVelocityTracker() {
@@ -366,6 +411,44 @@ public class PipTouchHandler implements TunerService.Tunable {
}
/**
+ * Registers the input consumer.
+ */
+ private void registerInputConsumer() {
+ final InputChannel inputChannel = new InputChannel();
+ try {
+ mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
+ mWindowManager.createInputConsumer(INPUT_CONSUMER_PIP, inputChannel);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to create PIP input consumer", e);
+ }
+ mInputEventReceiver = new PipInputEventReceiver(inputChannel, Looper.myLooper());
+ }
+
+ /**
+ * Unregisters the input consumer.
+ */
+ private void unregisterInputConsumer() {
+ try {
+ mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to destroy PIP input consumer", e);
+ }
+ mInputEventReceiver.dispose();
+ }
+
+ /**
+ * Sets the snap-to-edge state.
+ */
+ private void setSnapToEdge(boolean snapToEdge) {
+ mSnapAlgorithm.setSnapToEdge(snapToEdge);
+ try {
+ mPinnedStackController.setSnapToEdge(snapToEdge);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not set snap mode to edge", e);
+ }
+ }
+
+ /**
* Flings the PIP to the closest snap target.
*/
private void flingToSnapTarget(float velocity, float velocityX, float velocityY) {
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 9ae496f218da..45f54a9f8828 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -415,6 +415,12 @@ public class GnssLocationProvider implements LocationProviderInterface {
private int mYearOfHardware = 0;
+ // Set lower than the current ITAR limit of 600m/s to allow this to trigger even if GPS HAL
+ // stops output right at 600m/s, depriving this of the information of a device that reaches
+ // greater than 600m/s, and higher than the speed of sound to avoid impacting most use cases.
+ private static final float ITAR_SPEED_LIMIT_METERS_PER_SECOND = 400.0F;
+ private boolean mItarSpeedLimitExceeded = false;
+
private final IGnssStatusProvider mGnssStatusProvider = new IGnssStatusProvider.Stub() {
@Override
public void registerGnssStatusCallback(IGnssStatusListener callback) {
@@ -1400,6 +1406,12 @@ public class GnssLocationProvider implements LocationProviderInterface {
mStarted = true;
mSingleShot = singleShot;
mPositionMode = GPS_POSITION_MODE_STANDALONE;
+ // Notify about suppressed output, if speed limit was previously exceeded.
+ // Elsewhere, we check again with every speed output reported.
+ if (mItarSpeedLimitExceeded) {
+ Log.i(TAG, "startNavigating with ITAR limit in place. Output limited " +
+ "until slow enough speed reported.");
+ }
boolean agpsEnabled =
(Settings.Global.getInt(mContext.getContentResolver(),
@@ -1486,7 +1498,17 @@ public class GnssLocationProvider implements LocationProviderInterface {
* called from native code to update our position.
*/
private void reportLocation(int flags, double latitude, double longitude, double altitude,
- float speed, float bearing, float accuracy, long timestamp) {
+ float speedMetersPerSecond, float bearing, float accuracy, long timestamp) {
+ if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) {
+ mItarSpeedLimitExceeded = speedMetersPerSecond > ITAR_SPEED_LIMIT_METERS_PER_SECOND;
+ }
+
+ if (mItarSpeedLimitExceeded) {
+ Log.i(TAG, "Hal reported a speed in excess of ITAR limit." +
+ " GPS/GNSS Navigation output blocked.");
+ return; // No output of location allowed
+ }
+
if (VERBOSE) Log.v(TAG, "reportLocation lat: " + latitude + " long: " + longitude +
" timestamp: " + timestamp);
@@ -1506,7 +1528,7 @@ public class GnssLocationProvider implements LocationProviderInterface {
mLocation.removeAltitude();
}
if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) {
- mLocation.setSpeed(speed);
+ mLocation.setSpeed(speedMetersPerSecond);
} else {
mLocation.removeSpeed();
}
@@ -1690,23 +1712,29 @@ public class GnssLocationProvider implements LocationProviderInterface {
* called from native code to report NMEA data received
*/
private void reportNmea(long timestamp) {
- int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length);
- String nmea = new String(mNmeaBuffer, 0 /* offset */, length);
- mListenerHelper.onNmeaReceived(timestamp, nmea);
+ if (!mItarSpeedLimitExceeded) {
+ int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length);
+ String nmea = new String(mNmeaBuffer, 0 /* offset */, length);
+ mListenerHelper.onNmeaReceived(timestamp, nmea);
+ }
}
/**
* called from native code - Gps measurements callback
*/
private void reportMeasurementData(GnssMeasurementsEvent event) {
- mGnssMeasurementsProvider.onMeasurementsAvailable(event);
+ if (!mItarSpeedLimitExceeded) {
+ mGnssMeasurementsProvider.onMeasurementsAvailable(event);
+ }
}
/**
* called from native code - GPS navigation message callback
*/
private void reportNavigationMessage(GnssNavigationMessage event) {
- mGnssNavigationMessageProvider.onNavigationMessageAvailable(event);
+ if (!mItarSpeedLimitExceeded) {
+ mGnssNavigationMessageProvider.onNavigationMessageAvailable(event);
+ }
}
/**
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index d11d4de34256..18bf7a1e5460 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -151,8 +151,6 @@ public class RankingHelper implements RankingConfig {
if (TAG_PACKAGE.equals(tag)) {
int uid = safeInt(parser, ATT_UID, Record.UNKNOWN_UID);
String name = parser.getAttributeValue(null, ATT_NAME);
- int importance = safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
-
if (!TextUtils.isEmpty(name)) {
if (forRestore) {
try {
@@ -232,8 +230,8 @@ public class RankingHelper implements RankingConfig {
} else {
mRecords.put(key, r);
}
+ clampDefaultChannel(r);
}
- clampDefaultChannel(r);
return r;
}
@@ -733,7 +731,15 @@ public class RankingHelper implements RankingConfig {
// noop
}
}
+ try {
+ Record fullRecord = getRecord(pkg,
+ mPm.getPackageUidAsUser(pkg, UserHandle.USER_SYSTEM));
+ if (fullRecord != null) {
+ clampDefaultChannel(fullRecord);
+ }
+ } catch (NameNotFoundException e) {}
}
+
if (updated) {
updateConfig();
}
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 9f0f11a1443f..302f9f6c98ba 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -26,6 +26,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.XmlResourceParser;
+import android.database.ContentObserver;
import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -270,8 +271,21 @@ public class SystemImpl implements SystemInterface {
}
@Override
- public void setMultiprocessEnabled(boolean enabled) {
- WebViewZygote.setMultiprocessEnabled(enabled);
+ public void setMultiProcessEnabledFromContext(Context context) {
+ boolean enableMultiProcess = false;
+ try {
+ enableMultiProcess = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.WEBVIEW_MULTIPROCESS) == 1;
+ } catch (Settings.SettingNotFoundException ex) {
+ }
+ WebViewZygote.setMultiprocessEnabled(enableMultiProcess);
+ }
+
+ @Override
+ public void registerContentObserver(Context context, ContentObserver contentObserver) {
+ context.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.WEBVIEW_MULTIPROCESS),
+ false, contentObserver);
}
// flags declaring we want extra info from the package manager for webview providers
diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
index 7c934fc8cdf4..2d7a99872284 100644
--- a/services/core/java/com/android/server/webkit/SystemInterface.java
+++ b/services/core/java/com/android/server/webkit/SystemInterface.java
@@ -19,6 +19,7 @@ package com.android.server.webkit;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.ContentObserver;
import android.webkit.WebViewProviderInfo;
/**
@@ -49,5 +50,6 @@ public interface SystemInterface {
public PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
throws NameNotFoundException;
- public void setMultiprocessEnabled(boolean enabled);
+ public void setMultiProcessEnabledFromContext(Context context);
+ public void registerContentObserver(Context context, ContentObserver contentObserver);
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 57cd768a6257..453e7458b19c 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -20,12 +20,10 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
-import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Base64;
import android.util.Slog;
import android.webkit.WebViewFactory;
@@ -79,7 +77,7 @@ public class WebViewUpdateServiceImpl {
private SystemInterface mSystemInterface;
private WebViewUpdater mWebViewUpdater;
private SettingsObserver mSettingsObserver;
- private Context mContext;
+ final private Context mContext;
public WebViewUpdateServiceImpl(Context context, SystemInterface systemInterface) {
mContext = context;
@@ -725,15 +723,10 @@ public class WebViewUpdateServiceImpl {
* appropriately.
*/
private class SettingsObserver extends ContentObserver {
- private final ContentResolver mResolver;
-
SettingsObserver() {
super(new Handler());
- mResolver = mContext.getContentResolver();
- mResolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.WEBVIEW_MULTIPROCESS),
- false, this);
+ mSystemInterface.registerContentObserver(mContext, this);
// Push the current value of the setting immediately.
notifyZygote();
@@ -745,15 +738,7 @@ public class WebViewUpdateServiceImpl {
}
private void notifyZygote() {
- boolean enableMultiprocess = false;
-
- try {
- enableMultiprocess = Settings.Global.getInt(mResolver,
- Settings.Global.WEBVIEW_MULTIPROCESS) == 1;
- } catch (Settings.SettingNotFoundException ex) {
- }
-
- mSystemInterface.setMultiprocessEnabled(enableMultiprocess);
+ mSystemInterface.setMultiProcessEnabledFromContext(mContext);
}
}
}
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index faca8db3ab87..1ccf7229cc87 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -99,6 +99,13 @@ class PinnedStackController {
mInInteractiveMode = inInteractiveMode;
});
}
+
+ @Override
+ public void setSnapToEdge(final boolean snapToEdge) {
+ mHandler.post(() -> {
+ mSnapAlgorithm.setSnapToEdge(snapToEdge);
+ });
+ }
}
/**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6006c6bf6d3b..a156c4f968eb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9107,20 +9107,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private synchronized void disableDeviceOwnerManagedSingleUserFeaturesIfNeeded() {
+ if (!mOwners.hasDeviceOwner()) {
+ return;
+ }
if (!isDeviceOwnerManagedSingleUserDevice()) {
mInjector.securityLogSetLoggingEnabledProperty(false);
- Slog.w(LOG_TAG, "Security logging turned off as it's no longer a single user device.");
getDeviceOwnerAdminLocked().isNetworkLoggingEnabled = false;
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
setNetworkLoggingActiveInternal(false);
- Slog.w(LOG_TAG, "Network logging turned off as it's no longer a single user"
- + " device.");
- if (mOwners.hasDeviceOwner()) {
- setBackupServiceEnabledInternal(false);
- Slog.w(LOG_TAG, "Backup is off as it's a managed device that has more that one user.");
- }
+ setBackupServiceEnabledInternal(false);
+ Slog.w(LOG_TAG, "Security logging, network logging and backup service turned off as"
+ + " it's not a single user device.");
}
}
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index ad432d1ad18c..41e1f404c997 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -18,6 +18,7 @@ package com.android.server.notification;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+
import com.android.internal.util.FastXmlSerializer;
import org.mockito.Mock;
@@ -52,6 +53,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
@SmallTest
@@ -74,6 +76,8 @@ public class RankingHelperTest {
private RankingHelper mHelper;
private final String pkg = "com.android.server.notification";
private final int uid = 0;
+ private final String pkg2 = "pkg2";
+ private final int uid2 = 1111111;
private Context getContext() {
return InstrumentationRegistry.getTargetContext();
@@ -134,8 +138,11 @@ public class RankingHelperTest {
final ApplicationInfo legacy = new ApplicationInfo();
legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+ final ApplicationInfo upgrade = new ApplicationInfo();
+ upgrade.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
try {
- when(mPm.getApplicationInfo(anyString(), anyInt())).thenReturn(legacy);
+ when(mPm.getApplicationInfo(eq(pkg), anyInt())).thenReturn(legacy);
+ when(mPm.getApplicationInfo(eq(pkg2), anyInt())).thenReturn(upgrade);
} catch (PackageManager.NameNotFoundException e) {}
}
@@ -293,60 +300,156 @@ public class RankingHelperTest {
pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID).getImportance());
}
- // TODO: test with hardcoded N xml.
@Test
public void testChannelXml_upgradeCreateDefaultChannel() throws Exception {
- mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_HIGH);
- mHelper.setPriority(pkg, uid, Notification.PRIORITY_MAX);
- mHelper.setVisibilityOverride(pkg, uid, Notification.VISIBILITY_SECRET);
- // pre-O xml won't have channels.
- mHelper.deleteNotificationChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
-
- ByteArrayOutputStream baos =
- writeXmlAndPurge(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
-
+ final String preupgradeXml = "<ranking version=\"1\">\n"
+ + "<package name=\"" + pkg + "\" importance=\"" + NotificationManager.IMPORTANCE_HIGH
+ + "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
+ + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + uid + "\" />\n"
+ + "<package name=\"" + pkg2 + "\" uid=\"" + uid2 + "\" visibility=\""
+ + Notification.VISIBILITY_PRIVATE + "\" />\n"
+ + "</ranking>";
XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
- null);
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())),
+ null);
parser.nextTag();
mHelper.readXml(parser, false);
- final NotificationChannel updated =
- mHelper.getNotificationChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
- assertEquals(NotificationManager.IMPORTANCE_HIGH, updated.getImportance());
- assertTrue(updated.canBypassDnd());
- assertEquals(Notification.VISIBILITY_SECRET, updated.getLockscreenVisibility());
+ final NotificationChannel updated1 =
+ mHelper.getNotificationChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID);
+ assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
+ assertTrue(updated1.canBypassDnd());
+ assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
- | NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_VISIBILITY, updated.getUserLockedFields());
+ | NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_VISIBILITY, updated1.getUserLockedFields());
+
+ final NotificationChannel updated2 =
+ mHelper.getNotificationChannel(pkg2, uid2, NotificationChannel.DEFAULT_CHANNEL_ID);
+ // clamped
+ assertEquals(NotificationManager.IMPORTANCE_LOW, updated2.getImportance());
+ assertFalse(updated2.canBypassDnd());
+ assertEquals(Notification.VISIBILITY_PRIVATE, updated2.getLockscreenVisibility());
+ assertEquals(NotificationChannel.USER_LOCKED_VISIBILITY, updated2.getUserLockedFields());
}
- // TODO: test lock individually.
@Test
- public void testUpdate_userLockedChannelFields() throws Exception {
+ public void testUpdate_userLockedImportance() throws Exception {
// all fields locked by user
final NotificationChannel channel =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
- channel.setRingtone(new Uri.Builder().scheme("test").build());
- channel.setLights(true);
- channel.setBypassDnd(true);
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+
+ mHelper.createNotificationChannel(pkg, uid, channel);
+
+ // same id, try to update
+ final NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
+
+ mHelper.updateNotificationChannelFromRanker(pkg, uid, channel2);
+
+ // no fields should be changed
+ assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
+ }
+
+ @Test
+ public void testUpdate_userLockedVisibility() throws Exception {
+ // all fields locked by user
+ final NotificationChannel channel =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE
- | NotificationChannel.USER_LOCKED_VISIBILITY
- | NotificationChannel.USER_LOCKED_VIBRATION
- | NotificationChannel.USER_LOCKED_LIGHTS
- | NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_RINGTONE);
+ channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
+
+ mHelper.createNotificationChannel(pkg, uid, channel);
+
+ // same id, try to update
+ final NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
+ channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
+
+ mHelper.updateNotificationChannelFromRanker(pkg, uid, channel2);
+
+ // no fields should be changed
+ assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
+ }
+
+ @Test
+ public void testUpdate_userLockedVibration() throws Exception {
+ // all fields locked by user
+ final NotificationChannel channel =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ channel.setLights(false);
+ channel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
+
+ mHelper.createNotificationChannel(pkg, uid, channel);
+
+ // same id, try to update
+ final NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
+ channel2.setVibration(true);
+
+ mHelper.updateNotificationChannelFromRanker(pkg, uid, channel2);
+
+ // no fields should be changed
+ assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
+ }
+
+ @Test
+ public void testUpdate_userLockedLights() throws Exception {
+ // all fields locked by user
+ final NotificationChannel channel =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ channel.setLights(false);
+ channel.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
+
+ mHelper.createNotificationChannel(pkg, uid, channel);
+
+ // same id, try to update
+ final NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
+ channel2.setLights(true);
+
+ mHelper.updateNotificationChannelFromRanker(pkg, uid, channel2);
+
+ // no fields should be changed
+ assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
+ }
+
+ @Test
+ public void testUpdate_userLockedPriority() throws Exception {
+ // all fields locked by user
+ final NotificationChannel channel =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ channel.setBypassDnd(true);
+ channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
mHelper.createNotificationChannel(pkg, uid, channel);
// same id, try to update all fields
final NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
- channel2.setRingtone(new Uri.Builder().scheme("test2").build());
- channel2.setLights(false);
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setBypassDnd(false);
- channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
+
+ mHelper.updateNotificationChannelFromRanker(pkg, uid, channel2);
+
+ // no fields should be changed
+ assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId()));
+ }
+
+ @Test
+ public void testUpdate_userLockedRingtone() throws Exception {
+ // all fields locked by user
+ final NotificationChannel channel =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ channel.setRingtone(new Uri.Builder().scheme("test").build());
+ channel.lockFields(NotificationChannel.USER_LOCKED_RINGTONE);
+
+ mHelper.createNotificationChannel(pkg, uid, channel);
+
+ // same id, try to update all fields
+ final NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
+ channel2.setRingtone(new Uri.Builder().scheme("test2").build());
mHelper.updateNotificationChannelFromRanker(pkg, uid, channel2);
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index 763c50bbcd72..d2512ac335d8 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -19,6 +19,7 @@ package com.android.server.webkit;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.ContentObserver;
import android.webkit.WebViewProviderInfo;
import java.util.HashMap;
@@ -120,5 +121,8 @@ public class TestSystemImpl implements SystemInterface {
}
@Override
- public void setMultiprocessEnabled(boolean enabled) {}
+ public void setMultiProcessEnabledFromContext(Context context) {}
+
+ @Override
+ public void registerContentObserver(Context context, ContentObserver contentObserver) {}
}
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 0f898e594548..05194488b08a 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -16,27 +16,33 @@
package com.android.server.webkit;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.Signature;
import android.os.Bundle;
-import android.util.Base64;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.MediumTest;
-
+import android.util.Base64;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
-import java.util.concurrent.CountDownLatch;
-
import org.hamcrest.Description;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import org.mockito.Mockito;
import org.mockito.Matchers;
import org.mockito.ArgumentMatcher;
+import java.util.concurrent.CountDownLatch;
+
/**
* Tests for WebViewUpdateService
@@ -45,8 +51,9 @@ import org.mockito.ArgumentMatcher;
*/
// Use MediumTest instead of SmallTest as the implementation of WebViewUpdateService
// is intended to work on several threads and uses at least one sleep/wait-statement.
+@RunWith(AndroidJUnit4.class)
@MediumTest
-public class WebViewUpdateServiceTest extends AndroidTestCase {
+public class WebViewUpdateServiceTest {
private final static String TAG = WebViewUpdateServiceTest.class.getSimpleName();
private WebViewUpdateServiceImpl mWebViewUpdateServiceImpl;
@@ -54,11 +61,6 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
private static final String WEBVIEW_LIBRARY_FLAG = "com.android.webview.WebViewLibrary";
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- }
-
/**
* Creates a new instance.
*/
@@ -108,7 +110,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
// Add (enabled and valid) package infos for each provider
setEnabledAndValidPackageInfos(webviewPackages);
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
Mockito.verify(mTestSystemImpl).onWebViewProviderChanged(
Mockito.argThat(new IsPackageInfoWithName(expectedProviderName)));
@@ -205,12 +207,27 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
assertEquals(expectedPackage, response.packageInfo.packageName);
}
+ /**
+ * The WebView preparation boot phase is run on the main thread (especially on a thread with a
+ * looper) so to avoid bugs where our tests fail because a looper hasn't been attached to the
+ * thread running prepareWebViewInSystemServer we run it on the main thread.
+ */
+ private void runWebViewBootPreparationOnMainSync() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ }
+ });
+ }
+
// ****************
// Tests
// ****************
+ @Test
public void testWithSinglePackage() {
String testPackageName = "test.package.name";
checkCertainPackageUsedAfterWebViewBootPreparation(testPackageName,
@@ -219,6 +236,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
true /*default available*/, false /* fallback */, null)});
}
+ @Test
public void testDefaultPackageUsedOverNonDefault() {
String defaultPackage = "defaultPackage";
String nonDefaultPackage = "nonDefaultPackage";
@@ -228,6 +246,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
checkCertainPackageUsedAfterWebViewBootPreparation(defaultPackage, packages);
}
+ @Test
public void testSeveralRelros() {
String singlePackage = "singlePackage";
checkCertainPackageUsedAfterWebViewBootPreparation(
@@ -239,6 +258,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
// Ensure that package with valid signatures is chosen rather than package with invalid
// signatures.
+ @Test
public void testWithSignatures() {
String validPackage = "valid package";
String invalidPackage = "invalid package";
@@ -264,7 +284,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
true /* valid */, true /* installed */, new Signature[]{validSignature}
, 0 /* updateTime */));
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
checkPreparationPhasesForPackage(validPackage, 1 /* first preparation for this package */);
@@ -274,13 +294,14 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
assertEquals(validPackage, validPackages[0].packageName);
}
+ @Test
public void testFailWaitingForRelro() {
WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
new WebViewProviderInfo("packagename", "", true, true, null)};
setupWithPackages(packages);
setEnabledAndValidPackageInfos(packages);
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
Mockito.verify(mTestSystemImpl).onWebViewProviderChanged(
Mockito.argThat(new IsPackageInfoWithName(packages[0].packageName)));
@@ -291,12 +312,13 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
assertEquals(WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO, response.status);
}
+ @Test
public void testFailListingEmptyWebviewPackages() {
WebViewProviderInfo[] packages = new WebViewProviderInfo[0];
setupWithPackages(packages);
setEnabledAndValidPackageInfos(packages);
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged(
Matchers.anyObject());
@@ -330,6 +352,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage());
}
+ @Test
public void testFailListingInvalidWebviewPackage() {
WebViewProviderInfo wpi = new WebViewProviderInfo("package", "", true, true, null);
WebViewProviderInfo[] packages = new WebViewProviderInfo[] {wpi};
@@ -338,7 +361,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
createPackageInfo(wpi.packageName, true /* enabled */, false /* valid */,
true /* installed */));
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged(
Matchers.anyObject());
@@ -357,6 +380,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
}
// Test that switching provider using changeProviderAndSetting works.
+ @Test
public void testSwitchingProvider() {
String firstPackage = "first";
String secondPackage = "second";
@@ -366,6 +390,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
checkSwitchingProvider(packages, firstPackage, secondPackage);
}
+ @Test
public void testSwitchingProviderToNonDefault() {
String defaultPackage = "defaultPackage";
String nonDefaultPackage = "nonDefaultPackage";
@@ -386,11 +411,13 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
}
// Change provider during relro creation by using changeProviderAndSetting
+ @Test
public void testSwitchingProviderDuringRelroCreation() {
checkChangingProviderDuringRelroCreation(true);
}
// Change provider during relro creation by enabling a provider
+ @Test
public void testChangingProviderThroughEnablingDuringRelroCreation() {
checkChangingProviderDuringRelroCreation(false);
}
@@ -415,7 +442,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
CountDownLatch countdown = new CountDownLatch(1);
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
Mockito.verify(mTestSystemImpl).onWebViewProviderChanged(
Mockito.argThat(new IsPackageInfoWithName(firstPackage)));
@@ -463,10 +490,12 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
}
}
+ @Test
public void testRunFallbackLogicIfEnabled() {
checkFallbackLogicBeingRun(true);
}
+ @Test
public void testDontRunFallbackLogicIfDisabled() {
checkFallbackLogicBeingRun(false);
}
@@ -482,7 +511,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
setupWithPackages(packages, fallbackLogicEnabled);
setEnabledAndValidPackageInfos(packages);
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
// Verify that we disable the fallback package if fallback logic enabled, and don't disable
// the fallback package if that logic is disabled
if (fallbackLogicEnabled) {
@@ -518,6 +547,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
* 2. Install non-fallback
* 3. Fallback should be disabled
*/
+ @Test
public void testInstallingNonFallbackPackage() {
String primaryPackage = "primary";
String fallbackPackage = "fallback";
@@ -531,7 +561,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
createPackageInfo(fallbackPackage, true /* enabled */ , true /* valid */,
true /* installed */));
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
Mockito.verify(mTestSystemImpl, Mockito.never()).uninstallAndDisablePackageForAllUsers(
Matchers.anyObject(), Matchers.anyObject());
@@ -552,6 +582,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(fallbackPackage));
}
+ @Test
public void testFallbackChangesEnabledState() {
String primaryPackage = "primary";
String fallbackPackage = "fallback";
@@ -563,7 +594,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
setupWithPackages(packages, true /* fallbackLogicEnabled */);
setEnabledAndValidPackageInfos(packages);
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
// Verify fallback disabled at boot when primary package enabled
Mockito.verify(mTestSystemImpl).enablePackageForUser(
@@ -601,10 +632,12 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
checkPreparationPhasesForPackage(primaryPackage, 2);
}
+ @Test
public void testAddUserWhenFallbackLogicEnabled() {
checkAddingNewUser(true);
}
+ @Test
public void testAddUserWhenFallbackLogicDisabled() {
checkAddingNewUser(false);
}
@@ -638,6 +671,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
* Timing dependent test where we verify that the list of valid webview packages becoming empty
* at a certain point doesn't crash us or break our state.
*/
+ @Test
public void testNotifyRelroDoesntCrashIfNoPackages() {
String firstPackage = "first";
String secondPackage = "second";
@@ -650,7 +684,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
// Add (enabled and valid) package infos for each provider
setEnabledAndValidPackageInfos(packages);
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
Mockito.verify(mTestSystemImpl).onWebViewProviderChanged(
Mockito.argThat(new IsPackageInfoWithName(firstPackage)));
@@ -689,6 +723,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
* Verify that even if a user-chosen package is removed temporarily we start using it again when
* it is added back.
*/
+ @Test
public void testTempRemovePackageDoesntSwitchProviderPermanently() {
String firstPackage = "first";
String secondPackage = "second";
@@ -722,6 +757,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
* Ensure that we update the user-chosen setting across boots if the chosen package is no
* longer installed and valid.
*/
+ @Test
public void testProviderSettingChangedDuringBootIfProviderNotAvailable() {
String chosenPackage = "chosenPackage";
String nonChosenPackage = "non-chosenPackage";
@@ -739,7 +775,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
// Set user-chosen package
mTestSystemImpl.updateUserSetting(null, chosenPackage);
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
// Verify that we switch the setting to point to the current package
Mockito.verify(mTestSystemImpl).updateUserSetting(
@@ -749,10 +785,12 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
checkPreparationPhasesForPackage(nonChosenPackage, 1);
}
+ @Test
public void testRecoverFailedListingWebViewPackagesSettingsChange() {
checkRecoverAfterFailListingWebviewPackages(true);
}
+ @Test
public void testRecoverFailedListingWebViewPackagesAddedPackage() {
checkRecoverAfterFailListingWebviewPackages(false);
}
@@ -799,10 +837,12 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
checkPreparationPhasesForPackage(secondPackage, 1);
}
+ @Test
public void testDontKillIfPackageReplaced() {
checkDontKillIfPackageRemoved(true);
}
+ @Test
public void testDontKillIfPackageRemoved() {
checkDontKillIfPackageRemoved(false);
}
@@ -836,6 +876,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
Mockito.anyObject());
}
+ @Test
public void testKillIfSettingChanged() {
String firstPackage = "first";
String secondPackage = "second";
@@ -857,6 +898,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
* Test that we kill apps using an old provider when we change the provider setting, even if the
* new provider is not the one we intended to change to.
*/
+ @Test
public void testKillIfChangeProviderIncorrectly() {
String firstPackage = "first";
String secondPackage = "second";
@@ -874,7 +916,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
// Start with the setting pointing to the third package
mTestSystemImpl.updateUserSetting(null, thirdPackage);
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
checkPreparationPhasesForPackage(thirdPackage, 1);
mTestSystemImpl.setPackageInfo(
@@ -890,52 +932,62 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(thirdPackage));
}
+ @Test
public void testLowerPackageVersionNotValid() {
checkPackageVersions(new int[]{200000} /* system version */, 100000/* candidate version */,
false /* expected validity */);
}
+ @Test
public void testEqualPackageVersionValid() {
checkPackageVersions(new int[]{100000} /* system version */, 100000 /* candidate version */,
true /* expected validity */);
}
+ @Test
public void testGreaterPackageVersionValid() {
checkPackageVersions(new int[]{100000} /* system versions */, 200000 /* candidate version */,
true /* expected validity */);
}
+ @Test
public void testLastFiveDigitsIgnored() {
checkPackageVersions(new int[]{654321} /* system version */, 612345 /* candidate version */,
true /* expected validity */);
}
+ @Test
public void testMinimumSystemVersionUsedTwoDefaultsCandidateValid() {
checkPackageVersions(new int[]{300000, 100000} /* system versions */,
200000 /* candidate version */, true /* expected validity */);
}
+ @Test
public void testMinimumSystemVersionUsedTwoDefaultsCandidateInvalid() {
checkPackageVersions(new int[]{300000, 200000} /* system versions */,
100000 /* candidate version */, false /* expected validity */);
}
+ @Test
public void testMinimumSystemVersionUsedSeveralDefaultsCandidateValid() {
checkPackageVersions(new int[]{100000, 200000, 300000, 400000, 500000} /* system versions */,
100000 /* candidate version */, true /* expected validity */);
}
+ @Test
public void testMinimumSystemVersionUsedSeveralDefaultsCandidateInvalid() {
checkPackageVersions(new int[]{200000, 300000, 400000, 500000, 600000} /* system versions */,
100000 /* candidate version */, false /* expected validity */);
}
+ @Test
public void testMinimumSystemVersionUsedFallbackIgnored() {
checkPackageVersions(new int[]{300000, 400000, 500000, 600000, 700000} /* system versions */,
200000 /* candidate version */, false /* expected validity */, true /* add fallback */,
100000 /* fallback version */, false /* expected validity of fallback */);
}
+ @Test
public void testFallbackValid() {
checkPackageVersions(new int[]{300000, 400000, 500000, 600000, 700000} /* system versions */,
200000/* candidate version */, false /* expected validity */, true /* add fallback */,
@@ -1030,7 +1082,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
assertEquals(expectedNumValidPackages, validPackages.length);
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
// The non-system package is not available by default so it shouldn't be used here
checkPreparationPhasesForPackage(systemPackage + "1", 1);
@@ -1048,6 +1100,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
// Ensure that the update service uses an uninstalled package if that is the only package
// available.
+ @Test
public void testWithSingleUninstalledPackage() {
String testPackageName = "test.package.name";
WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] {
@@ -1057,15 +1110,17 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
mTestSystemImpl.setPackageInfo(createPackageInfo(testPackageName, true /* enabled */,
true /* valid */, false /* installed */));
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
checkPreparationPhasesForPackage(testPackageName, 1 /* first preparation phase */);
}
+ @Test
public void testNonhiddenPackageUserOverHidden() {
checkVisiblePackageUserOverNonVisible(false /* true == uninstalled, false == hidden */);
}
+ @Test
public void testInstalledPackageUsedOverUninstalled() {
checkVisiblePackageUserOverNonVisible(true /* true == uninstalled, false == hidden */);
}
@@ -1088,16 +1143,18 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
true /* valid */, (testUninstalled ? false : true) /* installed */,
null /* signatures */, 0 /* updateTime */, (testHidden ? true : false)));
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */);
}
+ @Test
public void testCantSwitchToHiddenPackage () {
checkCantSwitchToNonVisiblePackage(false /* true == uninstalled, false == hidden */);
}
+ @Test
public void testCantSwitchToUninstalledPackage () {
checkCantSwitchToNonVisiblePackage(true /* true == uninstalled, false == hidden */);
}
@@ -1126,7 +1183,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
null /* signatures */, 0 /* updateTime */,
(testHidden ? true : false) /* hidden */));
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */);
@@ -1146,11 +1203,13 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
Mockito.argThat(new IsPackageInfoWithName(installedPackage)));
}
+ @Test
public void testHiddenPackageNotPrioritizedEvenIfChosen() {
checkNonvisiblePackageNotPrioritizedEvenIfChosen(
false /* true == uninstalled, false == hidden */);
}
+ @Test
public void testUninstalledPackageNotPrioritizedEvenIfChosen() {
checkNonvisiblePackageNotPrioritizedEvenIfChosen(
true /* true == uninstalled, false == hidden */);
@@ -1178,7 +1237,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
// Start with the setting pointing to the uninstalled package
mTestSystemImpl.updateUserSetting(null, uninstalledPackage);
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */);
}
@@ -1187,6 +1246,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
* Ensures that fallback becomes enabled if the primary package is uninstalled for the current
* user.
*/
+ @Test
public void testFallbackEnabledIfPrimaryUninstalled() {
String primaryPackage = "primary";
String fallbackPackage = "fallback";
@@ -1201,7 +1261,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */,
true /* valid */, true /* installed */));
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
// Verify that we enable the fallback package
Mockito.verify(mTestSystemImpl).enablePackageForAllUsers(
Mockito.anyObject(), Mockito.eq(fallbackPackage), Mockito.eq(true) /* enable */);
@@ -1209,6 +1269,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
checkPreparationPhasesForPackage(fallbackPackage, 1 /* first preparation phase */);
}
+ @Test
public void testPreparationRunsIffNewPackage() {
String primaryPackage = "primary";
String fallbackPackage = "fallback";
@@ -1224,7 +1285,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */,
true /* valid */, true /* installed */));
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation phase */);
Mockito.verify(mTestSystemImpl, Mockito.times(1)).enablePackageForUser(
@@ -1270,6 +1331,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
checkPreparationPhasesForPackage(primaryPackage, 3 /* third preparation phase */);
}
+ @Test
public void testGetCurrentWebViewPackage() {
PackageInfo firstPackage = createPackageInfo("first", true /* enabled */,
true /* valid */, true /* installed */);
@@ -1280,7 +1342,7 @@ public class WebViewUpdateServiceTest extends AndroidTestCase {
setupWithPackages(packages, true);
mTestSystemImpl.setPackageInfo(firstPackage);
- mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+ runWebViewBootPreparationOnMainSync();
Mockito.verify(mTestSystemImpl).onWebViewProviderChanged(
Mockito.argThat(new IsPackageInfoWithName(firstPackage.packageName)));
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 197884dcf85c..1efd2ed6446c 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -194,14 +194,14 @@ include $(BUILD_HOST_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libaapt2_jni
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
-LOCAL_MODULE_HOST_OS := darwin linux
+LOCAL_MODULE_HOST_OS := darwin linux windows
LOCAL_CFLAGS := $(cFlags)
LOCAL_CFLAGS_darwin := $(cFlags_darwin)
LOCAL_CFLAGS_windows := $(cFlags_windows)
LOCAL_CPPFLAGS := $(cppFlags)
LOCAL_C_INCLUDES := $(protoIncludes)
LOCAL_SRC_FILES := $(toolSources) $(sourcesJni)
-LOCAL_STATIC_LIBRARIES := libaapt2 libnativehelper $(hostStaticLibs)
+LOCAL_STATIC_LIBRARIES := libaapt2 $(hostStaticLibs)
LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows)
LOCAL_LDLIBS := $(hostLdLibs)
LOCAL_LDLIBS_darwin := $(hostLdLibs_darwin)
diff --git a/tools/aapt2/jni/ScopedUtfChars.h b/tools/aapt2/jni/ScopedUtfChars.h
new file mode 100644
index 000000000000..a8c4b136dcf6
--- /dev/null
+++ b/tools/aapt2/jni/ScopedUtfChars.h
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+#ifndef SCOPED_UTF_CHARS_H_included
+#define SCOPED_UTF_CHARS_H_included
+
+#include <string.h>
+#include <jni.h>
+
+#include "android-base/logging.h"
+
+// This file was copied with some minor modifications from libnativehelper.
+// As soon as libnativehelper can be compiled for Windows, this file should be
+// replaced with libnativehelper's implementation.
+class ScopedUtfChars {
+ public:
+ ScopedUtfChars(JNIEnv* env, jstring s) : env_(env), string_(s) {
+ CHECK(s != nullptr);
+ utf_chars_ = env->GetStringUTFChars(s, nullptr);
+ }
+
+ ScopedUtfChars(ScopedUtfChars&& rhs) :
+ env_(rhs.env_), string_(rhs.string_), utf_chars_(rhs.utf_chars_) {
+ rhs.env_ = nullptr;
+ rhs.string_ = nullptr;
+ rhs.utf_chars_ = nullptr;
+ }
+
+ ~ScopedUtfChars() {
+ if (utf_chars_) {
+ env_->ReleaseStringUTFChars(string_, utf_chars_);
+ }
+ }
+
+ ScopedUtfChars& operator=(ScopedUtfChars&& rhs) {
+ if (this != &rhs) {
+ // Delete the currently owned UTF chars.
+ this->~ScopedUtfChars();
+
+ // Move the rhs ScopedUtfChars and zero it out.
+ env_ = rhs.env_;
+ string_ = rhs.string_;
+ utf_chars_ = rhs.utf_chars_;
+ rhs.env_ = nullptr;
+ rhs.string_ = nullptr;
+ rhs.utf_chars_ = nullptr;
+ }
+ return *this;
+ }
+
+ const char* c_str() const {
+ return utf_chars_;
+ }
+
+ size_t size() const {
+ return strlen(utf_chars_);
+ }
+
+ const char& operator[](size_t n) const {
+ return utf_chars_[n];
+ }
+
+ private:
+ JNIEnv* env_;
+ jstring string_;
+ const char* utf_chars_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedUtfChars);
+};
+
+#endif // SCOPED_UTF_CHARS_H_included
diff --git a/tools/aapt2/jni/aapt2_jni.cpp b/tools/aapt2/jni/aapt2_jni.cpp
index a5e09a2c9d57..24e83609c35a 100644
--- a/tools/aapt2/jni/aapt2_jni.cpp
+++ b/tools/aapt2/jni/aapt2_jni.cpp
@@ -22,7 +22,7 @@
#include <vector>
#include "android-base/logging.h"
-#include "nativehelper/ScopedUtfChars.h"
+#include "ScopedUtfChars.h"
#include "util/Util.h"