diff options
11 files changed, 235 insertions, 9 deletions
diff --git a/api/current.txt b/api/current.txt index 527d3644435c..08f5d003eefc 100644 --- a/api/current.txt +++ b/api/current.txt @@ -33160,6 +33160,7 @@ package android.os { method public android.os.StrictMode.VmPolicy.Builder detectLeakedClosableObjects(); method public android.os.StrictMode.VmPolicy.Builder detectLeakedRegistrationObjects(); method public android.os.StrictMode.VmPolicy.Builder detectLeakedSqlLiteObjects(); + method public android.os.StrictMode.VmPolicy.Builder detectNonSdkApiUsage(); method public android.os.StrictMode.VmPolicy.Builder detectUntaggedSockets(); method public android.os.StrictMode.VmPolicy.Builder penaltyDeath(); method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnCleartextNetwork(); @@ -33167,6 +33168,7 @@ package android.os { method public android.os.StrictMode.VmPolicy.Builder penaltyDropBox(); method public android.os.StrictMode.VmPolicy.Builder penaltyListener(java.util.concurrent.Executor, android.os.StrictMode.OnVmViolationListener); method public android.os.StrictMode.VmPolicy.Builder penaltyLog(); + method public android.os.StrictMode.VmPolicy.Builder permitNonSdkApiUsage(); method public android.os.StrictMode.VmPolicy.Builder setClassInstanceLimit(java.lang.Class, int); } @@ -33581,6 +33583,9 @@ package android.os.strictmode { public final class NetworkViolation extends android.os.strictmode.Violation { } + public final class NonSdkApiUsedViolation extends android.os.strictmode.Violation { + } + public final class ResourceMismatchViolation extends android.os.strictmode.Violation { } diff --git a/api/test-current.txt b/api/test-current.txt index 9f5fa0cb0863..9153a39d874a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -527,6 +527,7 @@ package android.os { field public static final int DETECT_VM_CURSOR_LEAKS = 256; // 0x100 field public static final int DETECT_VM_FILE_URI_EXPOSURE = 8192; // 0x2000 field public static final int DETECT_VM_INSTANCE_LEAKS = 2048; // 0x800 + field public static final int DETECT_VM_NON_SDK_API_USAGE = 1073741824; // 0x40000000 field public static final int DETECT_VM_REGISTRATION_LEAKS = 4096; // 0x1000 field public static final int DETECT_VM_UNTAGGED_SOCKET = -2147483648; // 0x80000000 } diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index a93e25aa6d89..59380fd3d06b 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -39,6 +39,7 @@ import android.os.strictmode.InstanceCountViolation; import android.os.strictmode.IntentReceiverLeakedViolation; import android.os.strictmode.LeakedClosableViolation; import android.os.strictmode.NetworkViolation; +import android.os.strictmode.NonSdkApiUsedViolation; import android.os.strictmode.ResourceMismatchViolation; import android.os.strictmode.ServiceConnectionLeakedViolation; import android.os.strictmode.SqliteObjectLeakedViolation; @@ -76,6 +77,7 @@ import java.util.HashMap; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; /** * StrictMode is a developer tool which detects things you might be doing by accident and brings @@ -262,6 +264,9 @@ public final class StrictMode { /** @hide */ @TestApi public static final int DETECT_VM_UNTAGGED_SOCKET = 0x80 << 24; // for VmPolicy + /** @hide */ + @TestApi public static final int DETECT_VM_NON_SDK_API_USAGE = 0x40 << 24; // for VmPolicy + private static final int ALL_VM_DETECT_BITS = DETECT_VM_CURSOR_LEAKS | DETECT_VM_CLOSABLE_LEAKS @@ -271,7 +276,9 @@ public final class StrictMode { | DETECT_VM_FILE_URI_EXPOSURE | DETECT_VM_CLEARTEXT_NETWORK | DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION - | DETECT_VM_UNTAGGED_SOCKET; + | DETECT_VM_UNTAGGED_SOCKET + | DETECT_VM_NON_SDK_API_USAGE; + // Byte 3: Penalty @@ -413,6 +420,13 @@ public final class StrictMode { */ private static final AtomicInteger sDropboxCallsInFlight = new AtomicInteger(0); + /** + * Callback supplied to dalvik / libcore to get informed of usages of java API that are not + * a part of the public SDK. + */ + private static final Consumer<String> sNonSdkApiUsageConsumer = + message -> onVmPolicyViolation(new NonSdkApiUsedViolation(message)); + private StrictMode() {} /** @@ -796,6 +810,23 @@ public final class StrictMode { } /** + * Detect reflective usage of APIs that are not part of the public Android SDK. + */ + public Builder detectNonSdkApiUsage() { + return enable(DETECT_VM_NON_SDK_API_USAGE); + } + + /** + * Permit reflective usage of APIs that are not part of the public Android SDK. Note + * that this <b>only</b> affects {@code StrictMode}, the underlying runtime may + * continue to restrict or warn on access to methods that are not part of the + * public SDK. + */ + public Builder permitNonSdkApiUsage() { + return disable(DETECT_VM_NON_SDK_API_USAGE); + } + + /** * Detect everything that's potentially suspect. * * <p>In the Honeycomb release this includes leaks of SQLite cursors, Activities, and @@ -826,6 +857,8 @@ public final class StrictMode { detectContentUriWithoutPermission(); detectUntaggedSockets(); } + + // TODO: Decide whether to detect non SDK API usage beyond a certain API level. return this; } @@ -1848,6 +1881,13 @@ public final class StrictMode { } else if (networkPolicy != NETWORK_POLICY_ACCEPT) { Log.w(TAG, "Dropping requested network policy due to missing service!"); } + + + if ((sVmPolicy.mask & DETECT_VM_NON_SDK_API_USAGE) != 0) { + VMRuntime.setNonSdkApiUsageConsumer(sNonSdkApiUsageConsumer); + } else { + VMRuntime.setNonSdkApiUsageConsumer(null); + } } } @@ -2576,6 +2616,8 @@ public final class StrictMode { return DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION; } else if (mViolation instanceof UntaggedSocketViolation) { return DETECT_VM_UNTAGGED_SOCKET; + } else if (mViolation instanceof NonSdkApiUsedViolation) { + return DETECT_VM_NON_SDK_API_USAGE; } throw new IllegalStateException("missing violation bit"); } diff --git a/core/java/android/os/strictmode/NonSdkApiUsedViolation.java b/core/java/android/os/strictmode/NonSdkApiUsedViolation.java new file mode 100644 index 000000000000..2f0cb50cdfc4 --- /dev/null +++ b/core/java/android/os/strictmode/NonSdkApiUsedViolation.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 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.os.strictmode; + +/** + * Subclass of {@code Violation} that is used when a process accesses + * a non SDK API. + */ +public final class NonSdkApiUsedViolation extends Violation { + /** @hide */ + public NonSdkApiUsedViolation(String message) { + super(message); + } +} diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 31abf9223e98..4210c5caa690 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -23,8 +23,10 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.AnyThread; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.Px; import android.annotation.TestApi; import android.annotation.WorkerThread; import android.content.ContentResolver; @@ -1020,10 +1022,11 @@ public final class ImageDecoder implements AutoCloseable { * <p>Like all setters on ImageDecoder, this must be called inside * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> * - * @param width must be greater than 0. - * @param height must be greater than 0. + * @param width width in pixels of the output, must be greater than 0 + * @param height height in pixels of the output, must be greater than 0 */ - public void setTargetSize(int width, int height) { + public void setTargetSize(@Px @IntRange(from = 1) int width, + @Px @IntRange(from = 1) int height) { if (width <= 0 || height <= 0) { throw new IllegalArgumentException("Dimensions must be positive! " + "provided (" + width + ", " + height + ")"); @@ -1083,14 +1086,20 @@ public final class ImageDecoder implements AutoCloseable { * * <p>Must be greater than or equal to 1.</p> * + * <p>This has the same effect as calling {@link #setTargetSize} with + * dimensions based on the {@code sampleSize}. Unlike dividing the original + * width and height by the {@code sampleSize} manually, calling this method + * allows {@code ImageDecoder} to round in the direction that it can do most + * efficiently.</p> + * * <p>Only the last call to this or {@link #setTargetSize} is respected.</p> * * <p>Like all setters on ImageDecoder, this must be called inside * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> * - * @param sampleSize Sampling rate of the encoded image. + * @param sampleSize sampling rate of the encoded image. */ - public void setTargetSampleSize(int sampleSize) { + public void setTargetSampleSize(@IntRange(from = 1) int sampleSize) { Size size = this.getSampledSize(sampleSize); int targetWidth = getTargetDimension(mWidth, sampleSize, size.getWidth()); int targetHeight = getTargetDimension(mHeight, sampleSize, size.getHeight()); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 039e7b5a6613..0ba26e94ce1e 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -44,6 +44,7 @@ import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBouncer; +import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LockIcon; import com.android.systemui.statusbar.phone.LockscreenWallpaper; @@ -142,5 +143,6 @@ public class SystemUIFactory { providers.put(NotificationViewHierarchyManager.class, () -> new NotificationViewHierarchyManager(context)); providers.put(NotificationEntryManager.class, () -> new NotificationEntryManager(context)); + providers.put(KeyguardDismissUtil.class, KeyguardDismissUtil::new); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissHandler.java new file mode 100644 index 000000000000..759a0d173cdd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissHandler.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 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.statusbar.phone; + +import android.annotation.Nullable; + +import com.android.keyguard.KeyguardHostView.OnDismissAction; + + +/** Executes actions that require the screen to be unlocked. */ +public interface KeyguardDismissHandler { + /** Executes an action that requres the screen to be unlocked. */ + void dismissKeyguardThenExecute( + OnDismissAction action, @Nullable Runnable cancelAction, boolean afterKeyguardGone); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java new file mode 100644 index 000000000000..c38b0b63190d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 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.statusbar.phone; + +import android.util.Log; + +import com.android.keyguard.KeyguardHostView.OnDismissAction; + +/** + * Executes actions that require the screen to be unlocked. Delegates the actual handling to an + * implementation passed via {@link #setDismissHandler}. + */ +public class KeyguardDismissUtil implements KeyguardDismissHandler { + private static final String TAG = "KeyguardDismissUtil"; + + private volatile KeyguardDismissHandler mDismissHandler; + + /** Sets the actual {@link DismissHandler} implementation. */ + public void setDismissHandler(KeyguardDismissHandler dismissHandler) { + mDismissHandler = dismissHandler; + } + + /** + * Executes an action that requres the screen to be unlocked. + * + * <p>Must be called after {@link #setDismissHandler}. + */ + @Override + public void dismissKeyguardThenExecute( + OnDismissAction action, Runnable cancelAction, boolean afterKeyguardGone) { + KeyguardDismissHandler dismissHandler = mDismissHandler; + if (dismissHandler == null) { + Log.wtf(TAG, "KeyguardDismissHandler not set."); + action.onDismiss(); + return; + } + dismissHandler.dismissKeyguardThenExecute(action, cancelAction, afterKeyguardGone); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 340b462d2540..7987bfd9bfb8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -213,6 +213,7 @@ import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener; +import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; import com.android.systemui.statusbar.policy.BrightnessMirrorController; @@ -1300,6 +1301,8 @@ public class StatusBar extends SystemUI implements DemoMode, mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback(); mLightBarController.setFingerprintUnlockController(mFingerprintUnlockController); + Dependency.get(KeyguardDismissUtil.class).setDismissHandler( + this::dismissKeyguardThenExecute); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java index 790135fc03ca..74b39268fc2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -20,8 +20,10 @@ import android.view.ViewGroup; import android.widget.Button; import com.android.internal.annotations.VisibleForTesting; +import com.android.keyguard.KeyguardHostView.OnDismissAction; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import java.text.BreakIterator; import java.util.Comparator; @@ -42,6 +44,7 @@ public class SmartReplyView extends ViewGroup { private static final int SQUEEZE_FAILED = -1; private final SmartReplyConstants mConstants; + private final KeyguardDismissUtil mKeyguardDismissUtil; /** Spacing to be applied between views. */ private final int mSpacing; @@ -62,6 +65,7 @@ public class SmartReplyView extends ViewGroup { public SmartReplyView(Context context, AttributeSet attrs) { super(context, attrs); mConstants = Dependency.get(SmartReplyConstants.class); + mKeyguardDismissUtil = Dependency.get(KeyguardDismissUtil.class); int spacing = 0; int singleLineButtonPaddingHorizontal = 0; @@ -126,12 +130,13 @@ public class SmartReplyView extends ViewGroup { } @VisibleForTesting - static Button inflateReplyButton(Context context, ViewGroup root, CharSequence choice, + Button inflateReplyButton(Context context, ViewGroup root, CharSequence choice, RemoteInput remoteInput, PendingIntent pendingIntent) { Button b = (Button) LayoutInflater.from(context).inflate( R.layout.smart_reply_button, root, false); b.setText(choice); - b.setOnClickListener(view -> { + + OnDismissAction action = () -> { Bundle results = new Bundle(); results.putString(remoteInput.getResultKey(), choice.toString()); Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); @@ -142,6 +147,12 @@ public class SmartReplyView extends ViewGroup { } catch (PendingIntent.CanceledException e) { Log.w(TAG, "Unable to send smart reply", e); } + return false; // do not defer + }; + + b.setOnClickListener(view -> { + mKeyguardDismissUtil.dismissKeyguardThenExecute( + action, null /* cancelAction */, false /* afterKeyguardGone */); }); return b; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java index a9b2c96d3ae1..56882c654b33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java @@ -18,6 +18,7 @@ import static android.view.View.MeasureSpec; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; @@ -34,8 +35,12 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; +import com.android.keyguard.KeyguardHostView.OnDismissAction; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.phone.KeyguardDismissUtil; + +import java.util.concurrent.atomic.AtomicReference; import org.junit.After; import org.junit.Before; @@ -65,6 +70,8 @@ public class SmartReplyViewTest extends SysuiTestCase { public void setUp() { mReceiver = new BlockingQueueIntentReceiver(); mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION)); + mDependency.get(KeyguardDismissUtil.class).setDismissHandler( + (action, cancelAction, afterKeyguardGone) -> action.onDismiss()); mView = SmartReplyView.inflate(mContext, null); @@ -95,6 +102,42 @@ public class SmartReplyViewTest extends SysuiTestCase { } @Test + public void testSendSmartReply_keyguardCancelled() throws InterruptedException { + mDependency.get(KeyguardDismissUtil.class).setDismissHandler( + (action, cancelAction, afterKeyguardGone) -> { + if (cancelAction != null) { + cancelAction.run(); + } + }); + setRepliesFromRemoteInput(TEST_CHOICES); + + mView.getChildAt(2).performClick(); + + assertNull(mReceiver.waitForIntent()); + } + + @Test + public void testSendSmartReply_waitsForKeyguard() throws InterruptedException { + AtomicReference<OnDismissAction> actionRef = new AtomicReference<>(); + mDependency.get(KeyguardDismissUtil.class).setDismissHandler( + (action, cancelAction, afterKeyguardGone) -> actionRef.set(action)); + setRepliesFromRemoteInput(TEST_CHOICES); + + mView.getChildAt(2).performClick(); + + // No intent until the screen is unlocked. + assertNull(mReceiver.waitForIntent()); + + actionRef.get().onDismiss(); + + // Now the intent should arrive. + Intent resultIntent = mReceiver.waitForIntent(); + assertEquals(TEST_CHOICES[2], + RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY)); + assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(resultIntent)); + } + + @Test public void testMeasure_empty() { mView.measure(WIDTH_SPEC, HEIGHT_SPEC); assertEquals(500, mView.getMeasuredWidthAndState()); @@ -301,7 +344,7 @@ public class SmartReplyViewTest extends SysuiTestCase { Button previous = null; for (CharSequence choice : choices) { - Button current = SmartReplyView.inflateReplyButton(mContext, mView, choice, null, null); + Button current = mView.inflateReplyButton(mContext, mView, choice, null, null); current.setPadding(paddingHorizontal, current.getPaddingTop(), paddingHorizontal, current.getPaddingBottom()); if (previous != null) { |