diff options
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" |