diff options
647 files changed, 25613 insertions, 6814 deletions
diff --git a/Android.mk b/Android.mk index e17fd0b6e265..88be12f23a77 100644 --- a/Android.mk +++ b/Android.mk @@ -38,7 +38,9 @@ ifneq ($(ANDROID_BUILD_EMBEDDED),true) include $(CLEAR_VARS) # FRAMEWORKS_BASE_SUBDIRS comes from build/core/pathmap.mk -LOCAL_SRC_FILES := $(call find-other-java-files,$(FRAMEWORKS_BASE_SUBDIRS)) +LOCAL_SRC_FILES := \ + $(call find-other-java-files,$(FRAMEWORKS_BASE_SUBDIRS)) \ + $(call all-proto-files-under, core/proto) # EventLogTags files. LOCAL_SRC_FILES += \ @@ -145,6 +147,7 @@ LOCAL_SRC_FILES += \ core/java/android/content/ISyncStatusObserver.aidl \ core/java/android/content/pm/ILauncherApps.aidl \ core/java/android/content/pm/IOnAppsChangedListener.aidl \ + core/java/android/content/pm/IOnPermissionsChangeListener.aidl \ core/java/android/content/pm/IOtaDexopt.aidl \ core/java/android/content/pm/IPackageDataObserver.aidl \ core/java/android/content/pm/IPackageDeleteObserver.aidl \ @@ -157,7 +160,7 @@ LOCAL_SRC_FILES += \ core/java/android/content/pm/IPackageManager.aidl \ core/java/android/content/pm/IPackageMoveObserver.aidl \ core/java/android/content/pm/IPackageStatsObserver.aidl \ - core/java/android/content/pm/IOnPermissionsChangeListener.aidl \ + core/java/android/content/pm/IPinItemRequest.aidl \ core/java/android/content/pm/IShortcutService.aidl \ core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl \ core/java/android/database/IContentObserver.aidl \ @@ -233,6 +236,9 @@ LOCAL_SRC_FILES += \ core/java/android/os/IDeviceIdentifiersPolicyService.aidl \ core/java/android/os/IDeviceIdleController.aidl \ core/java/android/os/IHardwarePropertiesManager.aidl \ + core/java/android/os/IIncidentManager.aidl \ + core/java/android/os/IIncidentReportCompletedListener.aidl \ + core/java/android/os/IIncidentReportStatusListener.aidl \ core/java/android/os/IMaintenanceActivityListener.aidl \ core/java/android/os/IMessenger.aidl \ core/java/android/os/INetworkActivityListener.aidl \ @@ -527,6 +533,10 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ android.hardware.thermal@1.0-java-constants \ android.hardware.health@1.0-java-constants \ +LOCAL_PROTOC_OPTIMIZE_TYPE := stream +LOCAL_PROTOC_FLAGS := \ + -Iexternal/protobuf/src + LOCAL_MODULE := framework LOCAL_JACK_FLAGS := --multi-dex native @@ -774,9 +784,6 @@ fwbase_dirs_to_document := \ # include definition of libcore_to_document include libcore/Docs.mk -# include definition of junit_to_document -include external/junit/Common.mk - non_base_dirs := \ ../opt/telephony/src/java/android/provider \ ../opt/telephony/src/java/android/telephony \ @@ -817,8 +824,7 @@ html_dirs := \ # Common sources for doc check and api check common_src_files := \ $(call find-other-html-files, $(html_dirs)) \ - $(addprefix ../../, $(libcore_to_document)) \ - $(addprefix ../../external/junit/, $(junit_to_document)) + $(addprefix ../../, $(libcore_to_document)) # These are relative to frameworks/base framework_docs_LOCAL_SRC_FILES := \ @@ -1388,6 +1394,35 @@ endif include $(BUILD_JAVA_LIBRARY) +# ==== c++ proto host library ============================== +include $(CLEAR_VARS) +LOCAL_MODULE := libplatformprotos +LOCAL_PROTOC_OPTIMIZE_TYPE := full +LOCAL_PROTOC_FLAGS := \ + --include_source_info \ + -Iexternal/protobuf/src +LOCAL_SRC_FILES := \ + $(call all-proto-files-under, core/proto) \ + $(call all-proto-files-under, libs/incident/proto) +LOCAL_C_INCLUDES := \ + $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto +LOCAL_EXPORT_C_INCLUDES := \ + $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto +include $(BUILD_HOST_SHARED_LIBRARY) + + +# ==== java proto host library ============================== +include $(CLEAR_VARS) +LOCAL_MODULE := platformprotos +LOCAL_PROTOC_OPTIMIZE_TYPE := full +LOCAL_PROTOC_FLAGS := \ + -Iexternal/protobuf/src +LOCAL_SOURCE_FILES_ALL_GENERATED := true +LOCAL_SRC_FILES := \ + $(call all-proto-files-under, core/proto) \ + $(call all-proto-files-under, libs/incident/proto) +include $(BUILD_HOST_JAVA_LIBRARY) + # Include subdirectory makefiles # ============================================================ diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index c28db57c23c0..75de4a7833f1 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,5 +1,5 @@ [Hook Scripts] -checkstyle_hook = ../../development/tools/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} +checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} -fw core/java/android/animation/ core/java/android/hardware/usb/ core/java/android/print/ diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/OutlinePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/OutlinePerfTest.java new file mode 100644 index 000000000000..3a4fc7206712 --- /dev/null +++ b/apct-tests/perftests/core/src/android/graphics/perftests/OutlinePerfTest.java @@ -0,0 +1,51 @@ +/* + * 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.graphics.perftests; + +import android.graphics.Outline; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.support.test.filters.LargeTest; +import android.view.RenderNode; + +import org.junit.Rule; +import org.junit.Test; + +@LargeTest +public class OutlinePerfTest { + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + @Test + public void testSetEmpty() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + + Outline outline = new Outline(); + while (state.keepRunning()) { + outline.setEmpty(); + } + } + + @Test + public void testSetRoundRect() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + Outline outline = new Outline(); + while (state.keepRunning()) { + outline.setRoundRect(50, 50, 150, 150, 5); + } + } +} diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/PathPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/PathPerfTest.java new file mode 100644 index 000000000000..7a49b4f456b1 --- /dev/null +++ b/apct-tests/perftests/core/src/android/graphics/perftests/PathPerfTest.java @@ -0,0 +1,112 @@ +/* + * 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.graphics.perftests; + +import android.graphics.Path; +import android.graphics.RectF; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.support.test.filters.LargeTest; + +import org.junit.Rule; +import org.junit.Test; + +@LargeTest +public class PathPerfTest { + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + @Test + public void testReset() { + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + Path path = new Path(); + while (state.keepRunning()) { + path.reset(); + } + } + + @Test + public void testAddReset() { + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + Path path = new Path(); + while (state.keepRunning()) { + path.addRect(0, 0, 100, 100, Path.Direction.CW); + path.reset(); + } + } + + @Test + public void testRewind() { + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + Path path = new Path(); + while (state.keepRunning()) { + path.rewind(); + } + } + + @Test + public void testAddRewind() { + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + Path path = new Path(); + while (state.keepRunning()) { + path.addRect(0, 0, 100, 100, Path.Direction.CW); + path.rewind(); + } + } + + @Test + public void testIsEmpty() { + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + Path path = new Path(); + path.addRect(0, 0, 100, 100, Path.Direction.CW); + while (state.keepRunning()) { + path.isEmpty(); + } + } + + @Test + public void testIsConvex() { + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + Path path = new Path(); + path.addRect(0, 0, 100, 100, Path.Direction.CW); + while (state.keepRunning()) { + path.isConvex(); + } + } + + @Test + public void testGetSetFillType() { + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + Path path = new Path(); + path.addRect(0, 0, 100, 100, Path.Direction.CW); + while (state.keepRunning()) { + path.setFillType(Path.FillType.EVEN_ODD); + path.getFillType(); + } + } + + @Test + public void testIsRect() { + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + Path path = new Path(); + path.addRect(0, 0, 100, 100, Path.Direction.CW); + final RectF outRect = new RectF(); + while (state.keepRunning()) { + path.isRect(outRect); + } + } +} 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 922a47542020..dfbabebbcc04 100644 --- a/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java +++ b/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java @@ -16,14 +16,18 @@ package android.graphics.perftests; +import android.graphics.Outline; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; import android.support.test.filters.LargeTest; +import android.view.DisplayListCanvas; import android.view.RenderNode; import org.junit.Rule; import org.junit.Test; +import java.util.ArrayList; + @LargeTest public class RenderNodePerfTest { @Rule @@ -65,4 +69,58 @@ public class RenderNodePerfTest { node.isValid(); } } + + @Test + public void testStartEnd() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + RenderNode node = RenderNode.create("LinearLayout", null); + while (state.keepRunning()) { + DisplayListCanvas canvas = node.start(100, 100); + node.end(canvas); + } + } + + @Test + public void testStartEndDeepHierarchy() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + RenderNode[] nodes = new RenderNode[30]; + DisplayListCanvas[] canvases = new DisplayListCanvas[nodes.length]; + for (int i = 0; i < nodes.length; i++) { + nodes[i] = RenderNode.create("LinearLayout", null); + } + + while (state.keepRunning()) { + for (int i = 0; i < nodes.length; i++) { + canvases[i] = nodes[i].start(100, 100); + } + for (int i = nodes.length - 1; i >= 0; i--) { + nodes[i].end(canvases[i]); + } + } + } + + @Test + public void testHasIdentityMatrix() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + RenderNode node = RenderNode.create("LinearLayout", null); + while (state.keepRunning()) { + node.hasIdentityMatrix(); + } + } + + @Test + public void testSetOutline() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + RenderNode node = RenderNode.create("LinearLayout", null); + Outline a = new Outline(); + a.setRoundRect(0, 0, 100, 100, 10); + Outline b = new Outline(); + b.setRect(50, 50, 150, 150); + b.setAlpha(0.5f); + + while (state.keepRunning()) { + node.setOutline(a); + node.setOutline(b); + } + } } diff --git a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java index c7da48c112f5..6159da4fc3f5 100644 --- a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java +++ b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java @@ -121,7 +121,7 @@ public class ViewShowHidePerfTest { @Parameterized.Parameters(name = "Factory:{0},depth:{1}") public static Iterable<Object[]> params() { List<Object[]> params = new ArrayList<>(); - for (int depth : new int[] {1, 6, 10}) { + for (int depth : new int[] { 6 }) { for (SubTreeFactory subTreeFactory : sSubTreeFactories) { params.add(new Object[]{ subTreeFactory, depth }); } @@ -198,6 +198,21 @@ public class ViewShowHidePerfTest { }); } + @Test + public void testRecordAfterAdd() throws Throwable { + testParentWithChild((state, width, height, parent, child) -> { + while (state.keepRunning()) { + state.pauseTiming(); + parent.removeAllViews(); + updateAndValidateDisplayList(parent); // Note, done to be safe, likely not needed + parent.addView(child); + layout(width, height, child); + state.resumeTiming(); + + updateAndValidateDisplayList(parent); + } + }); + } private void testVisibility(int fromVisibility, int toVisibility) throws Throwable { testParentWithChild((state, width, height, parent, child) -> { diff --git a/api/current.txt b/api/current.txt index 85aa5df723c9..395d7aac70c2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -750,6 +750,8 @@ package android { field public static final int keyWidth = 16843325; // 0x101023d field public static final int keyboardLayout = 16843691; // 0x10103ab field public static final int keyboardMode = 16843341; // 0x101024d + field public static final int keyboardNavigationCluster = 16844096; // 0x1010540 + field public static final int keyboardNavigationSection = 16844097; // 0x1010541 field public static final int keycode = 16842949; // 0x10100c5 field public static final int killAfterRestore = 16843420; // 0x101029c field public static final int label = 16842753; // 0x1010001 @@ -893,11 +895,13 @@ package android { field public static final int negativeButtonText = 16843254; // 0x10101f6 field public static final int nestedScrollingEnabled = 16843830; // 0x1010436 field public static final int networkSecurityConfig = 16844071; // 0x1010527 + field public static final int nextClusterForward = 16844098; // 0x1010542 field public static final int nextFocusDown = 16842980; // 0x10100e4 field public static final int nextFocusForward = 16843580; // 0x101033c field public static final int nextFocusLeft = 16842977; // 0x10100e1 field public static final int nextFocusRight = 16842978; // 0x10100e2 field public static final int nextFocusUp = 16842979; // 0x10100e3 + field public static final int nextSectionForward = 16844099; // 0x1010543 field public static final int noHistory = 16843309; // 0x101022d field public static final int normalScreens = 16843397; // 0x1010285 field public static final int notificationTimeout = 16843651; // 0x1010383 @@ -3653,6 +3657,7 @@ package android.app { method public void setIntent(android.content.Intent); method public final void setMediaController(android.media.session.MediaController); method public void setOverlayWithDecorCaptionEnabled(boolean); + method public void setPictureInPictureActions(java.util.List<android.app.RemoteAction>); method public void setPictureInPictureAspectRatio(float); method public final deprecated void setProgress(int); method public final deprecated void setProgressBarIndeterminate(boolean); @@ -3733,6 +3738,7 @@ package android.app { method public int getLauncherLargeIconDensity(); method public int getLauncherLargeIconSize(); method public int getLockTaskModeState(); + method public static int getMaxNumPictureInPictureActions(); method public int getMemoryClass(); method public void getMemoryInfo(android.app.ActivityManager.MemoryInfo); method public static void getMyMemoryState(android.app.ActivityManager.RunningAppProcessInfo); @@ -5543,6 +5549,22 @@ package android.app { field public static final int STYLE_SPINNER = 0; // 0x0 } + public final class RemoteAction implements android.os.Parcelable { + ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener); + method public android.app.RemoteAction clone(); + method public int describeContents(); + method public void dump(java.lang.String, java.io.PrintWriter); + method public java.lang.CharSequence getContentDescription(); + method public android.graphics.drawable.Icon getIcon(); + method public java.lang.CharSequence getTitle(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.RemoteAction> CREATOR; + } + + public static abstract interface RemoteAction.OnActionListener { + method public abstract void onAction(android.app.RemoteAction); + } + public final class RemoteInput implements android.os.Parcelable { method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle); method public int describeContents(); @@ -6202,6 +6224,9 @@ package android.app.admin { field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION"; field public static final deprecated java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME"; field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM = "android.app.extra.PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM"; + field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMERS = "android.app.extra.PROVISIONING_DISCLAIMERS"; + field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_CONTENT = "android.app.extra.PROVISIONING_DISCLAIMER_CONTENT"; + field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_HEADER = "android.app.extra.PROVISIONING_DISCLAIMER_HEADER"; field public static final java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS"; field public static final java.lang.String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION"; field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED"; @@ -8385,6 +8410,7 @@ package android.content { field public static final java.lang.String TELECOM_SERVICE = "telecom"; field public static final java.lang.String TELEPHONY_SERVICE = "phone"; field public static final java.lang.String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service"; + field public static final java.lang.String TEXT_CLASSIFICATION_SERVICE = "textclassification"; field public static final java.lang.String TEXT_SERVICES_MANAGER_SERVICE = "textservices"; field public static final java.lang.String TV_INPUT_SERVICE = "tv_input"; field public static final java.lang.String UI_MODE_SERVICE = "uimode"; @@ -9690,6 +9716,7 @@ package android.content.pm { public class LauncherApps { method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle); + method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent); method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int); method public android.graphics.drawable.Drawable getShortcutIconDrawable(android.content.pm.ShortcutInfo, int); method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle); @@ -9705,6 +9732,8 @@ package android.content.pm { method public void startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle); method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle); method public void unregisterCallback(android.content.pm.LauncherApps.Callback); + field public static final java.lang.String ACTION_CONFIRM_PIN_ITEM = "android.content.pm.action.CONFIRM_PIN_ITEM"; + field public static final java.lang.String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST"; } public static abstract class LauncherApps.Callback { @@ -9719,6 +9748,21 @@ package android.content.pm { method public void onShortcutsChanged(java.lang.String, java.util.List<android.content.pm.ShortcutInfo>, android.os.UserHandle); } + public static final class LauncherApps.PinItemRequest implements android.os.Parcelable { + method public boolean accept(android.os.Bundle); + method public boolean accept(); + method public int describeContents(); + method public int getRequestType(); + method public android.content.pm.ShortcutInfo getShortcutInfo(); + method public boolean isValid(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.content.pm.LauncherApps.PinItemRequest> CREATOR; + field public static final int REQUEST_TYPE_SHORTCUT = 1; // 0x1 + } + + public static abstract class LauncherApps.PinItemRequest.RequestType implements java.lang.annotation.Annotation { + } + public static class LauncherApps.ShortcutQuery { ctor public LauncherApps.ShortcutQuery(); method public android.content.pm.LauncherApps.ShortcutQuery setActivity(android.content.ComponentName); @@ -10283,9 +10327,11 @@ package android.content.pm { method public int getMaxShortcutCountPerActivity(); method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts(); method public boolean isRateLimitingActive(); + method public boolean isRequestPinShortcutSupported(); method public void removeAllDynamicShortcuts(); method public void removeDynamicShortcuts(java.util.List<java.lang.String>); method public void reportShortcutUsed(java.lang.String); + method public boolean requestPinShortcut(android.content.pm.ShortcutInfo, android.content.IntentSender); method public boolean setDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>); method public boolean updateShortcuts(java.util.List<android.content.pm.ShortcutInfo>); } @@ -11723,6 +11769,8 @@ package android.graphics { method public static android.graphics.Bitmap createBitmap(android.graphics.Bitmap, int, int, int, int, android.graphics.Matrix, boolean); method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config); + method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config, boolean); + method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config, boolean); method public static android.graphics.Bitmap createBitmap(int[], int, int, int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int[], int, int, int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(int[], int, int, android.graphics.Bitmap.Config); @@ -11786,6 +11834,8 @@ package android.graphics { enum_constant public static final android.graphics.Bitmap.Config ALPHA_8; enum_constant public static final deprecated android.graphics.Bitmap.Config ARGB_4444; enum_constant public static final android.graphics.Bitmap.Config ARGB_8888; + enum_constant public static final android.graphics.Bitmap.Config HARDWARE; + enum_constant public static final android.graphics.Bitmap.Config RGBA_F16; enum_constant public static final android.graphics.Bitmap.Config RGB_565; } @@ -12145,6 +12195,7 @@ package android.graphics { method public android.graphics.Bitmap render(); method public android.graphics.ColorSpace.Renderer showWhitePoint(boolean); method public android.graphics.ColorSpace.Renderer size(int); + method public android.graphics.ColorSpace.Renderer uniformChromaticityScale(boolean); } public static class ColorSpace.Rgb extends android.graphics.ColorSpace { @@ -12246,12 +12297,6 @@ package android.graphics { enum_constant public static final android.graphics.Interpolator.Result NORMAL; } - public deprecated class LayerRasterizer extends android.graphics.Rasterizer { - ctor public LayerRasterizer(); - method public void addLayer(android.graphics.Paint, float, float); - method public void addLayer(android.graphics.Paint); - } - public class LightingColorFilter extends android.graphics.ColorFilter { ctor public LightingColorFilter(int, int); } @@ -12413,7 +12458,6 @@ package android.graphics { method public int getOffsetForAdvance(char[], int, int, int, int, boolean, float); method public int getOffsetForAdvance(java.lang.CharSequence, int, int, int, int, boolean, float); method public android.graphics.PathEffect getPathEffect(); - method public deprecated android.graphics.Rasterizer getRasterizer(); method public float getRunAdvance(char[], int, int, int, int, boolean, int); method public float getRunAdvance(java.lang.CharSequence, int, int, int, int, boolean, int); method public android.graphics.Shader getShader(); @@ -12470,7 +12514,6 @@ package android.graphics { method public void setLinearText(boolean); method public android.graphics.MaskFilter setMaskFilter(android.graphics.MaskFilter); method public android.graphics.PathEffect setPathEffect(android.graphics.PathEffect); - method public deprecated android.graphics.Rasterizer setRasterizer(android.graphics.Rasterizer); method public android.graphics.Shader setShader(android.graphics.Shader); method public void setShadowLayer(float, float, float, int); method public void setStrikeThruText(boolean); @@ -12688,6 +12731,7 @@ package android.graphics { field public static final deprecated int RGBA_4444 = 7; // 0x7 field public static final deprecated int RGBA_5551 = 6; // 0x6 field public static final int RGBA_8888 = 1; // 0x1 + field public static final int RGBA_F16 = 22; // 0x16 field public static final int RGBX_8888 = 2; // 0x2 field public static final deprecated int RGB_332 = 11; // 0xb field public static final int RGB_565 = 4; // 0x4 @@ -12777,10 +12821,6 @@ package android.graphics { ctor public RadialGradient(float, float, float, int, int, android.graphics.Shader.TileMode); } - public deprecated class Rasterizer { - ctor public Rasterizer(); - } - public final class Rect implements android.os.Parcelable { ctor public Rect(); ctor public Rect(int, int, int, int); @@ -15248,6 +15288,10 @@ package android.icu.lang { field public static final int CONTROL = 1; // 0x1 field public static final int CR = 2; // 0x2 field public static final int EXTEND = 3; // 0x3 + field public static final int E_BASE = 13; // 0xd + field public static final int E_BASE_GAZ = 14; // 0xe + field public static final int E_MODIFIER = 15; // 0xf + field public static final int GLUE_AFTER_ZWJ = 16; // 0x10 field public static final int L = 4; // 0x4 field public static final int LF = 5; // 0x5 field public static final int LV = 6; // 0x6 @@ -15258,6 +15302,7 @@ package android.icu.lang { field public static final int SPACING_MARK = 10; // 0xa field public static final int T = 8; // 0x8 field public static final int V = 9; // 0x9 + field public static final int ZWJ = 17; // 0x11 } public static abstract interface UCharacter.HangulSyllableType { @@ -15270,6 +15315,9 @@ package android.icu.lang { } public static abstract interface UCharacter.JoiningGroup { + field public static final int AFRICAN_FEH = 86; // 0x56 + field public static final int AFRICAN_NOON = 87; // 0x57 + field public static final int AFRICAN_QAF = 88; // 0x58 field public static final int AIN = 1; // 0x1 field public static final int ALAPH = 2; // 0x2 field public static final int ALEF = 3; // 0x3 @@ -15383,6 +15431,8 @@ package android.icu.lang { field public static final int CONDITIONAL_JAPANESE_STARTER = 37; // 0x25 field public static final int CONTINGENT_BREAK = 7; // 0x7 field public static final int EXCLAMATION = 11; // 0xb + field public static final int E_BASE = 40; // 0x28 + field public static final int E_MODIFIER = 41; // 0x29 field public static final int GLUE = 12; // 0xc field public static final int H2 = 31; // 0x1f field public static final int H3 = 32; // 0x20 @@ -15409,6 +15459,7 @@ package android.icu.lang { field public static final int SURROGATE = 25; // 0x19 field public static final int UNKNOWN = 0; // 0x0 field public static final int WORD_JOINER = 30; // 0x1e + field public static final int ZWJ = 42; // 0x2a field public static final int ZWSPACE = 28; // 0x1c } @@ -15442,6 +15493,8 @@ package android.icu.lang { method public int getID(); method public static android.icu.lang.UCharacter.UnicodeBlock getInstance(int); method public static android.icu.lang.UCharacter.UnicodeBlock of(int); + field public static final android.icu.lang.UCharacter.UnicodeBlock ADLAM; + field public static final int ADLAM_ID = 263; // 0x107 field public static final android.icu.lang.UCharacter.UnicodeBlock AEGEAN_NUMBERS; field public static final int AEGEAN_NUMBERS_ID = 119; // 0x77 field public static final android.icu.lang.UCharacter.UnicodeBlock AHOM; @@ -15490,6 +15543,8 @@ package android.icu.lang { field public static final int BATAK_ID = 199; // 0xc7 field public static final android.icu.lang.UCharacter.UnicodeBlock BENGALI; field public static final int BENGALI_ID = 16; // 0x10 + field public static final android.icu.lang.UCharacter.UnicodeBlock BHAIKSUKI; + field public static final int BHAIKSUKI_ID = 264; // 0x108 field public static final android.icu.lang.UCharacter.UnicodeBlock BLOCK_ELEMENTS; field public static final int BLOCK_ELEMENTS_ID = 53; // 0x35 field public static final android.icu.lang.UCharacter.UnicodeBlock BOPOMOFO; @@ -15579,6 +15634,8 @@ package android.icu.lang { field public static final int CYRILLIC_EXTENDED_A_ID = 158; // 0x9e field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_EXTENDED_B; field public static final int CYRILLIC_EXTENDED_B_ID = 160; // 0xa0 + field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_EXTENDED_C; + field public static final int CYRILLIC_EXTENDED_C_ID = 265; // 0x109 field public static final int CYRILLIC_ID = 9; // 0x9 field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_SUPPLEMENT; field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_SUPPLEMENTARY; @@ -15632,6 +15689,8 @@ package android.icu.lang { field public static final int GEORGIAN_SUPPLEMENT_ID = 135; // 0x87 field public static final android.icu.lang.UCharacter.UnicodeBlock GLAGOLITIC; field public static final int GLAGOLITIC_ID = 136; // 0x88 + field public static final android.icu.lang.UCharacter.UnicodeBlock GLAGOLITIC_SUPPLEMENT; + field public static final int GLAGOLITIC_SUPPLEMENT_ID = 266; // 0x10a field public static final android.icu.lang.UCharacter.UnicodeBlock GOTHIC; field public static final int GOTHIC_ID = 89; // 0x59 field public static final android.icu.lang.UCharacter.UnicodeBlock GRANTHA; @@ -15670,6 +15729,8 @@ package android.icu.lang { field public static final int HIRAGANA_ID = 62; // 0x3e field public static final android.icu.lang.UCharacter.UnicodeBlock IDEOGRAPHIC_DESCRIPTION_CHARACTERS; field public static final int IDEOGRAPHIC_DESCRIPTION_CHARACTERS_ID = 60; // 0x3c + field public static final android.icu.lang.UCharacter.UnicodeBlock IDEOGRAPHIC_SYMBOLS_AND_PUNCTUATION; + field public static final int IDEOGRAPHIC_SYMBOLS_AND_PUNCTUATION_ID = 267; // 0x10b field public static final android.icu.lang.UCharacter.UnicodeBlock IMPERIAL_ARAMAIC; field public static final int IMPERIAL_ARAMAIC_ID = 186; // 0xba field public static final android.icu.lang.UCharacter.UnicodeBlock INSCRIPTIONAL_PAHLAVI; @@ -15754,6 +15815,8 @@ package android.icu.lang { field public static final int MANDAIC_ID = 198; // 0xc6 field public static final android.icu.lang.UCharacter.UnicodeBlock MANICHAEAN; field public static final int MANICHAEAN_ID = 234; // 0xea + field public static final android.icu.lang.UCharacter.UnicodeBlock MARCHEN; + field public static final int MARCHEN_ID = 268; // 0x10c field public static final android.icu.lang.UCharacter.UnicodeBlock MATHEMATICAL_ALPHANUMERIC_SYMBOLS; field public static final int MATHEMATICAL_ALPHANUMERIC_SYMBOLS_ID = 93; // 0x5d field public static final android.icu.lang.UCharacter.UnicodeBlock MATHEMATICAL_OPERATORS; @@ -15788,6 +15851,8 @@ package android.icu.lang { field public static final int MODI_ID = 236; // 0xec field public static final android.icu.lang.UCharacter.UnicodeBlock MONGOLIAN; field public static final int MONGOLIAN_ID = 37; // 0x25 + field public static final android.icu.lang.UCharacter.UnicodeBlock MONGOLIAN_SUPPLEMENT; + field public static final int MONGOLIAN_SUPPLEMENT_ID = 269; // 0x10d field public static final android.icu.lang.UCharacter.UnicodeBlock MRO; field public static final int MRO_ID = 237; // 0xed field public static final android.icu.lang.UCharacter.UnicodeBlock MULTANI; @@ -15802,6 +15867,8 @@ package android.icu.lang { field public static final int MYANMAR_ID = 28; // 0x1c field public static final android.icu.lang.UCharacter.UnicodeBlock NABATAEAN; field public static final int NABATAEAN_ID = 239; // 0xef + field public static final android.icu.lang.UCharacter.UnicodeBlock NEWA; + field public static final int NEWA_ID = 270; // 0x10e field public static final android.icu.lang.UCharacter.UnicodeBlock NEW_TAI_LUE; field public static final int NEW_TAI_LUE_ID = 139; // 0x8b field public static final android.icu.lang.UCharacter.UnicodeBlock NKO; @@ -15833,6 +15900,8 @@ package android.icu.lang { field public static final int ORIYA_ID = 19; // 0x13 field public static final android.icu.lang.UCharacter.UnicodeBlock ORNAMENTAL_DINGBATS; field public static final int ORNAMENTAL_DINGBATS_ID = 242; // 0xf2 + field public static final android.icu.lang.UCharacter.UnicodeBlock OSAGE; + field public static final int OSAGE_ID = 271; // 0x10f field public static final android.icu.lang.UCharacter.UnicodeBlock OSMANYA; field public static final int OSMANYA_ID = 122; // 0x7a field public static final android.icu.lang.UCharacter.UnicodeBlock PAHAWH_HMONG; @@ -15935,6 +16004,10 @@ package android.icu.lang { field public static final int TAKRI_ID = 220; // 0xdc field public static final android.icu.lang.UCharacter.UnicodeBlock TAMIL; field public static final int TAMIL_ID = 20; // 0x14 + field public static final android.icu.lang.UCharacter.UnicodeBlock TANGUT; + field public static final android.icu.lang.UCharacter.UnicodeBlock TANGUT_COMPONENTS; + field public static final int TANGUT_COMPONENTS_ID = 273; // 0x111 + field public static final int TANGUT_ID = 272; // 0x110 field public static final android.icu.lang.UCharacter.UnicodeBlock TELUGU; field public static final int TELUGU_ID = 21; // 0x15 field public static final android.icu.lang.UCharacter.UnicodeBlock THAANA; @@ -15981,7 +16054,11 @@ package android.icu.lang { field public static final int DOUBLE_QUOTE = 16; // 0x10 field public static final int EXTEND = 9; // 0x9 field public static final int EXTENDNUMLET = 7; // 0x7 + field public static final int E_BASE = 17; // 0x11 + field public static final int E_BASE_GAZ = 18; // 0x12 + field public static final int E_MODIFIER = 19; // 0x13 field public static final int FORMAT = 2; // 0x2 + field public static final int GLUE_AFTER_ZWJ = 20; // 0x14 field public static final int HEBREW_LETTER = 14; // 0xe field public static final int KATAKANA = 3; // 0x3 field public static final int LF = 10; // 0xa @@ -15993,6 +16070,7 @@ package android.icu.lang { field public static final int OTHER = 0; // 0x0 field public static final int REGIONAL_INDICATOR = 13; // 0xd field public static final int SINGLE_QUOTE = 15; // 0xf + field public static final int ZWJ = 21; // 0x15 } public final class UCharacterCategory implements android.icu.lang.UCharacterEnums.ECharacterCategory { @@ -16211,6 +16289,7 @@ package android.icu.lang { method public static final boolean hasScript(int, int); method public static final boolean isCased(int); method public static final boolean isRightToLeft(int); + field public static final int ADLAM = 167; // 0xa7 field public static final int AFAKA = 147; // 0x93 field public static final int AHOM = 161; // 0xa1 field public static final int ANATOLIAN_HIEROGLYPHS = 156; // 0x9c @@ -16222,6 +16301,7 @@ package android.icu.lang { field public static final int BASSA_VAH = 134; // 0x86 field public static final int BATAK = 63; // 0x3f field public static final int BENGALI = 4; // 0x4 + field public static final int BHAIKSUKI = 168; // 0xa8 field public static final int BLISSYMBOLS = 64; // 0x40 field public static final int BOOK_PAHLAVI = 124; // 0x7c field public static final int BOPOMOFO = 5; // 0x5 @@ -16260,6 +16340,7 @@ package android.icu.lang { field public static final int HAN = 17; // 0x11 field public static final int HANGUL = 18; // 0x12 field public static final int HANUNOO = 43; // 0x2b + field public static final int HAN_WITH_BOPOMOFO = 172; // 0xac field public static final int HARAPPAN_INDUS = 77; // 0x4d field public static final int HATRAN = 162; // 0xa2 field public static final int HEBREW = 19; // 0x13 @@ -16270,6 +16351,7 @@ package android.icu.lang { field public static final int INSCRIPTIONAL_PAHLAVI = 122; // 0x7a field public static final int INSCRIPTIONAL_PARTHIAN = 125; // 0x7d field public static final int INVALID_CODE = -1; // 0xffffffff + field public static final int JAMO = 173; // 0xad field public static final int JAPANESE = 105; // 0x69 field public static final int JAVANESE = 78; // 0x4e field public static final int JURCHEN = 148; // 0x94 @@ -16303,6 +16385,7 @@ package android.icu.lang { field public static final int MANDAEAN = 84; // 0x54 field public static final int MANDAIC = 84; // 0x54 field public static final int MANICHAEAN = 121; // 0x79 + field public static final int MARCHEN = 169; // 0xa9 field public static final int MATHEMATICAL_NOTATION = 128; // 0x80 field public static final int MAYAN_HIEROGLYPHS = 85; // 0x55 field public static final int MEITEI_MAYEK = 115; // 0x73 @@ -16319,6 +16402,7 @@ package android.icu.lang { field public static final int MYANMAR = 28; // 0x1c field public static final int NABATAEAN = 143; // 0x8f field public static final int NAKHI_GEBA = 132; // 0x84 + field public static final int NEWA = 170; // 0xaa field public static final int NEW_TAI_LUE = 59; // 0x3b field public static final int NKO = 87; // 0x57 field public static final int NUSHU = 150; // 0x96 @@ -16333,6 +16417,7 @@ package android.icu.lang { field public static final int OL_CHIKI = 109; // 0x6d field public static final int ORIYA = 31; // 0x1f field public static final int ORKHON = 88; // 0x58 + field public static final int OSAGE = 171; // 0xab field public static final int OSMANYA = 50; // 0x32 field public static final int PAHAWH_HMONG = 75; // 0x4b field public static final int PALMYRENE = 144; // 0x90 @@ -16358,6 +16443,7 @@ package android.icu.lang { field public static final int SUNDANESE = 113; // 0x71 field public static final int SYLOTI_NAGRI = 58; // 0x3a field public static final int SYMBOLS = 129; // 0x81 + field public static final int SYMBOLS_EMOJI = 174; // 0xae field public static final int SYRIAC = 34; // 0x22 field public static final int TAGALOG = 42; // 0x2a field public static final int TAGBANWA = 45; // 0x2d @@ -16884,6 +16970,8 @@ package android.icu.text { method public static final android.icu.text.DateFormat.BooleanAttribute[] values(); enum_constant public static final android.icu.text.DateFormat.BooleanAttribute PARSE_ALLOW_NUMERIC; enum_constant public static final android.icu.text.DateFormat.BooleanAttribute PARSE_ALLOW_WHITESPACE; + enum_constant public static final android.icu.text.DateFormat.BooleanAttribute PARSE_MULTIPLE_PATTERNS_FOR_MATCH; + enum_constant public static final android.icu.text.DateFormat.BooleanAttribute PARSE_PARTIAL_LITERAL_MATCH; } public static class DateFormat.Field extends java.text.Format.Field { @@ -17568,6 +17656,7 @@ package android.icu.text { field public static final int PERCENTSTYLE = 2; // 0x2 field public static final int PLURALCURRENCYSTYLE = 6; // 0x6 field public static final int SCIENTIFICSTYLE = 3; // 0x3 + field public static final int STANDARDCURRENCYSTYLE = 9; // 0x9 } public static class NumberFormat.Field extends java.text.Format.Field { @@ -18362,7 +18451,7 @@ package android.icu.util { field public static final int AM_PM = 9; // 0x9 field public static final int APRIL = 3; // 0x3 field public static final int AUGUST = 7; // 0x7 - field protected static final int BASE_FIELD_COUNT = 23; // 0x17 + field protected static final deprecated int BASE_FIELD_COUNT = 23; // 0x17 field public static final int DATE = 5; // 0x5 field public static final int DAY_OF_MONTH = 5; // 0x5 field public static final int DAY_OF_WEEK = 7; // 0x7 @@ -18390,7 +18479,7 @@ package android.icu.util { field public static final int MARCH = 2; // 0x2 field protected static final int MAXIMUM = 3; // 0x3 field protected static final java.util.Date MAX_DATE; - field protected static final int MAX_FIELD_COUNT = 32; // 0x20 + field protected static final deprecated int MAX_FIELD_COUNT = 32; // 0x20 field protected static final int MAX_JULIAN = 2130706432; // 0x7f000000 field protected static final long MAX_MILLIS = 183882168921600000L; // 0x28d47dbbf19b000L field public static final int MAY = 4; // 0x4 @@ -18710,6 +18799,7 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit CELSIUS; field public static final android.icu.util.MeasureUnit CENTILITER; field public static final android.icu.util.MeasureUnit CENTIMETER; + field public static final android.icu.util.MeasureUnit CENTURY; field public static final android.icu.util.MeasureUnit CUBIC_CENTIMETER; field public static final android.icu.util.MeasureUnit CUBIC_FOOT; field public static final android.icu.util.MeasureUnit CUBIC_INCH; @@ -18718,6 +18808,7 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit CUBIC_MILE; field public static final android.icu.util.MeasureUnit CUBIC_YARD; field public static final android.icu.util.MeasureUnit CUP; + field public static final android.icu.util.MeasureUnit CUP_METRIC; field public static final android.icu.util.TimeUnit DAY; field public static final android.icu.util.MeasureUnit DECILITER; field public static final android.icu.util.MeasureUnit DECIMETER; @@ -18729,6 +18820,7 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit FOOT; field public static final android.icu.util.MeasureUnit FURLONG; field public static final android.icu.util.MeasureUnit GALLON; + field public static final android.icu.util.MeasureUnit GENERIC_TEMPERATURE; field public static final android.icu.util.MeasureUnit GIGABIT; field public static final android.icu.util.MeasureUnit GIGABYTE; field public static final android.icu.util.MeasureUnit GIGAHERTZ; @@ -18756,8 +18848,10 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit KILOMETER_PER_HOUR; field public static final android.icu.util.MeasureUnit KILOWATT; field public static final android.icu.util.MeasureUnit KILOWATT_HOUR; + field public static final android.icu.util.MeasureUnit KNOT; field public static final android.icu.util.MeasureUnit LIGHT_YEAR; field public static final android.icu.util.MeasureUnit LITER; + field public static final android.icu.util.MeasureUnit LITER_PER_100KILOMETERS; field public static final android.icu.util.MeasureUnit LITER_PER_KILOMETER; field public static final android.icu.util.MeasureUnit LUX; field public static final android.icu.util.MeasureUnit MEGABIT; @@ -18775,6 +18869,7 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit MILE; field public static final android.icu.util.MeasureUnit MILE_PER_GALLON; field public static final android.icu.util.MeasureUnit MILE_PER_HOUR; + field public static final android.icu.util.MeasureUnit MILE_SCANDINAVIAN; field public static final android.icu.util.MeasureUnit MILLIAMPERE; field public static final android.icu.util.MeasureUnit MILLIBAR; field public static final android.icu.util.MeasureUnit MILLIGRAM; @@ -18794,10 +18889,12 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit PARSEC; field public static final android.icu.util.MeasureUnit PICOMETER; field public static final android.icu.util.MeasureUnit PINT; + field public static final android.icu.util.MeasureUnit PINT_METRIC; field public static final android.icu.util.MeasureUnit POUND; field public static final android.icu.util.MeasureUnit POUND_PER_SQUARE_INCH; field public static final android.icu.util.MeasureUnit QUART; field public static final android.icu.util.MeasureUnit RADIAN; + field public static final android.icu.util.MeasureUnit REVOLUTION_ANGLE; field public static final android.icu.util.TimeUnit SECOND; field public static final android.icu.util.MeasureUnit SQUARE_CENTIMETER; field public static final android.icu.util.MeasureUnit SQUARE_FOOT; @@ -19109,6 +19206,7 @@ package android.icu.util { field public static final android.icu.util.VersionInfo UNICODE_6_3; field public static final android.icu.util.VersionInfo UNICODE_7_0; field public static final android.icu.util.VersionInfo UNICODE_8_0; + field public static final android.icu.util.VersionInfo UNICODE_9_0; } } @@ -22615,6 +22713,7 @@ package android.media.browse { method public android.content.ComponentName getServiceComponent(); method public android.media.session.MediaSession.Token getSessionToken(); method public boolean isConnected(); + method public void search(java.lang.String, android.os.Bundle, android.media.browse.MediaBrowser.SearchCallback); method public void subscribe(java.lang.String, android.media.browse.MediaBrowser.SubscriptionCallback); method public void subscribe(java.lang.String, android.os.Bundle, android.media.browse.MediaBrowser.SubscriptionCallback); method public void unsubscribe(java.lang.String); @@ -22650,6 +22749,12 @@ package android.media.browse { field public static final int FLAG_PLAYABLE = 2; // 0x2 } + public static abstract class MediaBrowser.SearchCallback { + ctor public MediaBrowser.SearchCallback(); + method public void onError(java.lang.String, android.os.Bundle); + method public void onSearchResult(java.lang.String, android.os.Bundle, java.util.List<android.media.browse.MediaBrowser.MediaItem>); + } + public static abstract class MediaBrowser.SubscriptionCallback { ctor public MediaBrowser.SubscriptionCallback(); method public void onChildrenLoaded(java.lang.String, java.util.List<android.media.browse.MediaBrowser.MediaItem>); @@ -29720,6 +29825,7 @@ package android.os { method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle); method public static boolean supportsMultipleUsers(); field public static final java.lang.String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking"; + field public static final java.lang.String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile"; field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user"; field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume"; field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps"; @@ -29744,6 +29850,7 @@ package android.os { field public static final java.lang.String DISALLOW_NETWORK_RESET = "no_network_reset"; field public static final java.lang.String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam"; field public static final java.lang.String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls"; + field public static final java.lang.String DISALLOW_REMOVE_MANAGED_PROFILE = "no_remove_managed_profile"; field public static final java.lang.String DISALLOW_REMOVE_USER = "no_remove_user"; field public static final java.lang.String DISALLOW_SAFE_BOOT = "no_safe_boot"; field public static final java.lang.String DISALLOW_SET_USER_ICON = "no_set_user_icon"; @@ -34876,7 +34983,8 @@ package android.service.autofill { method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); - method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.CancellationSignal, android.service.autofill.FillCallback); + method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback); + method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback); field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService"; } @@ -34894,6 +35002,11 @@ package android.service.autofill { method public android.service.autofill.FillCallback.FillData.Builder setTextField(int, java.lang.String); } + public final class SaveCallback { + method public void onFailure(java.lang.CharSequence); + method public void onSuccess(int[]); + } + } package android.service.carrier { @@ -35075,6 +35188,7 @@ package android.service.media { method public abstract void onLoadChildren(java.lang.String, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>); method public void onLoadChildren(java.lang.String, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>, android.os.Bundle); method public void onLoadItem(java.lang.String, android.service.media.MediaBrowserService.Result<android.media.browse.MediaBrowser.MediaItem>); + method public void onSearch(java.lang.String, android.os.Bundle, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>); method public void setSessionToken(android.media.session.MediaSession.Token); field public static final java.lang.String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService"; } @@ -35164,8 +35278,12 @@ package android.service.notification { ctor public NotificationAssistantService(); method public final void adjustNotification(android.service.notification.Adjustment); method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>); + method public void createNotificationChannel(java.lang.String, android.app.NotificationChannel); + method public void deleteNotificationChannel(java.lang.String, java.lang.String); + method public java.util.List<android.app.NotificationChannel> getNotificationChannels(java.lang.String); method public final android.os.IBinder onBind(android.content.Intent); method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean); + method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel); field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; } @@ -37364,6 +37482,7 @@ package android.telephony { field public static final java.lang.String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool"; field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; field public static final java.lang.String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; + field public static final java.lang.String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool"; field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool"; field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool"; field public static final java.lang.String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool"; @@ -37901,6 +38020,7 @@ package android.telephony { method public java.lang.String getSimOperatorName(); method public java.lang.String getSimSerialNumber(); method public int getSimState(); + method public int getSimState(int); method public java.lang.String getSubscriberId(); method public java.lang.String getVoiceMailAlphaTag(); method public java.lang.String getVoiceMailNumber(); @@ -37985,7 +38105,11 @@ package android.telephony { field public static final int PHONE_TYPE_NONE = 0; // 0x0 field public static final int PHONE_TYPE_SIP = 3; // 0x3 field public static final int SIM_STATE_ABSENT = 1; // 0x1 + field public static final int SIM_STATE_CARD_IO_ERROR = 8; // 0x8 + field public static final int SIM_STATE_CARD_RESTRICTED = 9; // 0x9 field public static final int SIM_STATE_NETWORK_LOCKED = 4; // 0x4 + field public static final int SIM_STATE_NOT_READY = 6; // 0x6 + field public static final int SIM_STATE_PERM_DISABLED = 7; // 0x7 field public static final int SIM_STATE_PIN_REQUIRED = 2; // 0x2 field public static final int SIM_STATE_PUK_REQUIRED = 3; // 0x3 field public static final int SIM_STATE_READY = 5; // 0x5 @@ -38750,7 +38874,7 @@ package android.text { method public java.lang.CharSequence subSequence(int, int); } - public class AndroidCharacter { + public deprecated class AndroidCharacter { ctor public AndroidCharacter(); method public static void getDirectionalities(char[], byte[], int); method public static int getEastAsianWidth(char); @@ -39244,6 +39368,12 @@ package android.text { method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence(); } + public final class TextClassificationManager implements android.text.TextAssistant { + method public void addLinks(android.text.Spannable, int); + method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence); + method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int); + } + public abstract interface TextDirectionHeuristic { method public abstract boolean isRtl(char[], int, int); method public abstract boolean isRtl(java.lang.CharSequence, int, int); @@ -39259,6 +39389,13 @@ package android.text { field public static final android.text.TextDirectionHeuristic RTL; } + public final class TextLanguage { + ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>); + method public int getEndIndex(); + method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence(); + method public int getStartIndex(); + } + public class TextPaint extends android.graphics.Paint { ctor public TextPaint(); ctor public TextPaint(int); @@ -39953,12 +40090,6 @@ package android.text.style { method public void writeToParcel(android.os.Parcel, int); } - public class RasterizerSpan extends android.text.style.CharacterStyle implements android.text.style.UpdateAppearance { - ctor public RasterizerSpan(android.graphics.Rasterizer); - method public android.graphics.Rasterizer getRasterizer(); - method public void updateDrawState(android.text.TextPaint); - } - public class RelativeSizeSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan { ctor public RelativeSizeSpan(float); ctor public RelativeSizeSpan(android.os.Parcel); @@ -41606,6 +41737,7 @@ package android.view { method public android.view.View findNearestTouchable(android.view.ViewGroup, int, int, int, int[]); method public final android.view.View findNextFocus(android.view.ViewGroup, android.view.View, int); method public android.view.View findNextFocusFromRect(android.view.ViewGroup, android.graphics.Rect, int); + method public android.view.View findNextKeyboardNavigationCluster(android.view.ViewGroup, android.view.View, int); method public static android.view.FocusFinder getInstance(); } @@ -42898,6 +43030,7 @@ package android.view { method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>); method public void addFocusables(java.util.ArrayList<android.view.View>, int); method public void addFocusables(java.util.ArrayList<android.view.View>, int, int); + method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int); method public void addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener); method public void addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener); method public void addTouchables(java.util.ArrayList<android.view.View>); @@ -42954,7 +43087,8 @@ package android.view { method public boolean dispatchNestedPreScroll(int, int, int[], int[]); method public boolean dispatchNestedScroll(int, int, int, int, int[]); method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent); - method public void dispatchProvideStructure(android.view.ViewStructure); + method public deprecated void dispatchProvideStructure(android.view.ViewStructure); + method public void dispatchProvideStructure(android.view.ViewStructure, int); method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>); method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>); method protected void dispatchSetActivated(boolean); @@ -43051,11 +43185,13 @@ package android.view { method public final int getMeasuredWidthAndState(); method public int getMinimumHeight(); method public int getMinimumWidth(); + method public int getNextClusterForwardId(); method public int getNextFocusDownId(); method public int getNextFocusForwardId(); method public int getNextFocusLeftId(); method public int getNextFocusRightId(); method public int getNextFocusUpId(); + method public int getNextSectionForwardId(); method public android.view.View.OnFocusChangeListener getOnFocusChangeListener(); method public android.view.ViewOutlineProvider getOutlineProvider(); method public int getOverScrollMode(); @@ -43158,6 +43294,8 @@ package android.view { method public boolean isInEditMode(); method public boolean isInLayout(); method public boolean isInTouchMode(); + method public final boolean isKeyboardNavigationCluster(); + method public final boolean isKeyboardNavigationSection(); method public boolean isLaidOut(); method public boolean isLayoutDirectionResolved(); method public boolean isLayoutRequested(); @@ -43180,6 +43318,7 @@ package android.view { method public boolean isVerticalFadingEdgeEnabled(); method public boolean isVerticalScrollBarEnabled(); method public void jumpDrawablesToCurrentState(); + method public android.view.View keyboardNavigationClusterSearch(int); method public void layout(int, int, int, int); method public final void measure(int, int); method protected static int[] mergeDrawableStates(int[], int[]); @@ -43220,8 +43359,10 @@ package android.view { method protected void onMeasure(int, int); method protected void onOverScrolled(int, int, boolean, boolean); method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent); - method public void onProvideStructure(android.view.ViewStructure); - method public void onProvideVirtualStructure(android.view.ViewStructure); + method public deprecated void onProvideStructure(android.view.ViewStructure); + method public void onProvideStructure(android.view.ViewStructure, int); + method public deprecated void onProvideVirtualStructure(android.view.ViewStructure); + method public void onProvideVirtualStructure(android.view.ViewStructure, int); method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int); method protected void onRestoreInstanceState(android.os.Parcelable); method public void onRtlPropertiesChanged(int); @@ -43324,6 +43465,8 @@ package android.view { method public void setId(int); method public void setImportantForAccessibility(int); method public void setKeepScreenOn(boolean); + method public void setKeyboardNavigationCluster(boolean); + method public void setKeyboardNavigationSection(boolean); method public void setLabelFor(int); method public void setLayerPaint(android.graphics.Paint); method public void setLayerType(int, android.graphics.Paint); @@ -43335,11 +43478,13 @@ package android.view { method public void setMinimumHeight(int); method public void setMinimumWidth(int); method public void setNestedScrollingEnabled(boolean); + method public void setNextClusterForwardId(int); method public void setNextFocusDownId(int); method public void setNextFocusForwardId(int); method public void setNextFocusLeftId(int); method public void setNextFocusRightId(int); method public void setNextFocusUpId(int); + method public void setNextSectionForwardId(int); method public void setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener); method public void setOnClickListener(android.view.View.OnClickListener); method public void setOnContextClickListener(android.view.View.OnContextClickListener); @@ -43423,6 +43568,8 @@ package android.view { field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0 field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1 field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA; + field public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 2; // 0x2 + field public static final int ASSIST_FLAG_SANITIZED_TEXT = 1; // 0x1 field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100 field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40 field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80 @@ -43819,6 +43966,7 @@ package android.view { method protected deprecated boolean isChildrenDrawnWithCacheEnabled(); method public boolean isMotionEventSplittingEnabled(); method public boolean isTransitionGroup(); + method public android.view.View keyboardNavigationClusterSearch(android.view.View, int); method public final void layout(int, int, int, int); method protected void measureChild(android.view.View, int, int); method protected void measureChildWithMargins(android.view.View, int, int, int, int); @@ -43981,6 +44129,7 @@ package android.view { method public abstract boolean isLayoutRequested(); method public abstract boolean isTextAlignmentResolved(); method public abstract boolean isTextDirectionResolved(); + method public abstract android.view.View keyboardNavigationClusterSearch(android.view.View, int); method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int); method public abstract boolean onNestedFling(android.view.View, float, float, boolean); method public abstract boolean onNestedPreFling(android.view.View, float, float); @@ -59883,6 +60032,10 @@ package java.util { ctor public Locale(java.lang.String, java.lang.String); ctor public Locale(java.lang.String); method public java.lang.Object clone(); + method public static java.util.List<java.util.Locale> filter(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.util.Locale>, java.util.Locale.FilteringMode); + method public static java.util.List<java.util.Locale> filter(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.util.Locale>); + method public static java.util.List<java.lang.String> filterTags(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.lang.String>, java.util.Locale.FilteringMode); + method public static java.util.List<java.lang.String> filterTags(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.lang.String>); method public static java.util.Locale forLanguageTag(java.lang.String); method public static java.util.Locale[] getAvailableLocales(); method public java.lang.String getCountry(); @@ -59911,6 +60064,8 @@ package java.util { method public java.lang.String getUnicodeLocaleType(java.lang.String); method public java.lang.String getVariant(); method public boolean hasExtensions(); + method public static java.util.Locale lookup(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.util.Locale>); + method public static java.lang.String lookupTag(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.lang.String>); method public static synchronized void setDefault(java.util.Locale); method public static synchronized void setDefault(java.util.Locale.Category, java.util.Locale); method public java.util.Locale stripExtensions(); @@ -59966,6 +60121,28 @@ package java.util { enum_constant public static final java.util.Locale.Category FORMAT; } + public static final class Locale.FilteringMode extends java.lang.Enum { + method public static java.util.Locale.FilteringMode valueOf(java.lang.String); + method public static final java.util.Locale.FilteringMode[] values(); + enum_constant public static final java.util.Locale.FilteringMode AUTOSELECT_FILTERING; + enum_constant public static final java.util.Locale.FilteringMode EXTENDED_FILTERING; + enum_constant public static final java.util.Locale.FilteringMode IGNORE_EXTENDED_RANGES; + enum_constant public static final java.util.Locale.FilteringMode MAP_EXTENDED_RANGES; + enum_constant public static final java.util.Locale.FilteringMode REJECT_EXTENDED_RANGES; + } + + public static final class Locale.LanguageRange { + ctor public Locale.LanguageRange(java.lang.String); + ctor public Locale.LanguageRange(java.lang.String, double); + method public java.lang.String getRange(); + method public double getWeight(); + method public static java.util.List<java.util.Locale.LanguageRange> mapEquivalents(java.util.List<java.util.Locale.LanguageRange>, java.util.Map<java.lang.String, java.util.List<java.lang.String>>); + method public static java.util.List<java.util.Locale.LanguageRange> parse(java.lang.String); + method public static java.util.List<java.util.Locale.LanguageRange> parse(java.lang.String, java.util.Map<java.lang.String, java.util.List<java.lang.String>>); + field public static final double MAX_WEIGHT = 1.0; + field public static final double MIN_WEIGHT = 0.0; + } + public class LongSummaryStatistics implements java.util.function.IntConsumer java.util.function.LongConsumer { ctor public LongSummaryStatistics(); method public void accept(int); @@ -61249,31 +61426,31 @@ package java.util.concurrent { ctor public CopyOnWriteArrayList(); ctor public CopyOnWriteArrayList(java.util.Collection<? extends E>); ctor public CopyOnWriteArrayList(E[]); - method public synchronized boolean add(E); - method public synchronized void add(int, E); - method public synchronized boolean addAll(java.util.Collection<? extends E>); - method public synchronized boolean addAll(int, java.util.Collection<? extends E>); - method public synchronized int addAllAbsent(java.util.Collection<? extends E>); - method public synchronized boolean addIfAbsent(E); - method public synchronized void clear(); + method public boolean add(E); + method public void add(int, E); + method public boolean addAll(java.util.Collection<? extends E>); + method public boolean addAll(int, java.util.Collection<? extends E>); + method public int addAllAbsent(java.util.Collection<? extends E>); + method public boolean addIfAbsent(E); + method public void clear(); method public java.lang.Object clone(); method public boolean contains(java.lang.Object); method public boolean containsAll(java.util.Collection<?>); method public void forEach(java.util.function.Consumer<? super E>); method public E get(int); - method public int indexOf(E, int); method public int indexOf(java.lang.Object); + method public int indexOf(E, int); method public boolean isEmpty(); method public java.util.Iterator<E> iterator(); - method public int lastIndexOf(E, int); method public int lastIndexOf(java.lang.Object); - method public java.util.ListIterator<E> listIterator(int); + method public int lastIndexOf(E, int); method public java.util.ListIterator<E> listIterator(); - method public synchronized E remove(int); - method public synchronized boolean remove(java.lang.Object); - method public synchronized boolean removeAll(java.util.Collection<?>); - method public synchronized boolean retainAll(java.util.Collection<?>); - method public synchronized E set(int, E); + method public java.util.ListIterator<E> listIterator(int); + method public E remove(int); + method public boolean remove(java.lang.Object); + method public boolean removeAll(java.util.Collection<?>); + method public boolean retainAll(java.util.Collection<?>); + method public E set(int, E); method public int size(); method public java.util.List<E> subList(int, int); method public java.lang.Object[] toArray(); diff --git a/api/removed.txt b/api/removed.txt index 683a695ef8a0..10e6eb5e60d8 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -78,10 +78,25 @@ package android.graphics { enum_constant public static final android.graphics.AvoidXfermode.Mode TARGET; } + public deprecated class LayerRasterizer extends android.graphics.Rasterizer { + ctor public LayerRasterizer(); + method public void addLayer(android.graphics.Paint, float, float); + method public void addLayer(android.graphics.Paint); + } + + public class Paint { + method public deprecated android.graphics.Rasterizer getRasterizer(); + method public deprecated android.graphics.Rasterizer setRasterizer(android.graphics.Rasterizer); + } + public deprecated class PixelXorXfermode extends android.graphics.Xfermode { ctor public PixelXorXfermode(int); } + public class Rasterizer { + ctor public Rasterizer(); + } + } package android.location { @@ -393,6 +408,15 @@ package android.text.format { } +package android.text.style { + + public class RasterizerSpan extends android.text.style.CharacterStyle implements android.text.style.UpdateAppearance { + ctor public RasterizerSpan(android.graphics.Rasterizer); + method public android.graphics.Rasterizer getRasterizer(); + } + +} + package android.util { public deprecated class FloatMath { diff --git a/api/system-current.txt b/api/system-current.txt index 41fd0d442542..f38b02e7a838 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -857,6 +857,8 @@ package android { field public static final int keyWidth = 16843325; // 0x101023d field public static final int keyboardLayout = 16843691; // 0x10103ab field public static final int keyboardMode = 16843341; // 0x101024d + field public static final int keyboardNavigationCluster = 16844096; // 0x1010540 + field public static final int keyboardNavigationSection = 16844097; // 0x1010541 field public static final int keycode = 16842949; // 0x10100c5 field public static final int killAfterRestore = 16843420; // 0x101029c field public static final int label = 16842753; // 0x1010001 @@ -1000,11 +1002,13 @@ package android { field public static final int negativeButtonText = 16843254; // 0x10101f6 field public static final int nestedScrollingEnabled = 16843830; // 0x1010436 field public static final int networkSecurityConfig = 16844071; // 0x1010527 + field public static final int nextClusterForward = 16844098; // 0x1010542 field public static final int nextFocusDown = 16842980; // 0x10100e4 field public static final int nextFocusForward = 16843580; // 0x101033c field public static final int nextFocusLeft = 16842977; // 0x10100e1 field public static final int nextFocusRight = 16842978; // 0x10100e2 field public static final int nextFocusUp = 16842979; // 0x10100e3 + field public static final int nextSectionForward = 16844099; // 0x1010543 field public static final int noHistory = 16843309; // 0x101022d field public static final int normalScreens = 16843397; // 0x1010285 field public static final int notificationTimeout = 16843651; // 0x1010383 @@ -3772,6 +3776,7 @@ package android.app { method public void setIntent(android.content.Intent); method public final void setMediaController(android.media.session.MediaController); method public void setOverlayWithDecorCaptionEnabled(boolean); + method public void setPictureInPictureActions(java.util.List<android.app.RemoteAction>); method public void setPictureInPictureAspectRatio(float); method public final deprecated void setProgress(int); method public final deprecated void setProgressBarIndeterminate(boolean); @@ -3858,6 +3863,7 @@ package android.app { method public int getLauncherLargeIconDensity(); method public int getLauncherLargeIconSize(); method public int getLockTaskModeState(); + method public static int getMaxNumPictureInPictureActions(); method public int getMemoryClass(); method public void getMemoryInfo(android.app.ActivityManager.MemoryInfo); method public static void getMyMemoryState(android.app.ActivityManager.RunningAppProcessInfo); @@ -5141,6 +5147,7 @@ package android.app { method public java.lang.String getChannel(); method public java.lang.String getGroup(); method public android.graphics.drawable.Icon getLargeIcon(); + method public static java.lang.Class<? extends android.app.Notification.Style> getNotificationStyleClass(java.lang.String); method public android.graphics.drawable.Icon getSmallIcon(); method public java.lang.String getSortKey(); method public void writeToParcel(android.os.Parcel, int); @@ -5710,6 +5717,22 @@ package android.app { field public static final int STYLE_SPINNER = 0; // 0x0 } + public final class RemoteAction implements android.os.Parcelable { + ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener); + method public android.app.RemoteAction clone(); + method public int describeContents(); + method public void dump(java.lang.String, java.io.PrintWriter); + method public java.lang.CharSequence getContentDescription(); + method public android.graphics.drawable.Icon getIcon(); + method public java.lang.CharSequence getTitle(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.RemoteAction> CREATOR; + } + + public static abstract interface RemoteAction.OnActionListener { + method public abstract void onAction(android.app.RemoteAction); + } + public final class RemoteInput implements android.os.Parcelable { method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle); method public int describeContents(); @@ -6280,6 +6303,8 @@ package android.app.admin { method public boolean isCallerApplicationRestrictionsManagingPackage(); method public boolean isDeviceManaged(); method public boolean isDeviceOwnerApp(java.lang.String); + method public boolean isDeviceProvisioned(); + method public boolean isDeviceProvisioningConfigApplied(); method public boolean isLockTaskPermitted(java.lang.String); method public boolean isManagedProfile(android.content.ComponentName); method public boolean isMasterVolumeMuted(android.content.ComponentName); @@ -6315,6 +6340,7 @@ package android.app.admin { method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean); method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean); method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence); + method public void setDeviceProvisioningConfigApplied(); method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public boolean setKeyguardDisabled(android.content.ComponentName, boolean); method public void setKeyguardDisabledFeatures(android.content.ComponentName, int); @@ -6391,6 +6417,9 @@ package android.app.admin { field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION"; field public static final deprecated java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME"; field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM = "android.app.extra.PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM"; + field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMERS = "android.app.extra.PROVISIONING_DISCLAIMERS"; + field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_CONTENT = "android.app.extra.PROVISIONING_DISCLAIMER_CONTENT"; + field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_HEADER = "android.app.extra.PROVISIONING_DISCLAIMER_HEADER"; field public static final java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS"; field public static final java.lang.String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION"; field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED"; @@ -8731,6 +8760,7 @@ package android.content { field public static final java.lang.String TELECOM_SERVICE = "telecom"; field public static final java.lang.String TELEPHONY_SERVICE = "phone"; field public static final java.lang.String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service"; + field public static final java.lang.String TEXT_CLASSIFICATION_SERVICE = "textclassification"; field public static final java.lang.String TEXT_SERVICES_MANAGER_SERVICE = "textservices"; field public static final java.lang.String TV_INPUT_SERVICE = "tv_input"; field public static final java.lang.String UI_MODE_SERVICE = "uimode"; @@ -9088,6 +9118,7 @@ package android.content { field public static final java.lang.String ACTION_EDIT = "android.intent.action.EDIT"; field public static final java.lang.String ACTION_EXTERNAL_APPLICATIONS_AVAILABLE = "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE"; field public static final java.lang.String ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE = "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE"; + field public static final java.lang.String ACTION_FACTORY_RESET = "android.intent.action.FACTORY_RESET"; field public static final java.lang.String ACTION_FACTORY_TEST = "android.intent.action.FACTORY_TEST"; field public static final java.lang.String ACTION_GET_CONTENT = "android.intent.action.GET_CONTENT"; field public static final java.lang.String ACTION_GET_RESTRICTION_ENTRIES = "android.intent.action.GET_RESTRICTION_ENTRIES"; @@ -9251,6 +9282,7 @@ package android.content { field public static final java.lang.String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP"; field public static final java.lang.String EXTRA_EMAIL = "android.intent.extra.EMAIL"; field public static final java.lang.String EXTRA_EXCLUDE_COMPONENTS = "android.intent.extra.EXCLUDE_COMPONENTS"; + field public static final java.lang.String EXTRA_FORCE_FACTORY_RESET = "android.intent.extra.FORCE_FACTORY_RESET"; field public static final java.lang.String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT"; field public static final java.lang.String EXTRA_INDEX = "android.intent.extra.INDEX"; field public static final java.lang.String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS"; @@ -10090,6 +10122,7 @@ package android.content.pm { public class LauncherApps { method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle); + method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent); method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int); method public android.graphics.drawable.Drawable getShortcutIconDrawable(android.content.pm.ShortcutInfo, int); method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle); @@ -10105,6 +10138,8 @@ package android.content.pm { method public void startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle); method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle); method public void unregisterCallback(android.content.pm.LauncherApps.Callback); + field public static final java.lang.String ACTION_CONFIRM_PIN_ITEM = "android.content.pm.action.CONFIRM_PIN_ITEM"; + field public static final java.lang.String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST"; } public static abstract class LauncherApps.Callback { @@ -10119,6 +10154,21 @@ package android.content.pm { method public void onShortcutsChanged(java.lang.String, java.util.List<android.content.pm.ShortcutInfo>, android.os.UserHandle); } + public static final class LauncherApps.PinItemRequest implements android.os.Parcelable { + method public boolean accept(android.os.Bundle); + method public boolean accept(); + method public int describeContents(); + method public int getRequestType(); + method public android.content.pm.ShortcutInfo getShortcutInfo(); + method public boolean isValid(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.content.pm.LauncherApps.PinItemRequest> CREATOR; + field public static final int REQUEST_TYPE_SHORTCUT = 1; // 0x1 + } + + public static abstract class LauncherApps.PinItemRequest.RequestType implements java.lang.annotation.Annotation { + } + public static class LauncherApps.ShortcutQuery { ctor public LauncherApps.ShortcutQuery(); method public android.content.pm.LauncherApps.ShortcutQuery setActivity(android.content.ComponentName); @@ -10756,9 +10806,11 @@ package android.content.pm { method public int getMaxShortcutCountPerActivity(); method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts(); method public boolean isRateLimitingActive(); + method public boolean isRequestPinShortcutSupported(); method public void removeAllDynamicShortcuts(); method public void removeDynamicShortcuts(java.util.List<java.lang.String>); method public void reportShortcutUsed(java.lang.String); + method public boolean requestPinShortcut(android.content.pm.ShortcutInfo, android.content.IntentSender); method public boolean setDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>); method public boolean updateShortcuts(java.util.List<android.content.pm.ShortcutInfo>); } @@ -12210,6 +12262,8 @@ package android.graphics { method public static android.graphics.Bitmap createBitmap(android.graphics.Bitmap, int, int, int, int, android.graphics.Matrix, boolean); method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config); + method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config, boolean); + method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config, boolean); method public static android.graphics.Bitmap createBitmap(int[], int, int, int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int[], int, int, int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(int[], int, int, android.graphics.Bitmap.Config); @@ -12273,6 +12327,8 @@ package android.graphics { enum_constant public static final android.graphics.Bitmap.Config ALPHA_8; enum_constant public static final deprecated android.graphics.Bitmap.Config ARGB_4444; enum_constant public static final android.graphics.Bitmap.Config ARGB_8888; + enum_constant public static final android.graphics.Bitmap.Config HARDWARE; + enum_constant public static final android.graphics.Bitmap.Config RGBA_F16; enum_constant public static final android.graphics.Bitmap.Config RGB_565; } @@ -12632,6 +12688,7 @@ package android.graphics { method public android.graphics.Bitmap render(); method public android.graphics.ColorSpace.Renderer showWhitePoint(boolean); method public android.graphics.ColorSpace.Renderer size(int); + method public android.graphics.ColorSpace.Renderer uniformChromaticityScale(boolean); } public static class ColorSpace.Rgb extends android.graphics.ColorSpace { @@ -12733,12 +12790,6 @@ package android.graphics { enum_constant public static final android.graphics.Interpolator.Result NORMAL; } - public deprecated class LayerRasterizer extends android.graphics.Rasterizer { - ctor public LayerRasterizer(); - method public void addLayer(android.graphics.Paint, float, float); - method public void addLayer(android.graphics.Paint); - } - public class LightingColorFilter extends android.graphics.ColorFilter { ctor public LightingColorFilter(int, int); } @@ -12900,7 +12951,6 @@ package android.graphics { method public int getOffsetForAdvance(char[], int, int, int, int, boolean, float); method public int getOffsetForAdvance(java.lang.CharSequence, int, int, int, int, boolean, float); method public android.graphics.PathEffect getPathEffect(); - method public deprecated android.graphics.Rasterizer getRasterizer(); method public float getRunAdvance(char[], int, int, int, int, boolean, int); method public float getRunAdvance(java.lang.CharSequence, int, int, int, int, boolean, int); method public android.graphics.Shader getShader(); @@ -12957,7 +13007,6 @@ package android.graphics { method public void setLinearText(boolean); method public android.graphics.MaskFilter setMaskFilter(android.graphics.MaskFilter); method public android.graphics.PathEffect setPathEffect(android.graphics.PathEffect); - method public deprecated android.graphics.Rasterizer setRasterizer(android.graphics.Rasterizer); method public android.graphics.Shader setShader(android.graphics.Shader); method public void setShadowLayer(float, float, float, int); method public void setStrikeThruText(boolean); @@ -13175,6 +13224,7 @@ package android.graphics { field public static final deprecated int RGBA_4444 = 7; // 0x7 field public static final deprecated int RGBA_5551 = 6; // 0x6 field public static final int RGBA_8888 = 1; // 0x1 + field public static final int RGBA_F16 = 22; // 0x16 field public static final int RGBX_8888 = 2; // 0x2 field public static final deprecated int RGB_332 = 11; // 0xb field public static final int RGB_565 = 4; // 0x4 @@ -13264,10 +13314,6 @@ package android.graphics { ctor public RadialGradient(float, float, float, int, int, android.graphics.Shader.TileMode); } - public deprecated class Rasterizer { - ctor public Rasterizer(); - } - public final class Rect implements android.os.Parcelable { ctor public Rect(); ctor public Rect(int, int, int, int); @@ -15776,7 +15822,6 @@ package android.hardware.location { } public final class GeofenceHardware { - ctor public GeofenceHardware(android.hardware.location.IGeofenceHardware); method public boolean addGeofence(int, int, android.hardware.location.GeofenceHardwareRequest, android.hardware.location.GeofenceHardwareCallback); method public int[] getMonitoringTypes(); method public int getStatusOfMonitoringType(int); @@ -15851,46 +15896,6 @@ package android.hardware.location { method public void setUnknownTimer(int); } - public final class GeofenceHardwareRequestParcelable implements android.os.Parcelable { - ctor public GeofenceHardwareRequestParcelable(int, android.hardware.location.GeofenceHardwareRequest); - method public int describeContents(); - method public int getId(); - method public int getLastTransition(); - method public double getLatitude(); - method public double getLongitude(); - method public int getMonitorTransitions(); - method public int getNotificationResponsiveness(); - method public double getRadius(); - method public int getUnknownTimer(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.hardware.location.GeofenceHardwareRequestParcelable> CREATOR; - } - - public abstract interface IGeofenceHardware implements android.os.IInterface { - method public abstract boolean addCircularFence(int, android.hardware.location.GeofenceHardwareRequestParcelable, android.hardware.location.IGeofenceHardwareCallback) throws android.os.RemoteException; - method public abstract int[] getMonitoringTypes() throws android.os.RemoteException; - method public abstract int getStatusOfMonitoringType(int) throws android.os.RemoteException; - method public abstract boolean pauseGeofence(int, int) throws android.os.RemoteException; - method public abstract boolean registerForMonitorStateChangeCallback(int, android.hardware.location.IGeofenceHardwareMonitorCallback) throws android.os.RemoteException; - method public abstract boolean removeGeofence(int, int) throws android.os.RemoteException; - method public abstract boolean resumeGeofence(int, int, int) throws android.os.RemoteException; - method public abstract void setFusedGeofenceHardware(android.location.IFusedGeofenceHardware) throws android.os.RemoteException; - method public abstract void setGpsGeofenceHardware(android.location.IGpsGeofenceHardware) throws android.os.RemoteException; - method public abstract boolean unregisterForMonitorStateChangeCallback(int, android.hardware.location.IGeofenceHardwareMonitorCallback) throws android.os.RemoteException; - } - - public abstract interface IGeofenceHardwareCallback implements android.os.IInterface { - method public abstract void onGeofenceAdd(int, int) throws android.os.RemoteException; - method public abstract void onGeofencePause(int, int) throws android.os.RemoteException; - method public abstract void onGeofenceRemove(int, int) throws android.os.RemoteException; - method public abstract void onGeofenceResume(int, int) throws android.os.RemoteException; - method public abstract void onGeofenceTransition(int, int, android.location.Location, long, int) throws android.os.RemoteException; - } - - public abstract interface IGeofenceHardwareMonitorCallback implements android.os.IInterface { - method public abstract void onMonitoringSystemChange(android.hardware.location.GeofenceHardwareMonitorEvent) throws android.os.RemoteException; - } - public class MemoryRegion implements android.os.Parcelable { ctor public MemoryRegion(android.os.Parcel); method public int describeContents(); @@ -16498,6 +16503,10 @@ package android.icu.lang { field public static final int CONTROL = 1; // 0x1 field public static final int CR = 2; // 0x2 field public static final int EXTEND = 3; // 0x3 + field public static final int E_BASE = 13; // 0xd + field public static final int E_BASE_GAZ = 14; // 0xe + field public static final int E_MODIFIER = 15; // 0xf + field public static final int GLUE_AFTER_ZWJ = 16; // 0x10 field public static final int L = 4; // 0x4 field public static final int LF = 5; // 0x5 field public static final int LV = 6; // 0x6 @@ -16508,6 +16517,7 @@ package android.icu.lang { field public static final int SPACING_MARK = 10; // 0xa field public static final int T = 8; // 0x8 field public static final int V = 9; // 0x9 + field public static final int ZWJ = 17; // 0x11 } public static abstract interface UCharacter.HangulSyllableType { @@ -16520,6 +16530,9 @@ package android.icu.lang { } public static abstract interface UCharacter.JoiningGroup { + field public static final int AFRICAN_FEH = 86; // 0x56 + field public static final int AFRICAN_NOON = 87; // 0x57 + field public static final int AFRICAN_QAF = 88; // 0x58 field public static final int AIN = 1; // 0x1 field public static final int ALAPH = 2; // 0x2 field public static final int ALEF = 3; // 0x3 @@ -16633,6 +16646,8 @@ package android.icu.lang { field public static final int CONDITIONAL_JAPANESE_STARTER = 37; // 0x25 field public static final int CONTINGENT_BREAK = 7; // 0x7 field public static final int EXCLAMATION = 11; // 0xb + field public static final int E_BASE = 40; // 0x28 + field public static final int E_MODIFIER = 41; // 0x29 field public static final int GLUE = 12; // 0xc field public static final int H2 = 31; // 0x1f field public static final int H3 = 32; // 0x20 @@ -16659,6 +16674,7 @@ package android.icu.lang { field public static final int SURROGATE = 25; // 0x19 field public static final int UNKNOWN = 0; // 0x0 field public static final int WORD_JOINER = 30; // 0x1e + field public static final int ZWJ = 42; // 0x2a field public static final int ZWSPACE = 28; // 0x1c } @@ -16692,6 +16708,8 @@ package android.icu.lang { method public int getID(); method public static android.icu.lang.UCharacter.UnicodeBlock getInstance(int); method public static android.icu.lang.UCharacter.UnicodeBlock of(int); + field public static final android.icu.lang.UCharacter.UnicodeBlock ADLAM; + field public static final int ADLAM_ID = 263; // 0x107 field public static final android.icu.lang.UCharacter.UnicodeBlock AEGEAN_NUMBERS; field public static final int AEGEAN_NUMBERS_ID = 119; // 0x77 field public static final android.icu.lang.UCharacter.UnicodeBlock AHOM; @@ -16740,6 +16758,8 @@ package android.icu.lang { field public static final int BATAK_ID = 199; // 0xc7 field public static final android.icu.lang.UCharacter.UnicodeBlock BENGALI; field public static final int BENGALI_ID = 16; // 0x10 + field public static final android.icu.lang.UCharacter.UnicodeBlock BHAIKSUKI; + field public static final int BHAIKSUKI_ID = 264; // 0x108 field public static final android.icu.lang.UCharacter.UnicodeBlock BLOCK_ELEMENTS; field public static final int BLOCK_ELEMENTS_ID = 53; // 0x35 field public static final android.icu.lang.UCharacter.UnicodeBlock BOPOMOFO; @@ -16829,6 +16849,8 @@ package android.icu.lang { field public static final int CYRILLIC_EXTENDED_A_ID = 158; // 0x9e field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_EXTENDED_B; field public static final int CYRILLIC_EXTENDED_B_ID = 160; // 0xa0 + field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_EXTENDED_C; + field public static final int CYRILLIC_EXTENDED_C_ID = 265; // 0x109 field public static final int CYRILLIC_ID = 9; // 0x9 field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_SUPPLEMENT; field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_SUPPLEMENTARY; @@ -16882,6 +16904,8 @@ package android.icu.lang { field public static final int GEORGIAN_SUPPLEMENT_ID = 135; // 0x87 field public static final android.icu.lang.UCharacter.UnicodeBlock GLAGOLITIC; field public static final int GLAGOLITIC_ID = 136; // 0x88 + field public static final android.icu.lang.UCharacter.UnicodeBlock GLAGOLITIC_SUPPLEMENT; + field public static final int GLAGOLITIC_SUPPLEMENT_ID = 266; // 0x10a field public static final android.icu.lang.UCharacter.UnicodeBlock GOTHIC; field public static final int GOTHIC_ID = 89; // 0x59 field public static final android.icu.lang.UCharacter.UnicodeBlock GRANTHA; @@ -16920,6 +16944,8 @@ package android.icu.lang { field public static final int HIRAGANA_ID = 62; // 0x3e field public static final android.icu.lang.UCharacter.UnicodeBlock IDEOGRAPHIC_DESCRIPTION_CHARACTERS; field public static final int IDEOGRAPHIC_DESCRIPTION_CHARACTERS_ID = 60; // 0x3c + field public static final android.icu.lang.UCharacter.UnicodeBlock IDEOGRAPHIC_SYMBOLS_AND_PUNCTUATION; + field public static final int IDEOGRAPHIC_SYMBOLS_AND_PUNCTUATION_ID = 267; // 0x10b field public static final android.icu.lang.UCharacter.UnicodeBlock IMPERIAL_ARAMAIC; field public static final int IMPERIAL_ARAMAIC_ID = 186; // 0xba field public static final android.icu.lang.UCharacter.UnicodeBlock INSCRIPTIONAL_PAHLAVI; @@ -17004,6 +17030,8 @@ package android.icu.lang { field public static final int MANDAIC_ID = 198; // 0xc6 field public static final android.icu.lang.UCharacter.UnicodeBlock MANICHAEAN; field public static final int MANICHAEAN_ID = 234; // 0xea + field public static final android.icu.lang.UCharacter.UnicodeBlock MARCHEN; + field public static final int MARCHEN_ID = 268; // 0x10c field public static final android.icu.lang.UCharacter.UnicodeBlock MATHEMATICAL_ALPHANUMERIC_SYMBOLS; field public static final int MATHEMATICAL_ALPHANUMERIC_SYMBOLS_ID = 93; // 0x5d field public static final android.icu.lang.UCharacter.UnicodeBlock MATHEMATICAL_OPERATORS; @@ -17038,6 +17066,8 @@ package android.icu.lang { field public static final int MODI_ID = 236; // 0xec field public static final android.icu.lang.UCharacter.UnicodeBlock MONGOLIAN; field public static final int MONGOLIAN_ID = 37; // 0x25 + field public static final android.icu.lang.UCharacter.UnicodeBlock MONGOLIAN_SUPPLEMENT; + field public static final int MONGOLIAN_SUPPLEMENT_ID = 269; // 0x10d field public static final android.icu.lang.UCharacter.UnicodeBlock MRO; field public static final int MRO_ID = 237; // 0xed field public static final android.icu.lang.UCharacter.UnicodeBlock MULTANI; @@ -17052,6 +17082,8 @@ package android.icu.lang { field public static final int MYANMAR_ID = 28; // 0x1c field public static final android.icu.lang.UCharacter.UnicodeBlock NABATAEAN; field public static final int NABATAEAN_ID = 239; // 0xef + field public static final android.icu.lang.UCharacter.UnicodeBlock NEWA; + field public static final int NEWA_ID = 270; // 0x10e field public static final android.icu.lang.UCharacter.UnicodeBlock NEW_TAI_LUE; field public static final int NEW_TAI_LUE_ID = 139; // 0x8b field public static final android.icu.lang.UCharacter.UnicodeBlock NKO; @@ -17083,6 +17115,8 @@ package android.icu.lang { field public static final int ORIYA_ID = 19; // 0x13 field public static final android.icu.lang.UCharacter.UnicodeBlock ORNAMENTAL_DINGBATS; field public static final int ORNAMENTAL_DINGBATS_ID = 242; // 0xf2 + field public static final android.icu.lang.UCharacter.UnicodeBlock OSAGE; + field public static final int OSAGE_ID = 271; // 0x10f field public static final android.icu.lang.UCharacter.UnicodeBlock OSMANYA; field public static final int OSMANYA_ID = 122; // 0x7a field public static final android.icu.lang.UCharacter.UnicodeBlock PAHAWH_HMONG; @@ -17185,6 +17219,10 @@ package android.icu.lang { field public static final int TAKRI_ID = 220; // 0xdc field public static final android.icu.lang.UCharacter.UnicodeBlock TAMIL; field public static final int TAMIL_ID = 20; // 0x14 + field public static final android.icu.lang.UCharacter.UnicodeBlock TANGUT; + field public static final android.icu.lang.UCharacter.UnicodeBlock TANGUT_COMPONENTS; + field public static final int TANGUT_COMPONENTS_ID = 273; // 0x111 + field public static final int TANGUT_ID = 272; // 0x110 field public static final android.icu.lang.UCharacter.UnicodeBlock TELUGU; field public static final int TELUGU_ID = 21; // 0x15 field public static final android.icu.lang.UCharacter.UnicodeBlock THAANA; @@ -17231,7 +17269,11 @@ package android.icu.lang { field public static final int DOUBLE_QUOTE = 16; // 0x10 field public static final int EXTEND = 9; // 0x9 field public static final int EXTENDNUMLET = 7; // 0x7 + field public static final int E_BASE = 17; // 0x11 + field public static final int E_BASE_GAZ = 18; // 0x12 + field public static final int E_MODIFIER = 19; // 0x13 field public static final int FORMAT = 2; // 0x2 + field public static final int GLUE_AFTER_ZWJ = 20; // 0x14 field public static final int HEBREW_LETTER = 14; // 0xe field public static final int KATAKANA = 3; // 0x3 field public static final int LF = 10; // 0xa @@ -17243,6 +17285,7 @@ package android.icu.lang { field public static final int OTHER = 0; // 0x0 field public static final int REGIONAL_INDICATOR = 13; // 0xd field public static final int SINGLE_QUOTE = 15; // 0xf + field public static final int ZWJ = 21; // 0x15 } public final class UCharacterCategory implements android.icu.lang.UCharacterEnums.ECharacterCategory { @@ -17461,6 +17504,7 @@ package android.icu.lang { method public static final boolean hasScript(int, int); method public static final boolean isCased(int); method public static final boolean isRightToLeft(int); + field public static final int ADLAM = 167; // 0xa7 field public static final int AFAKA = 147; // 0x93 field public static final int AHOM = 161; // 0xa1 field public static final int ANATOLIAN_HIEROGLYPHS = 156; // 0x9c @@ -17472,6 +17516,7 @@ package android.icu.lang { field public static final int BASSA_VAH = 134; // 0x86 field public static final int BATAK = 63; // 0x3f field public static final int BENGALI = 4; // 0x4 + field public static final int BHAIKSUKI = 168; // 0xa8 field public static final int BLISSYMBOLS = 64; // 0x40 field public static final int BOOK_PAHLAVI = 124; // 0x7c field public static final int BOPOMOFO = 5; // 0x5 @@ -17510,6 +17555,7 @@ package android.icu.lang { field public static final int HAN = 17; // 0x11 field public static final int HANGUL = 18; // 0x12 field public static final int HANUNOO = 43; // 0x2b + field public static final int HAN_WITH_BOPOMOFO = 172; // 0xac field public static final int HARAPPAN_INDUS = 77; // 0x4d field public static final int HATRAN = 162; // 0xa2 field public static final int HEBREW = 19; // 0x13 @@ -17520,6 +17566,7 @@ package android.icu.lang { field public static final int INSCRIPTIONAL_PAHLAVI = 122; // 0x7a field public static final int INSCRIPTIONAL_PARTHIAN = 125; // 0x7d field public static final int INVALID_CODE = -1; // 0xffffffff + field public static final int JAMO = 173; // 0xad field public static final int JAPANESE = 105; // 0x69 field public static final int JAVANESE = 78; // 0x4e field public static final int JURCHEN = 148; // 0x94 @@ -17553,6 +17600,7 @@ package android.icu.lang { field public static final int MANDAEAN = 84; // 0x54 field public static final int MANDAIC = 84; // 0x54 field public static final int MANICHAEAN = 121; // 0x79 + field public static final int MARCHEN = 169; // 0xa9 field public static final int MATHEMATICAL_NOTATION = 128; // 0x80 field public static final int MAYAN_HIEROGLYPHS = 85; // 0x55 field public static final int MEITEI_MAYEK = 115; // 0x73 @@ -17569,6 +17617,7 @@ package android.icu.lang { field public static final int MYANMAR = 28; // 0x1c field public static final int NABATAEAN = 143; // 0x8f field public static final int NAKHI_GEBA = 132; // 0x84 + field public static final int NEWA = 170; // 0xaa field public static final int NEW_TAI_LUE = 59; // 0x3b field public static final int NKO = 87; // 0x57 field public static final int NUSHU = 150; // 0x96 @@ -17583,6 +17632,7 @@ package android.icu.lang { field public static final int OL_CHIKI = 109; // 0x6d field public static final int ORIYA = 31; // 0x1f field public static final int ORKHON = 88; // 0x58 + field public static final int OSAGE = 171; // 0xab field public static final int OSMANYA = 50; // 0x32 field public static final int PAHAWH_HMONG = 75; // 0x4b field public static final int PALMYRENE = 144; // 0x90 @@ -17608,6 +17658,7 @@ package android.icu.lang { field public static final int SUNDANESE = 113; // 0x71 field public static final int SYLOTI_NAGRI = 58; // 0x3a field public static final int SYMBOLS = 129; // 0x81 + field public static final int SYMBOLS_EMOJI = 174; // 0xae field public static final int SYRIAC = 34; // 0x22 field public static final int TAGALOG = 42; // 0x2a field public static final int TAGBANWA = 45; // 0x2d @@ -18134,6 +18185,8 @@ package android.icu.text { method public static final android.icu.text.DateFormat.BooleanAttribute[] values(); enum_constant public static final android.icu.text.DateFormat.BooleanAttribute PARSE_ALLOW_NUMERIC; enum_constant public static final android.icu.text.DateFormat.BooleanAttribute PARSE_ALLOW_WHITESPACE; + enum_constant public static final android.icu.text.DateFormat.BooleanAttribute PARSE_MULTIPLE_PATTERNS_FOR_MATCH; + enum_constant public static final android.icu.text.DateFormat.BooleanAttribute PARSE_PARTIAL_LITERAL_MATCH; } public static class DateFormat.Field extends java.text.Format.Field { @@ -18818,6 +18871,7 @@ package android.icu.text { field public static final int PERCENTSTYLE = 2; // 0x2 field public static final int PLURALCURRENCYSTYLE = 6; // 0x6 field public static final int SCIENTIFICSTYLE = 3; // 0x3 + field public static final int STANDARDCURRENCYSTYLE = 9; // 0x9 } public static class NumberFormat.Field extends java.text.Format.Field { @@ -19612,7 +19666,7 @@ package android.icu.util { field public static final int AM_PM = 9; // 0x9 field public static final int APRIL = 3; // 0x3 field public static final int AUGUST = 7; // 0x7 - field protected static final int BASE_FIELD_COUNT = 23; // 0x17 + field protected static final deprecated int BASE_FIELD_COUNT = 23; // 0x17 field public static final int DATE = 5; // 0x5 field public static final int DAY_OF_MONTH = 5; // 0x5 field public static final int DAY_OF_WEEK = 7; // 0x7 @@ -19640,7 +19694,7 @@ package android.icu.util { field public static final int MARCH = 2; // 0x2 field protected static final int MAXIMUM = 3; // 0x3 field protected static final java.util.Date MAX_DATE; - field protected static final int MAX_FIELD_COUNT = 32; // 0x20 + field protected static final deprecated int MAX_FIELD_COUNT = 32; // 0x20 field protected static final int MAX_JULIAN = 2130706432; // 0x7f000000 field protected static final long MAX_MILLIS = 183882168921600000L; // 0x28d47dbbf19b000L field public static final int MAY = 4; // 0x4 @@ -19960,6 +20014,7 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit CELSIUS; field public static final android.icu.util.MeasureUnit CENTILITER; field public static final android.icu.util.MeasureUnit CENTIMETER; + field public static final android.icu.util.MeasureUnit CENTURY; field public static final android.icu.util.MeasureUnit CUBIC_CENTIMETER; field public static final android.icu.util.MeasureUnit CUBIC_FOOT; field public static final android.icu.util.MeasureUnit CUBIC_INCH; @@ -19968,6 +20023,7 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit CUBIC_MILE; field public static final android.icu.util.MeasureUnit CUBIC_YARD; field public static final android.icu.util.MeasureUnit CUP; + field public static final android.icu.util.MeasureUnit CUP_METRIC; field public static final android.icu.util.TimeUnit DAY; field public static final android.icu.util.MeasureUnit DECILITER; field public static final android.icu.util.MeasureUnit DECIMETER; @@ -19979,6 +20035,7 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit FOOT; field public static final android.icu.util.MeasureUnit FURLONG; field public static final android.icu.util.MeasureUnit GALLON; + field public static final android.icu.util.MeasureUnit GENERIC_TEMPERATURE; field public static final android.icu.util.MeasureUnit GIGABIT; field public static final android.icu.util.MeasureUnit GIGABYTE; field public static final android.icu.util.MeasureUnit GIGAHERTZ; @@ -20006,8 +20063,10 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit KILOMETER_PER_HOUR; field public static final android.icu.util.MeasureUnit KILOWATT; field public static final android.icu.util.MeasureUnit KILOWATT_HOUR; + field public static final android.icu.util.MeasureUnit KNOT; field public static final android.icu.util.MeasureUnit LIGHT_YEAR; field public static final android.icu.util.MeasureUnit LITER; + field public static final android.icu.util.MeasureUnit LITER_PER_100KILOMETERS; field public static final android.icu.util.MeasureUnit LITER_PER_KILOMETER; field public static final android.icu.util.MeasureUnit LUX; field public static final android.icu.util.MeasureUnit MEGABIT; @@ -20025,6 +20084,7 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit MILE; field public static final android.icu.util.MeasureUnit MILE_PER_GALLON; field public static final android.icu.util.MeasureUnit MILE_PER_HOUR; + field public static final android.icu.util.MeasureUnit MILE_SCANDINAVIAN; field public static final android.icu.util.MeasureUnit MILLIAMPERE; field public static final android.icu.util.MeasureUnit MILLIBAR; field public static final android.icu.util.MeasureUnit MILLIGRAM; @@ -20044,10 +20104,12 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit PARSEC; field public static final android.icu.util.MeasureUnit PICOMETER; field public static final android.icu.util.MeasureUnit PINT; + field public static final android.icu.util.MeasureUnit PINT_METRIC; field public static final android.icu.util.MeasureUnit POUND; field public static final android.icu.util.MeasureUnit POUND_PER_SQUARE_INCH; field public static final android.icu.util.MeasureUnit QUART; field public static final android.icu.util.MeasureUnit RADIAN; + field public static final android.icu.util.MeasureUnit REVOLUTION_ANGLE; field public static final android.icu.util.TimeUnit SECOND; field public static final android.icu.util.MeasureUnit SQUARE_CENTIMETER; field public static final android.icu.util.MeasureUnit SQUARE_FOOT; @@ -20359,6 +20421,7 @@ package android.icu.util { field public static final android.icu.util.VersionInfo UNICODE_6_3; field public static final android.icu.util.VersionInfo UNICODE_7_0; field public static final android.icu.util.VersionInfo UNICODE_8_0; + field public static final android.icu.util.VersionInfo UNICODE_9_0; } } @@ -21139,23 +21202,6 @@ package android.location { method public abstract void onNmeaReceived(long, java.lang.String); } - public abstract interface IFusedGeofenceHardware implements android.os.IInterface { - method public abstract void addGeofences(android.hardware.location.GeofenceHardwareRequestParcelable[]) throws android.os.RemoteException; - method public abstract boolean isSupported() throws android.os.RemoteException; - method public abstract void modifyGeofenceOptions(int, int, int, int, int, int) throws android.os.RemoteException; - method public abstract void pauseMonitoringGeofence(int) throws android.os.RemoteException; - method public abstract void removeGeofences(int[]) throws android.os.RemoteException; - method public abstract void resumeMonitoringGeofence(int, int) throws android.os.RemoteException; - } - - public abstract interface IGpsGeofenceHardware implements android.os.IInterface { - method public abstract boolean addCircularHardwareGeofence(int, double, double, double, int, int, int, int) throws android.os.RemoteException; - method public abstract boolean isHardwareGeofenceSupported() throws android.os.RemoteException; - method public abstract boolean pauseHardwareGeofence(int) throws android.os.RemoteException; - method public abstract boolean removeHardwareGeofence(int) throws android.os.RemoteException; - method public abstract boolean resumeHardwareGeofence(int, int) throws android.os.RemoteException; - } - public class Location implements android.os.Parcelable { ctor public Location(java.lang.String); ctor public Location(android.location.Location); @@ -24258,6 +24304,7 @@ package android.media.browse { method public android.content.ComponentName getServiceComponent(); method public android.media.session.MediaSession.Token getSessionToken(); method public boolean isConnected(); + method public void search(java.lang.String, android.os.Bundle, android.media.browse.MediaBrowser.SearchCallback); method public void subscribe(java.lang.String, android.media.browse.MediaBrowser.SubscriptionCallback); method public void subscribe(java.lang.String, android.os.Bundle, android.media.browse.MediaBrowser.SubscriptionCallback); method public void unsubscribe(java.lang.String); @@ -24293,6 +24340,12 @@ package android.media.browse { field public static final int FLAG_PLAYABLE = 2; // 0x2 } + public static abstract class MediaBrowser.SearchCallback { + ctor public MediaBrowser.SearchCallback(); + method public void onError(java.lang.String, android.os.Bundle); + method public void onSearchResult(java.lang.String, android.os.Bundle, java.util.List<android.media.browse.MediaBrowser.MediaItem>); + } + public static abstract class MediaBrowser.SubscriptionCallback { ctor public MediaBrowser.SubscriptionCallback(); method public void onChildrenLoaded(java.lang.String, java.util.List<android.media.browse.MediaBrowser.MediaItem>); @@ -25997,11 +26050,16 @@ package android.net { public abstract class NetworkRecommendationProvider { ctor public NetworkRecommendationProvider(android.os.Handler); method public final android.os.IBinder getBinder(); - method public abstract android.net.RecommendationResult onRequestRecommendation(android.net.RecommendationRequest); + method public abstract void onRequestRecommendation(android.net.RecommendationRequest, android.net.NetworkRecommendationProvider.ResultCallback); + method public abstract void onRequestScores(android.net.NetworkKey[]); field public static final java.lang.String EXTRA_RECOMMENDATION_RESULT = "android.net.extra.RECOMMENDATION_RESULT"; field public static final java.lang.String EXTRA_SEQUENCE = "android.net.extra.SEQUENCE"; } + public static class NetworkRecommendationProvider.ResultCallback { + method public void onResult(android.net.RecommendationResult); + } + public class NetworkRequest implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); @@ -31681,6 +31739,27 @@ package android.os { method public abstract android.os.IBinder asBinder(); } + public class IncidentManager { + method public void reportIncident(android.os.IncidentReportArgs); + method public void reportIncident(java.lang.String, byte[]); + } + + public final class IncidentReportArgs implements android.os.Parcelable { + ctor public IncidentReportArgs(); + ctor public IncidentReportArgs(android.os.Parcel); + method public void addHeader(byte[]); + method public void addSection(int); + method public boolean containsSection(int); + method public int describeContents(); + method public boolean isAll(); + method public static android.os.IncidentReportArgs parseSetting(java.lang.String) throws java.lang.IllegalArgumentException; + method public void readFromParcel(android.os.Parcel); + method public int sectionCount(); + method public void setAll(boolean); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.os.IncidentReportArgs> CREATOR; + } + public final class LocaleList implements android.os.Parcelable { ctor public LocaleList(java.util.Locale...); method public int describeContents(); @@ -32368,6 +32447,7 @@ package android.os { method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle); method public static boolean supportsMultipleUsers(); field public static final java.lang.String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking"; + field public static final java.lang.String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile"; field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user"; field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume"; field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps"; @@ -32390,8 +32470,10 @@ package android.os { field public static final java.lang.String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts"; field public static final java.lang.String DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media"; field public static final java.lang.String DISALLOW_NETWORK_RESET = "no_network_reset"; + field public static final java.lang.String DISALLOW_OEM_UNLOCK = "no_oem_unlock"; field public static final java.lang.String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam"; field public static final java.lang.String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls"; + field public static final java.lang.String DISALLOW_REMOVE_MANAGED_PROFILE = "no_remove_managed_profile"; field public static final java.lang.String DISALLOW_REMOVE_USER = "no_remove_user"; field public static final java.lang.String DISALLOW_SAFE_BOOT = "no_safe_boot"; field public static final java.lang.String DISALLOW_SET_USER_ICON = "no_set_user_icon"; @@ -37709,7 +37791,8 @@ package android.service.autofill { method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); - method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.CancellationSignal, android.service.autofill.FillCallback); + method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback); + method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback); field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService"; } @@ -37727,6 +37810,11 @@ package android.service.autofill { method public android.service.autofill.FillCallback.FillData.Builder setTextField(int, java.lang.String); } + public final class SaveCallback { + method public void onFailure(java.lang.CharSequence); + method public void onSuccess(int[]); + } + } package android.service.carrier { @@ -37908,6 +37996,7 @@ package android.service.media { method public abstract void onLoadChildren(java.lang.String, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>); method public void onLoadChildren(java.lang.String, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>, android.os.Bundle); method public void onLoadItem(java.lang.String, android.service.media.MediaBrowserService.Result<android.media.browse.MediaBrowser.MediaItem>); + method public void onSearch(java.lang.String, android.os.Bundle, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>); method public void setSessionToken(android.media.session.MediaSession.Token); field public static final java.lang.String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService"; } @@ -37997,8 +38086,12 @@ package android.service.notification { ctor public NotificationAssistantService(); method public final void adjustNotification(android.service.notification.Adjustment); method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>); + method public void createNotificationChannel(java.lang.String, android.app.NotificationChannel); + method public void deleteNotificationChannel(java.lang.String, java.lang.String); + method public java.util.List<android.app.NotificationChannel> getNotificationChannels(java.lang.String); method public final android.os.IBinder onBind(android.content.Intent); method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean); + method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel); field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; } @@ -40470,6 +40563,7 @@ package android.telephony { field public static final java.lang.String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool"; field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; field public static final java.lang.String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; + field public static final java.lang.String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool"; field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool"; field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool"; field public static final java.lang.String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool"; @@ -41045,6 +41139,7 @@ package android.telephony { method public java.lang.String getSimOperatorName(); method public java.lang.String getSimSerialNumber(); method public int getSimState(); + method public int getSimState(int); method public java.lang.String getSubscriberId(); method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms(); method public java.lang.String getVoiceMailAlphaTag(); @@ -41160,7 +41255,11 @@ package android.telephony { field public static final int SIM_ACTIVATION_RESULT_IN_PROGRESS = 2; // 0x2 field public static final int SIM_ACTIVATION_RESULT_NOT_SUPPORTED = 1; // 0x1 field public static final int SIM_STATE_ABSENT = 1; // 0x1 + field public static final int SIM_STATE_CARD_IO_ERROR = 8; // 0x8 + field public static final int SIM_STATE_CARD_RESTRICTED = 9; // 0x9 field public static final int SIM_STATE_NETWORK_LOCKED = 4; // 0x4 + field public static final int SIM_STATE_NOT_READY = 6; // 0x6 + field public static final int SIM_STATE_PERM_DISABLED = 7; // 0x7 field public static final int SIM_STATE_PIN_REQUIRED = 2; // 0x2 field public static final int SIM_STATE_PUK_REQUIRED = 3; // 0x3 field public static final int SIM_STATE_READY = 5; // 0x5 @@ -41938,7 +42037,7 @@ package android.text { method public java.lang.CharSequence subSequence(int, int); } - public class AndroidCharacter { + public deprecated class AndroidCharacter { ctor public AndroidCharacter(); method public static void getDirectionalities(char[], byte[], int); method public static int getEastAsianWidth(char); @@ -42432,6 +42531,12 @@ package android.text { method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence(); } + public final class TextClassificationManager implements android.text.TextAssistant { + method public void addLinks(android.text.Spannable, int); + method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence); + method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int); + } + public abstract interface TextDirectionHeuristic { method public abstract boolean isRtl(char[], int, int); method public abstract boolean isRtl(java.lang.CharSequence, int, int); @@ -42447,6 +42552,13 @@ package android.text { field public static final android.text.TextDirectionHeuristic RTL; } + public final class TextLanguage { + ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>); + method public int getEndIndex(); + method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence(); + method public int getStartIndex(); + } + public class TextPaint extends android.graphics.Paint { ctor public TextPaint(); ctor public TextPaint(int); @@ -43141,12 +43253,6 @@ package android.text.style { method public void writeToParcel(android.os.Parcel, int); } - public class RasterizerSpan extends android.text.style.CharacterStyle implements android.text.style.UpdateAppearance { - ctor public RasterizerSpan(android.graphics.Rasterizer); - method public android.graphics.Rasterizer getRasterizer(); - method public void updateDrawState(android.text.TextPaint); - } - public class RelativeSizeSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan { ctor public RelativeSizeSpan(float); ctor public RelativeSizeSpan(android.os.Parcel); @@ -44794,6 +44900,7 @@ package android.view { method public android.view.View findNearestTouchable(android.view.ViewGroup, int, int, int, int[]); method public final android.view.View findNextFocus(android.view.ViewGroup, android.view.View, int); method public android.view.View findNextFocusFromRect(android.view.ViewGroup, android.graphics.Rect, int); + method public android.view.View findNextKeyboardNavigationCluster(android.view.ViewGroup, android.view.View, int); method public static android.view.FocusFinder getInstance(); } @@ -46086,6 +46193,7 @@ package android.view { method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>); method public void addFocusables(java.util.ArrayList<android.view.View>, int); method public void addFocusables(java.util.ArrayList<android.view.View>, int, int); + method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int); method public void addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener); method public void addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener); method public void addTouchables(java.util.ArrayList<android.view.View>); @@ -46142,7 +46250,8 @@ package android.view { method public boolean dispatchNestedPreScroll(int, int, int[], int[]); method public boolean dispatchNestedScroll(int, int, int, int, int[]); method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent); - method public void dispatchProvideStructure(android.view.ViewStructure); + method public deprecated void dispatchProvideStructure(android.view.ViewStructure); + method public void dispatchProvideStructure(android.view.ViewStructure, int); method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>); method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>); method protected void dispatchSetActivated(boolean); @@ -46239,11 +46348,13 @@ package android.view { method public final int getMeasuredWidthAndState(); method public int getMinimumHeight(); method public int getMinimumWidth(); + method public int getNextClusterForwardId(); method public int getNextFocusDownId(); method public int getNextFocusForwardId(); method public int getNextFocusLeftId(); method public int getNextFocusRightId(); method public int getNextFocusUpId(); + method public int getNextSectionForwardId(); method public android.view.View.OnFocusChangeListener getOnFocusChangeListener(); method public android.view.ViewOutlineProvider getOutlineProvider(); method public int getOverScrollMode(); @@ -46346,6 +46457,8 @@ package android.view { method public boolean isInEditMode(); method public boolean isInLayout(); method public boolean isInTouchMode(); + method public final boolean isKeyboardNavigationCluster(); + method public final boolean isKeyboardNavigationSection(); method public boolean isLaidOut(); method public boolean isLayoutDirectionResolved(); method public boolean isLayoutRequested(); @@ -46368,6 +46481,7 @@ package android.view { method public boolean isVerticalFadingEdgeEnabled(); method public boolean isVerticalScrollBarEnabled(); method public void jumpDrawablesToCurrentState(); + method public android.view.View keyboardNavigationClusterSearch(int); method public void layout(int, int, int, int); method public final void measure(int, int); method protected static int[] mergeDrawableStates(int[], int[]); @@ -46408,8 +46522,10 @@ package android.view { method protected void onMeasure(int, int); method protected void onOverScrolled(int, int, boolean, boolean); method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent); - method public void onProvideStructure(android.view.ViewStructure); - method public void onProvideVirtualStructure(android.view.ViewStructure); + method public deprecated void onProvideStructure(android.view.ViewStructure); + method public void onProvideStructure(android.view.ViewStructure, int); + method public deprecated void onProvideVirtualStructure(android.view.ViewStructure); + method public void onProvideVirtualStructure(android.view.ViewStructure, int); method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int); method protected void onRestoreInstanceState(android.os.Parcelable); method public void onRtlPropertiesChanged(int); @@ -46512,6 +46628,8 @@ package android.view { method public void setId(int); method public void setImportantForAccessibility(int); method public void setKeepScreenOn(boolean); + method public void setKeyboardNavigationCluster(boolean); + method public void setKeyboardNavigationSection(boolean); method public void setLabelFor(int); method public void setLayerPaint(android.graphics.Paint); method public void setLayerType(int, android.graphics.Paint); @@ -46523,11 +46641,13 @@ package android.view { method public void setMinimumHeight(int); method public void setMinimumWidth(int); method public void setNestedScrollingEnabled(boolean); + method public void setNextClusterForwardId(int); method public void setNextFocusDownId(int); method public void setNextFocusForwardId(int); method public void setNextFocusLeftId(int); method public void setNextFocusRightId(int); method public void setNextFocusUpId(int); + method public void setNextSectionForwardId(int); method public void setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener); method public void setOnClickListener(android.view.View.OnClickListener); method public void setOnContextClickListener(android.view.View.OnContextClickListener); @@ -46611,6 +46731,8 @@ package android.view { field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0 field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1 field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA; + field public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 2; // 0x2 + field public static final int ASSIST_FLAG_SANITIZED_TEXT = 1; // 0x1 field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100 field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40 field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80 @@ -47007,6 +47129,7 @@ package android.view { method protected deprecated boolean isChildrenDrawnWithCacheEnabled(); method public boolean isMotionEventSplittingEnabled(); method public boolean isTransitionGroup(); + method public android.view.View keyboardNavigationClusterSearch(android.view.View, int); method public final void layout(int, int, int, int); method protected void measureChild(android.view.View, int, int); method protected void measureChildWithMargins(android.view.View, int, int, int, int); @@ -47169,6 +47292,7 @@ package android.view { method public abstract boolean isLayoutRequested(); method public abstract boolean isTextAlignmentResolved(); method public abstract boolean isTextDirectionResolved(); + method public abstract android.view.View keyboardNavigationClusterSearch(android.view.View, int); method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int); method public abstract boolean onNestedFling(android.view.View, float, float, boolean); method public abstract boolean onNestedPreFling(android.view.View, float, float); @@ -49981,6 +50105,7 @@ package android.webkit { method public abstract void onMeasure(int, int); method public abstract void onOverScrolled(int, int, boolean, boolean); method public abstract void onProvideVirtualStructure(android.view.ViewStructure); + method public default void onProvideVirtualStructure(android.view.ViewStructure, int); method public abstract void onScrollChanged(int, int, int, int); method public abstract void onSizeChanged(int, int, int, int); method public abstract void onStartTemporaryDetach(); @@ -63430,6 +63555,10 @@ package java.util { ctor public Locale(java.lang.String, java.lang.String); ctor public Locale(java.lang.String); method public java.lang.Object clone(); + method public static java.util.List<java.util.Locale> filter(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.util.Locale>, java.util.Locale.FilteringMode); + method public static java.util.List<java.util.Locale> filter(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.util.Locale>); + method public static java.util.List<java.lang.String> filterTags(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.lang.String>, java.util.Locale.FilteringMode); + method public static java.util.List<java.lang.String> filterTags(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.lang.String>); method public static java.util.Locale forLanguageTag(java.lang.String); method public static java.util.Locale[] getAvailableLocales(); method public java.lang.String getCountry(); @@ -63458,6 +63587,8 @@ package java.util { method public java.lang.String getUnicodeLocaleType(java.lang.String); method public java.lang.String getVariant(); method public boolean hasExtensions(); + method public static java.util.Locale lookup(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.util.Locale>); + method public static java.lang.String lookupTag(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.lang.String>); method public static synchronized void setDefault(java.util.Locale); method public static synchronized void setDefault(java.util.Locale.Category, java.util.Locale); method public java.util.Locale stripExtensions(); @@ -63513,6 +63644,28 @@ package java.util { enum_constant public static final java.util.Locale.Category FORMAT; } + public static final class Locale.FilteringMode extends java.lang.Enum { + method public static java.util.Locale.FilteringMode valueOf(java.lang.String); + method public static final java.util.Locale.FilteringMode[] values(); + enum_constant public static final java.util.Locale.FilteringMode AUTOSELECT_FILTERING; + enum_constant public static final java.util.Locale.FilteringMode EXTENDED_FILTERING; + enum_constant public static final java.util.Locale.FilteringMode IGNORE_EXTENDED_RANGES; + enum_constant public static final java.util.Locale.FilteringMode MAP_EXTENDED_RANGES; + enum_constant public static final java.util.Locale.FilteringMode REJECT_EXTENDED_RANGES; + } + + public static final class Locale.LanguageRange { + ctor public Locale.LanguageRange(java.lang.String); + ctor public Locale.LanguageRange(java.lang.String, double); + method public java.lang.String getRange(); + method public double getWeight(); + method public static java.util.List<java.util.Locale.LanguageRange> mapEquivalents(java.util.List<java.util.Locale.LanguageRange>, java.util.Map<java.lang.String, java.util.List<java.lang.String>>); + method public static java.util.List<java.util.Locale.LanguageRange> parse(java.lang.String); + method public static java.util.List<java.util.Locale.LanguageRange> parse(java.lang.String, java.util.Map<java.lang.String, java.util.List<java.lang.String>>); + field public static final double MAX_WEIGHT = 1.0; + field public static final double MIN_WEIGHT = 0.0; + } + public class LongSummaryStatistics implements java.util.function.IntConsumer java.util.function.LongConsumer { ctor public LongSummaryStatistics(); method public void accept(int); @@ -64796,31 +64949,31 @@ package java.util.concurrent { ctor public CopyOnWriteArrayList(); ctor public CopyOnWriteArrayList(java.util.Collection<? extends E>); ctor public CopyOnWriteArrayList(E[]); - method public synchronized boolean add(E); - method public synchronized void add(int, E); - method public synchronized boolean addAll(java.util.Collection<? extends E>); - method public synchronized boolean addAll(int, java.util.Collection<? extends E>); - method public synchronized int addAllAbsent(java.util.Collection<? extends E>); - method public synchronized boolean addIfAbsent(E); - method public synchronized void clear(); + method public boolean add(E); + method public void add(int, E); + method public boolean addAll(java.util.Collection<? extends E>); + method public boolean addAll(int, java.util.Collection<? extends E>); + method public int addAllAbsent(java.util.Collection<? extends E>); + method public boolean addIfAbsent(E); + method public void clear(); method public java.lang.Object clone(); method public boolean contains(java.lang.Object); method public boolean containsAll(java.util.Collection<?>); method public void forEach(java.util.function.Consumer<? super E>); method public E get(int); - method public int indexOf(E, int); method public int indexOf(java.lang.Object); + method public int indexOf(E, int); method public boolean isEmpty(); method public java.util.Iterator<E> iterator(); - method public int lastIndexOf(E, int); method public int lastIndexOf(java.lang.Object); - method public java.util.ListIterator<E> listIterator(int); + method public int lastIndexOf(E, int); method public java.util.ListIterator<E> listIterator(); - method public synchronized E remove(int); - method public synchronized boolean remove(java.lang.Object); - method public synchronized boolean removeAll(java.util.Collection<?>); - method public synchronized boolean retainAll(java.util.Collection<?>); - method public synchronized E set(int, E); + method public java.util.ListIterator<E> listIterator(int); + method public E remove(int); + method public boolean remove(java.lang.Object); + method public boolean removeAll(java.util.Collection<?>); + method public boolean retainAll(java.util.Collection<?>); + method public E set(int, E); method public int size(); method public java.util.List<E> subList(int, int); method public java.lang.Object[] toArray(); diff --git a/api/system-removed.txt b/api/system-removed.txt index 98e7953b6b25..0919b8f4d275 100644 --- a/api/system-removed.txt +++ b/api/system-removed.txt @@ -76,10 +76,25 @@ package android.graphics { enum_constant public static final android.graphics.AvoidXfermode.Mode TARGET; } + public deprecated class LayerRasterizer extends android.graphics.Rasterizer { + ctor public LayerRasterizer(); + method public void addLayer(android.graphics.Paint, float, float); + method public void addLayer(android.graphics.Paint); + } + + public class Paint { + method public deprecated android.graphics.Rasterizer getRasterizer(); + method public deprecated android.graphics.Rasterizer setRasterizer(android.graphics.Rasterizer); + } + public deprecated class PixelXorXfermode extends android.graphics.Xfermode { ctor public PixelXorXfermode(int); } + public class Rasterizer { + ctor public Rasterizer(); + } + } package android.location { @@ -387,6 +402,15 @@ package android.text.format { } +package android.text.style { + + public class RasterizerSpan extends android.text.style.CharacterStyle implements android.text.style.UpdateAppearance { + ctor public RasterizerSpan(android.graphics.Rasterizer); + method public android.graphics.Rasterizer getRasterizer(); + } + +} + package android.util { public deprecated class FloatMath { diff --git a/api/test-current.txt b/api/test-current.txt index 2a347d717da4..e43d1b5426c3 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -750,6 +750,8 @@ package android { field public static final int keyWidth = 16843325; // 0x101023d field public static final int keyboardLayout = 16843691; // 0x10103ab field public static final int keyboardMode = 16843341; // 0x101024d + field public static final int keyboardNavigationCluster = 16844096; // 0x1010540 + field public static final int keyboardNavigationSection = 16844097; // 0x1010541 field public static final int keycode = 16842949; // 0x10100c5 field public static final int killAfterRestore = 16843420; // 0x101029c field public static final int label = 16842753; // 0x1010001 @@ -893,11 +895,13 @@ package android { field public static final int negativeButtonText = 16843254; // 0x10101f6 field public static final int nestedScrollingEnabled = 16843830; // 0x1010436 field public static final int networkSecurityConfig = 16844071; // 0x1010527 + field public static final int nextClusterForward = 16844098; // 0x1010542 field public static final int nextFocusDown = 16842980; // 0x10100e4 field public static final int nextFocusForward = 16843580; // 0x101033c field public static final int nextFocusLeft = 16842977; // 0x10100e1 field public static final int nextFocusRight = 16842978; // 0x10100e2 field public static final int nextFocusUp = 16842979; // 0x10100e3 + field public static final int nextSectionForward = 16844099; // 0x1010543 field public static final int noHistory = 16843309; // 0x101022d field public static final int normalScreens = 16843397; // 0x1010285 field public static final int notificationTimeout = 16843651; // 0x1010383 @@ -3655,6 +3659,7 @@ package android.app { method public void setIntent(android.content.Intent); method public final void setMediaController(android.media.session.MediaController); method public void setOverlayWithDecorCaptionEnabled(boolean); + method public void setPictureInPictureActions(java.util.List<android.app.RemoteAction>); method public void setPictureInPictureAspectRatio(float); method public final deprecated void setProgress(int); method public final deprecated void setProgressBarIndeterminate(boolean); @@ -3736,6 +3741,7 @@ package android.app { method public int getLauncherLargeIconDensity(); method public int getLauncherLargeIconSize(); method public int getLockTaskModeState(); + method public static int getMaxNumPictureInPictureActions(); method public int getMemoryClass(); method public void getMemoryInfo(android.app.ActivityManager.MemoryInfo); method public static void getMyMemoryState(android.app.ActivityManager.RunningAppProcessInfo); @@ -5554,6 +5560,22 @@ package android.app { field public static final int STYLE_SPINNER = 0; // 0x0 } + public final class RemoteAction implements android.os.Parcelable { + ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener); + method public android.app.RemoteAction clone(); + method public int describeContents(); + method public void dump(java.lang.String, java.io.PrintWriter); + method public java.lang.CharSequence getContentDescription(); + method public android.graphics.drawable.Icon getIcon(); + method public java.lang.CharSequence getTitle(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.RemoteAction> CREATOR; + } + + public static abstract interface RemoteAction.OnActionListener { + method public abstract void onAction(android.app.RemoteAction); + } + public final class RemoteInput implements android.os.Parcelable { method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle); method public int describeContents(); @@ -6224,6 +6246,9 @@ package android.app.admin { field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION"; field public static final deprecated java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME"; field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM = "android.app.extra.PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM"; + field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMERS = "android.app.extra.PROVISIONING_DISCLAIMERS"; + field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_CONTENT = "android.app.extra.PROVISIONING_DISCLAIMER_CONTENT"; + field public static final java.lang.String EXTRA_PROVISIONING_DISCLAIMER_HEADER = "android.app.extra.PROVISIONING_DISCLAIMER_HEADER"; field public static final java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS"; field public static final java.lang.String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION"; field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED"; @@ -8409,6 +8434,7 @@ package android.content { field public static final java.lang.String TELECOM_SERVICE = "telecom"; field public static final java.lang.String TELEPHONY_SERVICE = "phone"; field public static final java.lang.String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service"; + field public static final java.lang.String TEXT_CLASSIFICATION_SERVICE = "textclassification"; field public static final java.lang.String TEXT_SERVICES_MANAGER_SERVICE = "textservices"; field public static final java.lang.String TV_INPUT_SERVICE = "tv_input"; field public static final java.lang.String UI_MODE_SERVICE = "uimode"; @@ -9718,6 +9744,7 @@ package android.content.pm { public class LauncherApps { ctor public LauncherApps(android.content.Context); method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle); + method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent); method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int); method public android.graphics.drawable.Drawable getShortcutIconDrawable(android.content.pm.ShortcutInfo, int); method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle); @@ -9733,6 +9760,8 @@ package android.content.pm { method public void startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle); method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle); method public void unregisterCallback(android.content.pm.LauncherApps.Callback); + field public static final java.lang.String ACTION_CONFIRM_PIN_ITEM = "android.content.pm.action.CONFIRM_PIN_ITEM"; + field public static final java.lang.String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST"; } public static abstract class LauncherApps.Callback { @@ -9747,6 +9776,21 @@ package android.content.pm { method public void onShortcutsChanged(java.lang.String, java.util.List<android.content.pm.ShortcutInfo>, android.os.UserHandle); } + public static final class LauncherApps.PinItemRequest implements android.os.Parcelable { + method public boolean accept(android.os.Bundle); + method public boolean accept(); + method public int describeContents(); + method public int getRequestType(); + method public android.content.pm.ShortcutInfo getShortcutInfo(); + method public boolean isValid(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.content.pm.LauncherApps.PinItemRequest> CREATOR; + field public static final int REQUEST_TYPE_SHORTCUT = 1; // 0x1 + } + + public static abstract class LauncherApps.PinItemRequest.RequestType implements java.lang.annotation.Annotation { + } + public static class LauncherApps.ShortcutQuery { ctor public LauncherApps.ShortcutQuery(); method public android.content.pm.LauncherApps.ShortcutQuery setActivity(android.content.ComponentName); @@ -10314,9 +10358,11 @@ package android.content.pm { method public int getMaxShortcutCountPerActivity(); method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts(); method public boolean isRateLimitingActive(); + method public boolean isRequestPinShortcutSupported(); method public void removeAllDynamicShortcuts(); method public void removeDynamicShortcuts(java.util.List<java.lang.String>); method public void reportShortcutUsed(java.lang.String); + method public boolean requestPinShortcut(android.content.pm.ShortcutInfo, android.content.IntentSender); method public boolean setDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>); method public boolean updateShortcuts(java.util.List<android.content.pm.ShortcutInfo>); } @@ -11754,6 +11800,8 @@ package android.graphics { method public static android.graphics.Bitmap createBitmap(android.graphics.Bitmap, int, int, int, int, android.graphics.Matrix, boolean); method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config); + method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config, boolean); + method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config, boolean); method public static android.graphics.Bitmap createBitmap(int[], int, int, int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int[], int, int, int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(int[], int, int, android.graphics.Bitmap.Config); @@ -11817,6 +11865,8 @@ package android.graphics { enum_constant public static final android.graphics.Bitmap.Config ALPHA_8; enum_constant public static final deprecated android.graphics.Bitmap.Config ARGB_4444; enum_constant public static final android.graphics.Bitmap.Config ARGB_8888; + enum_constant public static final android.graphics.Bitmap.Config HARDWARE; + enum_constant public static final android.graphics.Bitmap.Config RGBA_F16; enum_constant public static final android.graphics.Bitmap.Config RGB_565; } @@ -12176,6 +12226,7 @@ package android.graphics { method public android.graphics.Bitmap render(); method public android.graphics.ColorSpace.Renderer showWhitePoint(boolean); method public android.graphics.ColorSpace.Renderer size(int); + method public android.graphics.ColorSpace.Renderer uniformChromaticityScale(boolean); } public static class ColorSpace.Rgb extends android.graphics.ColorSpace { @@ -12277,12 +12328,6 @@ package android.graphics { enum_constant public static final android.graphics.Interpolator.Result NORMAL; } - public deprecated class LayerRasterizer extends android.graphics.Rasterizer { - ctor public LayerRasterizer(); - method public void addLayer(android.graphics.Paint, float, float); - method public void addLayer(android.graphics.Paint); - } - public class LightingColorFilter extends android.graphics.ColorFilter { ctor public LightingColorFilter(int, int); } @@ -12444,7 +12489,6 @@ package android.graphics { method public int getOffsetForAdvance(char[], int, int, int, int, boolean, float); method public int getOffsetForAdvance(java.lang.CharSequence, int, int, int, int, boolean, float); method public android.graphics.PathEffect getPathEffect(); - method public deprecated android.graphics.Rasterizer getRasterizer(); method public float getRunAdvance(char[], int, int, int, int, boolean, int); method public float getRunAdvance(java.lang.CharSequence, int, int, int, int, boolean, int); method public android.graphics.Shader getShader(); @@ -12501,7 +12545,6 @@ package android.graphics { method public void setLinearText(boolean); method public android.graphics.MaskFilter setMaskFilter(android.graphics.MaskFilter); method public android.graphics.PathEffect setPathEffect(android.graphics.PathEffect); - method public deprecated android.graphics.Rasterizer setRasterizer(android.graphics.Rasterizer); method public android.graphics.Shader setShader(android.graphics.Shader); method public void setShadowLayer(float, float, float, int); method public void setStrikeThruText(boolean); @@ -12719,6 +12762,7 @@ package android.graphics { field public static final deprecated int RGBA_4444 = 7; // 0x7 field public static final deprecated int RGBA_5551 = 6; // 0x6 field public static final int RGBA_8888 = 1; // 0x1 + field public static final int RGBA_F16 = 22; // 0x16 field public static final int RGBX_8888 = 2; // 0x2 field public static final deprecated int RGB_332 = 11; // 0xb field public static final int RGB_565 = 4; // 0x4 @@ -12808,10 +12852,6 @@ package android.graphics { ctor public RadialGradient(float, float, float, int, int, android.graphics.Shader.TileMode); } - public deprecated class Rasterizer { - ctor public Rasterizer(); - } - public final class Rect implements android.os.Parcelable { ctor public Rect(); ctor public Rect(int, int, int, int); @@ -15279,6 +15319,10 @@ package android.icu.lang { field public static final int CONTROL = 1; // 0x1 field public static final int CR = 2; // 0x2 field public static final int EXTEND = 3; // 0x3 + field public static final int E_BASE = 13; // 0xd + field public static final int E_BASE_GAZ = 14; // 0xe + field public static final int E_MODIFIER = 15; // 0xf + field public static final int GLUE_AFTER_ZWJ = 16; // 0x10 field public static final int L = 4; // 0x4 field public static final int LF = 5; // 0x5 field public static final int LV = 6; // 0x6 @@ -15289,6 +15333,7 @@ package android.icu.lang { field public static final int SPACING_MARK = 10; // 0xa field public static final int T = 8; // 0x8 field public static final int V = 9; // 0x9 + field public static final int ZWJ = 17; // 0x11 } public static abstract interface UCharacter.HangulSyllableType { @@ -15301,6 +15346,9 @@ package android.icu.lang { } public static abstract interface UCharacter.JoiningGroup { + field public static final int AFRICAN_FEH = 86; // 0x56 + field public static final int AFRICAN_NOON = 87; // 0x57 + field public static final int AFRICAN_QAF = 88; // 0x58 field public static final int AIN = 1; // 0x1 field public static final int ALAPH = 2; // 0x2 field public static final int ALEF = 3; // 0x3 @@ -15414,6 +15462,8 @@ package android.icu.lang { field public static final int CONDITIONAL_JAPANESE_STARTER = 37; // 0x25 field public static final int CONTINGENT_BREAK = 7; // 0x7 field public static final int EXCLAMATION = 11; // 0xb + field public static final int E_BASE = 40; // 0x28 + field public static final int E_MODIFIER = 41; // 0x29 field public static final int GLUE = 12; // 0xc field public static final int H2 = 31; // 0x1f field public static final int H3 = 32; // 0x20 @@ -15440,6 +15490,7 @@ package android.icu.lang { field public static final int SURROGATE = 25; // 0x19 field public static final int UNKNOWN = 0; // 0x0 field public static final int WORD_JOINER = 30; // 0x1e + field public static final int ZWJ = 42; // 0x2a field public static final int ZWSPACE = 28; // 0x1c } @@ -15473,6 +15524,8 @@ package android.icu.lang { method public int getID(); method public static android.icu.lang.UCharacter.UnicodeBlock getInstance(int); method public static android.icu.lang.UCharacter.UnicodeBlock of(int); + field public static final android.icu.lang.UCharacter.UnicodeBlock ADLAM; + field public static final int ADLAM_ID = 263; // 0x107 field public static final android.icu.lang.UCharacter.UnicodeBlock AEGEAN_NUMBERS; field public static final int AEGEAN_NUMBERS_ID = 119; // 0x77 field public static final android.icu.lang.UCharacter.UnicodeBlock AHOM; @@ -15521,6 +15574,8 @@ package android.icu.lang { field public static final int BATAK_ID = 199; // 0xc7 field public static final android.icu.lang.UCharacter.UnicodeBlock BENGALI; field public static final int BENGALI_ID = 16; // 0x10 + field public static final android.icu.lang.UCharacter.UnicodeBlock BHAIKSUKI; + field public static final int BHAIKSUKI_ID = 264; // 0x108 field public static final android.icu.lang.UCharacter.UnicodeBlock BLOCK_ELEMENTS; field public static final int BLOCK_ELEMENTS_ID = 53; // 0x35 field public static final android.icu.lang.UCharacter.UnicodeBlock BOPOMOFO; @@ -15610,6 +15665,8 @@ package android.icu.lang { field public static final int CYRILLIC_EXTENDED_A_ID = 158; // 0x9e field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_EXTENDED_B; field public static final int CYRILLIC_EXTENDED_B_ID = 160; // 0xa0 + field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_EXTENDED_C; + field public static final int CYRILLIC_EXTENDED_C_ID = 265; // 0x109 field public static final int CYRILLIC_ID = 9; // 0x9 field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_SUPPLEMENT; field public static final android.icu.lang.UCharacter.UnicodeBlock CYRILLIC_SUPPLEMENTARY; @@ -15663,6 +15720,8 @@ package android.icu.lang { field public static final int GEORGIAN_SUPPLEMENT_ID = 135; // 0x87 field public static final android.icu.lang.UCharacter.UnicodeBlock GLAGOLITIC; field public static final int GLAGOLITIC_ID = 136; // 0x88 + field public static final android.icu.lang.UCharacter.UnicodeBlock GLAGOLITIC_SUPPLEMENT; + field public static final int GLAGOLITIC_SUPPLEMENT_ID = 266; // 0x10a field public static final android.icu.lang.UCharacter.UnicodeBlock GOTHIC; field public static final int GOTHIC_ID = 89; // 0x59 field public static final android.icu.lang.UCharacter.UnicodeBlock GRANTHA; @@ -15701,6 +15760,8 @@ package android.icu.lang { field public static final int HIRAGANA_ID = 62; // 0x3e field public static final android.icu.lang.UCharacter.UnicodeBlock IDEOGRAPHIC_DESCRIPTION_CHARACTERS; field public static final int IDEOGRAPHIC_DESCRIPTION_CHARACTERS_ID = 60; // 0x3c + field public static final android.icu.lang.UCharacter.UnicodeBlock IDEOGRAPHIC_SYMBOLS_AND_PUNCTUATION; + field public static final int IDEOGRAPHIC_SYMBOLS_AND_PUNCTUATION_ID = 267; // 0x10b field public static final android.icu.lang.UCharacter.UnicodeBlock IMPERIAL_ARAMAIC; field public static final int IMPERIAL_ARAMAIC_ID = 186; // 0xba field public static final android.icu.lang.UCharacter.UnicodeBlock INSCRIPTIONAL_PAHLAVI; @@ -15785,6 +15846,8 @@ package android.icu.lang { field public static final int MANDAIC_ID = 198; // 0xc6 field public static final android.icu.lang.UCharacter.UnicodeBlock MANICHAEAN; field public static final int MANICHAEAN_ID = 234; // 0xea + field public static final android.icu.lang.UCharacter.UnicodeBlock MARCHEN; + field public static final int MARCHEN_ID = 268; // 0x10c field public static final android.icu.lang.UCharacter.UnicodeBlock MATHEMATICAL_ALPHANUMERIC_SYMBOLS; field public static final int MATHEMATICAL_ALPHANUMERIC_SYMBOLS_ID = 93; // 0x5d field public static final android.icu.lang.UCharacter.UnicodeBlock MATHEMATICAL_OPERATORS; @@ -15819,6 +15882,8 @@ package android.icu.lang { field public static final int MODI_ID = 236; // 0xec field public static final android.icu.lang.UCharacter.UnicodeBlock MONGOLIAN; field public static final int MONGOLIAN_ID = 37; // 0x25 + field public static final android.icu.lang.UCharacter.UnicodeBlock MONGOLIAN_SUPPLEMENT; + field public static final int MONGOLIAN_SUPPLEMENT_ID = 269; // 0x10d field public static final android.icu.lang.UCharacter.UnicodeBlock MRO; field public static final int MRO_ID = 237; // 0xed field public static final android.icu.lang.UCharacter.UnicodeBlock MULTANI; @@ -15833,6 +15898,8 @@ package android.icu.lang { field public static final int MYANMAR_ID = 28; // 0x1c field public static final android.icu.lang.UCharacter.UnicodeBlock NABATAEAN; field public static final int NABATAEAN_ID = 239; // 0xef + field public static final android.icu.lang.UCharacter.UnicodeBlock NEWA; + field public static final int NEWA_ID = 270; // 0x10e field public static final android.icu.lang.UCharacter.UnicodeBlock NEW_TAI_LUE; field public static final int NEW_TAI_LUE_ID = 139; // 0x8b field public static final android.icu.lang.UCharacter.UnicodeBlock NKO; @@ -15864,6 +15931,8 @@ package android.icu.lang { field public static final int ORIYA_ID = 19; // 0x13 field public static final android.icu.lang.UCharacter.UnicodeBlock ORNAMENTAL_DINGBATS; field public static final int ORNAMENTAL_DINGBATS_ID = 242; // 0xf2 + field public static final android.icu.lang.UCharacter.UnicodeBlock OSAGE; + field public static final int OSAGE_ID = 271; // 0x10f field public static final android.icu.lang.UCharacter.UnicodeBlock OSMANYA; field public static final int OSMANYA_ID = 122; // 0x7a field public static final android.icu.lang.UCharacter.UnicodeBlock PAHAWH_HMONG; @@ -15966,6 +16035,10 @@ package android.icu.lang { field public static final int TAKRI_ID = 220; // 0xdc field public static final android.icu.lang.UCharacter.UnicodeBlock TAMIL; field public static final int TAMIL_ID = 20; // 0x14 + field public static final android.icu.lang.UCharacter.UnicodeBlock TANGUT; + field public static final android.icu.lang.UCharacter.UnicodeBlock TANGUT_COMPONENTS; + field public static final int TANGUT_COMPONENTS_ID = 273; // 0x111 + field public static final int TANGUT_ID = 272; // 0x110 field public static final android.icu.lang.UCharacter.UnicodeBlock TELUGU; field public static final int TELUGU_ID = 21; // 0x15 field public static final android.icu.lang.UCharacter.UnicodeBlock THAANA; @@ -16012,7 +16085,11 @@ package android.icu.lang { field public static final int DOUBLE_QUOTE = 16; // 0x10 field public static final int EXTEND = 9; // 0x9 field public static final int EXTENDNUMLET = 7; // 0x7 + field public static final int E_BASE = 17; // 0x11 + field public static final int E_BASE_GAZ = 18; // 0x12 + field public static final int E_MODIFIER = 19; // 0x13 field public static final int FORMAT = 2; // 0x2 + field public static final int GLUE_AFTER_ZWJ = 20; // 0x14 field public static final int HEBREW_LETTER = 14; // 0xe field public static final int KATAKANA = 3; // 0x3 field public static final int LF = 10; // 0xa @@ -16024,6 +16101,7 @@ package android.icu.lang { field public static final int OTHER = 0; // 0x0 field public static final int REGIONAL_INDICATOR = 13; // 0xd field public static final int SINGLE_QUOTE = 15; // 0xf + field public static final int ZWJ = 21; // 0x15 } public final class UCharacterCategory implements android.icu.lang.UCharacterEnums.ECharacterCategory { @@ -16242,6 +16320,7 @@ package android.icu.lang { method public static final boolean hasScript(int, int); method public static final boolean isCased(int); method public static final boolean isRightToLeft(int); + field public static final int ADLAM = 167; // 0xa7 field public static final int AFAKA = 147; // 0x93 field public static final int AHOM = 161; // 0xa1 field public static final int ANATOLIAN_HIEROGLYPHS = 156; // 0x9c @@ -16253,6 +16332,7 @@ package android.icu.lang { field public static final int BASSA_VAH = 134; // 0x86 field public static final int BATAK = 63; // 0x3f field public static final int BENGALI = 4; // 0x4 + field public static final int BHAIKSUKI = 168; // 0xa8 field public static final int BLISSYMBOLS = 64; // 0x40 field public static final int BOOK_PAHLAVI = 124; // 0x7c field public static final int BOPOMOFO = 5; // 0x5 @@ -16291,6 +16371,7 @@ package android.icu.lang { field public static final int HAN = 17; // 0x11 field public static final int HANGUL = 18; // 0x12 field public static final int HANUNOO = 43; // 0x2b + field public static final int HAN_WITH_BOPOMOFO = 172; // 0xac field public static final int HARAPPAN_INDUS = 77; // 0x4d field public static final int HATRAN = 162; // 0xa2 field public static final int HEBREW = 19; // 0x13 @@ -16301,6 +16382,7 @@ package android.icu.lang { field public static final int INSCRIPTIONAL_PAHLAVI = 122; // 0x7a field public static final int INSCRIPTIONAL_PARTHIAN = 125; // 0x7d field public static final int INVALID_CODE = -1; // 0xffffffff + field public static final int JAMO = 173; // 0xad field public static final int JAPANESE = 105; // 0x69 field public static final int JAVANESE = 78; // 0x4e field public static final int JURCHEN = 148; // 0x94 @@ -16334,6 +16416,7 @@ package android.icu.lang { field public static final int MANDAEAN = 84; // 0x54 field public static final int MANDAIC = 84; // 0x54 field public static final int MANICHAEAN = 121; // 0x79 + field public static final int MARCHEN = 169; // 0xa9 field public static final int MATHEMATICAL_NOTATION = 128; // 0x80 field public static final int MAYAN_HIEROGLYPHS = 85; // 0x55 field public static final int MEITEI_MAYEK = 115; // 0x73 @@ -16350,6 +16433,7 @@ package android.icu.lang { field public static final int MYANMAR = 28; // 0x1c field public static final int NABATAEAN = 143; // 0x8f field public static final int NAKHI_GEBA = 132; // 0x84 + field public static final int NEWA = 170; // 0xaa field public static final int NEW_TAI_LUE = 59; // 0x3b field public static final int NKO = 87; // 0x57 field public static final int NUSHU = 150; // 0x96 @@ -16364,6 +16448,7 @@ package android.icu.lang { field public static final int OL_CHIKI = 109; // 0x6d field public static final int ORIYA = 31; // 0x1f field public static final int ORKHON = 88; // 0x58 + field public static final int OSAGE = 171; // 0xab field public static final int OSMANYA = 50; // 0x32 field public static final int PAHAWH_HMONG = 75; // 0x4b field public static final int PALMYRENE = 144; // 0x90 @@ -16389,6 +16474,7 @@ package android.icu.lang { field public static final int SUNDANESE = 113; // 0x71 field public static final int SYLOTI_NAGRI = 58; // 0x3a field public static final int SYMBOLS = 129; // 0x81 + field public static final int SYMBOLS_EMOJI = 174; // 0xae field public static final int SYRIAC = 34; // 0x22 field public static final int TAGALOG = 42; // 0x2a field public static final int TAGBANWA = 45; // 0x2d @@ -16915,6 +17001,8 @@ package android.icu.text { method public static final android.icu.text.DateFormat.BooleanAttribute[] values(); enum_constant public static final android.icu.text.DateFormat.BooleanAttribute PARSE_ALLOW_NUMERIC; enum_constant public static final android.icu.text.DateFormat.BooleanAttribute PARSE_ALLOW_WHITESPACE; + enum_constant public static final android.icu.text.DateFormat.BooleanAttribute PARSE_MULTIPLE_PATTERNS_FOR_MATCH; + enum_constant public static final android.icu.text.DateFormat.BooleanAttribute PARSE_PARTIAL_LITERAL_MATCH; } public static class DateFormat.Field extends java.text.Format.Field { @@ -17599,6 +17687,7 @@ package android.icu.text { field public static final int PERCENTSTYLE = 2; // 0x2 field public static final int PLURALCURRENCYSTYLE = 6; // 0x6 field public static final int SCIENTIFICSTYLE = 3; // 0x3 + field public static final int STANDARDCURRENCYSTYLE = 9; // 0x9 } public static class NumberFormat.Field extends java.text.Format.Field { @@ -18393,7 +18482,7 @@ package android.icu.util { field public static final int AM_PM = 9; // 0x9 field public static final int APRIL = 3; // 0x3 field public static final int AUGUST = 7; // 0x7 - field protected static final int BASE_FIELD_COUNT = 23; // 0x17 + field protected static final deprecated int BASE_FIELD_COUNT = 23; // 0x17 field public static final int DATE = 5; // 0x5 field public static final int DAY_OF_MONTH = 5; // 0x5 field public static final int DAY_OF_WEEK = 7; // 0x7 @@ -18421,7 +18510,7 @@ package android.icu.util { field public static final int MARCH = 2; // 0x2 field protected static final int MAXIMUM = 3; // 0x3 field protected static final java.util.Date MAX_DATE; - field protected static final int MAX_FIELD_COUNT = 32; // 0x20 + field protected static final deprecated int MAX_FIELD_COUNT = 32; // 0x20 field protected static final int MAX_JULIAN = 2130706432; // 0x7f000000 field protected static final long MAX_MILLIS = 183882168921600000L; // 0x28d47dbbf19b000L field public static final int MAY = 4; // 0x4 @@ -18741,6 +18830,7 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit CELSIUS; field public static final android.icu.util.MeasureUnit CENTILITER; field public static final android.icu.util.MeasureUnit CENTIMETER; + field public static final android.icu.util.MeasureUnit CENTURY; field public static final android.icu.util.MeasureUnit CUBIC_CENTIMETER; field public static final android.icu.util.MeasureUnit CUBIC_FOOT; field public static final android.icu.util.MeasureUnit CUBIC_INCH; @@ -18749,6 +18839,7 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit CUBIC_MILE; field public static final android.icu.util.MeasureUnit CUBIC_YARD; field public static final android.icu.util.MeasureUnit CUP; + field public static final android.icu.util.MeasureUnit CUP_METRIC; field public static final android.icu.util.TimeUnit DAY; field public static final android.icu.util.MeasureUnit DECILITER; field public static final android.icu.util.MeasureUnit DECIMETER; @@ -18760,6 +18851,7 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit FOOT; field public static final android.icu.util.MeasureUnit FURLONG; field public static final android.icu.util.MeasureUnit GALLON; + field public static final android.icu.util.MeasureUnit GENERIC_TEMPERATURE; field public static final android.icu.util.MeasureUnit GIGABIT; field public static final android.icu.util.MeasureUnit GIGABYTE; field public static final android.icu.util.MeasureUnit GIGAHERTZ; @@ -18787,8 +18879,10 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit KILOMETER_PER_HOUR; field public static final android.icu.util.MeasureUnit KILOWATT; field public static final android.icu.util.MeasureUnit KILOWATT_HOUR; + field public static final android.icu.util.MeasureUnit KNOT; field public static final android.icu.util.MeasureUnit LIGHT_YEAR; field public static final android.icu.util.MeasureUnit LITER; + field public static final android.icu.util.MeasureUnit LITER_PER_100KILOMETERS; field public static final android.icu.util.MeasureUnit LITER_PER_KILOMETER; field public static final android.icu.util.MeasureUnit LUX; field public static final android.icu.util.MeasureUnit MEGABIT; @@ -18806,6 +18900,7 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit MILE; field public static final android.icu.util.MeasureUnit MILE_PER_GALLON; field public static final android.icu.util.MeasureUnit MILE_PER_HOUR; + field public static final android.icu.util.MeasureUnit MILE_SCANDINAVIAN; field public static final android.icu.util.MeasureUnit MILLIAMPERE; field public static final android.icu.util.MeasureUnit MILLIBAR; field public static final android.icu.util.MeasureUnit MILLIGRAM; @@ -18825,10 +18920,12 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit PARSEC; field public static final android.icu.util.MeasureUnit PICOMETER; field public static final android.icu.util.MeasureUnit PINT; + field public static final android.icu.util.MeasureUnit PINT_METRIC; field public static final android.icu.util.MeasureUnit POUND; field public static final android.icu.util.MeasureUnit POUND_PER_SQUARE_INCH; field public static final android.icu.util.MeasureUnit QUART; field public static final android.icu.util.MeasureUnit RADIAN; + field public static final android.icu.util.MeasureUnit REVOLUTION_ANGLE; field public static final android.icu.util.TimeUnit SECOND; field public static final android.icu.util.MeasureUnit SQUARE_CENTIMETER; field public static final android.icu.util.MeasureUnit SQUARE_FOOT; @@ -19140,6 +19237,7 @@ package android.icu.util { field public static final android.icu.util.VersionInfo UNICODE_6_3; field public static final android.icu.util.VersionInfo UNICODE_7_0; field public static final android.icu.util.VersionInfo UNICODE_8_0; + field public static final android.icu.util.VersionInfo UNICODE_9_0; } } @@ -22702,6 +22800,7 @@ package android.media.browse { method public android.content.ComponentName getServiceComponent(); method public android.media.session.MediaSession.Token getSessionToken(); method public boolean isConnected(); + method public void search(java.lang.String, android.os.Bundle, android.media.browse.MediaBrowser.SearchCallback); method public void subscribe(java.lang.String, android.media.browse.MediaBrowser.SubscriptionCallback); method public void subscribe(java.lang.String, android.os.Bundle, android.media.browse.MediaBrowser.SubscriptionCallback); method public void unsubscribe(java.lang.String); @@ -22737,6 +22836,12 @@ package android.media.browse { field public static final int FLAG_PLAYABLE = 2; // 0x2 } + public static abstract class MediaBrowser.SearchCallback { + ctor public MediaBrowser.SearchCallback(); + method public void onError(java.lang.String, android.os.Bundle); + method public void onSearchResult(java.lang.String, android.os.Bundle, java.util.List<android.media.browse.MediaBrowser.MediaItem>); + } + public static abstract class MediaBrowser.SubscriptionCallback { ctor public MediaBrowser.SubscriptionCallback(); method public void onChildrenLoaded(java.lang.String, java.util.List<android.media.browse.MediaBrowser.MediaItem>); @@ -29207,6 +29312,27 @@ package android.os { method public abstract android.os.IBinder asBinder(); } + public class IncidentManager { + method public void reportIncident(android.os.IncidentReportArgs); + method public void reportIncident(java.lang.String, byte[]); + } + + public final class IncidentReportArgs implements android.os.Parcelable { + ctor public IncidentReportArgs(); + ctor public IncidentReportArgs(android.os.Parcel); + method public void addHeader(byte[]); + method public void addSection(int); + method public boolean containsSection(int); + method public int describeContents(); + method public boolean isAll(); + method public static android.os.IncidentReportArgs parseSetting(java.lang.String) throws java.lang.IllegalArgumentException; + method public void readFromParcel(android.os.Parcel); + method public int sectionCount(); + method public void setAll(boolean); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.os.IncidentReportArgs> CREATOR; + } + public final class LocaleList implements android.os.Parcelable { ctor public LocaleList(java.util.Locale...); method public int describeContents(); @@ -29809,6 +29935,7 @@ package android.os { method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle); method public static boolean supportsMultipleUsers(); field public static final java.lang.String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking"; + field public static final java.lang.String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile"; field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user"; field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume"; field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps"; @@ -29833,6 +29960,7 @@ package android.os { field public static final java.lang.String DISALLOW_NETWORK_RESET = "no_network_reset"; field public static final java.lang.String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam"; field public static final java.lang.String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls"; + field public static final java.lang.String DISALLOW_REMOVE_MANAGED_PROFILE = "no_remove_managed_profile"; field public static final java.lang.String DISALLOW_REMOVE_USER = "no_remove_user"; field public static final java.lang.String DISALLOW_SAFE_BOOT = "no_safe_boot"; field public static final java.lang.String DISALLOW_SET_USER_ICON = "no_set_user_icon"; @@ -34973,7 +35101,8 @@ package android.service.autofill { method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); - method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.CancellationSignal, android.service.autofill.FillCallback); + method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback); + method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback); field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService"; } @@ -34991,6 +35120,11 @@ package android.service.autofill { method public android.service.autofill.FillCallback.FillData.Builder setTextField(int, java.lang.String); } + public final class SaveCallback { + method public void onFailure(java.lang.CharSequence); + method public void onSuccess(int[]); + } + } package android.service.carrier { @@ -35172,6 +35306,7 @@ package android.service.media { method public abstract void onLoadChildren(java.lang.String, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>); method public void onLoadChildren(java.lang.String, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>, android.os.Bundle); method public void onLoadItem(java.lang.String, android.service.media.MediaBrowserService.Result<android.media.browse.MediaBrowser.MediaItem>); + method public void onSearch(java.lang.String, android.os.Bundle, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>); method public void setSessionToken(android.media.session.MediaSession.Token); field public static final java.lang.String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService"; } @@ -35261,8 +35396,12 @@ package android.service.notification { ctor public NotificationAssistantService(); method public final void adjustNotification(android.service.notification.Adjustment); method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>); + method public void createNotificationChannel(java.lang.String, android.app.NotificationChannel); + method public void deleteNotificationChannel(java.lang.String, java.lang.String); + method public java.util.List<android.app.NotificationChannel> getNotificationChannels(java.lang.String); method public final android.os.IBinder onBind(android.content.Intent); method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean); + method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel); field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; } @@ -37461,6 +37600,7 @@ package android.telephony { field public static final java.lang.String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool"; field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; field public static final java.lang.String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; + field public static final java.lang.String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool"; field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool"; field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool"; field public static final java.lang.String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool"; @@ -37998,6 +38138,7 @@ package android.telephony { method public java.lang.String getSimOperatorName(); method public java.lang.String getSimSerialNumber(); method public int getSimState(); + method public int getSimState(int); method public java.lang.String getSubscriberId(); method public java.lang.String getVoiceMailAlphaTag(); method public java.lang.String getVoiceMailNumber(); @@ -38082,7 +38223,11 @@ package android.telephony { field public static final int PHONE_TYPE_NONE = 0; // 0x0 field public static final int PHONE_TYPE_SIP = 3; // 0x3 field public static final int SIM_STATE_ABSENT = 1; // 0x1 + field public static final int SIM_STATE_CARD_IO_ERROR = 8; // 0x8 + field public static final int SIM_STATE_CARD_RESTRICTED = 9; // 0x9 field public static final int SIM_STATE_NETWORK_LOCKED = 4; // 0x4 + field public static final int SIM_STATE_NOT_READY = 6; // 0x6 + field public static final int SIM_STATE_PERM_DISABLED = 7; // 0x7 field public static final int SIM_STATE_PIN_REQUIRED = 2; // 0x2 field public static final int SIM_STATE_PUK_REQUIRED = 3; // 0x3 field public static final int SIM_STATE_READY = 5; // 0x5 @@ -38849,7 +38994,7 @@ package android.text { method public java.lang.CharSequence subSequence(int, int); } - public class AndroidCharacter { + public deprecated class AndroidCharacter { ctor public AndroidCharacter(); method public static void getDirectionalities(char[], byte[], int); method public static int getEastAsianWidth(char); @@ -39343,6 +39488,12 @@ package android.text { method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence(); } + public final class TextClassificationManager implements android.text.TextAssistant { + method public void addLinks(android.text.Spannable, int); + method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence); + method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int); + } + public abstract interface TextDirectionHeuristic { method public abstract boolean isRtl(char[], int, int); method public abstract boolean isRtl(java.lang.CharSequence, int, int); @@ -39358,6 +39509,13 @@ package android.text { field public static final android.text.TextDirectionHeuristic RTL; } + public final class TextLanguage { + ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>); + method public int getEndIndex(); + method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence(); + method public int getStartIndex(); + } + public class TextPaint extends android.graphics.Paint { ctor public TextPaint(); ctor public TextPaint(int); @@ -40052,12 +40210,6 @@ package android.text.style { method public void writeToParcel(android.os.Parcel, int); } - public class RasterizerSpan extends android.text.style.CharacterStyle implements android.text.style.UpdateAppearance { - ctor public RasterizerSpan(android.graphics.Rasterizer); - method public android.graphics.Rasterizer getRasterizer(); - method public void updateDrawState(android.text.TextPaint); - } - public class RelativeSizeSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan { ctor public RelativeSizeSpan(float); ctor public RelativeSizeSpan(android.os.Parcel); @@ -41872,6 +42024,7 @@ package android.view { method public android.view.View findNearestTouchable(android.view.ViewGroup, int, int, int, int[]); method public final android.view.View findNextFocus(android.view.ViewGroup, android.view.View, int); method public android.view.View findNextFocusFromRect(android.view.ViewGroup, android.graphics.Rect, int); + method public android.view.View findNextKeyboardNavigationCluster(android.view.ViewGroup, android.view.View, int); method public static android.view.FocusFinder getInstance(); } @@ -43166,6 +43319,7 @@ package android.view { method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>); method public void addFocusables(java.util.ArrayList<android.view.View>, int); method public void addFocusables(java.util.ArrayList<android.view.View>, int, int); + method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int); method public void addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener); method public void addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener); method public void addTouchables(java.util.ArrayList<android.view.View>); @@ -43222,7 +43376,8 @@ package android.view { method public boolean dispatchNestedPreScroll(int, int, int[], int[]); method public boolean dispatchNestedScroll(int, int, int, int, int[]); method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent); - method public void dispatchProvideStructure(android.view.ViewStructure); + method public deprecated void dispatchProvideStructure(android.view.ViewStructure); + method public void dispatchProvideStructure(android.view.ViewStructure, int); method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>); method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>); method protected void dispatchSetActivated(boolean); @@ -43319,11 +43474,13 @@ package android.view { method public final int getMeasuredWidthAndState(); method public int getMinimumHeight(); method public int getMinimumWidth(); + method public int getNextClusterForwardId(); method public int getNextFocusDownId(); method public int getNextFocusForwardId(); method public int getNextFocusLeftId(); method public int getNextFocusRightId(); method public int getNextFocusUpId(); + method public int getNextSectionForwardId(); method public android.view.View.OnFocusChangeListener getOnFocusChangeListener(); method public android.view.ViewOutlineProvider getOutlineProvider(); method public int getOverScrollMode(); @@ -43427,6 +43584,8 @@ package android.view { method public boolean isInEditMode(); method public boolean isInLayout(); method public boolean isInTouchMode(); + method public final boolean isKeyboardNavigationCluster(); + method public final boolean isKeyboardNavigationSection(); method public boolean isLaidOut(); method public boolean isLayoutDirectionResolved(); method public boolean isLayoutRequested(); @@ -43449,6 +43608,7 @@ package android.view { method public boolean isVerticalFadingEdgeEnabled(); method public boolean isVerticalScrollBarEnabled(); method public void jumpDrawablesToCurrentState(); + method public android.view.View keyboardNavigationClusterSearch(int); method public void layout(int, int, int, int); method public final void measure(int, int); method protected static int[] mergeDrawableStates(int[], int[]); @@ -43489,8 +43649,10 @@ package android.view { method protected void onMeasure(int, int); method protected void onOverScrolled(int, int, boolean, boolean); method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent); - method public void onProvideStructure(android.view.ViewStructure); - method public void onProvideVirtualStructure(android.view.ViewStructure); + method public deprecated void onProvideStructure(android.view.ViewStructure); + method public void onProvideStructure(android.view.ViewStructure, int); + method public deprecated void onProvideVirtualStructure(android.view.ViewStructure); + method public void onProvideVirtualStructure(android.view.ViewStructure, int); method public android.view.PointerIcon onResolvePointerIcon(android.view.MotionEvent, int); method protected void onRestoreInstanceState(android.os.Parcelable); method public void onRtlPropertiesChanged(int); @@ -43593,6 +43755,8 @@ package android.view { method public void setId(int); method public void setImportantForAccessibility(int); method public void setKeepScreenOn(boolean); + method public void setKeyboardNavigationCluster(boolean); + method public void setKeyboardNavigationSection(boolean); method public void setLabelFor(int); method public void setLayerPaint(android.graphics.Paint); method public void setLayerType(int, android.graphics.Paint); @@ -43604,11 +43768,13 @@ package android.view { method public void setMinimumHeight(int); method public void setMinimumWidth(int); method public void setNestedScrollingEnabled(boolean); + method public void setNextClusterForwardId(int); method public void setNextFocusDownId(int); method public void setNextFocusForwardId(int); method public void setNextFocusLeftId(int); method public void setNextFocusRightId(int); method public void setNextFocusUpId(int); + method public void setNextSectionForwardId(int); method public void setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener); method public void setOnClickListener(android.view.View.OnClickListener); method public void setOnContextClickListener(android.view.View.OnContextClickListener); @@ -43692,6 +43858,8 @@ package android.view { field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0 field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1 field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA; + field public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 2; // 0x2 + field public static final int ASSIST_FLAG_SANITIZED_TEXT = 1; // 0x1 field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100 field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40 field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80 @@ -44092,6 +44260,7 @@ package android.view { method protected deprecated boolean isChildrenDrawnWithCacheEnabled(); method public boolean isMotionEventSplittingEnabled(); method public boolean isTransitionGroup(); + method public android.view.View keyboardNavigationClusterSearch(android.view.View, int); method public final void layout(int, int, int, int); method protected void measureChild(android.view.View, int, int); method protected void measureChildWithMargins(android.view.View, int, int, int, int); @@ -44254,6 +44423,7 @@ package android.view { method public abstract boolean isLayoutRequested(); method public abstract boolean isTextAlignmentResolved(); method public abstract boolean isTextDirectionResolved(); + method public abstract android.view.View keyboardNavigationClusterSearch(android.view.View, int); method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int); method public abstract boolean onNestedFling(android.view.View, float, float, boolean); method public abstract boolean onNestedPreFling(android.view.View, float, float); @@ -60172,6 +60342,10 @@ package java.util { ctor public Locale(java.lang.String, java.lang.String); ctor public Locale(java.lang.String); method public java.lang.Object clone(); + method public static java.util.List<java.util.Locale> filter(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.util.Locale>, java.util.Locale.FilteringMode); + method public static java.util.List<java.util.Locale> filter(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.util.Locale>); + method public static java.util.List<java.lang.String> filterTags(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.lang.String>, java.util.Locale.FilteringMode); + method public static java.util.List<java.lang.String> filterTags(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.lang.String>); method public static java.util.Locale forLanguageTag(java.lang.String); method public static java.util.Locale[] getAvailableLocales(); method public java.lang.String getCountry(); @@ -60200,6 +60374,8 @@ package java.util { method public java.lang.String getUnicodeLocaleType(java.lang.String); method public java.lang.String getVariant(); method public boolean hasExtensions(); + method public static java.util.Locale lookup(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.util.Locale>); + method public static java.lang.String lookupTag(java.util.List<java.util.Locale.LanguageRange>, java.util.Collection<java.lang.String>); method public static synchronized void setDefault(java.util.Locale); method public static synchronized void setDefault(java.util.Locale.Category, java.util.Locale); method public java.util.Locale stripExtensions(); @@ -60255,6 +60431,28 @@ package java.util { enum_constant public static final java.util.Locale.Category FORMAT; } + public static final class Locale.FilteringMode extends java.lang.Enum { + method public static java.util.Locale.FilteringMode valueOf(java.lang.String); + method public static final java.util.Locale.FilteringMode[] values(); + enum_constant public static final java.util.Locale.FilteringMode AUTOSELECT_FILTERING; + enum_constant public static final java.util.Locale.FilteringMode EXTENDED_FILTERING; + enum_constant public static final java.util.Locale.FilteringMode IGNORE_EXTENDED_RANGES; + enum_constant public static final java.util.Locale.FilteringMode MAP_EXTENDED_RANGES; + enum_constant public static final java.util.Locale.FilteringMode REJECT_EXTENDED_RANGES; + } + + public static final class Locale.LanguageRange { + ctor public Locale.LanguageRange(java.lang.String); + ctor public Locale.LanguageRange(java.lang.String, double); + method public java.lang.String getRange(); + method public double getWeight(); + method public static java.util.List<java.util.Locale.LanguageRange> mapEquivalents(java.util.List<java.util.Locale.LanguageRange>, java.util.Map<java.lang.String, java.util.List<java.lang.String>>); + method public static java.util.List<java.util.Locale.LanguageRange> parse(java.lang.String); + method public static java.util.List<java.util.Locale.LanguageRange> parse(java.lang.String, java.util.Map<java.lang.String, java.util.List<java.lang.String>>); + field public static final double MAX_WEIGHT = 1.0; + field public static final double MIN_WEIGHT = 0.0; + } + public class LongSummaryStatistics implements java.util.function.IntConsumer java.util.function.LongConsumer { ctor public LongSummaryStatistics(); method public void accept(int); @@ -61538,31 +61736,31 @@ package java.util.concurrent { ctor public CopyOnWriteArrayList(); ctor public CopyOnWriteArrayList(java.util.Collection<? extends E>); ctor public CopyOnWriteArrayList(E[]); - method public synchronized boolean add(E); - method public synchronized void add(int, E); - method public synchronized boolean addAll(java.util.Collection<? extends E>); - method public synchronized boolean addAll(int, java.util.Collection<? extends E>); - method public synchronized int addAllAbsent(java.util.Collection<? extends E>); - method public synchronized boolean addIfAbsent(E); - method public synchronized void clear(); + method public boolean add(E); + method public void add(int, E); + method public boolean addAll(java.util.Collection<? extends E>); + method public boolean addAll(int, java.util.Collection<? extends E>); + method public int addAllAbsent(java.util.Collection<? extends E>); + method public boolean addIfAbsent(E); + method public void clear(); method public java.lang.Object clone(); method public boolean contains(java.lang.Object); method public boolean containsAll(java.util.Collection<?>); method public void forEach(java.util.function.Consumer<? super E>); method public E get(int); - method public int indexOf(E, int); method public int indexOf(java.lang.Object); + method public int indexOf(E, int); method public boolean isEmpty(); method public java.util.Iterator<E> iterator(); - method public int lastIndexOf(E, int); method public int lastIndexOf(java.lang.Object); - method public java.util.ListIterator<E> listIterator(int); + method public int lastIndexOf(E, int); method public java.util.ListIterator<E> listIterator(); - method public synchronized E remove(int); - method public synchronized boolean remove(java.lang.Object); - method public synchronized boolean removeAll(java.util.Collection<?>); - method public synchronized boolean retainAll(java.util.Collection<?>); - method public synchronized E set(int, E); + method public java.util.ListIterator<E> listIterator(int); + method public E remove(int); + method public boolean remove(java.lang.Object); + method public boolean removeAll(java.util.Collection<?>); + method public boolean retainAll(java.util.Collection<?>); + method public E set(int, E); method public int size(); method public java.util.List<E> subList(int, int); method public java.lang.Object[] toArray(); diff --git a/api/test-removed.txt b/api/test-removed.txt index 683a695ef8a0..10e6eb5e60d8 100644 --- a/api/test-removed.txt +++ b/api/test-removed.txt @@ -78,10 +78,25 @@ package android.graphics { enum_constant public static final android.graphics.AvoidXfermode.Mode TARGET; } + public deprecated class LayerRasterizer extends android.graphics.Rasterizer { + ctor public LayerRasterizer(); + method public void addLayer(android.graphics.Paint, float, float); + method public void addLayer(android.graphics.Paint); + } + + public class Paint { + method public deprecated android.graphics.Rasterizer getRasterizer(); + method public deprecated android.graphics.Rasterizer setRasterizer(android.graphics.Rasterizer); + } + public deprecated class PixelXorXfermode extends android.graphics.Xfermode { ctor public PixelXorXfermode(int); } + public class Rasterizer { + ctor public Rasterizer(); + } + } package android.location { @@ -393,6 +408,15 @@ package android.text.format { } +package android.text.style { + + public class RasterizerSpan extends android.text.style.CharacterStyle implements android.text.style.UpdateAppearance { + ctor public RasterizerSpan(android.graphics.Rasterizer); + method public android.graphics.Rasterizer getRasterizer(); + } + +} + package android.util { public deprecated class FloatMath { diff --git a/cmds/bootanimation/bootanimation_main.cpp b/cmds/bootanimation/bootanimation_main.cpp index 48a34e7dbf62..dca7ea6daa3e 100644 --- a/cmds/bootanimation/bootanimation_main.cpp +++ b/cmds/bootanimation/bootanimation_main.cpp @@ -37,6 +37,10 @@ int main() char value[PROPERTY_VALUE_MAX]; property_get("debug.sf.nobootanimation", value, "0"); int noBootAnimation = atoi(value); + if (!noBootAnimation) { + property_get("ro.boot.quiescent", value, "0"); + noBootAnimation = atoi(value); + } ALOGI_IF(noBootAnimation, "boot animation disabled"); if (!noBootAnimation) { @@ -47,7 +51,6 @@ int main() sp<BootAnimation> boot = new BootAnimation(); IPCThreadState::self()->joinThreadPool(); - } return 0; } diff --git a/cmds/incident/Android.mk b/cmds/incident/Android.mk new file mode 100644 index 000000000000..e1c9b93a8e6d --- /dev/null +++ b/cmds/incident/Android.mk @@ -0,0 +1,48 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + main.cpp + +LOCAL_MODULE := incident + +LOCAL_SHARED_LIBRARIES := \ + libbase \ + libbinder \ + libcutils \ + liblog \ + libutils \ + libincident + +LOCAL_CFLAGS += \ + -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter + +LOCAL_MODULE_CLASS := EXECUTABLES +gen_src_dir := $(local-generated-sources-dir) + +gen := $(gen_src_dir)/incident_sections.cpp +$(gen): $(HOST_OUT_EXECUTABLES)/incident-section-gen +$(gen): PRIVATE_CUSTOM_TOOL = \ + $(HOST_OUT_EXECUTABLES)/incident-section-gen > $@ +$(gen): $(HOST_OUT_EXECUTABLES)/incident-section-gen + $(transform-generated-source) +LOCAL_GENERATED_SOURCES += $(gen) + +gen_src_dir:= +gen:= + +include $(BUILD_EXECUTABLE) diff --git a/cmds/incident/incident_sections.h b/cmds/incident/incident_sections.h new file mode 100644 index 000000000000..1972088fc82c --- /dev/null +++ b/cmds/incident/incident_sections.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#ifndef INCIDENT_SECTIONS_H +#define INCIDENT_SECTIONS_H + +struct IncidentSection +{ + int id; + char const* name; +}; + +extern IncidentSection const INCIDENT_SECTIONS[]; +extern const int INCIDENT_SECTION_COUNT; + +#endif // INCIDENT_SECTIONS_H diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp new file mode 100644 index 000000000000..91b7c22b2038 --- /dev/null +++ b/cmds/incident/main.cpp @@ -0,0 +1,236 @@ +/* + * 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. + */ + +#define LOG_TAG "incident" + +#include "incident_sections.h" + +#include <android/os/BnIncidentReportStatusListener.h> +#include <android/os/IIncidentManager.h> +#include <android/os/IncidentReportArgs.h> +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <utils/Looper.h> + +#include <fcntl.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +using namespace android; +using namespace android::base; +using namespace android::binder; +using namespace android::os; + +// ================================================================================ +class StatusListener : public BnIncidentReportStatusListener { +public: + StatusListener(); + virtual ~StatusListener(); + + virtual Status onReportStarted(); + virtual Status onReportSectionStatus(int32_t section, int32_t status); + virtual Status onReportServiceStatus(const String16& service, int32_t status); + virtual Status onReportFinished(); + virtual Status onReportFailed(); +}; + +StatusListener::StatusListener() +{ +} + +StatusListener::~StatusListener() +{ +} + +Status +StatusListener::onReportStarted() +{ + return Status::ok(); +} + +Status +StatusListener::onReportSectionStatus(int32_t section, int32_t status) +{ + fprintf(stderr, "section %d status %d\n", section, status); + return Status::ok(); +} + +Status +StatusListener::onReportServiceStatus(const String16& service, int32_t status) +{ + fprintf(stderr, "service '%s' status %d\n", String8(service).string(), status); + return Status::ok(); +} + +Status +StatusListener::onReportFinished() +{ + fprintf(stderr, "done\n"); + exit(0); + return Status::ok(); +} + +Status +StatusListener::onReportFailed() +{ + fprintf(stderr, "failed\n"); + exit(1); + return Status::ok(); +} + +// ================================================================================ +static IncidentSection const* +find_section(const char* name) +{ + size_t low = 0; + size_t high = INCIDENT_SECTION_COUNT - 1; + + while (low <= high) { + size_t mid = (low + high) >> 1; + IncidentSection const* section = INCIDENT_SECTIONS + mid; + + int cmp = strcmp(section->name, name); + if (cmp < 0) { + low = mid + 1; + } else if (cmp > 0) { + high = mid - 1; + } else { + return section; + } + } + return NULL; +} + +// ================================================================================ +static void +usage(FILE* out) +{ + fprintf(out, "usage: incident OPTIONS [SECTION...]\n"); + fprintf(out, "\n"); + fprintf(out, "Takes an incident report.\n"); + fprintf(out, "\n"); + fprintf(out, "OPTIONS\n"); + fprintf(out, " -b (default) print the report to stdout (in proto format)\n"); + fprintf(out, " -d send the report into dropbox\n"); + fprintf(out, "\n"); + fprintf(out, " SECTION the field numbers of the incident report fields to include\n"); + fprintf(out, "\n"); +} + +int +main(int argc, char** argv) +{ + Status status; + IncidentReportArgs args; + enum { DEST_DROPBOX, DEST_STDOUT } destination = DEST_STDOUT; + + // Parse the args + int opt; + while ((opt = getopt(argc, argv, "bhd")) != -1) { + switch (opt) { + case 'b': + destination = DEST_STDOUT; + break; + case 'h': + usage(stdout); + return 0; + case 'd': + destination = DEST_DROPBOX; + break; + default: + usage(stderr); + return 1; + } + } + + if (optind == argc) { + args.setAll(true); + } else { + for (int i=optind; i<argc; i++) { + const char* arg = argv[i]; + char* end; + if (arg[0] != '\0') { + int section = strtol(arg, &end, 0); + if (*end == '\0') { + args.addSection(section); + } else { + IncidentSection const* ic = find_section(arg); + if (ic == NULL) { + fprintf(stderr, "Invalid section: %s\n", arg); + return 1; + } + args.addSection(ic->id); + } + } + } + } + + + + // Start the thread pool. + sp<ProcessState> ps(ProcessState::self()); + ps->startThreadPool(); + ps->giveThreadPoolName(); + + // Look up the service + sp<IIncidentManager> service = interface_cast<IIncidentManager>( + defaultServiceManager()->getService(android::String16("incident"))); + if (service == NULL) { + fprintf(stderr, "Couldn't look up the incident service\n"); + return 1; + } + + // Construct the stream + int fds[2]; + pipe(fds); + + unique_fd readEnd(fds[0]); + unique_fd writeEnd(fds[1]); + + if (destination == DEST_STDOUT) { + // Call into the service + sp<StatusListener> listener(new StatusListener()); + status = service->reportIncidentToStream(args, listener, writeEnd); + + if (!status.isOk()) { + fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string()); + } + + // Wait for the result and print out the data they send. + //IPCThreadState::self()->joinThreadPool(); + + while (true) { + int amt = splice(fds[0], NULL, STDOUT_FILENO, NULL, 4096, 0); + fprintf(stderr, "spliced %d bytes\n", amt); + if (amt < 0) { + return errno; + } else if (amt == 0) { + return 0; + } + } + } else { + status = service->reportIncident(args); + if (!status.isOk()) { + fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string()); + return 1; + } else { + return 0; + } + } + +} diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk new file mode 100644 index 000000000000..bacf672e81b5 --- /dev/null +++ b/cmds/incidentd/Android.mk @@ -0,0 +1,56 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := incidentd + +LOCAL_SRC_FILES := \ + src/FdBuffer.cpp \ + src/IncidentService.cpp \ + src/Reporter.cpp \ + src/Section.cpp \ + src/main.cpp \ + src/protobuf.cpp \ + src/report_directory.cpp \ + src/section_list.cpp + +LOCAL_CFLAGS += \ + -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter + +ifeq (debug,) + LOCAL_CFLAGS += \ + -g -O0 +else + # optimize for size (protobuf glop can get big) + LOCAL_CFLAGS += \ + -Os +endif + +LOCAL_SHARED_LIBRARIES := \ + libbase \ + libbinder \ + libcutils \ + libincident \ + liblog \ + libselinux \ + libservices \ + libutils + +ifeq (BUILD_WITH_INCIDENTD_RC,true) +LOCAL_INIT_RC := incidentd.rc +endif + +include $(BUILD_EXECUTABLE) diff --git a/cmds/incidentd/incidentd.rc b/cmds/incidentd/incidentd.rc new file mode 100644 index 000000000000..d11e3cf70567 --- /dev/null +++ b/cmds/incidentd/incidentd.rc @@ -0,0 +1,16 @@ +# 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. + +#service incidentd /system/bin/incidentd +# class main diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp new file mode 100644 index 000000000000..527d7eef3a96 --- /dev/null +++ b/cmds/incidentd/src/FdBuffer.cpp @@ -0,0 +1,129 @@ +/* + * 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. + */ + +#define LOG_TAG "incidentd" + +#include "FdBuffer.h" + +#include <cutils/log.h> +#include <utils/SystemClock.h> + +#include <fcntl.h> +#include <poll.h> +#include <unistd.h> + +const ssize_t BUFFER_SIZE = 16 * 1024; +const ssize_t MAX_BUFFER_COUNT = 256; // 4 MB max + + +FdBuffer::FdBuffer() + :mBuffers(), + mStartTime(-1), + mFinishTime(-1), + mCurrentWritten(-1), + mTimedOut(false), + mTruncated(false) +{ +} + +FdBuffer::~FdBuffer() +{ + const int N = mBuffers.size(); + for (int i=0; i<N; i++) { + uint8_t* buf = mBuffers[i]; + free(buf); + } +} + +status_t +FdBuffer::read(int fd, int64_t timeout) +{ + struct pollfd pfds = { + .fd = fd, + .events = POLLIN + }; + mStartTime = uptimeMillis(); + + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + + uint8_t* buf = NULL; + while (true) { + if (mCurrentWritten >= BUFFER_SIZE || mCurrentWritten < 0) { + if (mBuffers.size() == MAX_BUFFER_COUNT) { + mTruncated = true; + break; + } + buf = (uint8_t*)malloc(BUFFER_SIZE); + if (buf == NULL) { + return NO_MEMORY; + } + mBuffers.push_back(buf); + mCurrentWritten = 0; + } + + int64_t remainingTime = (mStartTime + timeout) - uptimeMillis(); + if (remainingTime <= 0) { + mTimedOut = true; + break; + } + + int count = poll(&pfds, 1, remainingTime); + if (count == 0) { + mTimedOut = true; + break; + } else if (count < 0) { + return -errno; + } else { + if ((pfds.revents & POLLERR) != 0) { + return errno != 0 ? -errno : UNKNOWN_ERROR; + } else { + ssize_t amt = ::read(fd, buf + mCurrentWritten, BUFFER_SIZE - mCurrentWritten); + if (amt < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + continue; + } else { + return -errno; + } + } else if (amt == 0) { + break; + } + mCurrentWritten += amt; + } + } + } + + mFinishTime = uptimeMillis(); + return NO_ERROR; +} + +size_t +FdBuffer::size() +{ + return ((mBuffers.size() - 1) * BUFFER_SIZE) + mCurrentWritten; +} + +status_t +FdBuffer::write(ReportRequestSet* reporter) +{ + const int N = mBuffers.size() - 1; + for (int i=0; i<N; i++) { + reporter->write(mBuffers[i], BUFFER_SIZE); + } + reporter->write(mBuffers[N], mCurrentWritten); + return NO_ERROR; +} + + diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h new file mode 100644 index 000000000000..e12374f21558 --- /dev/null +++ b/cmds/incidentd/src/FdBuffer.h @@ -0,0 +1,86 @@ +/* + * 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. + */ + +#ifndef FD_BUFFER_H +#define FD_BUFFER_H + +#include "Reporter.h" + +#include <utils/Errors.h> + +#include <set> +#include <vector> + +using namespace android; +using namespace std; + +/** + * Reads a file into a buffer, and then writes that data to an FdSet. + */ +class FdBuffer +{ +public: + FdBuffer(); + ~FdBuffer(); + + /** + * Read the data until the timeout is hit or we hit eof. + * Returns NO_ERROR if there were no errors or if we timed out. + * Will mark the file O_NONBLOCK. + */ + status_t read(int fd, int64_t timeoutMs); + + /** + * Whether we timed out. + */ + bool timedOut() { return mTimedOut; } + + /** + * If more than 4 MB is read, we truncate the data and return success. + * Downstream tools must handle truncated incident reports as best as possible + * anyway because they could be cut off for a lot of reasons and it's best + * to get as much useful information out of the system as possible. If this + * happens, truncated() will return true so it can be marked. If the data is + * exactly 4 MB, truncated is still set. Sorry. + */ + bool truncated() { return mTruncated; } + + /** + * How much data was read. + */ + size_t size(); + + /** + * Write the data that we recorded to the fd given. + */ + status_t write(ReportRequestSet* requests); + + /** + * How long the read took in milliseconds. + */ + int64_t durationMs() { return mFinishTime - mStartTime; } + +private: + vector<uint8_t*> mBuffers; + int64_t mStartTime; + int64_t mFinishTime; + ssize_t mCurrentWritten; + bool mTimedOut; + bool mTruncated; +}; + + +#endif // FD_BUFFER_H diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp new file mode 100644 index 000000000000..7c6789e6e5ba --- /dev/null +++ b/cmds/incidentd/src/IncidentService.cpp @@ -0,0 +1,244 @@ +/* + * 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. + */ + +#define LOG_TAG "incidentd" + +#include "IncidentService.h" + +#include "Reporter.h" + +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <cutils/log.h> +#include <private/android_filesystem_config.h> +#include <utils/Looper.h> + +#include <unistd.h> + +using namespace android; + +enum { + WHAT_RUN_REPORT = 1, + WHAT_SEND_BACKLOG_TO_DROPBOX = 2 +}; + +//#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL * 60 * 5) +#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL) + +// ================================================================================ +String16 const DUMP_PERMISSION("android.permission.DUMP"); +String16 const USAGE_STATS_PERMISSION("android.permission.PACKAGE_USAGE_STATS"); + +static Status +checkIncidentPermissions() +{ + if (!checkCallingPermission(DUMP_PERMISSION)) { + ALOGW("Calling pid %d and uid %d does not have permission: android.permission.DUMP", + IPCThreadState::self()->getCallingPid(), IPCThreadState::self()->getCallingUid()); + return Status::fromExceptionCode(Status::EX_SECURITY, + "Calling process does not have permission: android.permission.DUMP"); + } + if (!checkCallingPermission(USAGE_STATS_PERMISSION)) { + ALOGW("Calling pid %d and uid %d does not have permission: android.permission.USAGE_STATS", + IPCThreadState::self()->getCallingPid(), IPCThreadState::self()->getCallingUid()); + return Status::fromExceptionCode(Status::EX_SECURITY, + "Calling process does not have permission: android.permission.USAGE_STATS"); + } + return Status::ok(); +} + + +// ================================================================================ +ReportRequestQueue::ReportRequestQueue() +{ +} + +ReportRequestQueue::~ReportRequestQueue() +{ +} + +void +ReportRequestQueue::addRequest(const sp<ReportRequest>& request) +{ + unique_lock<mutex> lock(mLock); + mQueue.push_back(request); +} + +sp<ReportRequest> +ReportRequestQueue::getNextRequest() +{ + unique_lock<mutex> lock(mLock); + if (mQueue.empty()) { + return NULL; + } else { + sp<ReportRequest> front(mQueue.front()); + mQueue.pop_front(); + return front; + } +} + + +// ================================================================================ +ReportHandler::ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue) + :mBacklogDelay(DEFAULT_BACKLOG_DELAY_NS), + mHandlerLooper(handlerLooper), + mQueue(queue) +{ +} + +ReportHandler::~ReportHandler() +{ +} + +void +ReportHandler::handleMessage(const Message& message) +{ + switch (message.what) { + case WHAT_RUN_REPORT: + run_report(); + break; + case WHAT_SEND_BACKLOG_TO_DROPBOX: + send_backlog_to_dropbox(); + break; + } +} + +void +ReportHandler::scheduleRunReport(const sp<ReportRequest>& request) +{ + mQueue->addRequest(request); + mHandlerLooper->removeMessages(this, WHAT_RUN_REPORT); + mHandlerLooper->sendMessage(this, Message(WHAT_RUN_REPORT)); +} + +void +ReportHandler::scheduleSendBacklogToDropbox() +{ + unique_lock<mutex> lock(mLock); + mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS; + schedule_send_backlog_to_dropbox_locked(); +} + +void +ReportHandler::schedule_send_backlog_to_dropbox_locked() +{ + mHandlerLooper->removeMessages(this, WHAT_SEND_BACKLOG_TO_DROPBOX); + mHandlerLooper->sendMessageDelayed(mBacklogDelay, this, + Message(WHAT_SEND_BACKLOG_TO_DROPBOX)); +} + +void +ReportHandler::run_report() +{ + sp<Reporter> reporter = new Reporter(); + + // Merge all of the requests into one that has all of the + // requested fields. + while (true) { + sp<ReportRequest> request = mQueue->getNextRequest(); + if (request == NULL) { + break; + } + reporter->batch.add(request); + reporter->args.merge(request->args); + } + + // Take the report, which might take a while. More requests might queue + // up while we're doing this, and we'll handle them in their next batch. + // TODO: We should further rate-limit the reports to no more than N per time-period. + Reporter::run_report_status_t reportStatus = reporter->runReport(); + if (reportStatus == Reporter::REPORT_NEEDS_DROPBOX) { + unique_lock<mutex> lock(mLock); + schedule_send_backlog_to_dropbox_locked(); + } +} + +void +ReportHandler::send_backlog_to_dropbox() +{ + if (Reporter::upload_backlog() == Reporter::REPORT_NEEDS_DROPBOX) { + // There was a failure. Exponential backoff. + unique_lock<mutex> lock(mLock); + mBacklogDelay *= 2; + ALOGI("Error sending to dropbox. Trying again in %lld minutes", + (mBacklogDelay / (1000000000LL * 60))); + schedule_send_backlog_to_dropbox_locked(); + } else { + mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS; + } +} + +// ================================================================================ +IncidentService::IncidentService(const sp<Looper>& handlerLooper) + :mQueue(new ReportRequestQueue()) +{ + mHandler = new ReportHandler(handlerLooper, mQueue); +} + +IncidentService::~IncidentService() +{ +} + +Status +IncidentService::reportIncident(const IncidentReportArgs& args) +{ + ALOGI("reportIncident"); + + Status status = checkIncidentPermissions(); + if (!status.isOk()) { + return status; + } + + mHandler->scheduleRunReport(new ReportRequest(args, NULL, -1)); + + return Status::ok(); +} + +Status +IncidentService::reportIncidentToStream(const IncidentReportArgs& args, + const sp<IIncidentReportStatusListener>& listener, const unique_fd& stream) +{ + ALOGI("reportIncidentToStream"); + + Status status = checkIncidentPermissions(); + if (!status.isOk()) { + return status; + } + + int fd = dup(stream.get()); + if (fd < 0) { + return Status::fromStatusT(-errno); + } + + mHandler->scheduleRunReport(new ReportRequest(args, listener, fd)); + + return Status::ok(); +} + +Status +IncidentService::systemRunning() +{ + if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) { + return Status::fromExceptionCode(Status::EX_SECURITY, + "Only system uid can call systemRunning"); + } + + // When system_server is up and running, schedule the dropbox task to run. + mHandler->scheduleSendBacklogToDropbox(); + + return Status::ok(); +} + diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h new file mode 100644 index 000000000000..d6f33dfb1a86 --- /dev/null +++ b/cmds/incidentd/src/IncidentService.h @@ -0,0 +1,112 @@ +/* + * 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. + */ + +#ifndef INCIDENT_SERVICE_H +#define INCIDENT_SERVICE_H + +#include "Reporter.h" + +#include <android/os/BnIncidentManager.h> +#include <utils/Looper.h> + +#include <deque> +#include <mutex> + +using namespace android; +using namespace android::base; +using namespace android::binder; +using namespace android::os; +using namespace std; + +// ================================================================================ +class ReportRequestQueue : public virtual RefBase +{ +public: + ReportRequestQueue(); + virtual ~ReportRequestQueue(); + + void addRequest(const sp<ReportRequest>& request); + sp<ReportRequest> getNextRequest(); + +private: + mutex mLock; + deque<sp<ReportRequest> > mQueue; +}; + + +// ================================================================================ +class ReportHandler : public MessageHandler +{ +public: + ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue); + virtual ~ReportHandler(); + + virtual void handleMessage(const Message& message); + + /** + * Adds a ReportRequest to the queue. + */ + void scheduleRunReport(const sp<ReportRequest>& request); + + /** + * Resets mBacklogDelay to the default and schedules sending + * the messages to dropbox. + */ + void scheduleSendBacklogToDropbox(); + +private: + mutex mLock; + nsecs_t mBacklogDelay; + sp<Looper> mHandlerLooper; + sp<ReportRequestQueue> mQueue; + + /** + * Runs all of the reports that have been queued. + */ + void run_report(); + + /** + * Schedules a dropbox task mBacklogDelay nanoseconds from now. + */ + void schedule_send_backlog_to_dropbox_locked(); + + /** + * Sends the backlog to the dropbox service. + */ + void send_backlog_to_dropbox(); +}; + + +// ================================================================================ +class IncidentService : public BnIncidentManager { +public: + IncidentService(const sp<Looper>& handlerLooper); + virtual ~IncidentService(); + + virtual Status reportIncident(const IncidentReportArgs& args); + + virtual Status reportIncidentToStream(const IncidentReportArgs& args, + const sp<IIncidentReportStatusListener>& listener, const unique_fd& stream); + + virtual Status systemRunning(); + +private: + sp<ReportRequestQueue> mQueue; + sp<ReportHandler> mHandler; +}; + + +#endif // INCIDENT_SERVICE_H diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp new file mode 100644 index 000000000000..1ecb291c84a1 --- /dev/null +++ b/cmds/incidentd/src/Reporter.cpp @@ -0,0 +1,352 @@ +/* + * 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. + */ + +#define LOG_TAG "incidentd" + +#include "Reporter.h" +#include "protobuf.h" + +#include "report_directory.h" +#include "section_list.h" + +#include <private/android_filesystem_config.h> +#include <android/os/DropBoxManager.h> +#include <utils/SystemClock.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <fcntl.h> +#include <errno.h> + +/** + * The directory where the incident reports are stored. + */ +static const String8 INCIDENT_DIRECTORY("/data/incidents"); + +static status_t +write_all(int fd, uint8_t const* buf, size_t size) +{ + while (size > 0) { + ssize_t amt = ::write(fd, buf, size); + if (amt < 0) { + return -errno; + } + size -= amt; + buf += amt; + } + return NO_ERROR; +} + +// ================================================================================ +ReportRequest::ReportRequest(const IncidentReportArgs& a, + const sp<IIncidentReportStatusListener> &l, int f) + :args(a), + listener(l), + fd(f), + err(NO_ERROR) +{ +} + +ReportRequest::~ReportRequest() +{ +} + +// ================================================================================ +ReportRequestSet::ReportRequestSet() + :mRequests(), + mWritableCount(0), + mMainFd(-1) +{ +} + +ReportRequestSet::~ReportRequestSet() +{ +} + +void +ReportRequestSet::add(const sp<ReportRequest>& request) +{ + mRequests.push_back(request); + mWritableCount++; +} + +void +ReportRequestSet::setMainFd(int fd) +{ + mMainFd = fd; + mWritableCount++; +} + +status_t +ReportRequestSet::write(uint8_t const* buf, size_t size) +{ + status_t err = EBADF; + + // The streaming ones + int const N = mRequests.size(); + for (int i=N-1; i>=0; i--) { + sp<ReportRequest> request = mRequests[i]; + if (request->fd >= 0 && request->err == NO_ERROR) { + err = write_all(request->fd, buf, size); + if (err != NO_ERROR) { + request->err = err; + mWritableCount--; + } + } + } + + // The dropbox file + if (mMainFd >= 0) { + err = write_all(mMainFd, buf, size); + if (err != NO_ERROR) { + mMainFd = -1; + mWritableCount--; + } + } + + // Return an error only when there are no FDs to write. + return mWritableCount > 0 ? NO_ERROR : err; +} + + +// ================================================================================ +Reporter::Reporter() + :args(), + batch() +{ + char buf[100]; + + // TODO: Make the max size smaller for user builds. + mMaxSize = 100 * 1024 * 1024; + mMaxCount = 100; + + // There can't be two at the same time because it's on one thread. + mStartTime = time(NULL); + strftime(buf, sizeof(buf), "/incident-%Y%m%d-%H%M%S", localtime(&mStartTime)); + mFilename = INCIDENT_DIRECTORY + buf; +} + +Reporter::~Reporter() +{ +} + +Reporter::run_report_status_t +Reporter::runReport() +{ + + status_t err = NO_ERROR; + bool needMainFd = false; + int mainFd = -1; + + // See if we need the main file + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if ((*it)->fd < 0 && mainFd < 0) { + needMainFd = true; + break; + } + } + if (needMainFd) { + // Create the directory + err = create_directory(INCIDENT_DIRECTORY); + if (err != NO_ERROR) { + goto done; + } + + // If there are too many files in the directory (for whatever reason), + // delete the oldest ones until it's under the limit. Doing this first + // does mean that we can go over, so the max size is not a hard limit. + clean_directory(INCIDENT_DIRECTORY, mMaxSize, mMaxCount); + + // Open the file. + err = create_file(&mainFd); + if (err != NO_ERROR) { + goto done; + } + + // Add to the set + batch.setMainFd(mainFd); + } + + // Tell everyone that we're starting. + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if ((*it)->listener != NULL) { + (*it)->listener->onReportStarted(); + } + } + + // Write the incident headers + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + const sp<ReportRequest> request = (*it); + const vector<vector<int8_t>>& headers = request->args.headers(); + + for (vector<vector<int8_t>>::const_iterator buf=headers.begin(); buf!=headers.end(); + buf++) { + int fd = request->fd >= 0 ? request->fd : mainFd; + + uint8_t buffer[20]; + uint8_t* p = write_length_delimited_tag_header(buffer, FIELD_ID_INCIDENT_HEADER, + buf->size()); + write_all(fd, buffer, p-buffer); + + write_all(fd, (uint8_t const*)buf->data(), buf->size()); + // If there was an error now, there will be an error later and we will remove + // it from the list then. + } + } + + // For each of the report fields, see if we need it, and if so, execute the command + // and report to those that care that we're doing it. + for (const Section** section=SECTION_LIST; *section; section++) { + const int id = (*section)->id; + ALOGD("Taking incident report section %d '%s'", id, (*section)->name.string()); + + if (this->args.containsSection(id)) { + // Notify listener of starting + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if ((*it)->listener != NULL && (*it)->args.containsSection(id)) { + (*it)->listener->onReportSectionStatus(id, + IIncidentReportStatusListener::STATUS_STARTING); + } + } + + // Execute - go get the data and write it into the file descriptors. + err = (*section)->Execute(&batch); + if (err != NO_ERROR) { + ALOGW("Incident section %s (%d) failed. Stopping report.", + (*section)->name.string(), id); + goto done; + } + + // Notify listener of starting + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if ((*it)->listener != NULL && (*it)->args.containsSection(id)) { + (*it)->listener->onReportSectionStatus(id, + IIncidentReportStatusListener::STATUS_FINISHED); + } + } + } + } + +done: + // Close the file. + if (mainFd >= 0) { + close(mainFd); + } + + // Tell everyone that we're done. + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if ((*it)->listener != NULL) { + if (err == NO_ERROR) { + (*it)->listener->onReportFinished(); + } else { + (*it)->listener->onReportFailed(); + } + } + } + + // Put the report into dropbox. + if (needMainFd && err == NO_ERROR) { + sp<DropBoxManager> dropbox = new DropBoxManager(); + Status status = dropbox->addFile(String16("incident"), mFilename, 0); + ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string()); + if (!status.isOk()) { + return REPORT_NEEDS_DROPBOX; + } + + // If the status was ok, delete the file. If not, leave it around until the next + // boot or the next checkin. If the directory gets too big older files will + // be rotated out. + unlink(mFilename.c_str()); + } + + return REPORT_FINISHED; +} + +/** + * Create our output file and set the access permissions to -rw-rw---- + */ +status_t +Reporter::create_file(int* fd) +{ + const char* filename = mFilename.c_str(); + + *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0660); + if (*fd < 0) { + ALOGE("Couldn't open incident file: %s (%s)", filename, strerror(errno)); + return -errno; + } + + // Override umask. Not super critical. If it fails go on with life. + chmod(filename, 0660); + + if (chown(filename, AID_SYSTEM, AID_SYSTEM)) { + ALOGE("Unable to change ownership of incident file %s: %s\n", filename, strerror(errno)); + status_t err = -errno; + unlink(mFilename.c_str()); + return err; + } + + return NO_ERROR; +} + +// ================================================================================ +Reporter::run_report_status_t +Reporter::upload_backlog() +{ + DIR* dir; + struct dirent* entry; + struct stat st; + + if ((dir = opendir(INCIDENT_DIRECTORY.string())) == NULL) { + ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY.string()); + return REPORT_NEEDS_DROPBOX; + } + + String8 dirbase(INCIDENT_DIRECTORY + "/"); + sp<DropBoxManager> dropbox = new DropBoxManager(); + + // Enumerate, count and add up size + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + String8 filename = dirbase + entry->d_name; + if (stat(filename.string(), &st) != 0) { + ALOGE("Unable to stat file %s", filename.string()); + continue; + } + if (!S_ISREG(st.st_mode)) { + continue; + } + + Status status = dropbox->addFile(String16("incident"), filename.string(), 0); + ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string()); + if (!status.isOk()) { + return REPORT_NEEDS_DROPBOX; + } + + // If the status was ok, delete the file. If not, leave it around until the next + // boot or the next checkin. If the directory gets too big older files will + // be rotated out. + unlink(filename.string()); + } + + closedir(dir); + + return REPORT_FINISHED; +} + diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h new file mode 100644 index 000000000000..5b86561520f8 --- /dev/null +++ b/cmds/incidentd/src/Reporter.h @@ -0,0 +1,101 @@ +/* + * 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. + */ + +#ifndef REPORTER_H +#define REPORTER_H + +#include <android/os/IIncidentReportStatusListener.h> +#include <android/os/IncidentReportArgs.h> + +#include <string> +#include <vector> + +#include <time.h> + +using namespace android; +using namespace android::os; +using namespace std; + +// ================================================================================ +struct ReportRequest : public virtual RefBase +{ + IncidentReportArgs args; + sp<IIncidentReportStatusListener> listener; + int fd; + status_t err; + + ReportRequest(const IncidentReportArgs& args, + const sp<IIncidentReportStatusListener> &listener, int fd); + virtual ~ReportRequest(); +}; + +// ================================================================================ +class ReportRequestSet +{ +public: + ReportRequestSet(); + ~ReportRequestSet(); + + void add(const sp<ReportRequest>& request); + void setMainFd(int fd); + + // Write to all of the fds for the requests. If a write fails, it stops + // writing to that fd and returns NO_ERROR. When we are out of fds to write + // to it returns an error. + status_t write(uint8_t const* buf, size_t size); + + typedef vector<sp<ReportRequest>>::iterator iterator; + + iterator begin() { return mRequests.begin(); } + iterator end() { return mRequests.end(); } + +private: + vector<sp<ReportRequest>> mRequests; + int mWritableCount; + int mMainFd; +}; + +// ================================================================================ +class Reporter : public virtual RefBase +{ +public: + enum run_report_status_t { + REPORT_FINISHED = 0, + REPORT_NEEDS_DROPBOX = 1 + }; + + IncidentReportArgs args; + ReportRequestSet batch; + + Reporter(); + virtual ~Reporter(); + + // Run the report as described in the batch and args parameters. + run_report_status_t runReport(); + + static run_report_status_t upload_backlog(); + +private: + string mFilename; + off_t mMaxSize; + size_t mMaxCount; + time_t mStartTime; + + status_t create_file(int* fd); +}; + + +#endif // REPORTER_H diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp new file mode 100644 index 000000000000..fac299ed0dcd --- /dev/null +++ b/cmds/incidentd/src/Section.cpp @@ -0,0 +1,292 @@ +/* + * 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. + */ + +#define LOG_TAG "incidentd" + +#include "Section.h" +#include "protobuf.h" + +#include <binder/IServiceManager.h> +#include <mutex> + +using namespace std; + +const int64_t REMOTE_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds + +// ================================================================================ +Section::Section(int i) + :id(i) +{ +} + +Section::~Section() +{ +} + +status_t +Section::WriteHeader(ReportRequestSet* requests, size_t size) const +{ + ssize_t amt; + uint8_t buf[20]; + uint8_t* p = write_length_delimited_tag_header(buf, this->id, size); + return requests->write(buf, p-buf); +} + +// ================================================================================ +struct WorkerThreadData : public virtual RefBase +{ + const WorkerThreadSection* section; + int fds[2]; + + // Lock protects these fields + mutex lock; + bool workerDone; + status_t workerError; + + WorkerThreadData(const WorkerThreadSection* section); + virtual ~WorkerThreadData(); + + int readFd() { return fds[0]; } + int writeFd() { return fds[1]; } +}; + +WorkerThreadData::WorkerThreadData(const WorkerThreadSection* sec) + :section(sec), + workerDone(false), + workerError(NO_ERROR) +{ + fds[0] = -1; + fds[1] = -1; +} + +WorkerThreadData::~WorkerThreadData() +{ +} + +// ================================================================================ +WorkerThreadSection::WorkerThreadSection(int id) + :Section(id) +{ +} + +WorkerThreadSection::~WorkerThreadSection() +{ +} + +static void* +worker_thread_func(void* cookie) +{ + WorkerThreadData* data = (WorkerThreadData*)cookie; + status_t err = data->section->BlockingCall(data->writeFd()); + + { + unique_lock<mutex> lock(data->lock); + data->workerDone = true; + data->workerError = err; + } + + close(data->writeFd()); + data->decStrong(data->section); + // data might be gone now. don't use it after this point in this thread. + return NULL; +} + +status_t +WorkerThreadSection::Execute(ReportRequestSet* requests) const +{ + status_t err = NO_ERROR; + pthread_t thread; + pthread_attr_t attr; + bool timedOut = false; + FdBuffer buffer; + + // Data shared between this thread and the worker thread. + sp<WorkerThreadData> data = new WorkerThreadData(this); + + // Create the pipe + err = pipe(data->fds); + if (err != 0) { + return -errno; + } + + // The worker thread needs a reference and we can't let the count go to zero + // if that thread is slow to start. + data->incStrong(this); + + // Create the thread + err = pthread_attr_init(&attr); + if (err != 0) { + return -err; + } + // TODO: Do we need to tweak thread priority? + err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (err != 0) { + pthread_attr_destroy(&attr); + return -err; + } + err = pthread_create(&thread, &attr, worker_thread_func, (void*)data.get()); + if (err != 0) { + pthread_attr_destroy(&attr); + return -err; + } + pthread_attr_destroy(&attr); + + // Loop reading until either the timeout or the worker side is done (i.e. eof). + err = buffer.read(data->readFd(), REMOTE_CALL_TIMEOUT_MS); + if (err != NO_ERROR) { + // TODO: Log this error into the incident report. + ALOGW("WorkerThreadSection '%s' reader failed with error '%s'", this->name.string(), + strerror(-err)); + } + + // Done with the read fd. The worker thread closes the write one so + // we never race and get here first. + close(data->readFd()); + + // If the worker side is finished, then return its error (which may overwrite + // our possible error -- but it's more interesting anyway). If not, then we timed out. + { + unique_lock<mutex> lock(data->lock); + if (!data->workerDone) { + // We timed out + timedOut = true; + } else { + if (data->workerError != NO_ERROR) { + err = data->workerError; + // TODO: Log this error into the incident report. + ALOGW("WorkerThreadSection '%s' worker failed with error '%s'", this->name.string(), + strerror(-err)); + } + } + } + + if (timedOut || buffer.timedOut()) { + ALOGW("WorkerThreadSection '%s' timed out", this->name.string()); + return NO_ERROR; + } + + if (buffer.truncated()) { + // TODO: Log this into the incident report. + } + + // TODO: There was an error with the command or buffering. Report that. For now + // just exit with a log messasge. + if (err != NO_ERROR) { + ALOGW("WorkerThreadSection '%s' failed with error '%s'", this->name.string(), + strerror(-err)); + return NO_ERROR; + } + + // Write the data that was collected + ALOGD("section '%s' wrote %zd bytes in %d ms", name.string(), buffer.size(), + (int)buffer.durationMs()); + WriteHeader(requests, buffer.size()); + err = buffer.write(requests); + if (err != NO_ERROR) { + ALOGW("WorkerThreadSection '%s' failed writing: '%s'", this->name.string(), strerror(-err)); + return err; + } + + return NO_ERROR; +} + +// ================================================================================ +CommandSection::CommandSection(int id, const char* first, ...) + :Section(id) +{ + va_list args; + int count = 0; + + va_start(args, first); + while (va_arg(args, const char*) != NULL) { + count++; + } + va_end(args); + + mCommand = (const char**)malloc(sizeof(const char*) * count); + + mCommand[0] = first; + name = first; + name += " "; + va_start(args, first); + for (int i=0; i<count; i++) { + const char* arg = va_arg(args, const char*); + mCommand[i+1] = arg; + if (arg != NULL) { + name += va_arg(args, const char*); + name += " "; + } + } + va_end(args); +} + +CommandSection::~CommandSection() +{ +} + +status_t +CommandSection::Execute(ReportRequestSet* /*requests*/) const +{ + return NO_ERROR; +} + +// ================================================================================ +DumpsysSection::DumpsysSection(int id, const char* service, ...) + :WorkerThreadSection(id), + mService(service) +{ + name = "dumpsys "; + name += service; + + va_list args; + va_start(args, service); + while (true) { + const char* arg = va_arg(args, const char*); + if (arg == NULL) { + break; + } + mArgs.add(String16(arg)); + name += " "; + name += arg; + } + va_end(args); +} + +DumpsysSection::~DumpsysSection() +{ +} + +status_t +DumpsysSection::BlockingCall(int pipeWriteFd) const +{ + // checkService won't wait for the service to show up like getService will. + sp<IBinder> service = defaultServiceManager()->checkService(mService); + + if (service == NULL) { + // Returning an error interrupts the entire incident report, so just + // log the failure. + // TODO: have a meta record inside the report that would log this + // failure inside the report, because the fact that we can't find + // the service is good data in and of itself. This is running in + // another thread so lock that carefully... + ALOGW("DumpsysSection: Can't lookup service: %s", String8(mService).string()); + return NO_ERROR; + } + + service->dump(pipeWriteFd, mArgs); + + return NO_ERROR; +} diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h new file mode 100644 index 000000000000..35740e9771d5 --- /dev/null +++ b/cmds/incidentd/src/Section.h @@ -0,0 +1,106 @@ +/* + * 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. + */ + +#ifndef SECTIONS_H +#define SECTIONS_H + +#include "FdBuffer.h" + +#include <utils/String8.h> +#include <utils/String16.h> +#include <utils/Vector.h> + +using namespace android; + +/** + * Base class for sections + */ +class Section +{ +public: + int id; + String8 name; + + Section(int id); + virtual ~Section(); + + virtual status_t Execute(ReportRequestSet* requests) const = 0; + + status_t WriteHeader(ReportRequestSet* requests, size_t size) const; +}; + +/** + * Section that reads in a file. + */ +class FileSection : public Section +{ +public: + FileSection(int id, const char* filename); + virtual ~FileSection(); + + virtual status_t Execute(ReportRequestSet* requests) const; + +private: + const char* mFilename; +}; + +/** + * Base class for sections that call a command that might need a timeout. + */ +class WorkerThreadSection : public Section +{ +public: + WorkerThreadSection(int id); + virtual ~WorkerThreadSection(); + + virtual status_t Execute(ReportRequestSet* requests) const; + + virtual status_t BlockingCall(int pipeWriteFd) const = 0; +}; + +/** + * Section that forks and execs a command, and puts stdout as the section. + */ +class CommandSection : public Section +{ +public: + CommandSection(int id, const char* first, ...); + virtual ~CommandSection(); + + virtual status_t Execute(ReportRequestSet* requests) const; + +private: + const char** mCommand; +}; + +/** + * Section that calls dumpsys on a system service. + */ +class DumpsysSection : public WorkerThreadSection +{ +public: + DumpsysSection(int id, const char* service, ...); + virtual ~DumpsysSection(); + + virtual status_t BlockingCall(int pipeWriteFd) const; + +private: + String16 mService; + Vector<String16> mArgs; +}; + +#endif // SECTIONS_H + diff --git a/cmds/incidentd/src/main.cpp b/cmds/incidentd/src/main.cpp new file mode 100644 index 000000000000..3a7511d43048 --- /dev/null +++ b/cmds/incidentd/src/main.cpp @@ -0,0 +1,64 @@ +/* + * 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. + */ + +#define LOG_TAG "incidentd" + +#include "IncidentService.h" + +#include <binder/IInterface.h> +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <binder/ProcessState.h> +#include <binder/Status.h> +#include <cutils/log.h> +#include <utils/Looper.h> +#include <utils/StrongPointer.h> + +#include <sys/types.h> +#include <sys/stat.h> + +using namespace android; + +// ================================================================================ +int +main(int /*argc*/, char** /*argv*/) +{ + // Set up the looper + sp<Looper> looper(Looper::prepare(0 /* opts */)); + + // Set up the binder + sp<ProcessState> ps(ProcessState::self()); + ps->setThreadPoolMaxThreadCount(1); // everything is oneway, let it queue and save ram + ps->startThreadPool(); + ps->giveThreadPoolName(); + IPCThreadState::self()->disableBackgroundScheduling(true); + + // Create the service + android::sp<IncidentService> service = new IncidentService(looper); + if (defaultServiceManager()->addService(String16("incident"), service) != 0) { + ALOGE("Failed to add service"); + return -1; + } + + // Loop forever -- the reports run on this thread in a handler, and the + // binder calls remain responsive in their pool of one thread. + while (true) { + looper->pollAll(-1 /* timeoutMillis */); + } + ALOGW("incidentd escaped from its loop."); + + return 1; +} diff --git a/cmds/incidentd/src/protobuf.cpp b/cmds/incidentd/src/protobuf.cpp new file mode 100644 index 000000000000..cb864fd3b619 --- /dev/null +++ b/cmds/incidentd/src/protobuf.cpp @@ -0,0 +1,41 @@ +/* + * 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 "protobuf.h" + +uint8_t* +write_raw_varint(uint8_t* buf, uint32_t val) +{ + uint8_t* p = buf; + while (true) { + if ((val & ~0x7F) == 0) { + *p++ = (uint8_t)val; + return p; + } else { + *p++ = (uint8_t)((val & 0x7F) | 0x80); + val >>= 7; + } + } +} + +uint8_t* +write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size) +{ + buf = write_raw_varint(buf, (fieldId << 3) | 2); + buf = write_raw_varint(buf, size); + return buf; +} + diff --git a/cmds/incidentd/src/protobuf.h b/cmds/incidentd/src/protobuf.h new file mode 100644 index 000000000000..a24399832b00 --- /dev/null +++ b/cmds/incidentd/src/protobuf.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#ifndef PROTOBUF_H +#define PROTOBUF_H + +#include <stdint.h> + +/** + * Write a varint into the buffer. Return the next position to write at. + * There must be 10 bytes in the buffer. The same as EncodedBuffer.writeRawVarint32 + */ +uint8_t* write_raw_varint(uint8_t* buf, uint32_t val); + +/** + * Write a protobuf WIRE_TYPE_LENGTH_DELIMITED header. Return the next position to write at. + * There must be 20 bytes in the buffer. + */ +uint8_t* write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size); + +enum { + // IncidentProto.header + FIELD_ID_INCIDENT_HEADER = 1 +}; + +#endif // PROTOBUF_H + diff --git a/cmds/incidentd/src/report_directory.cpp b/cmds/incidentd/src/report_directory.cpp new file mode 100644 index 000000000000..f60b8ac46cca --- /dev/null +++ b/cmds/incidentd/src/report_directory.cpp @@ -0,0 +1,173 @@ +/* + * 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. + */ + +#define LOG_TAG "incidentd" + +#include "report_directory.h" + +#include <cutils/log.h> +#include <private/android_filesystem_config.h> +#include <utils/String8.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <libgen.h> +#include <unistd.h> + +#include <vector> + +using namespace android; +using namespace std; + +status_t +create_directory(const char* directory) +{ + struct stat st; + status_t err = NO_ERROR; + char* dir = strdup(directory); + + // Skip first slash + char* d = dir + 1; + + // Create directories, assigning them to the system user + bool last = false; + while (!last) { + d = strchr(d, '/'); + if (d != NULL) { + *d = '\0'; + } else { + last = true; + } + if (stat(dir, &st) == 0) { + if (!S_ISDIR(st.st_mode)) { + err = ALREADY_EXISTS; + goto done; + } + } else { + if (mkdir(dir, 0770)) { + ALOGE("No incident reports today. " + "Unable to create incident report dir %s: %s", dir, + strerror(errno)); + err = -errno; + goto done; + } + if (chmod(dir, 0770)) { + ALOGE("No incident reports today. " + "Unable to set permissions for incident report dir %s: %s", dir, + strerror(errno)); + err = -errno; + goto done; + } + if (chown(dir, AID_SYSTEM, AID_SYSTEM)) { + ALOGE("No incident reports today. Unable to change ownership of dir %s: %s\n", + dir, strerror(errno)); + err = -errno; + goto done; + } + } + if (!last) { + *d++ = '/'; + } + } + + // Ensure that the final directory is owned by the system with 0770. If it isn't + // we won't write into it. + if (stat(directory, &st) != 0) { + ALOGE("No incident reports today. Can't stat: %s", directory); + err = -errno; + goto done; + } + if ((st.st_mode & 0777) != 0770) { + ALOGE("No incident reports today. Mode is %0o on report directory %s", + st.st_mode, directory); + err = BAD_VALUE; + goto done; + } + if (st.st_uid != AID_SYSTEM || st.st_gid != AID_SYSTEM) { + ALOGE("No incident reports today. Owner is %d and group is %d on report directory %s", + st.st_uid, st.st_gid, directory); + err = BAD_VALUE; + goto done; + } + +done: + free(dir); + return err; +} + +static bool +stat_mtime_cmp(const pair<String8,struct stat>& a, const pair<String8,struct stat>& b) +{ + return a.second.st_mtime < b.second.st_mtime; +} + +void +clean_directory(const char* directory, off_t maxSize, size_t maxCount) +{ + DIR* dir; + struct dirent* entry; + struct stat st; + + vector<pair<String8,struct stat>> files; + + if ((dir = opendir(directory)) == NULL) { + ALOGE("Couldn't open incident directory: %s", directory); + return; + } + + String8 dirbase(String8(directory) + "/"); + + off_t totalSize = 0; + size_t totalCount = 0; + + // Enumerate, count and add up size + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + String8 filename = dirbase + entry->d_name; + if (stat(filename.string(), &st) != 0) { + ALOGE("Unable to stat file %s", filename.string()); + continue; + } + if (!S_ISREG(st.st_mode)) { + continue; + } + files.push_back(pair<String8,struct stat>(filename, st)); + + totalSize += st.st_size; + totalCount++; + } + + closedir(dir); + + // Count or size is less than max, then we're done. + if (totalSize < maxSize && totalCount < maxCount) { + return; + } + + // Oldest files first. + sort(files.begin(), files.end(), stat_mtime_cmp); + + // Remove files until we're under our limits. + for (vector<pair<String8,struct stat>>::iterator it = files.begin(); + it != files.end() && totalSize >= maxSize && totalCount >= maxCount; it++) { + remove(it->first.string()); + totalSize -= it->second.st_size; + totalCount--; + } +} diff --git a/cmds/incidentd/src/report_directory.h b/cmds/incidentd/src/report_directory.h new file mode 100644 index 000000000000..bed4f869cfe4 --- /dev/null +++ b/cmds/incidentd/src/report_directory.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#ifndef DIRECTORY_CLEANER_H +#define DIRECTORY_CLEANER_H + +#include <utils/Errors.h> + +#include <sys/types.h> + +using namespace android; + +status_t create_directory(const char* directory); +void clean_directory(const char* directory, off_t maxSize, size_t maxCount); + +#endif // DIRECTORY_CLEANER_H diff --git a/cmds/incidentd/src/section_list.cpp b/cmds/incidentd/src/section_list.cpp new file mode 100644 index 000000000000..b6112ed0f7df --- /dev/null +++ b/cmds/incidentd/src/section_list.cpp @@ -0,0 +1,29 @@ +/* + * 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 "section_list.h" + +//using namespace android::util; + +/** + * This is the mapping of section IDs to the commands that are run to get those commands. + */ +const Section* SECTION_LIST[] = { + new DumpsysSection(3000, + "fingerprint", "--proto", "--incident", NULL), + NULL +}; + diff --git a/cmds/incidentd/src/section_list.h b/cmds/incidentd/src/section_list.h new file mode 100644 index 000000000000..c97751937d6d --- /dev/null +++ b/cmds/incidentd/src/section_list.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#ifndef SECTION_LIST_H +#define SECTION_LIST_H + +#include "Section.h" + +/** + * This is the mapping of section IDs to the commands that are run to get those commands. + */ +extern const Section* SECTION_LIST[]; + +#endif // SECTION_LIST_H + diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 6dd488f351ed..2ccfe0e3b72d 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -45,6 +45,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; @@ -75,6 +76,7 @@ import android.service.autofill.IAutoFillCallback; import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.TextAssistant; +import android.text.TextClassificationManager; import android.text.TextUtils; import android.text.method.TextKeyListener; import android.transition.Scene; @@ -1402,7 +1404,7 @@ public class Activity extends ContextThemeWrapper if (mTextAssistant != null) { return mTextAssistant; } - return TextAssistant.NO_OP; + return getSystemService(TextClassificationManager.class); } /** @@ -1737,8 +1739,8 @@ public class Activity extends ContextThemeWrapper FillableInputField autoFillField = (FillableInputField) field; final int viewId = autoFillField.getId(); final View view = root.findViewByAccessibilityIdTraversal(viewId); - // TODO: should handle other types of view as well, but that will - // require: + // TODO(b/33197203): should handle other types of view as well, but + // that will require: // - a new interface like AutoFillable // - a way for the views to define the type of the autofield value if ((view instanceof EditText)) { @@ -1751,7 +1753,7 @@ public class Activity extends ContextThemeWrapper @Override public void showError(String message) { runOnUiThread(() -> { - // TODO: temporary show a toast until it uses the Snack bar. + // TODO(b/33197203): temporary show a toast until it uses the Snack bar. Toast.makeText(Activity.this, "Auto-fill request failed: " + message, Toast.LENGTH_LONG).show(); }); @@ -2018,7 +2020,29 @@ public class Activity extends ContextThemeWrapper } /** - * Updates the aspect ratio of the current picture-in-picture activity. + * Requests to the system that the activity can be automatically put into picture-in-picture + * mode when the user leaves the activity causing it normally to be hidden. Generally, this + * happens when another task is brought to the forground or the task containing this activity + * is moved to the background. This is a *not* a guarantee that the activity will actually be + * put in picture-in-picture mode, and depends on a number of factors, including whether there + * is already something in picture-in-picture. + * + * @param enterPictureInPictureOnMoveToBg whether or not this activity can automatically enter + * picture-in-picture + */ + public void enterPictureInPictureModeOnMoveToBackground( + boolean enterPictureInPictureOnMoveToBg) { + try { + ActivityManagerNative.getDefault().enterPictureInPictureModeOnMoveToBackground(mToken, + enterPictureInPictureOnMoveToBg); + } catch (RemoteException e) { + } + } + + /** + * Updates the aspect ratio of the current picture-in-picture activity if this activity is + * already in picture-in-picture mode, or sets it to be used later if + * {@link #enterPictureInPictureModeOnMoveToBackground(boolean)} is requested. * * @param aspectRatio the new aspect ratio of the picture-in-picture. */ @@ -2030,23 +2054,19 @@ public class Activity extends ContextThemeWrapper } /** - * Requests to the system that the activity can be automatically put into picture-in-picture - * mode when the user leaves the activity causing it normally to be hidden. This is a *not* - * a guarantee that the activity will actually be put in picture-in-picture mode, and depends - * on a number of factors, including whether there is already something in picture-in-picture. - * - * If {@param enterPictureInPictureOnMoveToBg} is true, then you may also call - * {@link #setPictureInPictureAspectRatio(float)} to specify the aspect ratio to automatically - * enter picture-in-picture with. + * Updates the set of user actions associated with the picture-in-picture activity. * - * @param enterPictureInPictureOnMoveToBg whether or not this activity can automatically enter - * picture-in-picture + * @param actions the new actions for picture-in-picture (can be null to reset the set of + * actions). The maximum number of actions that will be displayed on this device + * is defined by {@link ActivityManager#getMaxNumPictureInPictureActions()}. */ - public void enterPictureInPictureModeOnMoveToBackground( - boolean enterPictureInPictureOnMoveToBg) { + public void setPictureInPictureActions(List<RemoteAction> actions) { try { - ActivityManagerNative.getDefault().enterPictureInPictureModeOnMoveToBackground(mToken, - enterPictureInPictureOnMoveToBg); + if (actions == null) { + actions = new ArrayList<>(); + } + ActivityManagerNative.getDefault().setPictureInPictureActions(mToken, + new ParceledListSlice<RemoteAction>(actions)); } catch (RemoteException e) { } } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 312d3a57792c..761da35f3607 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -87,8 +87,9 @@ public class ActivityManager { private static int gMaxRecentTasks = -1; + private static final int NUM_ALLOWED_PIP_ACTIONS = 3; + private final Context mContext; - private final Handler mHandler; private static volatile boolean sSystemReady = false; @@ -450,9 +451,6 @@ public class ActivityManager { /** @hide requestType for assist context: generate full AssistStructure. */ public static final int ASSIST_CONTEXT_FULL = 1; - /** @hide requestType for assist context: generate full AssistStructure for auto-fill. */ - public static final int ASSIST_CONTEXT_AUTOFILL = 2; - /** @hide Flag for registerUidObserver: report changes in process state. */ public static final int UID_OBSERVER_PROCSTATE = 1<<0; @@ -494,7 +492,6 @@ public class ActivityManager { /*package*/ ActivityManager(Context context, Handler handler) { mContext = context; - mHandler = handler; } /** @@ -1005,6 +1002,24 @@ public class ActivityManager { } /** + * Returns true if the system supports split screen multi-window. + * @hide + */ + static public boolean supportsSplitScreenMultiWindow() { + return supportsMultiWindow() + && Resources.getSystem().getBoolean( + com.android.internal.R.bool.config_supportsSplitScreenMultiWindow); + } + + /** + * Return the maximum number of actions that will be displayed in the picture-in-picture UI when + * the user interacts with the activity currently in picture-in-picture mode. + */ + public static int getMaxNumPictureInPictureActions() { + return NUM_ALLOWED_PIP_ACTIONS; + } + + /** * Information you can set and retrieve about the current activity within the recent task list. */ public static class TaskDescription implements Parcelable { diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 913edfd81081..87700dc7aa35 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -28,6 +28,8 @@ import android.service.voice.IVoiceInteractionSession; import com.android.internal.app.IVoiceInteractor; +import java.io.PrintWriter; +import java.util.ArrayList; import java.util.List; /** @@ -62,6 +64,36 @@ public abstract class ActivityManagerInternal { public static final int APP_TRANSITION_TIMEOUT = 3; /** + * Class to hold deferred properties to apply for picture-in-picture for a given activity. + */ + public static class PictureInPictureArguments { + /** + * The expected aspect ratio of the picture-in-picture. + */ + public float aspectRatio; + + /** + * The set of actions that are associated with this activity when in picture in picture. + */ + public List<RemoteAction> userActions = new ArrayList<>(); + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "aspectRatio=" + aspectRatio); + if (userActions.isEmpty()) { + pw.println(prefix + " userActions=[]"); + } else { + pw.println(prefix + " userActions=["); + for (int i = 0; i < userActions.size(); i++) { + RemoteAction action = userActions.get(i); + pw.print(prefix + " Action[" + i + "]: "); + action.dump("", pw); + } + pw.println(prefix + " ]"); + } + } + } + + /** * Grant Uri permissions from one app to another. This method only extends * permission grants if {@code callingUid} has permission to them. */ diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index f052bf7fb717..e34fabced517 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -88,6 +88,7 @@ import android.provider.Downloads; import android.provider.Settings; import android.security.NetworkSecurityPolicy; import android.security.net.config.NetworkSecurityConfigProvider; +import android.service.autofill.AutoFillService; import android.service.autofill.IAutoFillCallback; import android.service.voice.VoiceInteractionSession; import android.util.AndroidRuntimeException; @@ -112,9 +113,6 @@ import android.view.Window; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.renderscript.RenderScriptCacheDir; -import android.system.Os; -import android.system.OsConstants; -import android.system.ErrnoException; import android.webkit.WebView; import com.android.internal.annotations.GuardedBy; @@ -137,11 +135,9 @@ import java.io.IOException; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.net.InetAddress; import java.text.DateFormat; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; @@ -640,6 +636,7 @@ public final class ActivityThread { IBinder requestToken; int requestType; int sessionId; + int flags; } static final class ActivityConfigChangeData { @@ -651,8 +648,6 @@ public final class ActivityThread { } } - private native void dumpGraphicsInfo(FileDescriptor fd); - private class ApplicationThread extends IApplicationThread.Stub { private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s"; @@ -1193,7 +1188,7 @@ public final class ActivityThread { @Override public void dumpGfxInfo(ParcelFileDescriptor pfd, String[] args) { - dumpGraphicsInfo(pfd.getFileDescriptor()); + nDumpGraphicsInfo(pfd.getFileDescriptor()); WindowManagerGlobal.getInstance().dumpGfxInfo(pfd.getFileDescriptor(), args); IoUtils.closeQuietly(pfd); } @@ -1245,12 +1240,13 @@ public final class ActivityThread { @Override public void requestAssistContextExtras(IBinder activityToken, IBinder requestToken, - int requestType, int sessionId) { + int requestType, int sessionId, int flags) { RequestAssistContextExtras cmd = new RequestAssistContextExtras(); cmd.activityToken = activityToken; cmd.requestToken = requestToken; cmd.requestType = requestType; cmd.sessionId = sessionId; + cmd.flags = flags; sendMessage(H.REQUEST_ASSIST_CONTEXT_EXTRAS, cmd); } @@ -2883,6 +2879,16 @@ public final class ActivityThread { } public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) { + // Filling for auto-fill has a few differences: + // - it does not need an AssistContent + // - it does not call onProvideAssistData() + // - it needs an IAutoFillCallback + // - it sets the flags so views can provide autofill-specific data (such as passwords) + boolean forAutoFill = (cmd.flags + & (View.ASSIST_FLAG_SANITIZED_TEXT + | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0; + + // TODO(b/33197203): decide if lastSessionId logic applies to auto-fill sessions if (mLastSessionId != cmd.sessionId) { // Clear the existing structures mLastSessionId = cmd.sessionId; @@ -2894,46 +2900,45 @@ public final class ActivityThread { mLastAssistStructures.remove(i); } } - // Filling for auto-fill has a few differences: - // - it does not need an AssistContent - // - it does not call onProvideAssistData() - // - it needs an IAutoFillCallback - boolean forAutofill = cmd.requestType == ActivityManager.ASSIST_CONTEXT_AUTOFILL; Bundle data = new Bundle(); AssistStructure structure = null; - AssistContent content = forAutofill ? null : new AssistContent(); + AssistContent content = forAutoFill ? null : new AssistContent(); ActivityClientRecord r = mActivities.get(cmd.activityToken); Uri referrer = null; if (r != null) { - r.activity.getApplication().dispatchOnProvideAssistData(r.activity, data); - if (!forAutofill) { + if (!forAutoFill) { + r.activity.getApplication().dispatchOnProvideAssistData(r.activity, data); r.activity.onProvideAssistData(data); } referrer = r.activity.onProvideReferrer(); - if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutofill) { - structure = new AssistStructure(r.activity); + if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutoFill) { + structure = new AssistStructure(r.activity, cmd.flags); Intent activityIntent = r.activity.getIntent(); + if (cmd.flags > 0) { + data.putInt(VoiceInteractionSession.KEY_FLAGS, cmd.flags); + } + // TODO(b/33197203): re-evaluate conditions below for auto-fill. In particular, + // FLAG_SECURE might be allowed on AUTO_FILL but not on AUTO_FILL_SAVE) if (activityIntent != null && (r.window == null || (r.window.getAttributes().flags & WindowManager.LayoutParams.FLAG_SECURE) == 0)) { - Intent intent = new Intent(activityIntent); - intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)); - intent.removeUnsafeExtras(); - if (forAutofill) { + if (forAutoFill) { IAutoFillCallback autoFillCallback = r.activity.getAutoFillCallback(); - data.putBinder(VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK, - autoFillCallback.asBinder()); + data.putBinder(AutoFillService.KEY_CALLBACK, autoFillCallback.asBinder()); } else { + Intent intent = new Intent(activityIntent); + intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)); + intent.removeUnsafeExtras(); content.setDefaultIntent(intent); } } else { - if (!forAutofill) { + if (!forAutoFill) { content.setDefaultIntent(new Intent()); } } - if (!forAutofill) { + if (!forAutoFill) { r.activity.onProvideAssistContent(content); } } @@ -2941,6 +2946,7 @@ public final class ActivityThread { if (structure == null) { structure = new AssistStructure(); } + // TODO(b/33197203): decide if lastSessionId logic applies to auto-fill sessions mLastAssistStructures.add(new WeakReference<>(structure)); IActivityManager mgr = ActivityManager.getService(); try { @@ -6227,4 +6233,8 @@ public final class ActivityThread { throw new RuntimeException("Main thread loop unexpectedly exited"); } + + // ------------------ Regular JNI ------------------------ + + private native void nDumpGraphicsInfo(FileDescriptor fd); } diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 9c1778010f43..72ccf72d2d05 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -185,6 +185,11 @@ public class Dialog implements DialogInterface, Window.Callback, mWindow = w; w.setCallback(this); w.setOnWindowDismissedCallback(this); + w.setOnWindowSwipeDismissedCallback(() -> { + if (mCancelable) { + cancel(); + } + }); w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index e378f4ad5dd3..fcc6e3d1e913 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -474,6 +474,7 @@ interface IActivityManager { void enterPictureInPictureModeOnMoveToBackground(in IBinder token, boolean enterPictureInPictureOnMoveToBg); void setPictureInPictureAspectRatio(in IBinder token, float aspectRatio); + void setPictureInPictureActions(in IBinder token, in ParceledListSlice actions); void activityRelaunched(in IBinder token); IBinder getUriPermissionOwnerForActivity(in IBinder activityToken); /** @@ -575,7 +576,7 @@ interface IActivityManager { void unregisterTaskStackListener(ITaskStackListener listener); void moveStackToDisplay(int stackId, int displayId); boolean requestAutoFillData(in IResultReceiver receiver, in Bundle receiverExtras, - in IBinder activityToken); + in IBinder activityToken, int flags); void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback); // WARNING: when these transactions are updated, check if they are any callers on the native diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index 6b962b92a66d..7f168c95a688 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -134,7 +134,7 @@ oneway interface IApplicationThread { void dumpDbInfo(in ParcelFileDescriptor fd, in String[] args); void unstableProviderDied(IBinder provider); void requestAssistContextExtras(IBinder activityToken, IBinder requestToken, - int requestType, int sessionId); + int requestType, int sessionId, int flags); void scheduleTranslucentConversionComplete(IBinder token, boolean timeout); void setProcessState(int state); void scheduleInstallProvider(in ProviderInfo provider); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 927ef6c35b7a..2c4e7a0871b9 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -99,8 +99,12 @@ interface INotificationManager void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim); void setInterruptionFilter(String pkg, int interruptionFilter); - void applyAdjustmentFromAssistantService(in INotificationListener token, in Adjustment adjustment); - void applyAdjustmentsFromAssistantService(in INotificationListener token, in List<Adjustment> adjustments); + void applyAdjustmentFromAssistant(in INotificationListener token, in Adjustment adjustment); + void applyAdjustmentsFromAssistant(in INotificationListener token, in List<Adjustment> adjustments); + void createNotificationChannelFromAssistant(in INotificationListener token, String pkg, in NotificationChannel channel); + void updateNotificationChannelFromAssistant(in INotificationListener token, String pkg, in NotificationChannel channel); + void deleteNotificationChannelFromAssistant(in INotificationListener token, String pkg, String channelId); + ParceledListSlice getNotificationChannelsFromAssistant(in INotificationListener token, String pkg); ComponentName getEffectsSuppressor(); boolean matchesCallFilter(in Bundle extras); diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 94d24e49945b..db1162a3cbb3 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -51,6 +51,7 @@ import android.util.SparseArray; import android.view.Display; import android.view.DisplayAdjustments; +import dalvik.system.BaseDexClassLoader; import dalvik.system.VMRuntime; import java.io.File; @@ -600,6 +601,40 @@ public final class LoadedApk { VMRuntime.registerAppInfo(profileFile.getPath(), mApplicationInfo.dataDir, codePaths.toArray(new String[codePaths.size()]), foreignDexProfilesFile.getPath()); + + // Setup the reporter to notify package manager of any relevant dex loads. + // At this point the primary apk is loaded and will not be reported. + // Anything loaded from now on will be tracked as a potential secondary + // or foreign dex file. The goal is to enable: + // 1) monitoring and compilation of secondary dex file + // 2) track foreign dex file usage (used to determined the + // compilation filter of apks). + if (BaseDexClassLoader.getReporter() != DexLoadReporter.INSTANCE) { + // Set the dex load reporter if not already set. + // Note that during the app's life cycle different LoadedApks may be + // created and loaded (e.g. if two different apps share the same runtime). + BaseDexClassLoader.setReporter(DexLoadReporter.INSTANCE); + } + } + + private static class DexLoadReporter implements BaseDexClassLoader.Reporter { + private static final DexLoadReporter INSTANCE = new DexLoadReporter(); + + private DexLoadReporter() {} + + @Override + public void report(List<String> dexPaths) { + if (dexPaths.isEmpty()) { + return; + } + String packageName = ActivityThread.currentPackageName(); + try { + ActivityThread.getPackageManager().notifyDexLoad( + packageName, dexPaths, VMRuntime.getRuntime().vmInstructionSet()); + } catch (RemoteException re) { + Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re); + } + } } /** diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index c1e20726dff8..119b0553d054 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3985,19 +3985,6 @@ public class Notification implements Parcelable return new Builder(builderContext, n); } - private static Class<? extends Style> getNotificationStyleClass(String templateClass) { - Class<? extends Style>[] classes = new Class[] { - BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, - DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, - MessagingStyle.class }; - for (Class<? extends Style> innerClass : classes) { - if (templateClass.equals(innerClass.getName())) { - return innerClass; - } - } - return null; - } - /** * @deprecated Use {@link #build()} instead. */ @@ -4175,6 +4162,23 @@ public class Notification implements Parcelable } /** + * @hide + */ + @SystemApi + public static Class<? extends Style> getNotificationStyleClass(String templateClass) { + Class<? extends Style>[] classes = new Class[] { + BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, + DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, + MessagingStyle.class }; + for (Class<? extends Style> innerClass : classes) { + if (templateClass.equals(innerClass.getName())) { + return innerClass; + } + } + return null; + } + + /** * An object that can apply a rich notification style to a {@link Notification.Builder} * object. */ diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java index 6ee478059171..0ae85056da56 100644 --- a/core/java/android/app/QueuedWork.java +++ b/core/java/android/app/QueuedWork.java @@ -16,86 +16,197 @@ package android.app; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.util.LinkedList; /** - * Internal utility class to keep track of process-global work that's - * outstanding and hasn't been finished yet. + * Internal utility class to keep track of process-global work that's outstanding and hasn't been + * finished yet. + * + * New work will be {@link #queue queued}. * - * This was created for writing SharedPreference edits out - * asynchronously so we'd have a mechanism to wait for the writes in - * Activity.onPause and similar places, but we may use this mechanism - * for other things in the future. + * It is possible to add 'finisher'-runnables that are {@link #waitToFinish guaranteed to be run}. + * This is used to make sure the work has been finished. + * + * This was created for writing SharedPreference edits out asynchronously so we'd have a mechanism + * to wait for the writes in Activity.onPause and similar places, but we may use this mechanism for + * other things in the future. + * + * The queued asynchronous work is performed on a separate, dedicated thread. * * @hide */ public class QueuedWork { + private static final String LOG_TAG = QueuedWork.class.getSimpleName(); + + /** Delay for delayed runnables */ + private static final long DELAY = 50; - // The set of Runnables that will finish or wait on any async - // activities started by the application. - private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers = - new ConcurrentLinkedQueue<Runnable>(); + /** Lock for this class */ + private static final Object sLock = new Object(); - private static ExecutorService sSingleThreadExecutor = null; // lazy, guarded by class + /** Finishers {@link #addFinisher added} and not yet {@link #removeFinisher removed} */ + @GuardedBy("sLock") + private static final LinkedList<Runnable> sFinishers = new LinkedList<>(); + + /** {@link #getHandler() Lazily} created handler */ + @GuardedBy("sLock") + private static Handler sHandler = null; + + /** Work queued via {@link #queue} */ + @GuardedBy("sLock") + private static final LinkedList<Runnable> sWork = new LinkedList<>(); + + /** If new work can be delayed or not */ + @GuardedBy("sLock") + private static boolean sCanDelay = true; /** - * Returns a single-thread Executor shared by the entire process, - * creating it if necessary. + * Lazily create a handler on a separate thread. + * + * @return the handler */ - public static ExecutorService singleThreadExecutor() { - synchronized (QueuedWork.class) { - if (sSingleThreadExecutor == null) { - // TODO: can we give this single thread a thread name? - sSingleThreadExecutor = Executors.newSingleThreadExecutor(); + private static Handler getHandler() { + synchronized (sLock) { + if (sHandler == null) { + HandlerThread handlerThread = new HandlerThread("queued-work-looper", + Process.THREAD_PRIORITY_BACKGROUND); + handlerThread.start(); + + sHandler = new QueuedWorkHandler(handlerThread.getLooper()); } - return sSingleThreadExecutor; + return sHandler; } } /** - * Add a runnable to finish (or wait for) a deferred operation - * started in this context earlier. Typically finished by e.g. - * an Activity#onPause. Used by SharedPreferences$Editor#startCommit(). + * Add a finisher-runnable to wait for {@link #queue asynchronously processed work}. + * + * Used by SharedPreferences$Editor#startCommit(). * - * Note that this doesn't actually start it running. This is just - * a scratch set for callers doing async work to keep updated with - * what's in-flight. In the common case, caller code - * (e.g. SharedPreferences) will pretty quickly call remove() - * after an add(). The only time these Runnables are run is from - * waitToFinish(), below. + * Note that this doesn't actually start it running. This is just a scratch set for callers + * doing async work to keep updated with what's in-flight. In the common case, caller code + * (e.g. SharedPreferences) will pretty quickly call remove() after an add(). The only time + * these Runnables are run is from {@link #waitToFinish}. + * + * @param finisher The runnable to add as finisher */ - public static void add(Runnable finisher) { - sPendingWorkFinishers.add(finisher); + public static void addFinisher(Runnable finisher) { + synchronized (sLock) { + sFinishers.add(finisher); + } } - public static void remove(Runnable finisher) { - sPendingWorkFinishers.remove(finisher); + /** + * Remove a previously {@link #addFinisher added} finisher-runnable. + * + * @param finisher The runnable to remove. + */ + public static void removeFinisher(Runnable finisher) { + synchronized (sLock) { + sFinishers.remove(finisher); + } } /** - * Finishes or waits for async operations to complete. - * (e.g. SharedPreferences$Editor#startCommit writes) + * Trigger queued work to be processed immediately. The queued work is processed on a separate + * thread asynchronous. While doing that run and process all finishers on this thread. The + * finishers can be implemented in a way to check weather the queued work is finished. * - * Is called from the Activity base class's onPause(), after - * BroadcastReceiver's onReceive, after Service command handling, - * etc. (so async work is never lost) + * Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive, + * after Service command handling, etc. (so async work is never lost) */ public static void waitToFinish() { - Runnable toFinish; - while ((toFinish = sPendingWorkFinishers.poll()) != null) { - toFinish.run(); + Handler handler = getHandler(); + + synchronized (sLock) { + if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) { + // Force the delayed work to be processed now + handler.removeMessages(QueuedWorkHandler.MSG_RUN); + handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN); + } + + // We should not delay any work as this might delay the finishers + sCanDelay = false; + } + + try { + while (true) { + Runnable finisher; + + synchronized (sLock) { + finisher = sFinishers.poll(); + } + + if (finisher == null) { + break; + } + + finisher.run(); + } + } finally { + sCanDelay = true; + } + } + + /** + * Queue a work-runnable for processing asynchronously. + * + * @param work The new runnable to process + * @param shouldDelay If the message should be delayed + */ + public static void queue(Runnable work, boolean shouldDelay) { + Handler handler = getHandler(); + + synchronized (sLock) { + sWork.add(work); + + if (shouldDelay && sCanDelay) { + handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY); + } else { + handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN); + } } } - + /** - * Returns true if there is pending work to be done. Note that the - * result is out of data as soon as you receive it, so be careful how you - * use it. + * @return True iff there is any {@link #queue async work queued}. */ public static boolean hasPendingWork() { - return !sPendingWorkFinishers.isEmpty(); + synchronized (sLock) { + return !sWork.isEmpty(); + } + } + + private static class QueuedWorkHandler extends Handler { + static final int MSG_RUN = 1; + + QueuedWorkHandler(Looper looper) { + super(looper); + } + + public void handleMessage(Message msg) { + if (msg.what == MSG_RUN) { + LinkedList<Runnable> work; + + synchronized (sWork) { + work = (LinkedList<Runnable>) sWork.clone(); + sWork.clear(); + + // Remove all msg-s as all work will be processed now + removeMessages(MSG_RUN); + } + + work.forEach(Runnable::run); + } + } } - } diff --git a/core/java/android/app/RemoteAction.aidl b/core/java/android/app/RemoteAction.aidl new file mode 100644 index 000000000000..2f9f1cce7389 --- /dev/null +++ b/core/java/android/app/RemoteAction.aidl @@ -0,0 +1,19 @@ +/** + * 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.app; + +parcelable RemoteAction; diff --git a/core/java/android/app/RemoteAction.java b/core/java/android/app/RemoteAction.java new file mode 100644 index 000000000000..a37680f7a8f8 --- /dev/null +++ b/core/java/android/app/RemoteAction.java @@ -0,0 +1,159 @@ +/* + * 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.app; + +import android.annotation.NonNull; +import android.graphics.drawable.Icon; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; + +import java.io.PrintWriter; + +/** + * Represents a remote action that can be called from another process. The action can have an + * associated visualization including metadata like an icon or title. + */ +public final class RemoteAction implements Parcelable { + + /** + * Interface definition for a callback to be invoked when an action is invoked. + */ + public interface OnActionListener { + /** + * Called when the associated action is invoked. + * + * @param action The action that was invoked. + */ + void onAction(RemoteAction action); + } + + private static final String TAG = "RemoteAction"; + + private static final int MESSAGE_ACTION_INVOKED = 1; + + private final Icon mIcon; + private final CharSequence mTitle; + private final CharSequence mContentDescription; + private OnActionListener mActionCallback; + private final Messenger mMessenger; + + RemoteAction(Parcel in) { + mIcon = Icon.CREATOR.createFromParcel(in); + mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mMessenger = in.readParcelable(Messenger.class.getClassLoader()); + } + + public RemoteAction(@NonNull Icon icon, @NonNull CharSequence title, + @NonNull CharSequence contentDescription, @NonNull OnActionListener callback) { + if (icon == null || title == null || contentDescription == null || callback == null) { + throw new IllegalArgumentException("Expected icon, title, content description and " + + "action callback"); + } + mIcon = icon; + mTitle = title; + mContentDescription = contentDescription; + mActionCallback = callback; + mMessenger = new Messenger(new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_ACTION_INVOKED: + mActionCallback.onAction(RemoteAction.this); + break; + } + } + }); + } + + /** + * Return an icon representing the action. + */ + public @NonNull Icon getIcon() { + return mIcon; + } + + /** + * Return an title representing the action. + */ + public @NonNull CharSequence getTitle() { + return mTitle; + } + + /** + * Return a content description representing the action. + */ + public @NonNull CharSequence getContentDescription() { + return mContentDescription; + } + + /** + * Sends a message that the action was invoked. + * @hide + */ + public void sendActionInvoked() { + Message m = Message.obtain(); + m.what = MESSAGE_ACTION_INVOKED; + try { + mMessenger.send(m); + } catch (RemoteException e) { + Log.e(TAG, "Could not send action-invoked", e); + } + } + + @Override + public RemoteAction clone() { + return new RemoteAction(mIcon, mTitle, mContentDescription, mActionCallback); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + mIcon.writeToParcel(out, 0); + TextUtils.writeToParcel(mTitle, out, flags); + TextUtils.writeToParcel(mContentDescription, out, flags); + out.writeParcelable(mMessenger, flags); + } + + public void dump(String prefix, PrintWriter pw) { + pw.print(prefix); + pw.print("title=" + mTitle); + pw.print(" contentDescription=" + mContentDescription); + pw.print(" icon=" + mIcon); + pw.println(); + } + + public static final Parcelable.Creator<RemoteAction> CREATOR = + new Parcelable.Creator<RemoteAction>() { + public RemoteAction createFromParcel(Parcel in) { + return new RemoteAction(in); + } + public RemoteAction[] newArray(int size) { + return new RemoteAction[size]; + } + }; +}
\ No newline at end of file diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java index c5a8288b500f..023b4f38ff1c 100644 --- a/core/java/android/app/SharedPreferencesImpl.java +++ b/core/java/android/app/SharedPreferencesImpl.java @@ -54,23 +54,34 @@ import libcore.io.IoUtils; final class SharedPreferencesImpl implements SharedPreferences { private static final String TAG = "SharedPreferencesImpl"; private static final boolean DEBUG = false; + private static final Object CONTENT = new Object(); // Lock ordering rules: - // - acquire SharedPreferencesImpl.this before EditorImpl.this - // - acquire mWritingToDiskLock before EditorImpl.this + // - acquire SharedPreferencesImpl.mLock before EditorImpl.mLock + // - acquire mWritingToDiskLock before EditorImpl.mLock private final File mFile; private final File mBackupFile; private final int mMode; + private final Object mLock = new Object(); + private final Object mWritingToDiskLock = new Object(); - private Map<String, Object> mMap; // guarded by 'this' - private int mDiskWritesInFlight = 0; // guarded by 'this' - private boolean mLoaded = false; // guarded by 'this' - private long mStatTimestamp; // guarded by 'this' - private long mStatSize; // guarded by 'this' + @GuardedBy("mLock") + private Map<String, Object> mMap; - private final Object mWritingToDiskLock = new Object(); - private static final Object mContent = new Object(); + @GuardedBy("mLock") + private int mDiskWritesInFlight = 0; + + @GuardedBy("mLock") + private boolean mLoaded = false; + + @GuardedBy("mLock") + private long mStatTimestamp; + + @GuardedBy("mLock") + private long mStatSize; + + @GuardedBy("mLock") private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners = new WeakHashMap<OnSharedPreferenceChangeListener, Object>(); @@ -92,7 +103,7 @@ final class SharedPreferencesImpl implements SharedPreferences { } private void startLoadFromDisk() { - synchronized (this) { + synchronized (mLock) { mLoaded = false; } new Thread("SharedPreferencesImpl-load") { @@ -103,7 +114,7 @@ final class SharedPreferencesImpl implements SharedPreferences { } private void loadFromDisk() { - synchronized (SharedPreferencesImpl.this) { + synchronized (mLock) { if (mLoaded) { return; } @@ -138,7 +149,7 @@ final class SharedPreferencesImpl implements SharedPreferences { /* ignore */ } - synchronized (SharedPreferencesImpl.this) { + synchronized (mLock) { mLoaded = true; if (map != null) { mMap = map; @@ -147,7 +158,7 @@ final class SharedPreferencesImpl implements SharedPreferences { } else { mMap = new HashMap<>(); } - notifyAll(); + mLock.notifyAll(); } } @@ -156,7 +167,7 @@ final class SharedPreferencesImpl implements SharedPreferences { } void startReloadIfChangedUnexpectedly() { - synchronized (this) { + synchronized (mLock) { // TODO: wait for any pending writes to disk? if (!hasFileChangedUnexpectedly()) { return; @@ -168,7 +179,7 @@ final class SharedPreferencesImpl implements SharedPreferences { // Has the file changed out from under us? i.e. writes that // we didn't instigate. private boolean hasFileChangedUnexpectedly() { - synchronized (this) { + synchronized (mLock) { if (mDiskWritesInFlight > 0) { // If we know we caused it, it's not unexpected. if (DEBUG) Log.d(TAG, "disk write in flight, not unexpected."); @@ -188,19 +199,19 @@ final class SharedPreferencesImpl implements SharedPreferences { return true; } - synchronized (this) { + synchronized (mLock) { return mStatTimestamp != stat.st_mtime || mStatSize != stat.st_size; } } public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { - synchronized(this) { - mListeners.put(listener, mContent); + synchronized(mLock) { + mListeners.put(listener, CONTENT); } } public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { - synchronized(this) { + synchronized(mLock) { mListeners.remove(listener); } } @@ -214,14 +225,14 @@ final class SharedPreferencesImpl implements SharedPreferences { } while (!mLoaded) { try { - wait(); + mLock.wait(); } catch (InterruptedException unused) { } } } public Map<String, ?> getAll() { - synchronized (this) { + synchronized (mLock) { awaitLoadedLocked(); //noinspection unchecked return new HashMap<String, Object>(mMap); @@ -230,7 +241,7 @@ final class SharedPreferencesImpl implements SharedPreferences { @Nullable public String getString(String key, @Nullable String defValue) { - synchronized (this) { + synchronized (mLock) { awaitLoadedLocked(); String v = (String)mMap.get(key); return v != null ? v : defValue; @@ -239,7 +250,7 @@ final class SharedPreferencesImpl implements SharedPreferences { @Nullable public Set<String> getStringSet(String key, @Nullable Set<String> defValues) { - synchronized (this) { + synchronized (mLock) { awaitLoadedLocked(); Set<String> v = (Set<String>) mMap.get(key); return v != null ? v : defValues; @@ -247,28 +258,28 @@ final class SharedPreferencesImpl implements SharedPreferences { } public int getInt(String key, int defValue) { - synchronized (this) { + synchronized (mLock) { awaitLoadedLocked(); Integer v = (Integer)mMap.get(key); return v != null ? v : defValue; } } public long getLong(String key, long defValue) { - synchronized (this) { + synchronized (mLock) { awaitLoadedLocked(); Long v = (Long)mMap.get(key); return v != null ? v : defValue; } } public float getFloat(String key, float defValue) { - synchronized (this) { + synchronized (mLock) { awaitLoadedLocked(); Float v = (Float)mMap.get(key); return v != null ? v : defValue; } } public boolean getBoolean(String key, boolean defValue) { - synchronized (this) { + synchronized (mLock) { awaitLoadedLocked(); Boolean v = (Boolean)mMap.get(key); return v != null ? v : defValue; @@ -276,7 +287,7 @@ final class SharedPreferencesImpl implements SharedPreferences { } public boolean contains(String key) { - synchronized (this) { + synchronized (mLock) { awaitLoadedLocked(); return mMap.containsKey(key); } @@ -290,7 +301,7 @@ final class SharedPreferencesImpl implements SharedPreferences { // context.getSharedPreferences(..).edit().putString(..).apply() // // ... all without blocking. - synchronized (this) { + synchronized (mLock) { awaitLoadedLocked(); } @@ -299,70 +310,86 @@ final class SharedPreferencesImpl implements SharedPreferences { // Return value from EditorImpl#commitToMemory() private static class MemoryCommitResult { - public long memoryStateGeneration; - public List<String> keysModified; // may be null - public Set<OnSharedPreferenceChangeListener> listeners; // may be null - public Map<?, ?> mapToWriteToDisk; - public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1); - public volatile boolean writeToDiskResult = false; - - public void setDiskWriteResult(boolean result) { + final long memoryStateGeneration; + @Nullable final List<String> keysModified; + @Nullable final Set<OnSharedPreferenceChangeListener> listeners; + final Map<String, Object> mapToWriteToDisk; + final CountDownLatch writtenToDiskLatch = new CountDownLatch(1); + + @GuardedBy("mWritingToDiskLock") + volatile boolean writeToDiskResult = false; + + private MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified, + @Nullable Set<OnSharedPreferenceChangeListener> listeners, + Map<String, Object> mapToWriteToDisk) { + this.memoryStateGeneration = memoryStateGeneration; + this.keysModified = keysModified; + this.listeners = listeners; + this.mapToWriteToDisk = mapToWriteToDisk; + } + + void setDiskWriteResult(boolean result) { writeToDiskResult = result; writtenToDiskLatch.countDown(); } } public final class EditorImpl implements Editor { + private final Object mLock = new Object(); + + @GuardedBy("mLock") private final Map<String, Object> mModified = Maps.newHashMap(); + + @GuardedBy("mLock") private boolean mClear = false; public Editor putString(String key, @Nullable String value) { - synchronized (this) { + synchronized (mLock) { mModified.put(key, value); return this; } } public Editor putStringSet(String key, @Nullable Set<String> values) { - synchronized (this) { + synchronized (mLock) { mModified.put(key, (values == null) ? null : new HashSet<String>(values)); return this; } } public Editor putInt(String key, int value) { - synchronized (this) { + synchronized (mLock) { mModified.put(key, value); return this; } } public Editor putLong(String key, long value) { - synchronized (this) { + synchronized (mLock) { mModified.put(key, value); return this; } } public Editor putFloat(String key, float value) { - synchronized (this) { + synchronized (mLock) { mModified.put(key, value); return this; } } public Editor putBoolean(String key, boolean value) { - synchronized (this) { + synchronized (mLock) { mModified.put(key, value); return this; } } public Editor remove(String key) { - synchronized (this) { + synchronized (mLock) { mModified.put(key, this); return this; } } public Editor clear() { - synchronized (this) { + synchronized (mLock) { mClear = true; return this; } @@ -379,12 +406,12 @@ final class SharedPreferencesImpl implements SharedPreferences { } }; - QueuedWork.add(awaitCommit); + QueuedWork.addFinisher(awaitCommit); Runnable postWriteRunnable = new Runnable() { public void run() { awaitCommit.run(); - QueuedWork.remove(awaitCommit); + QueuedWork.removeFinisher(awaitCommit); } }; @@ -399,8 +426,12 @@ final class SharedPreferencesImpl implements SharedPreferences { // Returns true if any changes were made private MemoryCommitResult commitToMemory() { - MemoryCommitResult mcr = new MemoryCommitResult(); - synchronized (SharedPreferencesImpl.this) { + long memoryStateGeneration; + List<String> keysModified = null; + Set<OnSharedPreferenceChangeListener> listeners = null; + Map<String, Object> mapToWriteToDisk; + + synchronized (SharedPreferencesImpl.this.mLock) { // We optimistically don't make a deep copy until // a memory commit comes in when we're already // writing to disk. @@ -411,17 +442,16 @@ final class SharedPreferencesImpl implements SharedPreferences { // noinspection unchecked mMap = new HashMap<String, Object>(mMap); } - mcr.mapToWriteToDisk = mMap; + mapToWriteToDisk = mMap; mDiskWritesInFlight++; boolean hasListeners = mListeners.size() > 0; if (hasListeners) { - mcr.keysModified = new ArrayList<String>(); - mcr.listeners = - new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); + keysModified = new ArrayList<String>(); + listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); } - synchronized (this) { + synchronized (mLock) { boolean changesMade = false; if (mClear) { @@ -455,7 +485,7 @@ final class SharedPreferencesImpl implements SharedPreferences { changesMade = true; if (hasListeners) { - mcr.keysModified.add(k); + keysModified.add(k); } } @@ -465,10 +495,11 @@ final class SharedPreferencesImpl implements SharedPreferences { mCurrentMemoryStateGeneration++; } - mcr.memoryStateGeneration = mCurrentMemoryStateGeneration; + memoryStateGeneration = mCurrentMemoryStateGeneration; } } - return mcr; + return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners, + mapToWriteToDisk); } public boolean commit() { @@ -534,7 +565,7 @@ final class SharedPreferencesImpl implements SharedPreferences { synchronized (mWritingToDiskLock) { writeToFile(mcr, isFromSyncCommit); } - synchronized (SharedPreferencesImpl.this) { + synchronized (mLock) { mDiskWritesInFlight--; } if (postWriteRunnable != null) { @@ -547,7 +578,7 @@ final class SharedPreferencesImpl implements SharedPreferences { // the current thread. if (isFromSyncCommit) { boolean wasEmpty = false; - synchronized (SharedPreferencesImpl.this) { + synchronized (mLock) { wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { @@ -557,10 +588,10 @@ final class SharedPreferencesImpl implements SharedPreferences { } if (DEBUG) { - Log.d(TAG, "added " + mcr.memoryStateGeneration + " -> " + mFile.getName()); + Log.d(TAG, "queued " + mcr.memoryStateGeneration + " -> " + mFile.getName()); } - QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable); + QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); } private static FileOutputStream createFileOutputStream(File file) { @@ -592,17 +623,17 @@ final class SharedPreferencesImpl implements SharedPreferences { if (mFile.exists()) { boolean needsWrite = false; - if (isFromSyncCommit) { - // Only need to write if the disk state is older than this commit - if (mDiskStateGeneration < mcr.memoryStateGeneration) { + // Only need to write if the disk state is older than this commit + if (mDiskStateGeneration < mcr.memoryStateGeneration) { + if (isFromSyncCommit) { needsWrite = true; - } - } else { - synchronized (this) { - // No need to persist intermediate states. Just wait for the latest state to be - // persisted. - if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) { - needsWrite = true; + } else { + synchronized (mLock) { + // No need to persist intermediate states. Just wait for the latest state to + // be persisted. + if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) { + needsWrite = true; + } } } } @@ -647,7 +678,7 @@ final class SharedPreferencesImpl implements SharedPreferences { ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0); try { final StructStat stat = Os.stat(mFile.getPath()); - synchronized (this) { + synchronized (mLock) { mStatTimestamp = stat.st_mtime; mStatSize = stat.st_size; } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 67ce3425feea..6bddfba163d0 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -96,6 +96,7 @@ import android.os.IHardwarePropertiesManager; import android.os.IPowerManager; import android.os.IRecoverySystem; import android.os.IUserManager; +import android.os.IncidentManager; import android.os.PowerManager; import android.os.Process; import android.os.RecoverySystem; @@ -115,6 +116,7 @@ import android.telecom.TelecomManager; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.TextClassificationManager; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; @@ -221,6 +223,13 @@ final class SystemServiceRegistry { return new HdmiControlManager(IHdmiControlService.Stub.asInterface(b)); }}); + registerService(Context.TEXT_CLASSIFICATION_SERVICE, TextClassificationManager.class, + new StaticServiceFetcher<TextClassificationManager>() { + @Override + public TextClassificationManager createService() { + return new TextClassificationManager(); + }}); + registerService(Context.CLIPBOARD_SERVICE, ClipboardManager.class, new CachedServiceFetcher<ClipboardManager>() { @Override @@ -766,6 +775,13 @@ final class SystemServiceRegistry { return new ContextHubManager(ctx.getOuterContext(), ctx.mMainThread.getHandler().getLooper()); }}); + + registerService(Context.INCIDENT_SERVICE, IncidentManager.class, + new CachedServiceFetcher<IncidentManager>() { + @Override + public IncidentManager createService(ContextImpl ctx) throws ServiceNotFoundException { + return new IncidentManager(ctx); + }}); } /** diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 39f415e6ad8f..617288428916 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -44,6 +44,7 @@ import android.graphics.Bitmap; import android.net.ProxyInfo; import android.net.Uri; import android.os.Bundle; +import android.os.Parcelable; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteCallback; @@ -152,6 +153,7 @@ public class DevicePolicyManager { * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li> * <li>{@link #EXTRA_PROVISIONING_SKIP_USER_CONSENT}, optional</li> * <li>{@link #EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DISCLAIMERS}, optional</li> * </ul> * * <p>When managed provisioning has completed, broadcasts are sent to the application specified @@ -233,6 +235,7 @@ public class DevicePolicyManager { * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li> * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li> * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li> + * <li>{@link #EXTRA_PROVISIONING_DISCLAIMERS}, optional</li> * </ul> * * <p>When device owner provisioning has completed, an intent of the type @@ -382,7 +385,7 @@ public class DevicePolicyManager { "com.android.server.action.BUGREPORT_SHARING_DECLINED"; /** - * Action: Bugreport has been collected and is dispatched to {@link DevicePolicyManagerService}. + * Action: Bugreport has been collected and is dispatched to {@code DevicePolicyManagerService}. * * @hide */ @@ -804,7 +807,7 @@ public class DevicePolicyManager { * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li> * </ul> * - * <p> It is the responsability of the caller to provide an image with a reasonable + * <p> It is the responsibility of the caller to provide an image with a reasonable * pixed density for the device. * * <p> If a content: URI is passed, the intent should have the flag @@ -818,6 +821,58 @@ public class DevicePolicyManager { "android.app.extra.PROVISIONING_LOGO_URI"; /** + * A {@link Bundle}[] extra consisting of list of disclaimer headers and disclaimer contents. + * Each {@link Bundle} must have both {@link #EXTRA_PROVISIONING_DISCLAIMER_HEADER} + * as disclaimer header, and {@link #EXTRA_PROVISIONING_DISCLAIMER_CONTENT} as disclaimer + * content. + * + * <p> The extra typically contains one disclaimer from the company of mobile device + * management application (MDM), and one disclaimer from the organization. + * + * <p> Call {@link Bundle#putParcelableArray(String, Parcelable[])} to put the {@link Bundle}[] + * + * <p> Maximum 3 key-value pairs can be specified. The rest will be ignored. + * + * <p> Use in an intent with action {@link #ACTION_PROVISION_MANAGED_PROFILE} or + * {@link #ACTION_PROVISION_MANAGED_DEVICE} + */ + public static final String EXTRA_PROVISIONING_DISCLAIMERS = + "android.app.extra.PROVISIONING_DISCLAIMERS"; + + /** + * A String extra of localized disclaimer header. + * + * <p> The extra is typically the company name of mobile device management application (MDM) + * or the organization name. + * + * <p> Use in Bundle {@link #EXTRA_PROVISIONING_DISCLAIMERS} + */ + public static final String EXTRA_PROVISIONING_DISCLAIMER_HEADER = + "android.app.extra.PROVISIONING_DISCLAIMER_HEADER"; + + /** + * A {@link Uri} extra pointing to disclaimer content. + * + * <h5>The following URI schemes are accepted:</h5> + * <ul> + * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li> + * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li> + * </ul> + * + * <p> Styled text is supported in the disclaimer content. The content is parsed by + * {@link android.text.Html#fromHtml(String)} and displayed in a + * {@link android.widget.TextView}. + * + * <p> If a <code>content:</code> URI is passed, URI is passed, the intent should have the flag + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and the uri should be added to the + * {@link android.content.ClipData} of the intent too. + * + * <p> Use in Bundle {@link #EXTRA_PROVISIONING_DISCLAIMERS} + */ + public static final String EXTRA_PROVISIONING_DISCLAIMER_CONTENT = + "android.app.extra.PROVISIONING_DISCLAIMER_CONTENT"; + + /** * A boolean extra indicating if user setup should be skipped, for when provisioning is started * during setup-wizard. * @@ -1005,6 +1060,15 @@ public class DevicePolicyManager { = "android.app.action.SET_NEW_PARENT_PROFILE_PASSWORD"; /** + * Broadcast action: Tell the status bar to open the device monitoring dialog, e.g. when + * Network logging was enabled and the user tapped the notification. + * <p class="note">This is a protected intent that can only be sent by the system.</p> + * @hide + */ + public static final String ACTION_SHOW_DEVICE_MONITORING_DIALOG + = "android.app.action.SHOW_DEVICE_MONITORING_DIALOG"; + + /** * Flag used by {@link #addCrossProfileIntentFilter} to allow activities in * the parent profile to access intents sent from the managed profile. * That is, when an app in the managed profile calls @@ -1110,7 +1174,7 @@ public class DevicePolicyManager { public @interface UserProvisioningState {} /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_PROFILE}, {@link #ACTION_PROVISION_MANAGED_USER} and @@ -1121,7 +1185,7 @@ public class DevicePolicyManager { public static final int CODE_OK = 0; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the device already has a device @@ -1132,7 +1196,7 @@ public class DevicePolicyManager { public static final int CODE_HAS_DEVICE_OWNER = 1; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the user has a profile owner and for @@ -1143,7 +1207,7 @@ public class DevicePolicyManager { public static final int CODE_USER_HAS_PROFILE_OWNER = 2; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the user isn't running. @@ -1153,7 +1217,7 @@ public class DevicePolicyManager { public static final int CODE_USER_NOT_RUNNING = 3; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} if the device has already been setup and @@ -1178,7 +1242,7 @@ public class DevicePolicyManager { public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} if the user is not a system user. @@ -1188,7 +1252,7 @@ public class DevicePolicyManager { public static final int CODE_NOT_SYSTEM_USER = 7; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} and {@link #ACTION_PROVISION_MANAGED_USER} @@ -1199,7 +1263,7 @@ public class DevicePolicyManager { public static final int CODE_HAS_PAIRED = 8; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} and * {@link #ACTION_PROVISION_MANAGED_USER} on devices which do not support managed users. @@ -1210,7 +1274,7 @@ public class DevicePolicyManager { public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_USER} if the user is a system user. * @@ -1219,7 +1283,7 @@ public class DevicePolicyManager { public static final int CODE_SYSTEM_USER = 10; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the user cannot have more * managed profiles. @@ -1229,7 +1293,7 @@ public class DevicePolicyManager { public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_USER} and * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} on devices not running with split system @@ -1240,7 +1304,7 @@ public class DevicePolicyManager { public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_PROFILE}, {@link #ACTION_PROVISION_MANAGED_USER} and @@ -1252,17 +1316,27 @@ public class DevicePolicyManager { public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * - * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the device has a device owner - * and the user is a system user on a split system user device. + * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the device the user is a + * system user on a split system user device. * * @hide */ public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; /** - * Result codes for {@link checkProvisioningPreCondition} indicating all the provisioning pre + * Result code for {@link #checkProvisioningPreCondition}. + * + * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when adding a managed profile is + * disallowed by {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE}. + * + * @hide + */ + public static final int CODE_ADD_MANAGED_PROFILE_DISALLOWED = 15; + + /** + * Result codes for {@link #checkProvisioningPreCondition} indicating all the provisioning pre * conditions. * * @hide @@ -1272,7 +1346,7 @@ public class DevicePolicyManager { CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED, CODE_MANAGED_USERS_NOT_SUPPORTED, CODE_SYSTEM_USER, CODE_CANNOT_ADD_MANAGED_PROFILE, CODE_NOT_SYSTEM_USER_SPLIT, CODE_DEVICE_ADMIN_NOT_SUPPORTED, - CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER}) + CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER, CODE_ADD_MANAGED_PROFILE_DISALLOWED}) public @interface ProvisioningPreCondition {} /** @@ -2239,7 +2313,7 @@ public class DevicePolicyManager { * Determine whether the current password the user has set is sufficient to meet the policy * requirements (e.g. quality, minimum length) that have been requested by the admins of this * user and its participating profiles. Restrictions on profiles that have a separate challenge - * are not taken into account. + * are not taken into account. The user must be unlocked in order to perform the check. * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has @@ -2252,6 +2326,7 @@ public class DevicePolicyManager { * @return Returns true if the password meets the current requirements, else false. * @throws SecurityException if the calling application does not own an active administrator * that uses {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} + * @throws InvalidStateException if the user is not unlocked. */ public boolean isActivePasswordSufficient() { if (mService != null) { @@ -3762,6 +3837,19 @@ public class DevicePolicyManager { /** * @hide */ + public void reportPasswordChanged(@UserIdInt int userId) { + if (mService != null) { + try { + mService.reportPasswordChanged(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * @hide + */ public void reportFailedPasswordAttempt(int userHandle) { if (mService != null) { try { @@ -6129,34 +6217,40 @@ public class DevicePolicyManager { } /** - * Returns if provisioning a managed profile or device is possible or not. + * Returns whether it is possible for the caller to initiate provisioning of a managed profile + * or device, setting itself as the device or profile owner. + * * @param action One of {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_PROFILE}. - * @return if provisioning a managed profile or device is possible or not. + * @return whether provisioning a managed profile or device is possible. * @throws IllegalArgumentException if the supplied action is not valid. */ - public boolean isProvisioningAllowed(String action) { + public boolean isProvisioningAllowed(@NonNull String action) { throwIfParentInstance("isProvisioningAllowed"); try { - return mService.isProvisioningAllowed(action); + return mService.isProvisioningAllowed(action, mContext.getPackageName()); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** - * Checks if provisioning a managed profile or device is possible and returns one of the - * {@link ProvisioningPreCondition}. + * Checks whether it is possible to initiate provisioning a managed device, + * profile or user, setting the given package as owner. * * @param action One of {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_PROFILE}, * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_USER} + * @param packageName The package of the component that would be set as device, user, or profile + * owner. + * @return A {@link ProvisioningPreCondition} value indicating whether provisioning is allowed. * @hide */ - public @ProvisioningPreCondition int checkProvisioningPreCondition(String action) { + public @ProvisioningPreCondition int checkProvisioningPreCondition( + String action, @NonNull String packageName) { try { - return mService.checkProvisioningPreCondition(action); + return mService.checkProvisioningPreCondition(action, packageName); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -6827,10 +6921,13 @@ public class DevicePolicyManager { } /** + * Returns whether the device has been provisioned. + * + * <p>Not for use by third-party applications. + * * @hide - * @return whether {@link android.provider.Settings.Global#DEVICE_PROVISIONED} has ever been set - * to 1. */ + @SystemApi public boolean isDeviceProvisioned() { try { return mService.isDeviceProvisioned(); @@ -6840,9 +6937,16 @@ public class DevicePolicyManager { } /** - * @hide - * Writes that the provisioning configuration has been applied. - */ + * Writes that the provisioning configuration has been applied. + * + * <p>The caller must hold the {@link android.Manifest.permission#MANAGE_USERS} + * permission. + * + * <p>Not for use by third-party applications. + * + * @hide + */ + @SystemApi public void setDeviceProvisioningConfigApplied() { try { mService.setDeviceProvisioningConfigApplied(); @@ -6852,9 +6956,17 @@ public class DevicePolicyManager { } /** - * @hide + * Returns whether the provisioning configuration has been applied. + * + * <p>The caller must hold the {@link android.Manifest.permission#MANAGE_USERS} permission. + * + * <p>Not for use by third-party applications. + * * @return whether the provisioning configuration has been applied. + * + * @hide */ + @SystemApi public boolean isDeviceProvisioningConfigApplied() { try { return mService.isDeviceProvisioningConfigApplied(); @@ -6867,8 +6979,8 @@ public class DevicePolicyManager { * @hide * Force update user setup completed status. This API has no effect on user build. * @throws {@link SecurityException} if the caller has no - * {@link android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS} or the caller is - * not {@link UserHandle.SYSTEM_USER} + * {@code android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS} or the caller is + * not {@link UserHandle#SYSTEM_USER} */ public void forceUpdateUserSetupComplete() { try { @@ -7040,7 +7152,7 @@ public class DevicePolicyManager { * <li>The managed profile is a profile of the user where the device owner is set. * See {@link UserManager#getUserProfiles()} * <li>Both users are affiliated. - * STOPSHIP(b/32326223) Add reference to setAffiliationIds here once public. + * See {@link #setAffiliationIds}. * </ul> */ public @NonNull List<UserHandle> getBindDeviceAdminTargetUsers(@NonNull ComponentName admin) { diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 3e2282550d4d..9be694e8a319 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -123,6 +123,7 @@ interface IDevicePolicyManager { boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle); void setActivePasswordState(in PasswordMetrics metrics, int userHandle); + void reportPasswordChanged(int userId); void reportFailedPasswordAttempt(int userHandle); void reportSuccessfulPasswordAttempt(int userHandle); void reportFailedFingerprintAttempt(int userHandle); @@ -269,8 +270,8 @@ interface IDevicePolicyManager { boolean setPermissionGrantState(in ComponentName admin, String packageName, String permission, int grantState); int getPermissionGrantState(in ComponentName admin, String packageName, String permission); - boolean isProvisioningAllowed(String action); - int checkProvisioningPreCondition(String action); + boolean isProvisioningAllowed(String action, String packageName); + int checkProvisioningPreCondition(String action, String packageName); void setKeepUninstalledPackages(in ComponentName admin,in List<String> packageList); List<String> getKeepUninstalledPackages(in ComponentName admin); boolean isManagedProfile(in ComponentName admin); diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index a6f23666cc6d..1988e4265fb0 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -400,7 +400,7 @@ public class AssistStructure implements Parcelable { final int mDisplayId; final ViewNode mRoot; - WindowNode(AssistStructure assist, ViewRootImpl root) { + WindowNode(AssistStructure assist, ViewRootImpl root, int flags) { View view = root.getView(); Rect rect = new Rect(); view.getBoundsOnScreen(rect); @@ -415,11 +415,22 @@ public class AssistStructure implements Parcelable { if ((root.getWindowFlags()& WindowManager.LayoutParams.FLAG_SECURE) != 0) { // This is a secure window, so it doesn't want a screenshot, and that // means we should also not copy out its view hierarchy. - view.onProvideStructure(builder); + + // Must explicitly set which method to calls since View subclasses might + // have implemented the deprecated method. + if (flags == 0) { + view.onProvideStructure(builder); + } else { + view.onProvideStructure(builder, flags); + } builder.setAssistBlocked(true); return; } - view.dispatchProvideStructure(builder); + if (flags == 0) { + view.dispatchProvideStructure(builder); + } else { + view.dispatchProvideStructure(builder, flags); + } } WindowNode(ParcelTransferReader reader) { @@ -1351,14 +1362,14 @@ public class AssistStructure implements Parcelable { } /** @hide */ - public AssistStructure(Activity activity) { + public AssistStructure(Activity activity, int flags) { mHaveData = true; mActivityComponent = activity.getComponentName(); ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews( activity.getActivityToken()); for (int i=0; i<views.size(); i++) { ViewRootImpl root = views.get(i); - mWindowNodes.add(new WindowNode(this, root)); + mWindowNodes.add(new WindowNode(this, root, flags)); } } diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl index 342c2858ab0f..31b235977a04 100644 --- a/core/java/android/app/usage/IUsageStatsManager.aidl +++ b/core/java/android/app/usage/IUsageStatsManager.aidl @@ -34,4 +34,6 @@ interface IUsageStatsManager { boolean isAppInactive(String packageName, int userId); void whitelistAppTemporarily(String packageName, long duration, int userId); void onCarrierPrivilegedAppsChanged(); + void reportChooserSelection(String packageName, int userId, String contentType, + in String[] annotations, String action); } diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index a0da258bdf1c..ce8b05abf365 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -86,6 +86,12 @@ public final class UsageEvents implements Parcelable { public static final int SHORTCUT_INVOCATION = 8; /** + * An event type denoting that a package was selected by the user for ChooserActivity. + * @hide + */ + public static final int CHOOSER_ACTION = 9; + + /** * {@hide} */ public String mPackage; @@ -119,6 +125,27 @@ public final class UsageEvents implements Parcelable { public String mShortcutId; /** + * Action type passed to ChooserActivity + * Only present for {@link #CHOOSER_ACTION} event types. + * {@hide} + */ + public String mAction; + + /** + * Content type passed to ChooserActivity. + * Only present for {@link #CHOOSER_ACTION} event types. + * {@hide} + */ + public String mContentType; + + /** + * Content annotations passed to ChooserActivity. + * Only present for {@link #CHOOSER_ACTION} event types. + * {@hide} + */ + public String[] mContentAnnotations; + + /** * The package name of the source of this event. */ public String getPackageName() { @@ -307,6 +334,11 @@ public final class UsageEvents implements Parcelable { case Event.SHORTCUT_INVOCATION: p.writeString(event.mShortcutId); break; + case Event.CHOOSER_ACTION: + p.writeString(event.mAction); + p.writeString(event.mContentType); + p.writeStringArray(event.mContentAnnotations); + break; } } @@ -333,6 +365,9 @@ public final class UsageEvents implements Parcelable { // Fill out the event-dependant fields. eventOut.mConfiguration = null; eventOut.mShortcutId = null; + eventOut.mAction = null; + eventOut.mContentType = null; + eventOut.mContentAnnotations = null; switch (eventOut.mEventType) { case Event.CONFIGURATION_CHANGE: @@ -342,6 +377,11 @@ public final class UsageEvents implements Parcelable { case Event.SHORTCUT_INVOCATION: eventOut.mShortcutId = p.readString(); break; + case Event.CHOOSER_ACTION: + eventOut.mAction = p.readString(); + eventOut.mContentType = p.readString(); + eventOut.mContentAnnotations = p.createStringArray(); + break; } } diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java index 2937ccc879a5..57f18f1a0fc0 100644 --- a/core/java/android/app/usage/UsageStats.java +++ b/core/java/android/app/usage/UsageStats.java @@ -16,8 +16,10 @@ package android.app.usage; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.util.ArrayMap; /** * Contains usage statistics for an app package for a specific @@ -64,6 +66,11 @@ public final class UsageStats implements Parcelable { /** * {@hide} */ + public ArrayMap<String, ArrayMap<String, Integer>> mChooserCounts; + + /** + * {@hide} + */ public UsageStats() { } @@ -75,6 +82,7 @@ public final class UsageStats implements Parcelable { mTotalTimeInForeground = stats.mTotalTimeInForeground; mLaunchCount = stats.mLaunchCount; mLastEvent = stats.mLastEvent; + mChooserCounts = stats.mChooserCounts; } public String getPackageName() { @@ -142,6 +150,26 @@ public final class UsageStats implements Parcelable { mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp); mTotalTimeInForeground += right.mTotalTimeInForeground; mLaunchCount += right.mLaunchCount; + if (mChooserCounts == null) { + mChooserCounts = right.mChooserCounts; + } else if (right.mChooserCounts != null) { + final int chooserCountsSize = right.mChooserCounts.size(); + for (int i = 0; i < chooserCountsSize; i++) { + String action = right.mChooserCounts.keyAt(i); + ArrayMap<String, Integer> counts = right.mChooserCounts.valueAt(i); + if (!mChooserCounts.containsKey(action) || mChooserCounts.get(action) == null) { + mChooserCounts.put(action, counts); + continue; + } + final int annotationSize = counts.size(); + for (int j = 0; j < annotationSize; j++) { + String key = counts.keyAt(j); + int rightValue = counts.valueAt(j); + int leftValue = mChooserCounts.get(action).getOrDefault(key, 0); + mChooserCounts.get(action).put(key, leftValue + rightValue); + } + } + } } @Override @@ -158,6 +186,21 @@ public final class UsageStats implements Parcelable { dest.writeLong(mTotalTimeInForeground); dest.writeInt(mLaunchCount); dest.writeInt(mLastEvent); + Bundle allCounts = new Bundle(); + if (mChooserCounts != null) { + final int chooserCountSize = mChooserCounts.size(); + for (int i = 0; i < chooserCountSize; i++) { + String action = mChooserCounts.keyAt(i); + ArrayMap<String, Integer> counts = mChooserCounts.valueAt(i); + Bundle currentCounts = new Bundle(); + final int annotationSize = counts.size(); + for (int j = 0; j < annotationSize; j++) { + currentCounts.putInt(counts.keyAt(j), counts.valueAt(j)); + } + allCounts.putBundle(action, currentCounts); + } + } + dest.writeBundle(allCounts); } public static final Creator<UsageStats> CREATOR = new Creator<UsageStats>() { @@ -171,6 +214,25 @@ public final class UsageStats implements Parcelable { stats.mTotalTimeInForeground = in.readLong(); stats.mLaunchCount = in.readInt(); stats.mLastEvent = in.readInt(); + Bundle allCounts = in.readBundle(); + if (allCounts != null) { + stats.mChooserCounts = new ArrayMap<>(); + for (String action : allCounts.keySet()) { + if (!stats.mChooserCounts.containsKey(action)) { + ArrayMap<String, Integer> newCounts = new ArrayMap<>(); + stats.mChooserCounts.put(action, newCounts); + } + Bundle currentCounts = allCounts.getBundle(action); + if (currentCounts != null) { + for (String key : currentCounts.keySet()) { + int value = currentCounts.getInt(key); + if (value > 0) { + stats.mChooserCounts.get(action).put(key, value); + } + } + } + } + } return stats; } diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 2aeecfabd48c..75a4a535186e 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -278,4 +278,23 @@ public final class UsageStatsManager { } catch (RemoteException re) { } } + + /** + * Reports a Chooser action to the UsageStatsManager. + * + * @param packageName The package name of the app that is selected. + * @param userId The user id of who makes the selection. + * @param contentType The type of the content, e.g., Image, Video, App. + * @param annotations The annotations of the content, e.g., Game, Selfie. + * @param action The action type of Intent that invokes ChooserActivity. + * {@link UsageEvents} + * @hide + */ + public void reportChooserSelection(String packageName, int userId, String contentType, + String[] annotations, String action) { + try { + mService.reportChooserSelection(packageName, userId, contentType, annotations, action); + } catch (RemoteException re) { + } + } } diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index b0e27a4ab546..6c1e2a932700 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -201,6 +201,23 @@ public final class BluetoothAdapter { public static final int STATE_BLE_TURNING_OFF = 16; /** + * Human-readable string helper for AdapterState + * @hide + */ + public static String nameForState(@AdapterState int state) { + switch(state) { + case STATE_OFF: return "OFF"; + case STATE_TURNING_ON: return "TURNING_ON"; + case STATE_ON: return "ON"; + case STATE_TURNING_OFF: return "TURNING_OFF"; + case STATE_BLE_TURNING_ON: return "BLE_TURNING_ON"; + case STATE_BLE_ON: return "BLE_ON"; + case STATE_BLE_TURNING_OFF: return "BLE_TURNING_OFF"; + default: return "?!?!? (" + state + ")"; + } + } + + /** * Activity Action: Show a system activity that requests discoverable mode. * This activity will also request the user to turn on Bluetooth if it * is not currently enabled. @@ -658,15 +675,8 @@ public final class BluetoothAdapter { @SystemApi public boolean isLeEnabled() { final int state = getLeState(); - if (state == BluetoothAdapter.STATE_ON) { - if (DBG) Log.d (TAG, "STATE_ON"); - } else if (state == BluetoothAdapter.STATE_BLE_ON) { - if (DBG) Log.d (TAG, "STATE_BLE_ON"); - } else { - if (DBG) Log.d (TAG, "STATE_OFF"); - return false; - } - return true; + if (DBG) Log.d(TAG, "isLeEnabled(): " + BluetoothAdapter.nameForState(state)); + return (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_BLE_ON); } /** @@ -831,10 +841,10 @@ public final class BluetoothAdapter { if (state == BluetoothAdapter.STATE_BLE_ON || state == BluetoothAdapter.STATE_BLE_TURNING_ON || state == BluetoothAdapter.STATE_BLE_TURNING_OFF) { - if (VDBG) Log.d(TAG, "Consider internal state as OFF"); + if (VDBG) Log.d(TAG, "Consider " + BluetoothAdapter.nameForState(state) + " state as OFF"); state = BluetoothAdapter.STATE_OFF; } - if (VDBG) Log.d(TAG, "" + hashCode() + ": getState(). Returning " + state); + if (VDBG) Log.d(TAG, "" + hashCode() + ": getState(). Returning " + BluetoothAdapter.nameForState(state)); return state; } @@ -871,12 +881,12 @@ public final class BluetoothAdapter { mServiceLock.readLock().unlock(); } - if (VDBG) Log.d(TAG,"getLeState() returning " + state); + if (VDBG) Log.d(TAG,"getLeState() returning " + BluetoothAdapter.nameForState(state)); return state; } boolean getLeAccess() { - if(getLeState() == STATE_ON) + if (getLeState() == STATE_ON) return true; else if (getLeState() == STATE_BLE_ON) @@ -914,8 +924,8 @@ public final class BluetoothAdapter { */ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) public boolean enable() { - if (isEnabled() == true) { - if (DBG) Log.d(TAG, "enable(): BT is already enabled..!"); + if (isEnabled()) { + if (DBG) Log.d(TAG, "enable(): BT already enabled!"); return true; } try { @@ -1518,8 +1528,9 @@ public final class BluetoothAdapter { } } } - } catch (RemoteException e) {Log.e(TAG, "getSupportedProfiles:", e);} - + } catch (RemoteException e) { + Log.e(TAG, "getSupportedProfiles:", e); + } return supportedProfiles; } @@ -1870,34 +1881,6 @@ public final class BluetoothAdapter { * @hide */ public Pair<byte[], byte[]> readOutOfBandData() { - if (getState() != STATE_ON) return null; - //TODO(BT - /* - try { - byte[] hash; - byte[] randomizer; - - byte[] ret = null; - mServiceLock.readLock().lock(); - if (mService != null) mService.readOutOfBandData(); - - if (ret == null || ret.length != 32) return null; - - hash = Arrays.copyOfRange(ret, 0, 16); - randomizer = Arrays.copyOfRange(ret, 16, 32); - - if (DBG) { - Log.d(TAG, "readOutOfBandData:" + Arrays.toString(hash) + - ":" + Arrays.toString(randomizer)); - } - return new Pair<byte[], byte[]>(hash, randomizer); - - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - */ return null; } @@ -2051,7 +2034,7 @@ public final class BluetoothAdapter { if (cb != null) { cb.onBluetoothServiceUp(bluetoothService); } else { - Log.d(TAG, "onBluetoothServiceUp: cb is null!!!"); + Log.d(TAG, "onBluetoothServiceUp: cb is null!"); } } catch (Exception e) { Log.e(TAG,"",e); @@ -2079,7 +2062,7 @@ public final class BluetoothAdapter { if (cb != null) { cb.onBluetoothServiceDown(); } else { - Log.d(TAG, "onBluetoothServiceDown: cb is null!!!"); + Log.d(TAG, "onBluetoothServiceDown: cb is null!"); } } catch (Exception e) { Log.e(TAG,"",e); @@ -2089,7 +2072,7 @@ public final class BluetoothAdapter { } public void onBrEdrDown() { - if (DBG) Log.i(TAG, "onBrEdrDown:"); + if (VDBG) Log.i(TAG, "onBrEdrDown: " + mService); } }; @@ -2100,7 +2083,7 @@ public final class BluetoothAdapter { */ public boolean enableNoAutoConnect() { if (isEnabled() == true){ - if (DBG) Log.d(TAG, "enableNoAutoConnect(): BT is already enabled..!"); + if (DBG) Log.d(TAG, "enableNoAutoConnect(): BT already enabled!"); return true; } try { @@ -2140,22 +2123,6 @@ public final class BluetoothAdapter { */ public boolean changeApplicationBluetoothState(boolean on, BluetoothStateChangeCallback callback) { - if (callback == null) return false; - - //TODO(BT) - /* - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.changeApplicationBluetoothState(on, new - StateChangeCallbackWrapper(callback), new Binder()); - } - } catch (RemoteException e) { - Log.e(TAG, "changeBluetoothState", e); - } finally { - mServiceLock.readLock().unlock(); - } - */ return false; } diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index 231dacefa5a1..c3d6606089cb 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -27,189 +27,25 @@ import android.util.Log; import android.util.Slog; /** - * Base class for code that will receive intents sent by sendBroadcast(). - * - * <p>If you don't need to send broadcasts across applications, consider using - * this class with {@link android.support.v4.content.LocalBroadcastManager} instead - * of the more general facilities described below. This will give you a much - * more efficient implementation (no cross-process communication needed) and allow - * you to avoid thinking about any security issues related to other applications - * being able to receive or send your broadcasts. + * Base class for code that receives and handles broadcast intents sent by + * {@link android.content.Context#sendBroadcast(Intent)}. * * <p>You can either dynamically register an instance of this class with * {@link Context#registerReceiver Context.registerReceiver()} - * or statically publish an implementation through the + * or statically declare an implementation with the * {@link android.R.styleable#AndroidManifestReceiver <receiver>} * tag in your <code>AndroidManifest.xml</code>. - * - * <p><em><strong>Note:</strong></em> - * If registering a receiver in your - * {@link android.app.Activity#onResume() Activity.onResume()} - * implementation, you should unregister it in - * {@link android.app.Activity#onPause() Activity.onPause()}. - * (You won't receive intents when paused, - * and this will cut down on unnecessary system overhead). Do not unregister in - * {@link android.app.Activity#onSaveInstanceState(android.os.Bundle) Activity.onSaveInstanceState()}, - * because this won't be called if the user moves back in the history - * stack. - * - * <p>There are two major classes of broadcasts that can be received:</p> - * <ul> - * <li> <b>Normal broadcasts</b> (sent with {@link Context#sendBroadcast(Intent) - * Context.sendBroadcast}) are completely asynchronous. All receivers of the - * broadcast are run in an undefined order, often at the same time. This is - * more efficient, but means that receivers cannot use the result or abort - * APIs included here. - * <li> <b>Ordered broadcasts</b> (sent with {@link Context#sendOrderedBroadcast(Intent, String) - * Context.sendOrderedBroadcast}) are delivered to one receiver at a time. - * As each receiver executes in turn, it can propagate a result to the next - * receiver, or it can completely abort the broadcast so that it won't be passed - * to other receivers. The order receivers run in can be controlled with the - * {@link android.R.styleable#AndroidManifestIntentFilter_priority - * android:priority} attribute of the matching intent-filter; receivers with - * the same priority will be run in an arbitrary order. - * </ul> - * - * <p>Even in the case of normal broadcasts, the system may in some - * situations revert to delivering the broadcast one receiver at a time. In - * particular, for receivers that may require the creation of a process, only - * one will be run at a time to avoid overloading the system with new processes. - * In this situation, however, the non-ordered semantics hold: these receivers still - * cannot return results or abort their broadcast.</p> - * - * <p>Note that, although the Intent class is used for sending and receiving - * these broadcasts, the Intent broadcast mechanism here is completely separate - * from Intents that are used to start Activities with - * {@link Context#startActivity Context.startActivity()}. - * There is no way for a BroadcastReceiver - * to see or capture Intents used with startActivity(); likewise, when - * you broadcast an Intent, you will never find or start an Activity. - * These two operations are semantically very different: starting an - * Activity with an Intent is a foreground operation that modifies what the - * user is currently interacting with; broadcasting an Intent is a background - * operation that the user is not normally aware of. - * - * <p>The BroadcastReceiver class (when launched as a component through - * a manifest's {@link android.R.styleable#AndroidManifestReceiver <receiver>} - * tag) is an important part of an - * <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">application's overall lifecycle</a>.</p> - * - * <p>Topics covered here: - * <ol> - * <li><a href="#Security">Security</a> - * <li><a href="#ReceiverLifecycle">Receiver Lifecycle</a> - * <li><a href="#ProcessLifecycle">Process Lifecycle</a> - * </ol> * * <div class="special reference"> * <h3>Developer Guides</h3> - * <p>For information about how to use this class to receive and resolve intents, read the - * <a href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Filters</a> - * developer guide.</p> - * </div> - * - * <a name="Security"></a> - * <h3>Security</h3> - * - * <p>Receivers used with the {@link Context} APIs are by their nature a - * cross-application facility, so you must consider how other applications - * may be able to abuse your use of them. Some things to consider are: - * - * <ul> - * <li><p>The Intent namespace is global. Make sure that Intent action names and - * other strings are written in a namespace you own, or else you may inadvertently - * conflict with other applications. - * <li><p>When you use {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)}, - * <em>any</em> application may send broadcasts to that registered receiver. You can - * control who can send broadcasts to it through permissions described below. - * <li><p>When you publish a receiver in your application's manifest and specify - * intent-filters for it, any other application can send broadcasts to it regardless - * of the filters you specify. To prevent others from sending to it, make it - * unavailable to them with <code>android:exported="false"</code>. - * <li><p>When you use {@link Context#sendBroadcast(Intent)} or related methods, - * normally any other application can receive these broadcasts. You can control who - * can receive such broadcasts through permissions described below. Alternatively, - * starting with {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, you - * can also safely restrict the broadcast to a single application with - * {@link Intent#setPackage(String) Intent.setPackage} - * </ul> + * <p>For more information about using BroadcastReceiver, read the + * <a href="{@docRoot}guide/components/broadcasts.html">Broadcasts</a> developer guide.</p></div> * - * <p>None of these issues exist when using - * {@link android.support.v4.content.LocalBroadcastManager}, since intents - * broadcast it never go outside of the current process. - * - * <p>Access permissions can be enforced by either the sender or receiver - * of a broadcast. - * - * <p>To enforce a permission when sending, you supply a non-null - * <var>permission</var> argument to - * {@link Context#sendBroadcast(Intent, String)} or - * {@link Context#sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler, int, String, Bundle)}. - * Only receivers who have been granted this permission - * (by requesting it with the - * {@link android.R.styleable#AndroidManifestUsesPermission <uses-permission>} - * tag in their <code>AndroidManifest.xml</code>) will be able to receive - * the broadcast. - * - * <p>To enforce a permission when receiving, you supply a non-null - * <var>permission</var> when registering your receiver -- either when calling - * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter, String, android.os.Handler)} - * or in the static - * {@link android.R.styleable#AndroidManifestReceiver <receiver>} - * tag in your <code>AndroidManifest.xml</code>. Only broadcasters who have - * been granted this permission (by requesting it with the - * {@link android.R.styleable#AndroidManifestUsesPermission <uses-permission>} - * tag in their <code>AndroidManifest.xml</code>) will be able to send an - * Intent to the receiver. - * - * <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a> - * document for more information on permissions and security in general. - * - * <a name="ReceiverLifecycle"></a> - * <h3>Receiver Lifecycle</h3> - * - * <p>A BroadcastReceiver object is only valid for the duration of the call - * to {@link #onReceive}. Once your code returns from this function, - * the system considers the object to be finished and no longer active. - * - * <p>This has important repercussions to what you can do in an - * {@link #onReceive} implementation: anything that requires asynchronous - * operation is not available, because you will need to return from the - * function to handle the asynchronous operation, but at that point the - * BroadcastReceiver is no longer active and thus the system is free to kill - * its process before the asynchronous operation completes. - * - * <p>In particular, you may <i>not</i> show a dialog or bind to a service from - * within a BroadcastReceiver. For the former, you should instead use the - * {@link android.app.NotificationManager} API. For the latter, you can - * use {@link android.content.Context#startService Context.startService()} to - * send a command to the service. - * - * <a name="ProcessLifecycle"></a> - * <h3>Process Lifecycle</h3> - * - * <p>A process that is currently executing a BroadcastReceiver (that is, - * currently running the code in its {@link #onReceive} method) is - * considered to be a foreground process and will be kept running by the - * system except under cases of extreme memory pressure. - * - * <p>Once you return from onReceive(), the BroadcastReceiver is no longer - * active, and its hosting process is only as important as any other application - * components that are running in it. This is especially important because if - * that process was only hosting the BroadcastReceiver (a common case for - * applications that the user has never or not recently interacted with), then - * upon returning from onReceive() the system will consider its process - * to be empty and aggressively kill it so that resources are available for other - * more important processes. - * - * <p>This means that for longer-running operations you will often use - * a {@link android.app.Service} in conjunction with a BroadcastReceiver to keep - * the containing process active for the entire time of your operation. */ public abstract class BroadcastReceiver { private PendingResult mPendingResult; private boolean mDebugUnregister; - + /** * State for a result that is pending for a broadcast receiver. Returned * by {@link BroadcastReceiver#goAsync() goAsync()} @@ -218,7 +54,7 @@ public abstract class BroadcastReceiver { * terminate; you must call {@link #finish()} once you are done with the * broadcast. This allows you to process the broadcast off of the main * thread of your app. - * + * * <p>Note on threading: the state inside of this class is not itself * thread-safe, however you can use it from any thread if you properly * sure that you do not have races. Typically this means you will hand @@ -232,14 +68,14 @@ public abstract class BroadcastReceiver { public static final int TYPE_REGISTERED = 1; /** @hide */ public static final int TYPE_UNREGISTERED = 2; - + final int mType; final boolean mOrderedHint; final boolean mInitialStickyHint; final IBinder mToken; final int mSendingUser; final int mFlags; - + int mResultCode; String mResultData; Bundle mResultExtras; @@ -259,7 +95,7 @@ public abstract class BroadcastReceiver { mSendingUser = userId; mFlags = flags; } - + /** * Version of {@link BroadcastReceiver#setResultCode(int) * BroadcastReceiver.setResultCode(int)} for @@ -331,7 +167,7 @@ public abstract class BroadcastReceiver { mResultData = data; mResultExtras = extras; } - + /** * Version of {@link BroadcastReceiver#getAbortBroadcast() * BroadcastReceiver.getAbortBroadcast()} for @@ -350,7 +186,7 @@ public abstract class BroadcastReceiver { checkSynchronousHint(); mAbortBroadcast = true; } - + /** * Version of {@link BroadcastReceiver#clearAbortBroadcast() * BroadcastReceiver.clearAbortBroadcast()} for @@ -359,7 +195,7 @@ public abstract class BroadcastReceiver { public final void clearAbortBroadcast() { mAbortBroadcast = false; } - + /** * Finish the broadcast. The current result will be sent and the * next broadcast will proceed. @@ -375,16 +211,16 @@ public abstract class BroadcastReceiver { // of the list to finish the broadcast, so we don't block this // thread (which may be the main thread) to have it finished. // - // Note that we don't need to use QueuedWork.add() with the + // Note that we don't need to use QueuedWork.addFinisher() with the // runnable, since we know the AM is waiting for us until the // executor gets to it. - QueuedWork.singleThreadExecutor().execute( new Runnable() { + QueuedWork.queue(new Runnable() { @Override public void run() { if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, "Finishing broadcast after work to component " + mToken); sendFinished(mgr); } - }); + }, false); } else { if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, "Finishing broadcast to component " + mToken); @@ -397,14 +233,14 @@ public abstract class BroadcastReceiver { sendFinished(mgr); } } - + /** @hide */ public void setExtrasClassLoader(ClassLoader cl) { if (mResultExtras != null) { mResultExtras.setClassLoader(cl); } } - + /** @hide */ public void sendFinished(IActivityManager am) { synchronized (this) { @@ -412,7 +248,7 @@ public abstract class BroadcastReceiver { throw new IllegalStateException("Broadcast already finished"); } mFinished = true; - + try { if (mResultExtras != null) { mResultExtras.setAllowFds(false); @@ -448,7 +284,7 @@ public abstract class BroadcastReceiver { Log.e("BroadcastReceiver", e.getMessage(), e); } } - + public BroadcastReceiver() { } @@ -468,14 +304,15 @@ public abstract class BroadcastReceiver { * * <p><b>If this BroadcastReceiver was launched through a <receiver> tag, * then the object is no longer alive after returning from this - * function.</b> This means you should not perform any operations that - * return a result to you asynchronously -- in particular, for interacting - * with services, you should use - * {@link Context#startService(Intent)} instead of - * {@link Context#bindService(Intent, ServiceConnection, int)}. If you wish - * to interact with a service that is already running, you can use - * {@link #peekService}. - * + * function.</b> This means you should not perform any operations that + * return a result to you asynchronously. If you need to perform any follow up + * background work, schedule a {@link android.app.job.JobService} with + * {@link android.app.job.JobScheduler}. + * + * If you wish to interact with a service that is already running and previously + * bound using {@link android.content.Context#bindService(Intent, ServiceConnection, int) bindService()}, + * you can use {@link #peekService}. + * * <p>The Intent filters used in {@link android.content.Context#registerReceiver} * and in application manifests are <em>not</em> guaranteed to be exclusive. They * are hints to the operating system about how to find suitable recipients. It is @@ -483,7 +320,7 @@ public abstract class BroadcastReceiver { * resolution. For this reason, {@link #onReceive(Context, Intent) onReceive()} * implementations should respond only to known actions, ignoring any unexpected * Intents that they may receive. - * + * * @param context The Context in which the receiver is running. * @param intent The Intent being received. */ @@ -496,7 +333,7 @@ public abstract class BroadcastReceiver { * responsive to the broadcast (finishing it within 10s), but does allow * the implementation to move work related to it over to another thread * to avoid glitching the main UI thread due to disk IO. - * + * * @return Returns a {@link PendingResult} representing the result of * the active broadcast. The BroadcastRecord itself is no longer active; * all data and other interaction must go through {@link PendingResult} @@ -508,15 +345,20 @@ public abstract class BroadcastReceiver { mPendingResult = null; return res; } - + /** - * Provide a binder to an already-running service. This method is synchronous + * Provide a binder to an already-bound service. This method is synchronous * and will not start the target service if it is not present, so it is safe * to call from {@link #onReceive}. - * + * + * For peekService() to return a non null {@link android.os.IBinder} interface + * the service must have published it before. In other words some component + * must have called {@link android.content.Context#bindService(Intent, ServiceConnection, int)} on it. + * * @param myContext The Context that had been passed to {@link #onReceive(Context, Intent)} - * @param service The Intent indicating the service you wish to use. See {@link - * Context#startService(Intent)} for more information. + * @param service Identifies the already-bound service you wish to use. See + * {@link android.content.Context#bindService(Intent, ServiceConnection, int)} + * for more information. */ public IBinder peekService(Context myContext, Intent service) { IActivityManager am = ActivityManager.getService(); @@ -538,13 +380,13 @@ public abstract class BroadcastReceiver { * Activity {@link android.app.Activity#RESULT_CANCELED} and * {@link android.app.Activity#RESULT_OK} constants, though the * actual meaning of this value is ultimately up to the broadcaster. - * + * * <p class="note">This method does not work with non-ordered broadcasts such * as those sent with {@link Context#sendBroadcast(Intent) * Context.sendBroadcast}</p> - * + * * @param code The new result code. - * + * * @see #setResult(int, String, Bundle) */ public final void setResultCode(int code) { @@ -554,7 +396,7 @@ public abstract class BroadcastReceiver { /** * Retrieve the current result code, as set by the previous receiver. - * + * * @return int The current result code. */ public final int getResultCode() { @@ -567,13 +409,13 @@ public abstract class BroadcastReceiver { * {@link Context#sendOrderedBroadcast(Intent, String) * Context.sendOrderedBroadcast}. This is an arbitrary * string whose interpretation is up to the broadcaster. - * + * * <p><strong>This method does not work with non-ordered broadcasts such * as those sent with {@link Context#sendBroadcast(Intent) * Context.sendBroadcast}</strong></p> - * + * * @param data The new result data; may be null. - * + * * @see #setResult(int, String, Bundle) */ public final void setResultData(String data) { @@ -584,7 +426,7 @@ public abstract class BroadcastReceiver { /** * Retrieve the current result data, as set by the previous receiver. * Often this is null. - * + * * @return String The current result data; may be null. */ public final String getResultData() { @@ -599,13 +441,13 @@ public abstract class BroadcastReceiver { * holding arbitrary data, whose interpretation is up to the * broadcaster. Can be set to null. Calling this method completely * replaces the current map (if any). - * + * * <p><strong>This method does not work with non-ordered broadcasts such * as those sent with {@link Context#sendBroadcast(Intent) * Context.sendBroadcast}</strong></p> - * + * * @param extras The new extra data map; may be null. - * + * * @see #setResult(int, String, Bundle) */ public final void setResultExtras(Bundle extras) { @@ -617,11 +459,11 @@ public abstract class BroadcastReceiver { * Retrieve the current result extra data, as set by the previous receiver. * Any changes you make to the returned Map will be propagated to the next * receiver. - * + * * @param makeMap If true then a new empty Map will be made for you if the * current Map is null; if false you should be prepared to * receive a null Map. - * + * * @return Map The current extras map. */ public final Bundle getResultExtras(boolean makeMap) { @@ -640,11 +482,11 @@ public abstract class BroadcastReceiver { * {@link Context#sendOrderedBroadcast(Intent, String) * Context.sendOrderedBroadcast}. All current result data is replaced * by the value given to this method. - * + * * <p><strong>This method does not work with non-ordered broadcasts such * as those sent with {@link Context#sendBroadcast(Intent) * Context.sendBroadcast}</strong></p> - * + * * @param code The new result code. Often uses the * Activity {@link android.app.Activity#RESULT_CANCELED} and * {@link android.app.Activity#RESULT_OK} constants, though the @@ -662,11 +504,11 @@ public abstract class BroadcastReceiver { mPendingResult.mResultData = data; mPendingResult.mResultExtras = extras; } - + /** * Returns the flag indicating whether or not this receiver should * abort the current broadcast. - * + * * @return True if the broadcast should be aborted. */ public final boolean getAbortBroadcast() { @@ -679,10 +521,10 @@ public abstract class BroadcastReceiver { * {@link Context#sendOrderedBroadcast(Intent, String) * Context.sendOrderedBroadcast}. This will prevent * any other broadcast receivers from receiving the broadcast. It will still - * call {@link #onReceive} of the BroadcastReceiver that the caller of + * call {@link #onReceive} of the BroadcastReceiver that the caller of * {@link Context#sendOrderedBroadcast(Intent, String) * Context.sendOrderedBroadcast} passed in. - * + * * <p><strong>This method does not work with non-ordered broadcasts such * as those sent with {@link Context#sendBroadcast(Intent) * Context.sendBroadcast}</strong></p> @@ -691,7 +533,7 @@ public abstract class BroadcastReceiver { checkSynchronousHint(); mPendingResult.mAbortBroadcast = true; } - + /** * Clears the flag indicating that this receiver should abort the current * broadcast. @@ -701,7 +543,7 @@ public abstract class BroadcastReceiver { mPendingResult.mAbortBroadcast = false; } } - + /** * Returns true if the receiver is currently processing an ordered * broadcast. @@ -709,7 +551,7 @@ public abstract class BroadcastReceiver { public final boolean isOrderedBroadcast() { return mPendingResult != null ? mPendingResult.mOrderedHint : false; } - + /** * Returns true if the receiver is currently processing the initial * value of a sticky broadcast -- that is, the value that was last @@ -719,7 +561,7 @@ public abstract class BroadcastReceiver { public final boolean isInitialStickyBroadcast() { return mPendingResult != null ? mPendingResult.mInitialStickyHint : false; } - + /** * For internal use, sets the hint about whether this BroadcastReceiver is * running in ordered mode. @@ -727,21 +569,21 @@ public abstract class BroadcastReceiver { public final void setOrderedHint(boolean isOrdered) { // Accidentally left in the SDK. } - + /** * For internal use to set the result data that is active. @hide */ public final void setPendingResult(PendingResult result) { mPendingResult = result; } - + /** * For internal use to set the result data that is active. @hide */ public final PendingResult getPendingResult() { return mPendingResult; } - + /** @hide */ public int getSendingUserId() { return mPendingResult.mSendingUser; @@ -761,19 +603,19 @@ public abstract class BroadcastReceiver { public final void setDebugUnregister(boolean debug) { mDebugUnregister = debug; } - + /** * Return the last value given to {@link #setDebugUnregister}. */ public final boolean getDebugUnregister() { return mDebugUnregister; } - + void checkSynchronousHint() { if (mPendingResult == null) { throw new IllegalStateException("Call while result is not pending"); } - + // Note that we don't assert when receiving the initial sticky value, // since that may have come from an ordered broadcast. We'll catch // them later when the real broadcast happens again. diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 5fa427514ebe..f0f1d99b9f96 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2499,9 +2499,8 @@ public abstract class Context { * for high frequency calls. * </p> * - * @param service Identifies the service to be started. The Intent must be either - * fully explicit (supplying a component name) or specify a specific package - * name it is targetted to. Additional values + * @param service Identifies the service to be started. The Intent must be + * fully explicit (supplying a component name). Additional values * may be included in the Intent extras to supply arguments along with * this specific start call. * @@ -2579,10 +2578,8 @@ public abstract class Context { * {@link #registerReceiver}, since the lifetime of this BroadcastReceiver * is tied to another object (the one that registered it).</p> * - * @param service Identifies the service to connect to. The Intent may - * specify either an explicit component name, or a logical - * description (action, category, etc) to match an - * {@link IntentFilter} published by a service. + * @param service Identifies the service to connect to. The Intent must + * specify an explicit component name. * @param conn Receives information as the service is started and stopped. * This must be a valid ServiceConnection object; it must not be null. * @param flags Operation options for the binding. May be 0, @@ -2747,7 +2744,8 @@ public abstract class Context { //@hide: SOUND_TRIGGER_SERVICE, SHORTCUT_SERVICE, //@hide: CONTEXTHUB_SERVICE, - SYSTEM_HEALTH_SERVICE + SYSTEM_HEALTH_SERVICE, + //@hide: INCIDENT_SERVICE }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -3309,6 +3307,15 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a + * {@link android.text.TextClassificationManager} for text classification services. + * + * @see #getSystemService + * @see android.text.TextClassificationManager + */ + public static final String TEXT_CLASSIFICATION_SERVICE = "textclassification"; + + /** + * Use with {@link #getSystemService} to retrieve a * {@link android.view.inputmethod.InputMethodManager} for accessing input * methods. * @@ -3696,6 +3703,12 @@ public abstract class Context { public static final String DEVICE_IDENTIFIERS_SERVICE = "device_identifiers"; /** + * Service to report a system health "incident" + * @hide + */ + public static final String INCIDENT_SERVICE = "incident"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index cda81760c52b..c7984b3d7f9d 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1131,6 +1131,12 @@ public class Intent implements Parcelable, Cloneable { * for compatibility with old applications. If you don't set a ClipData, * it will be copied there for you when calling {@link Context#startActivity(Intent)}. * <p> + * Starting from {@link android.os.Build.VERSION_CODES#O}, if + * {@link #CATEGORY_TYPED_OPENABLE} is passed, then the Uris passed in + * either {@link #EXTRA_STREAM} or via {@link #setClipData(ClipData)} may + * be openable only as asset typed files using + * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}. + * <p> * Optional standard extras, which may be interpreted by some recipients as * appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC}, * {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}. @@ -1169,6 +1175,12 @@ public class Intent implements Parcelable, Cloneable { * for compatibility with old applications. If you don't set a ClipData, * it will be copied there for you when calling {@link Context#startActivity(Intent)}. * <p> + * Starting from {@link android.os.Build.VERSION_CODES#O}, if + * {@link #CATEGORY_TYPED_OPENABLE} is passed, then the Uris passed in + * either {@link #EXTRA_STREAM} or via {@link #setClipData(ClipData)} may + * be openable only as asset typed files using + * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}. + * <p> * Optional standard extras, which may be interpreted by some recipients as * appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC}, * {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}. @@ -3226,18 +3238,54 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_DYNAMIC_SENSOR_CHANGED = "android.intent.action.DYNAMIC_SENSOR_CHANGED"; - /** {@hide} */ + /** + * Deprecated - use {@link #ACTION_FACTORY_RESET} instead. + * + * {@hide} + */ + @Deprecated public static final String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR"; /** - * Boolean intent extra to be used with {@link ACTION_MASTER_CLEAR} in order to force a factory - * reset even if {@link android.os.UserManager.DISALLOW_FACTORY_RESET} is set. + * Boolean intent extra to be used with {@link #ACTION_MASTER_CLEAR} in order to force a factory + * reset even if {@link android.os.UserManager#DISALLOW_FACTORY_RESET} is set. + * + * <p>Deprecated - use {@link #EXTRA_FORCE_FACTORY_RESET} instead. + * * @hide */ + @Deprecated public static final String EXTRA_FORCE_MASTER_CLEAR = "android.intent.extra.FORCE_MASTER_CLEAR"; /** + * A broadcast action to trigger a factory reset. + * + * <p> The sender must hold the {@link android.Manifest.permission#MASTER_CLEAR} permission. + * + * <p>Not for use by third-party applications. + * + * @see #EXTRA_FORCE_MASTER_CLEAR + * + * {@hide} + */ + @SystemApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_FACTORY_RESET = "android.intent.action.FACTORY_RESET"; + + /** + * Boolean intent extra to be used with {@link #ACTION_MASTER_CLEAR} in order to force a factory + * reset even if {@link android.os.UserManager#DISALLOW_FACTORY_RESET} is set. + * + * <p>Not for use by third-party applications. + * + * @hide + */ + @SystemApi + public static final String EXTRA_FORCE_FACTORY_RESET = + "android.intent.extra.FORCE_FACTORY_RESET"; + + /** * Broadcast action: report that a settings element is being restored from backup. The intent * contains three extras: EXTRA_SETTING_NAME is a string naming the restored setting, * EXTRA_SETTING_NEW_VALUE is the value being restored, and EXTRA_SETTING_PREVIOUS_VALUE diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index 2c97ec42591f..7036f87b98f9 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -531,7 +531,7 @@ public class IntentFilter implements Parcelable { * @hide */ public final boolean getAutoVerify() { - return ((mVerifyState & STATE_VERIFY_AUTO) == 1); + return ((mVerifyState & STATE_VERIFY_AUTO) == STATE_VERIFY_AUTO); } /** diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index d753a6eaa1c6..b9b61dba8623 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -458,6 +458,15 @@ interface IPackageManager { void notifyPackageUse(String packageName, int reason); /** + * Notify the package manager that a list of dex files have been loaded. + * + * @param loadingPackageName the name of the package who performs the load + * @param dexPats the list of the dex files paths that have been loaded + * @param loaderIsa the ISA of the loader process + */ + void notifyDexLoad(String loadingPackageName, in List<String> dexPaths, String loaderIsa); + + /** * Ask the package manager to perform dex-opt (if needed) on the given * package if it already hasn't done so. * diff --git a/core/java/android/content/pm/IPinItemRequest.aidl b/core/java/android/content/pm/IPinItemRequest.aidl new file mode 100644 index 000000000000..efe283527b01 --- /dev/null +++ b/core/java/android/content/pm/IPinItemRequest.aidl @@ -0,0 +1,26 @@ +/* + * 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.content.pm; + +import android.os.Bundle; + +/** + * {@hide} + */ +interface IPinItemRequest { + boolean isValid(); + boolean accept(in Bundle options); +} diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl index 1bf2ab0abbad..91df8e8b84c3 100644 --- a/core/java/android/content/pm/IShortcutService.aidl +++ b/core/java/android/content/pm/IShortcutService.aidl @@ -15,6 +15,7 @@ */ package android.content.pm; +import android.content.IntentSender; import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; @@ -41,6 +42,9 @@ interface IShortcutService { boolean updateShortcuts(String packageName, in ParceledListSlice shortcuts, int userId); + boolean requestPinShortcut(String packageName, in ShortcutInfo shortcut, + in IntentSender resultIntent, int userId); + void disableShortcuts(String packageName, in List shortcutIds, CharSequence disabledMessage, int disabledMessageResId, int userId); @@ -63,4 +67,6 @@ interface IShortcutService { byte[] getBackupPayload(int user); void applyRestore(in byte[] payload, int user); + + boolean isRequestPinShortcutSupported(int user); }
\ No newline at end of file diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.java b/core/java/android/content/pm/IntentFilterVerificationInfo.java index f47ad1fb03e9..f12abf36c172 100644 --- a/core/java/android/content/pm/IntentFilterVerificationInfo.java +++ b/core/java/android/content/pm/IntentFilterVerificationInfo.java @@ -181,7 +181,7 @@ public final class IntentFilterVerificationInfo implements Parcelable { } public String getStatusString() { - return getStatusStringFromValue(mMainStatus); + return getStatusStringFromValue(((long)mMainStatus) << 32); } public static String getStatusStringFromValue(long val) { diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 7fc80442be90..5f4bc00a28f0 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -19,6 +19,8 @@ package android.content.pm; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.annotation.TestApi; import android.content.ActivityNotFoundException; import android.content.ComponentName; @@ -32,11 +34,14 @@ import android.graphics.BitmapFactory; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.Parcel; import android.os.ParcelFileDescriptor; +import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -69,6 +74,36 @@ public class LauncherApps { static final String TAG = "LauncherApps"; static final boolean DEBUG = false; + /** + * Activity Action: For the default launcher to show the confirmation dialog to create + * a pinned shortcut. + * + * <p>See the {@link ShortcutManager} javadoc for details. + * + * <p> + * Use {@link #getPinItemRequest(Intent)} to get a {@link PinItemRequest} object, + * and call {@link PinItemRequest#accept(Bundle)} + * if the user accepts. If the user doesn't accept, no further action is required. + * + * @see #EXTRA_PIN_ITEM_REQUEST + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CONFIRM_PIN_ITEM = + "android.content.pm.action.CONFIRM_PIN_ITEM"; + + /** + * An extra for {@link #ACTION_CONFIRM_PIN_ITEM} containing a + * {@link ShortcutInfo} of the shortcut the publisher app asked to pin. + * + * <p>A helper function {@link #getPinItemRequest(Intent)} can be used + * instead of using this constant directly. + * + * @see #ACTION_CONFIRM_PIN_ITEM + */ + public static final String EXTRA_PIN_ITEM_REQUEST = + "android.content.pm.extra.PIN_ITEM_REQUEST"; + + private Context mContext; private ILauncherApps mService; private PackageManager mPm; @@ -655,23 +690,41 @@ public class LauncherApps { } } } else if (shortcut.hasIconResource()) { - try { - final int resId = shortcut.getIconResourceId(); - if (resId == 0) { - return null; // Shouldn't happen but just in case. + return loadDrawableResourceFromPackage(shortcut.getPackage(), + shortcut.getIconResourceId(), shortcut.getUserHandle(), density); + } else if (shortcut.getIcon() != null) { + // This happens if a shortcut is pending-approval. + final Icon icon = shortcut.getIcon(); + switch (icon.getType()) { + case Icon.TYPE_RESOURCE: { + return loadDrawableResourceFromPackage(shortcut.getPackage(), + icon.getResId(), shortcut.getUserHandle(), density); } - final ApplicationInfo ai = getApplicationInfo(shortcut.getPackage(), - /* flags =*/ 0, shortcut.getUserHandle()); - final Resources res = mContext.getPackageManager().getResourcesForApplication(ai); - return res.getDrawableForDensity(resId, density); - } catch (NameNotFoundException | Resources.NotFoundException e) { - return null; + case Icon.TYPE_BITMAP: { + return icon.loadDrawable(mContext); + } + default: + return null; // Shouldn't happen though. } } else { return null; // Has no icon. } } + private Drawable loadDrawableResourceFromPackage(String packageName, int resId, + UserHandle user, int density) { + try { + if (resId == 0) { + return null; // Shouldn't happen but just in case. + } + final ApplicationInfo ai = getApplicationInfo(packageName, /* flags =*/ 0, user); + final Resources res = mContext.getPackageManager().getResourcesForApplication(ai); + return res.getDrawableForDensity(resId, density); + } catch (NameNotFoundException | Resources.NotFoundException e) { + return null; + } + } + /** * Returns the shortcut icon with badging appropriate for the profile. * @@ -1064,4 +1117,121 @@ public class LauncherApps { obtainMessage(MSG_SHORTCUT_CHANGED, info).sendToTarget(); } } + + /** + * A helper method to extract a {@link PinItemRequest} set to + * the {@link #EXTRA_PIN_ITEM_REQUEST} extra. + */ + public PinItemRequest getPinItemRequest(Intent intent) { + return intent.getParcelableExtra(EXTRA_PIN_ITEM_REQUEST); + } + + /** + * Represents a "pin shortcut" request made by an app, which is sent with + * an {@link #ACTION_CONFIRM_PIN_ITEM} intent to the default launcher app. + * + * @see #EXTRA_PIN_ITEM_REQUEST + * @see #getPinItemRequest(Intent) + */ + public static final class PinItemRequest implements Parcelable { + + /** This is a request to pin shortcut. */ + public static final int REQUEST_TYPE_SHORTCUT = 1; + + @IntDef(value = {REQUEST_TYPE_SHORTCUT}) + @Retention(RetentionPolicy.SOURCE) + public @interface RequestType {} + + private final int mRequestType; + private final ShortcutInfo mShortcutInfo; + private final IPinItemRequest mInner; + + /** + * @hide + */ + public PinItemRequest(@RequestType int requestType, ShortcutInfo shortcutInfo, + IPinItemRequest inner) { + mRequestType = requestType; + mShortcutInfo = shortcutInfo; + mInner = inner; + } + + /** + * Represents the type of a request. For now {@link #REQUEST_TYPE_SHORTCUT} is the only + * valid type. + */ + @RequestType + public int getRequestType() { + return mRequestType; + } + + /** + * {@link ShortcutInfo} sent by the requesting app. Always non-null for a + * {@link #REQUEST_TYPE_SHORTCUT} request. + */ + @Nullable + public ShortcutInfo getShortcutInfo() { + return mShortcutInfo; + } + + /** + * Return {@code TRUE} if a request is valid -- i.e. {@link #accept(Bundle)} has not been + * called, and it has not been canceled. + */ + public boolean isValid() { + try { + return mInner.isValid(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Called by the receiving launcher app when the user accepts the request. + */ + public boolean accept(@Nullable Bundle options) { + try { + return mInner.accept(options); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Same as as {@link #accept(Bundle)} with no options. + */ + public boolean accept() { + return accept(/* options= */ null); + } + + private PinItemRequest(Parcel source) { + final ClassLoader cl = getClass().getClassLoader(); + + mRequestType = source.readInt(); + mShortcutInfo = source.readParcelable(cl); + mInner = IPinItemRequest.Stub.asInterface(source.readStrongBinder()); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRequestType); + dest.writeParcelable(mShortcutInfo, flags); + dest.writeStrongBinder(mInner.asBinder()); + } + + public static final Creator<PinItemRequest> CREATOR = + new Creator<PinItemRequest>() { + public PinItemRequest createFromParcel(Parcel source) { + return new PinItemRequest(source); + } + public PinItemRequest[] newArray(int size) { + return new PinItemRequest[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + } } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 4411572b1fb6..2fbb5b1c5296 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3319,7 +3319,8 @@ public abstract class PackageManager { * Grant a runtime permission to an application which the application does not * already have. The permission must have been requested by the application. * If the application is not allowed to hold the permission, a {@link - * java.lang.SecurityException} is thrown. + * java.lang.SecurityException} is thrown. If the package or permission is + * invalid, a {@link java.lang.IllegalArgumentException} is thrown. * <p> * <strong>Note: </strong>Using this API requires holding * android.permission.GRANT_RUNTIME_PERMISSIONS and if the user id is @@ -3343,7 +3344,8 @@ public abstract class PackageManager { * #grantRuntimePermission(String, String, android.os.UserHandle)}. The * permission must have been requested by and granted to the application. * If the application is not allowed to hold the permission, a {@link - * java.lang.SecurityException} is thrown. + * java.lang.SecurityException} is thrown. If the package or permission is + * invalid, a {@link java.lang.IllegalArgumentException} is thrown. * <p> * <strong>Note: </strong>Using this API requires holding * android.permission.REVOKE_RUNTIME_PERMISSIONS and if the user id is @@ -3487,15 +3489,15 @@ public abstract class PackageManager { public abstract @Nullable String[] getPackagesForUid(int uid); /** - * Retrieve the official name associated with a user id. This name is + * Retrieve the official name associated with a uid. This name is * guaranteed to never change, though it is possible for the underlying - * user id to be changed. That is, if you are storing information about - * user ids in persistent storage, you should use the string returned - * by this function instead of the raw user-id. + * uid to be changed. That is, if you are storing information about + * uids in persistent storage, you should use the string returned + * by this function instead of the raw uid. * - * @param uid The user id for which you would like to retrieve a name. - * @return Returns a unique name for the given user id, or null if the - * user id is not currently assigned. + * @param uid The uid for which you would like to retrieve a name. + * @return Returns a unique name for the given uid, or null if the + * uid is not currently assigned. */ public abstract @Nullable String getNameForUid(int uid); diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index ad0a6b25c4f1..1ba68a64dab4 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -197,15 +197,15 @@ public abstract class PackageManagerInternal { boolean overridePolicy); /** - * Retrieve the official name associated with a user id. This name is + * Retrieve the official name associated with a uid. This name is * guaranteed to never change, though it is possible for the underlying - * user id to be changed. That is, if you are storing information about - * user ids in persistent storage, you should use the string returned - * by this function instead of the raw user-id. + * uid to be changed. That is, if you are storing information about + * uids in persistent storage, you should use the string returned + * by this function instead of the raw uid. * - * @param uid The user id for which you would like to retrieve a name. - * @return Returns a unique name for the given user id, or null if the - * user id is not currently assigned. + * @param uid The uid for which you would like to retrieve a name. + * @return Returns a unique name for the given uid, or null if the + * uid is not currently assigned. */ public abstract String getNameForUid(int uid); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index d7c3722a1d55..2236291fd248 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -24,7 +24,10 @@ import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.IntRange; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Intent; @@ -58,7 +61,6 @@ import android.util.jar.StrictJarFile; import android.view.Gravity; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -85,7 +87,6 @@ import java.util.zip.ZipEntry; import libcore.io.IoUtils; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; -import static android.content.pm.ActivityInfo.FLAG_IMMERSIVE; import static android.content.pm.ActivityInfo.FLAG_ON_TOP_LAUNCHER; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY; @@ -357,6 +358,7 @@ public class PackageParser { public final int[] splitRevisionCodes; public final boolean coreApp; + public final boolean debuggable; public final boolean multiArch; public final boolean use32bitAbi; public final boolean extractNativeLibs; @@ -374,6 +376,7 @@ public class PackageParser { this.baseRevisionCode = baseApk.revisionCode; this.splitRevisionCodes = splitRevisionCodes; this.coreApp = baseApk.coreApp; + this.debuggable = baseApk.debuggable; this.multiArch = baseApk.multiArch; this.use32bitAbi = baseApk.use32bitAbi; this.extractNativeLibs = baseApk.extractNativeLibs; @@ -403,6 +406,7 @@ public class PackageParser { public final Signature[] signatures; public final Certificate[][] certificates; public final boolean coreApp; + public final boolean debuggable; public final boolean multiArch; public final boolean use32bitAbi; public final boolean extractNativeLibs; @@ -410,7 +414,8 @@ public class PackageParser { public ApkLite(String codePath, String packageName, String splitName, int versionCode, int revisionCode, int installLocation, List<VerifierInfo> verifiers, Signature[] signatures, Certificate[][] certificates, boolean coreApp, - boolean multiArch, boolean use32bitAbi, boolean extractNativeLibs) { + boolean debuggable, boolean multiArch, boolean use32bitAbi, + boolean extractNativeLibs) { this.codePath = codePath; this.packageName = packageName; this.splitName = splitName; @@ -421,6 +426,7 @@ public class PackageParser { this.signatures = signatures; this.certificates = certificates; this.coreApp = coreApp; + this.debuggable = debuggable; this.multiArch = multiArch; this.use32bitAbi = use32bitAbi; this.extractNativeLibs = extractNativeLibs; @@ -1621,6 +1627,7 @@ public class PackageParser { int versionCode = 0; int revisionCode = 0; boolean coreApp = false; + boolean debuggable = false; boolean multiArch = false; boolean use32bitAbi = false; boolean extractNativeLibs = true; @@ -1660,6 +1667,9 @@ public class PackageParser { if (parser.getDepth() == searchDepth && "application".equals(parser.getName())) { for (int i = 0; i < attrs.getAttributeCount(); ++i) { final String attr = attrs.getAttributeName(i); + if ("debuggable".equals(attr)) { + debuggable = attrs.getAttributeBooleanValue(i, false); + } if ("multiArch".equals(attr)) { multiArch = attrs.getAttributeBooleanValue(i, false); } @@ -1675,7 +1685,7 @@ public class PackageParser { return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode, revisionCode, installLocation, verifiers, signatures, certificates, coreApp, - multiArch, use32bitAbi, extractNativeLibs); + debuggable, multiArch, use32bitAbi, extractNativeLibs); } /** @@ -2094,63 +2104,22 @@ public class PackageParser { sa.recycle(); - if (minCode != null) { - boolean allowedCodename = false; - for (String codename : SDK_CODENAMES) { - if (minCode.equals(codename)) { - allowedCodename = true; - break; - } - } - if (!allowedCodename) { - if (SDK_CODENAMES.length > 0) { - outError[0] = "Requires development platform " + minCode - + " (current platform is any of " - + Arrays.toString(SDK_CODENAMES) + ")"; - } else { - outError[0] = "Requires development platform " + minCode - + " but this is a release platform."; - } - mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; - return null; - } - pkg.applicationInfo.minSdkVersion = - android.os.Build.VERSION_CODES.CUR_DEVELOPMENT; - } else if (minVers > SDK_VERSION) { - outError[0] = "Requires newer sdk version #" + minVers - + " (current version is #" + SDK_VERSION + ")"; + final int minSdkVersion = PackageParser.computeMinSdkVersion(minVers, minCode, + SDK_VERSION, SDK_CODENAMES, outError); + if (minSdkVersion < 0) { mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; return null; - } else { - pkg.applicationInfo.minSdkVersion = minVers; } - if (targetCode != null) { - boolean allowedCodename = false; - for (String codename : SDK_CODENAMES) { - if (targetCode.equals(codename)) { - allowedCodename = true; - break; - } - } - if (!allowedCodename) { - if (SDK_CODENAMES.length > 0) { - outError[0] = "Requires development platform " + targetCode - + " (current platform is any of " - + Arrays.toString(SDK_CODENAMES) + ")"; - } else { - outError[0] = "Requires development platform " + targetCode - + " but this is a release platform."; - } - mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; - return null; - } - // If the code matches, it definitely targets this SDK. - pkg.applicationInfo.targetSdkVersion - = android.os.Build.VERSION_CODES.CUR_DEVELOPMENT; - } else { - pkg.applicationInfo.targetSdkVersion = targetVers; + final int targetSdkVersion = PackageParser.computeTargetSdkVersion(targetVers, + targetCode, SDK_VERSION, SDK_CODENAMES, outError); + if (targetSdkVersion < 0) { + mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; + return null; } + + pkg.applicationInfo.minSdkVersion = minSdkVersion; + pkg.applicationInfo.targetSdkVersion = targetSdkVersion; } XmlUtils.skipCurrentTag(parser); @@ -2398,6 +2367,137 @@ public class PackageParser { return pkg; } + /** + * Computes the targetSdkVersion to use at runtime. If the package is not + * compatible with this platform, populates {@code outError[0]} with an + * error message. + * <p> + * If {@code targetCode} is not specified, e.g. the value is {@code null}, + * then the {@code targetVers} will be returned unmodified. + * <p> + * Otherwise, the behavior varies based on whether the current platform + * is a pre-release version, e.g. the {@code platformSdkCodenames} array + * has length > 0: + * <ul> + * <li>If this is a pre-release platform and the value specified by + * {@code targetCode} is contained within the array of allowed pre-release + * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}. + * <li>If this is a released platform, this method will return -1 to + * indicate that the package is not compatible with this platform. + * </ul> + * + * @param targetVers targetSdkVersion number, if specified in the + * application manifest, or 0 otherwise + * @param targetCode targetSdkVersion code, if specified in the application + * manifest, or {@code null} otherwise + * @param platformSdkVersion platform SDK version number, typically + * Build.VERSION.SDK_INT + * @param platformSdkCodenames array of allowed pre-release SDK codenames + * for this platform + * @param outError output array to populate with error, if applicable + * @return the targetSdkVersion to use at runtime, or -1 if the package is + * not compatible with this platform + * @hide Exposed for unit testing only. + */ + @TestApi + public static int computeTargetSdkVersion(@IntRange(from = 0) int targetVers, + @Nullable String targetCode, @IntRange(from = 1) int platformSdkVersion, + @NonNull String[] platformSdkCodenames, @NonNull String[] outError) { + // If it's a release SDK, return the version number unmodified. + if (targetCode == null) { + return targetVers; + } + + // If it's a pre-release SDK and the codename matches this platform, it + // definitely targets this SDK. + if (ArrayUtils.contains(platformSdkCodenames, targetCode)) { + return Build.VERSION_CODES.CUR_DEVELOPMENT; + } + + // Otherwise, we're looking at an incompatible pre-release SDK. + if (platformSdkCodenames.length > 0) { + outError[0] = "Requires development platform " + targetCode + + " (current platform is any of " + + Arrays.toString(platformSdkCodenames) + ")"; + } else { + outError[0] = "Requires development platform " + targetCode + + " but this is a release platform."; + } + return -1; + } + + /** + * Computes the minSdkVersion to use at runtime. If the package is not + * compatible with this platform, populates {@code outError[0]} with an + * error message. + * <p> + * If {@code minCode} is not specified, e.g. the value is {@code null}, + * then behavior varies based on the {@code platformSdkVersion}: + * <ul> + * <li>If the platform SDK version is greater than or equal to the + * {@code minVers}, returns the {@code mniVers} unmodified. + * <li>Otherwise, returns -1 to indicate that the package is not + * compatible with this platform. + * </ul> + * <p> + * Otherwise, the behavior varies based on whether the current platform + * is a pre-release version, e.g. the {@code platformSdkCodenames} array + * has length > 0: + * <ul> + * <li>If this is a pre-release platform and the value specified by + * {@code targetCode} is contained within the array of allowed pre-release + * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}. + * <li>If this is a released platform, this method will return -1 to + * indicate that the package is not compatible with this platform. + * </ul> + * + * @param minVers minSdkVersion number, if specified in the application + * manifest, or 1 otherwise + * @param minCode minSdkVersion code, if specified in the application + * manifest, or {@code null} otherwise + * @param platformSdkVersion platform SDK version number, typically + * Build.VERSION.SDK_INT + * @param platformSdkCodenames array of allowed prerelease SDK codenames + * for this platform + * @param outError output array to populate with error, if applicable + * @return the minSdkVersion to use at runtime, or -1 if the package is not + * compatible with this platform + * @hide Exposed for unit testing only. + */ + @TestApi + public static int computeMinSdkVersion(@IntRange(from = 1) int minVers, + @Nullable String minCode, @IntRange(from = 1) int platformSdkVersion, + @NonNull String[] platformSdkCodenames, @NonNull String[] outError) { + // If it's a release SDK, make sure we meet the minimum SDK requirement. + if (minCode == null) { + if (minVers <= platformSdkVersion) { + return minVers; + } + + // We don't meet the minimum SDK requirement. + outError[0] = "Requires newer sdk version #" + minVers + + " (current version is #" + platformSdkVersion + ")"; + return -1; + } + + // If it's a pre-release SDK and the codename matches this platform, we + // definitely meet the minimum SDK requirement. + if (ArrayUtils.contains(platformSdkCodenames, minCode)) { + return Build.VERSION_CODES.CUR_DEVELOPMENT; + } + + // Otherwise, we're looking at an incompatible pre-release SDK. + if (platformSdkCodenames.length > 0) { + outError[0] = "Requires development platform " + minCode + + " (current platform is any of " + + Arrays.toString(platformSdkCodenames) + ")"; + } else { + outError[0] = "Requires development platform " + minCode + + " but this is a release platform."; + } + return -1; + } + private FeatureInfo parseUsesFeature(Resources res, AttributeSet attrs) { FeatureInfo fi = new FeatureInfo(); TypedArray sa = res.obtainAttributes(attrs, @@ -3780,6 +3880,8 @@ public class PackageParser { ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE; } + final boolean hasVisibleToEphemeral = + sa.hasValue(R.styleable.AndroidManifestActivity_visibleToEphemeral); final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0); final boolean visibleToEphemeral = isEphemeral || sa.getBoolean(R.styleable.AndroidManifestActivity_visibleToEphemeral, false); @@ -3818,7 +3920,7 @@ public class PackageParser { return null; } intent.setEphemeral(isEphemeral); - intent.setVisibleToEphemeral(visibleToEphemeral); + intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent)); if (intent.countActions() == 0) { Slog.w(TAG, "No actions in intent filter at " + mArchiveSourcePath + " " @@ -3826,6 +3928,10 @@ public class PackageParser { } else { a.intents.add(intent); } + // adjust activity flags when we implicitly expose it via a browsable filter + if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) { + a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL; + } } else if (!receiver && parser.getName().equals("preferred")) { ActivityIntentInfo intent = new ActivityIntentInfo(a); if (!parseIntent(res, parser, false /*allowGlobs*/, false /*allowAutoVerify*/, @@ -3833,7 +3939,7 @@ public class PackageParser { return null; } intent.setEphemeral(isEphemeral); - intent.setVisibleToEphemeral(visibleToEphemeral); + intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent)); if (intent.countActions() == 0) { Slog.w(TAG, "No actions in preferred at " + mArchiveSourcePath + " " @@ -3844,6 +3950,10 @@ public class PackageParser { } owner.preferredActivityFilters.add(intent); } + // adjust activity flags when we implicitly expose it via a browsable filter + if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) { + a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL; + } } else if (parser.getName().equals("meta-data")) { if ((a.metaData = parseMetaData(res, parser, a.metaData, outError)) == null) { @@ -4084,6 +4194,7 @@ public class PackageParser { } } + // TODO add visibleToInstantApp attribute to activity alias final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0); final boolean visibleToEphemeral = isEphemeral || ((a.info.flags & ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL) != 0); @@ -4115,9 +4226,14 @@ public class PackageParser { + parser.getPositionDescription()); } else { intent.setEphemeral(isEphemeral); - intent.setVisibleToEphemeral(visibleToEphemeral); + intent.setVisibleToEphemeral(visibleToEphemeral + || isWebBrowsableIntent(intent)); a.intents.add(intent); } + // adjust activity flags when we implicitly expose it via a browsable filter + if (intent.isVisibleToEphemeral()) { + a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL; + } } else if (parser.getName().equals("meta-data")) { if ((a.metaData=parseMetaData(res, parser, a.metaData, outError)) == null) { @@ -4253,6 +4369,8 @@ public class PackageParser { ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE; } + final boolean hasVisibleToEphemeral = + sa.hasValue(R.styleable.AndroidManifestProvider_visibleToEphemeral); final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0); final boolean visibleToEphemeral = isEphemeral || sa.getBoolean(R.styleable.AndroidManifestProvider_visibleToEphemeral, false); @@ -4282,7 +4400,8 @@ public class PackageParser { } p.info.authority = cpname.intern(); - if (!parseProviderTags(res, parser, isEphemeral, visibleToEphemeral, p, outError)) { + if (!parseProviderTags( + res, parser, isEphemeral, hasVisibleToEphemeral, visibleToEphemeral, p, outError)) { return null; } @@ -4290,8 +4409,9 @@ public class PackageParser { } private boolean parseProviderTags(Resources res, XmlResourceParser parser, - boolean isEphemeral, boolean visibleToEphemeral, Provider outInfo, String[] outError) - throws XmlPullParserException, IOException { + boolean isEphemeral, boolean hasVisibleToEphemeral, boolean visibleToEphemeral, + Provider outInfo, String[] outError) + throws XmlPullParserException, IOException { int outerDepth = parser.getDepth(); int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -4308,8 +4428,12 @@ public class PackageParser { return false; } intent.setEphemeral(isEphemeral); - intent.setVisibleToEphemeral(visibleToEphemeral); + intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent)); outInfo.intents.add(intent); + // adjust provider flags when we implicitly expose it via a browsable filter + if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) { + outInfo.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_EPHEMERAL; + } } else if (parser.getName().equals("meta-data")) { if ((outInfo.metaData=parseMetaData(res, parser, @@ -4556,6 +4680,8 @@ public class PackageParser { ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE; } + final boolean hasVisibleToEphemeral = + sa.hasValue(R.styleable.AndroidManifestService_visibleToEphemeral); final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0); final boolean visibleToEphemeral = isEphemeral || sa.getBoolean(R.styleable.AndroidManifestService_visibleToEphemeral, false); @@ -4591,8 +4717,11 @@ public class PackageParser { return null; } intent.setEphemeral(isEphemeral); - intent.setVisibleToEphemeral(visibleToEphemeral); - + intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent)); + // adjust activity flags when we implicitly expose it via a browsable filter + if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) { + s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_EPHEMERAL; + } s.intents.add(intent); } else if (parser.getName().equals("meta-data")) { if ((s.metaData=parseMetaData(res, parser, s.metaData, @@ -4620,6 +4749,12 @@ public class PackageParser { return s; } + private boolean isWebBrowsableIntent(IntentInfo intent) { + return intent.hasAction(Intent.ACTION_VIEW) + && intent.hasCategory(Intent.CATEGORY_BROWSABLE) + && (intent.hasDataScheme("http") || intent.hasDataScheme("https")); + } + private boolean parseAllMetaData(Resources res, XmlResourceParser parser, String tag, Component<?> outInfo, String[] outError) throws XmlPullParserException, IOException { int outerDepth = parser.getDepth(); diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 56f914e5f3c3..a4d891650c88 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -129,6 +129,10 @@ public final class ShortcutInfo implements Parcelable { | CLONE_REMOVE_RES_NAMES; /** @hide */ + public static final int CLONE_REMOVE_FOR_LAUNCHER_APPROVAL = CLONE_REMOVE_INTENT + | CLONE_REMOVE_RES_NAMES; + + /** @hide */ @IntDef(flag = true, value = { CLONE_REMOVE_ICON, diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index f7c4d592b3a9..c8f00b8e53c6 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -16,21 +16,31 @@ package android.content.pm; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.annotation.TestApi; import android.annotation.UserIdInt; import android.app.Activity; import android.app.usage.UsageStatsManager; import android.content.Context; import android.content.Intent; +import android.content.IntentSender; +import android.os.Binder; +import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; import java.util.List; /** + * <p><strong>TODO Update the overview to how to use the O new features.</strong></p> + * * The ShortcutManager manages an app's <em>shortcuts</em>. Shortcuts provide users * with quick access to activities other than an app's main activity in the currently-active * launcher. For example, @@ -618,7 +628,7 @@ public class ShortcutManager { * * @throws IllegalStateException when the user is locked. */ - public boolean updateShortcuts(List<ShortcutInfo> shortcutInfoList) { + public boolean updateShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) { try { return mService.updateShortcuts(mContext.getPackageName(), new ParceledListSlice(shortcutInfoList), injectMyUserId()); @@ -815,6 +825,61 @@ public class ShortcutManager { } /** + * Return {@code TRUE} if the default launcher supports + * {@link #requestPinShortcut(ShortcutInfo, IntentSender)}. + */ + public boolean isRequestPinShortcutSupported() { + try { + return mService.isRequestPinShortcutSupported(injectMyUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Request to create a pinned shortcut. The default launcher will receive this request and + * ask the user for approval. If the user approves it, the shortcut will be created and + * {@code resultIntent} will be sent. Otherwise, no responses will be sent to the caller. + * + * <p>When a request is denied by the user, the caller app will not get any response. + * + * <p>Only apps with a foreground activity or a foreground service can call it. Otherwise + * it'll throw {@link IllegalStateException}. + * + * <p>When an app calls this API when a previous request is still waiting for a response, + * the previous request will be canceled. + * + * @param shortcut New shortcut to pin. If an app wants to pin an existing (either dynamic + * or manifest) shortcut, then it only needs to have an ID, and other fields don't have to + * be set, in which case, the target shortcut must be enabled. + * If it's a new shortcut, all the mandatory fields, such as a short label, must be + * set. + * @param resultIntent If not null, this intent will be sent when the shortcut is pinned. + * Use {@link android.app.PendingIntent#getIntentSender()} to create a {@link IntentSender}. + * + * @return {@code TRUE} if the launcher supports this feature. Note the API will return without + * waiting for the user to respond, so getting {@code TRUE} from this API does *not* mean + * the shortcut is pinned. {@code FALSE} if the launcher doesn't support this feature. + * + * @see #isRequestPinShortcutSupported() + * @see IntentSender + * @see android.app.PendingIntent#getIntentSender() + * + * @throws IllegalArgumentException if a shortcut with the same ID exists and is disabled. + * @throws IllegalStateException The caller doesn't have a foreground activity or a foreground + * service. + */ + public boolean requestPinShortcut(@NonNull ShortcutInfo shortcut, + @Nullable IntentSender resultIntent) { + try { + return mService.requestPinShortcut(mContext.getPackageName(), shortcut, + resultIntent, injectMyUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Called internally when an app is considered to have come to the foreground * even when technically it's not. This method resets the throttling for this package. * For example, when the user sends an "inline reply" on a notification, the system UI will diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index ee1f1907fcb8..227dc91cf108 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -1047,18 +1047,36 @@ public final class Configuration implements Parcelable, Comparable<Configuration changed |= ActivityInfo.CONFIG_ORIENTATION; orientation = delta.orientation; } - if (getScreenLayoutNoDirection(delta.screenLayout) != - (SCREENLAYOUT_SIZE_UNDEFINED | SCREENLAYOUT_LONG_UNDEFINED) - && (getScreenLayoutNoDirection(screenLayout) != - getScreenLayoutNoDirection(delta.screenLayout))) { + + if (((delta.screenLayout & SCREENLAYOUT_SIZE_MASK) != SCREENLAYOUT_SIZE_UNDEFINED) + && (delta.screenLayout & SCREENLAYOUT_SIZE_MASK) + != (screenLayout & SCREENLAYOUT_SIZE_MASK)) { changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT; - // We need to preserve the previous layout dir bits if they were defined - if ((delta.screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK) == 0) { - screenLayout = (screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK)|delta.screenLayout; - } else { - screenLayout = delta.screenLayout; - } + screenLayout = (screenLayout & ~SCREENLAYOUT_SIZE_MASK) + | (delta.screenLayout & SCREENLAYOUT_SIZE_MASK); + } + if (((delta.screenLayout & SCREENLAYOUT_LONG_MASK) != SCREENLAYOUT_LONG_UNDEFINED) + && (delta.screenLayout & SCREENLAYOUT_LONG_MASK) + != (screenLayout & SCREENLAYOUT_LONG_MASK)) { + changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT; + screenLayout = (screenLayout & ~SCREENLAYOUT_LONG_MASK) + | (delta.screenLayout & SCREENLAYOUT_LONG_MASK); } + if (((delta.screenLayout & SCREENLAYOUT_ROUND_MASK) != SCREENLAYOUT_ROUND_UNDEFINED) + && (delta.screenLayout & SCREENLAYOUT_ROUND_MASK) + != (screenLayout & SCREENLAYOUT_ROUND_MASK)) { + changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT; + screenLayout = (screenLayout & ~SCREENLAYOUT_ROUND_MASK) + | (delta.screenLayout & SCREENLAYOUT_ROUND_MASK); + } + if ((delta.screenLayout & SCREENLAYOUT_COMPAT_NEEDED) + != (screenLayout & SCREENLAYOUT_COMPAT_NEEDED) + && delta.screenLayout != 0) { + changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT; + screenLayout = (screenLayout & ~SCREENLAYOUT_COMPAT_NEEDED) + | (delta.screenLayout & SCREENLAYOUT_COMPAT_NEEDED); + } + if (delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED) && uiMode != delta.uiMode) { changed |= ActivityInfo.CONFIG_UI_MODE; diff --git a/core/java/android/hardware/CameraStatus.java b/core/java/android/hardware/CameraStatus.java new file mode 100644 index 000000000000..bc97e3865244 --- /dev/null +++ b/core/java/android/hardware/CameraStatus.java @@ -0,0 +1,65 @@ +/* + * 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.hardware; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Status information about a camera. + * + * Contains the name of the camera device, and its current status, one of the + * ICameraServiceListener.STATUS_ values. + * + * @hide + */ +public class CameraStatus implements Parcelable { + public String cameraId; + public int status; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(cameraId); + out.writeInt(status); + } + + public void readFromParcel(Parcel in) { + cameraId = in.readString(); + status = in.readInt(); + } + + public static final Parcelable.Creator<CameraStatus> CREATOR = + new Parcelable.Creator<CameraStatus>() { + @Override + public CameraStatus createFromParcel(Parcel in) { + CameraStatus status = new CameraStatus(); + status.readFromParcel(in); + + return status; + } + + @Override + public CameraStatus[] newArray(int size) { + return new CameraStatus[size]; + } + }; +}; diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 145b1d076e80..e8e989f20ef7 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -23,6 +23,7 @@ import android.content.Context; import android.hardware.ICameraService; import android.hardware.ICameraServiceListener; import android.hardware.CameraInfo; +import android.hardware.CameraStatus; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.legacy.CameraDeviceUserShim; import android.hardware.camera2.legacy.LegacyMetadataMapper; @@ -92,11 +93,7 @@ public final class CameraManager { */ @NonNull public String[] getCameraIdList() throws CameraAccessException { - synchronized (mLock) { - // ID list creation handles various known failures in device enumeration, so only - // exceptions it'll throw are unexpected, and should be propagated upward. - return getOrCreateDeviceIdListLocked().toArray(new String[0]); - } + return CameraManagerGlobal.get().getCameraIdList(); } /** @@ -218,18 +215,10 @@ public final class CameraManager { CameraCharacteristics characteristics = null; synchronized (mLock) { - if (!getOrCreateDeviceIdListLocked().contains(cameraId)) { - throw new IllegalArgumentException(String.format("Camera id %s does not match any" + - " currently connected camera device", cameraId)); - } - - int id = Integer.parseInt(cameraId); - /* * Get the camera characteristics from the camera service directly if it supports it, * otherwise get them from the legacy shim instead. */ - ICameraService cameraService = CameraManagerGlobal.get().getCameraService(); if (cameraService == null) { throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, @@ -239,6 +228,8 @@ public final class CameraManager { if (!supportsCamera2ApiLocked(cameraId)) { // Legacy backwards compatibility path; build static info from the camera // parameters + int id = Integer.parseInt(cameraId); + String parameters = cameraService.getLegacyParameters(id); CameraInfo info = cameraService.getCameraInfo(id); @@ -246,7 +237,7 @@ public final class CameraManager { characteristics = LegacyMetadataMapper.createCharacteristics(parameters, info); } else { // Normal path: Get the camera characteristics directly from the camera service - CameraMetadataNative info = cameraService.getCameraCharacteristics(id); + CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId); characteristics = new CameraCharacteristics(info); } @@ -303,14 +294,6 @@ public final class CameraManager { ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks(); - int id; - try { - id = Integer.parseInt(cameraId); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: " - + cameraId); - } - try { if (supportsCamera2ApiLocked(cameraId)) { // Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices @@ -320,10 +303,18 @@ public final class CameraManager { ICameraService.ERROR_DISCONNECTED, "Camera service is currently unavailable"); } - cameraUser = cameraService.connectDevice(callbacks, id, + cameraUser = cameraService.connectDevice(callbacks, cameraId, mContext.getOpPackageName(), uid); } else { // Use legacy camera implementation for HAL1 devices + int id; + try { + id = Integer.parseInt(cameraId); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: " + + cameraId); + } + Log.i(TAG, "Using legacy camera HAL."); cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id); } @@ -672,69 +663,6 @@ public final class CameraManager { } /** - * Return or create the list of currently connected camera devices. - * - * <p>In case of errors connecting to the camera service, will return an empty list.</p> - */ - private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException { - if (mDeviceIdList == null) { - int numCameras = 0; - ICameraService cameraService = CameraManagerGlobal.get().getCameraService(); - ArrayList<String> deviceIdList = new ArrayList<>(); - - // If no camera service, then no devices - if (cameraService == null) { - return deviceIdList; - } - - try { - numCameras = cameraService.getNumberOfCameras(CAMERA_TYPE_ALL); - } catch(ServiceSpecificException e) { - throwAsPublicException(e); - } catch (RemoteException e) { - // camera service just died - if no camera service, then no devices - return deviceIdList; - } - - for (int i = 0; i < numCameras; ++i) { - // Non-removable cameras use integers starting at 0 for their - // identifiers - boolean isDeviceSupported = false; - try { - CameraMetadataNative info = cameraService.getCameraCharacteristics(i); - if (!info.isEmpty()) { - isDeviceSupported = true; - } else { - throw new AssertionError("Expected to get non-empty characteristics"); - } - } catch(ServiceSpecificException e) { - // DISCONNECTED means that the HAL reported an low-level error getting the - // device info; ILLEGAL_ARGUMENT means that this devices is not supported. - // Skip listing the device. Other errors, - // propagate exception onward - if (e.errorCode != ICameraService.ERROR_DISCONNECTED || - e.errorCode != ICameraService.ERROR_ILLEGAL_ARGUMENT) { - throwAsPublicException(e); - } - } catch(RemoteException e) { - // Camera service died - no devices to list - deviceIdList.clear(); - return deviceIdList; - } - - if (isDeviceSupported) { - deviceIdList.add(String.valueOf(i)); - } else { - Log.w(TAG, "Error querying camera device " + i + " for listing."); - } - - } - mDeviceIdList = deviceIdList; - } - return mDeviceIdList; - } - - /** * Queries the camera service if it supports the camera2 api directly, or needs a shim. * * @param cameraId a non-{@code null} camera identifier @@ -752,8 +680,6 @@ public final class CameraManager { * @return {@code true} if connecting will work for that device version. */ private boolean supportsCameraApiLocked(String cameraId, int apiVersion) { - int id = Integer.parseInt(cameraId); - /* * Possible return values: * - NO_ERROR => CameraX API is supported @@ -767,7 +693,7 @@ public final class CameraManager { // If no camera service, no support if (cameraService == null) return false; - return cameraService.supportsCameraApi(id, apiVersion); + return cameraService.supportsCameraApi(cameraId, apiVersion); } catch (RemoteException e) { // Camera service is now down, no support for any API level } @@ -880,7 +806,10 @@ public final class CameraManager { } try { - cameraService.addListener(this); + CameraStatus[] cameraStatuses = cameraService.addListener(this); + for (CameraStatus c : cameraStatuses) { + onStatusChangedLocked(c.status, c.cameraId); + } mCameraService = cameraService; } catch(ServiceSpecificException e) { // Unexpected failure @@ -890,6 +819,36 @@ public final class CameraManager { } } + /** + * Get a list of all camera IDs that are at least PRESENT; ignore devices that are + * NOT_PRESENT or ENUMERATING, since they cannot be used by anyone. + */ + public String[] getCameraIdList() { + String[] cameraIds = null; + synchronized(mLock) { + // Try to make sure we have an up-to-date list of camera devices. + connectCameraServiceLocked(); + + int idCount = 0; + for (int i = 0; i < mDeviceStatus.size(); i++) { + int status = mDeviceStatus.valueAt(i); + if (status == ICameraServiceListener.STATUS_NOT_PRESENT || + status == ICameraServiceListener.STATUS_ENUMERATING) continue; + idCount++; + } + cameraIds = new String[idCount]; + idCount = 0; + for (int i = 0; i < mDeviceStatus.size(); i++) { + int status = mDeviceStatus.valueAt(i); + if (status == ICameraServiceListener.STATUS_NOT_PRESENT || + status == ICameraServiceListener.STATUS_ENUMERATING) continue; + cameraIds[idCount] = mDeviceStatus.keyAt(i); + idCount++; + } + } + return cameraIds; + } + public void setTorchMode(String cameraId, boolean enabled) throws CameraAccessException { synchronized(mLock) { @@ -1173,9 +1132,9 @@ public final class CameraManager { * Callback from camera service notifying the process about camera availability changes */ @Override - public void onStatusChanged(int status, int cameraId) throws RemoteException { + public void onStatusChanged(int status, String cameraId) throws RemoteException { synchronized(mLock) { - onStatusChangedLocked(status, String.valueOf(cameraId)); + onStatusChangedLocked(status, cameraId); } } diff --git a/core/java/android/hardware/location/GeofenceHardware.java b/core/java/android/hardware/location/GeofenceHardware.java index f537a77493d2..66dd9fce8fa3 100644 --- a/core/java/android/hardware/location/GeofenceHardware.java +++ b/core/java/android/hardware/location/GeofenceHardware.java @@ -167,6 +167,7 @@ public final class GeofenceHardware { mMonitorCallbacks = new HashMap<GeofenceHardwareMonitorCallback, GeofenceHardwareMonitorCallbackWrapper>(); + /** @hide */ public GeofenceHardware(IGeofenceHardware service) { mService = service; } diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 3636e4edeb35..ae92457b6dd2 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -183,6 +183,14 @@ public class UsbManager { public static final String USB_DATA_UNLOCKED = "unlocked"; /** + * Boolean extra indicating whether the intent represents a change in the usb + * configuration (as opposed to a state update). + * + * {@hide} + */ + public static final String USB_CONFIG_CHANGED = "config_changed"; + + /** * A placeholder indicating that no USB function is being specified. * Used to distinguish between selecting no function vs. the default function in * {@link #setCurrentFunction(String)}. diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 0073ddecb6b8..d1d5f40739db 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -87,6 +87,13 @@ public class ConnectivityManager { * sent as an extra; it should be consulted to see what kind of * connectivity event occurred. * <p/> + * Apps targeting Android 7.0 (API level 24) and higher do not receive this + * broadcast if they declare the broadcast receiver in their manifest. Apps + * will still receive broadcasts if they register their + * {@link android.content.BroadcastReceiver} with + * {@link android.content.Context#registerReceiver Context.registerReceiver()} + * and that context is still valid. + * <p/> * If this is a connection that was the result of failing over from a * disconnected network, then the FAILOVER_CONNECTION boolean extra is * set to true. @@ -223,6 +230,13 @@ public class ConnectivityManager { public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL"; /** + * Key for passing a user agent string to the captive portal login activity. + * {@hide} + */ + public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = + "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; + + /** * Broadcast action to indicate the change of data activity status * (idle or active) on a network in a recent period. * The network becomes active when data transmission is started, or diff --git a/core/java/android/net/INetworkRecommendationProvider.aidl b/core/java/android/net/INetworkRecommendationProvider.aidl index 5e455d3e33fe..052c92c0309f 100644 --- a/core/java/android/net/INetworkRecommendationProvider.aidl +++ b/core/java/android/net/INetworkRecommendationProvider.aidl @@ -16,6 +16,7 @@ package android.net; +import android.net.NetworkKey; import android.net.RecommendationRequest; import android.os.IRemoteCallback; @@ -38,4 +39,15 @@ oneway interface INetworkRecommendationProvider { void requestRecommendation(in RecommendationRequest request, in IRemoteCallback callback, int sequence); + + /** + * Request scoring for networks. + * + * Implementations should use {@link NetworkScoreManager#updateScores(ScoredNetwork[])} to + * respond to score requests. + * + * @param networks an array of {@link NetworkKey}s to score + * @hide + */ + void requestScores(in NetworkKey[] networks); }
\ No newline at end of file diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl index 24f450420751..932f03116f15 100644 --- a/core/java/android/net/INetworkScoreService.aidl +++ b/core/java/android/net/INetworkScoreService.aidl @@ -17,6 +17,7 @@ package android.net; import android.net.INetworkScoreCache; +import android.net.NetworkKey; import android.net.RecommendationRequest; import android.net.RecommendationResult; import android.net.ScoredNetwork; @@ -87,4 +88,16 @@ interface INetworkScoreService */ RecommendationResult requestRecommendation(in RecommendationRequest request); + /** + * Request scoring for networks. + * + * Implementations should delegate to the registered network recommendation provider or + * fulfill the request locally if possible. + * + * @param networks an array of {@link NetworkKey}s to score + * @return true if the request was delegated or fulfilled locally, false otherwise + * @throws SecurityException if the caller is not the system + * @hide + */ + boolean requestScores(in NetworkKey[] networks); } diff --git a/core/java/android/net/NetworkRecommendationProvider.java b/core/java/android/net/NetworkRecommendationProvider.java index cd2ede84cfdc..16ae867d81e7 100644 --- a/core/java/android/net/NetworkRecommendationProvider.java +++ b/core/java/android/net/NetworkRecommendationProvider.java @@ -10,6 +10,11 @@ import android.os.Message; import android.os.RemoteException; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; + /** * The base class for implementing a network recommendation provider. * @hide @@ -42,11 +47,21 @@ public abstract class NetworkRecommendationProvider { * * @param request a {@link RecommendationRequest} instance containing additional * request details - * @return a {@link RecommendationResult} instance containing the recommended - * network to connect to + * @param callback a {@link ResultCallback} instance. When a {@link RecommendationResult} is + * available it must be passed into + * {@link ResultCallback#onResult(RecommendationResult)}. */ - public abstract RecommendationResult onRequestRecommendation(RecommendationRequest request); + public abstract void onRequestRecommendation(RecommendationRequest request, + ResultCallback callback); + /** + * Invoked when network scores have been requested. + * <p> + * Use {@link NetworkScoreManager#updateScores(ScoredNetwork[])} to respond to score requests. + * + * @param networks a non-empty array of {@link NetworkKey}s to score. + */ + public abstract void onRequestScores(NetworkKey[] networks); /** * Services that can handle {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS} should @@ -56,8 +71,63 @@ public abstract class NetworkRecommendationProvider { return mService; } + /** + * A callback implementing applications should invoke when a {@link RecommendationResult} + * is available. + */ + public static class ResultCallback { + private final IRemoteCallback mCallback; + private final int mSequence; + private final AtomicBoolean mCallbackRun; + + /** + * @hide + */ + @VisibleForTesting + public ResultCallback(IRemoteCallback callback, int sequence) { + mCallback = callback; + mSequence = sequence; + mCallbackRun = new AtomicBoolean(false); + } + + /** + * Run the callback with the available {@link RecommendationResult}. + * @param result a {@link RecommendationResult} instance. + */ + public void onResult(RecommendationResult result) { + if (!mCallbackRun.compareAndSet(false, true)) { + throw new IllegalStateException("The callback cannot be run more than once."); + } + final Bundle data = new Bundle(); + data.putInt(EXTRA_SEQUENCE, mSequence); + data.putParcelable(EXTRA_RECOMMENDATION_RESULT, result); + try { + mCallback.sendResult(data); + } catch (RemoteException e) { + Log.w(TAG, "Callback failed for seq: " + mSequence, e); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ResultCallback that = (ResultCallback) o; + + return mSequence == that.mSequence + && Objects.equals(mCallback, that.mCallback); + } + + @Override + public int hashCode() { + return Objects.hash(mCallback, mSequence); + } + } + private final class ServiceHandler extends Handler { static final int MSG_GET_RECOMMENDATION = 1; + static final int MSG_REQUEST_SCORES = 2; ServiceHandler(Looper looper) { super(looper, null /*callback*/, true /*async*/); @@ -72,16 +142,13 @@ public abstract class NetworkRecommendationProvider { final int seq = msg.arg1; final RecommendationRequest request = msg.getData().getParcelable(EXTRA_RECOMMENDATION_REQUEST); - final RecommendationResult result = onRequestRecommendation(request); - final Bundle data = new Bundle(); - data.putInt(EXTRA_SEQUENCE, seq); - data.putParcelable(EXTRA_RECOMMENDATION_RESULT, result); - try { - callback.sendResult(data); - } catch (RemoteException e) { - Log.w(TAG, "Callback failed for seq: " + seq, e); - } + final ResultCallback resultCallback = new ResultCallback(callback, seq); + onRequestRecommendation(request, resultCallback); + break; + case MSG_REQUEST_SCORES: + final NetworkKey[] networks = (NetworkKey[]) msg.obj; + onRequestScores(networks); break; default: @@ -110,5 +177,12 @@ public abstract class NetworkRecommendationProvider { msg.setData(data); msg.sendToTarget(); } + + @Override + public void requestScores(NetworkKey[] networks) throws RemoteException { + if (networks != null && networks.length > 0) { + mHandler.obtainMessage(ServiceHandler.MSG_REQUEST_SCORES, networks).sendToTarget(); + } + } } } diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index 865b8dd1fc46..4e606ef4eede 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -183,7 +183,7 @@ public class NetworkScoreManager { if (app == null) { return null; } - return app.mPackageName; + return app.packageName; } /** @@ -272,19 +272,11 @@ public class NetworkScoreManager { * @hide */ public boolean requestScores(NetworkKey[] networks) throws SecurityException { - String activeScorer = getActiveScorerPackage(); - if (activeScorer == null) { - return false; + try { + return mService.requestScores(networks); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } - Intent intent = new Intent(ACTION_SCORE_NETWORKS); - intent.setPackage(activeScorer); - intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - intent.putExtra(EXTRA_NETWORKS_TO_SCORE, networks); - // A scorer should never become active if its package doesn't hold SCORE_NETWORKS, but - // ensure the package still holds it to be extra safe. - // TODO: http://b/23422763 - mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM, Manifest.permission.SCORE_NETWORKS); - return true; } /** @@ -344,6 +336,8 @@ public class NetworkScoreManager { /** * Request a recommendation for which network to connect to. * + * <p>It is not safe to call this method from the main thread. + * * @param request a {@link RecommendationRequest} instance containing additional * request details * @return a {@link RecommendationResult} instance containing the recommended network diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java index ebb31c9056a9..4282ca75f2b6 100644 --- a/core/java/android/net/NetworkScorerAppManager.java +++ b/core/java/android/net/NetworkScorerAppManager.java @@ -19,160 +19,176 @@ package android.net; import android.Manifest; import android.Manifest.permission; import android.annotation.Nullable; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; - +import com.android.internal.R; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; /** - * Internal class for managing the primary network scorer application. - * - * TODO: Rename this to something more generic. + * Internal class for discovering and managing the network scorer/recommendation application. * * @hide */ public class NetworkScorerAppManager { private static final String TAG = "NetworkScorerAppManager"; - - private static final Intent SCORE_INTENT = - new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS); - + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); private final Context mContext; public NetworkScorerAppManager(Context context) { mContext = context; } + /** + * Holds metadata about a discovered network scorer/recommendation application. + */ public static class NetworkScorerAppData { /** Package name of this scorer app. */ - public final String mPackageName; + public final String packageName; /** UID of the scorer app. */ - public final int mPackageUid; - - /** Name of this scorer app for display. */ - public final CharSequence mScorerName; + public final int packageUid; /** - * Optional class name of a configuration activity. Null if none is set. - * - * @see NetworkScoreManager#ACTION_CUSTOM_ENABLE + * Name of the recommendation service we can bind to. */ - public final String mConfigurationActivityClassName; + public final String recommendationServiceClassName; - /** - * Optional class name of the scoring service we can bind to. Null if none is set. - */ - public final String mScoringServiceClassName; - - public NetworkScorerAppData(String packageName, int packageUid, CharSequence scorerName, - @Nullable String configurationActivityClassName, - @Nullable String scoringServiceClassName) { - mScorerName = scorerName; - mPackageName = packageName; - mPackageUid = packageUid; - mConfigurationActivityClassName = configurationActivityClassName; - mScoringServiceClassName = scoringServiceClassName; + public NetworkScorerAppData(String packageName, int packageUid, + String recommendationServiceClassName) { + this.packageName = packageName; + this.packageUid = packageUid; + this.recommendationServiceClassName = recommendationServiceClassName; } @Override public String toString() { final StringBuilder sb = new StringBuilder("NetworkScorerAppData{"); - sb.append("mPackageName='").append(mPackageName).append('\''); - sb.append(", mPackageUid=").append(mPackageUid); - sb.append(", mScorerName=").append(mScorerName); - sb.append(", mConfigurationActivityClassName='").append(mConfigurationActivityClassName) - .append('\''); - sb.append(", mScoringServiceClassName='").append(mScoringServiceClassName).append('\''); + sb.append("mPackageName='").append(packageName).append('\''); + sb.append(", packageUid=").append(packageUid); + sb.append(", recommendationServiceClassName='") + .append(recommendationServiceClassName).append('\''); sb.append('}'); return sb.toString(); } } /** - * Returns the list of available scorer apps. + * @return A {@link NetworkScorerAppData} instance containing information about the + * best configured network recommendation provider installed or {@code null} + * if none of the configured packages can recommend networks. * - * <p>A network scorer is any application which: + * <p>A network recommendation provider is any application which: * <ul> + * <li>Is listed in the <code>config_networkRecommendationPackageNames</code> config. * <li>Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission. - * <li>Includes a receiver for {@link NetworkScoreManager#ACTION_SCORE_NETWORKS} guarded by the - * {@link android.Manifest.permission#BROADCAST_NETWORK_PRIVILEGED} permission. + * <li>Includes a Service for {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS}. * </ul> - * - * @return the list of scorers, or the empty list if there are no valid scorers. */ - public Collection<NetworkScorerAppData> getAllValidScorers() { - // Network scorer apps can only run as the primary user so exit early if we're not the - // primary user. + public NetworkScorerAppData getNetworkRecommendationProviderData() { + // Network recommendation apps can only run as the primary user right now. + // http://b/23422763 if (UserHandle.getCallingUserId() != UserHandle.USER_SYSTEM) { - return Collections.emptyList(); + return null; } - List<NetworkScorerAppData> scorers = new ArrayList<>(); - PackageManager pm = mContext.getPackageManager(); - // Only apps installed under the primary user of the device can be scorers. - // TODO: http://b/23422763 - List<ResolveInfo> receivers = - pm.queryBroadcastReceiversAsUser(SCORE_INTENT, 0 /* flags */, UserHandle.USER_SYSTEM); - for (ResolveInfo receiver : receivers) { - // This field is a misnomer, see android.content.pm.ResolveInfo#activityInfo - final ActivityInfo receiverInfo = receiver.activityInfo; - if (receiverInfo == null) { - // Should never happen with queryBroadcastReceivers, but invalid nonetheless. - continue; + final List<String> potentialPkgs = getPotentialRecommendationProviderPackages(); + if (potentialPkgs.isEmpty()) { + if (DEBUG) { + Log.d(TAG, "No Network Recommendation Providers specified."); } - if (!permission.BROADCAST_NETWORK_PRIVILEGED.equals(receiverInfo.permission)) { - // Receiver doesn't require the BROADCAST_NETWORK_PRIVILEGED permission, which - // means anyone could trigger network scoring and flood the framework with score - // requests. - continue; + return null; + } + + final PackageManager pm = mContext.getPackageManager(); + for (int i = 0; i < potentialPkgs.size(); i++) { + final String potentialPkg = potentialPkgs.get(i); + + // Look for the recommendation service class and required receiver. + final ResolveInfo resolveServiceInfo = findRecommendationService(potentialPkg); + if (resolveServiceInfo != null) { + return new NetworkScorerAppData(potentialPkg, + resolveServiceInfo.serviceInfo.applicationInfo.uid, + resolveServiceInfo.serviceInfo.name); + } else { + if (DEBUG) { + Log.d(TAG, potentialPkg + " does not have the required components, skipping."); + } } - if (pm.checkPermission(permission.SCORE_NETWORKS, receiverInfo.packageName) != - PackageManager.PERMISSION_GRANTED) { - // Application doesn't hold the SCORE_NETWORKS permission, so the user never - // approved it as a network scorer. - continue; + } + + // None of the configured packages are valid. + return null; + } + + /** + * @return A priority order list of package names that have been granted the + * permission needed for them to act as a network recommendation provider. + * The packages in the returned list may not contain the other required + * network recommendation provider components so additional checks are required + * before making a package the network recommendation provider. + */ + public List<String> getPotentialRecommendationProviderPackages() { + final String[] packageArray = mContext.getResources().getStringArray( + R.array.config_networkRecommendationPackageNames); + if (packageArray == null || packageArray.length == 0) { + if (DEBUG) { + Log.d(TAG, "No Network Recommendation Providers specified."); } + return Collections.emptyList(); + } + + if (VERBOSE) { + Log.d(TAG, "Configured packages: " + TextUtils.join(", ", packageArray)); + } - // Optionally, this package may specify a configuration activity. - String configurationActivityClassName = null; - Intent intent = new Intent(NetworkScoreManager.ACTION_CUSTOM_ENABLE); - intent.setPackage(receiverInfo.packageName); - List<ResolveInfo> configActivities = pm.queryIntentActivities(intent, 0 /* flags */); - if (configActivities != null && !configActivities.isEmpty()) { - ActivityInfo activityInfo = configActivities.get(0).activityInfo; - if (activityInfo != null) { - configurationActivityClassName = activityInfo.name; + List<String> packages = new ArrayList<>(); + final PackageManager pm = mContext.getPackageManager(); + for (String potentialPkg : packageArray) { + if (pm.checkPermission(permission.SCORE_NETWORKS, potentialPkg) + == PackageManager.PERMISSION_GRANTED) { + packages.add(potentialPkg); + } else { + if (DEBUG) { + Log.d(TAG, potentialPkg + " has not been granted " + permission.SCORE_NETWORKS + + ", skipping."); } } + } - // Find the scoring service class we can bind to, if any. - String scoringServiceClassName = null; - Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS); - serviceIntent.setPackage(receiverInfo.packageName); - ResolveInfo resolveServiceInfo = pm.resolveService(serviceIntent, 0 /* flags */); - if (resolveServiceInfo != null && resolveServiceInfo.serviceInfo != null) { - scoringServiceClassName = resolveServiceInfo.serviceInfo.name; - } + return packages; + } - // NOTE: loadLabel will attempt to load the receiver's label and fall back to the - // app label if none is present. - scorers.add(new NetworkScorerAppData(receiverInfo.packageName, - receiverInfo.applicationInfo.uid, receiverInfo.loadLabel(pm), - configurationActivityClassName, scoringServiceClassName)); + private ResolveInfo findRecommendationService(String packageName) { + final PackageManager pm = mContext.getPackageManager(); + final int resolveFlags = 0; + + final Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS); + serviceIntent.setPackage(packageName); + final ResolveInfo resolveServiceInfo = + pm.resolveService(serviceIntent, resolveFlags); + + if (VERBOSE) { + Log.d(TAG, "Resolved " + serviceIntent + " to " + resolveServiceInfo); + } + + if (resolveServiceInfo != null && resolveServiceInfo.serviceInfo != null) { + return resolveServiceInfo; } - return scorers; + if (VERBOSE) { + Log.v(TAG, packageName + " does not have a service for " + serviceIntent); + } + return null; } /** @@ -182,10 +198,15 @@ public class NetworkScorerAppManager { * selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because * it was disabled or uninstalled). */ + @Nullable public NetworkScorerAppData getActiveScorer() { - String scorerPackage = Settings.Global.getString(mContext.getContentResolver(), - Settings.Global.NETWORK_SCORER_APP); - return getScorer(scorerPackage); + if (isNetworkRecommendationsDisabled()) { + // If recommendations are disabled then there can't be an active scorer. + return null; + } + + // Otherwise return the recommendation provider (which may be null). + return getNetworkRecommendationProviderData(); } /** @@ -195,33 +216,13 @@ public class NetworkScorerAppManager { * * @param packageName the packageName of the new scorer to use. If null, scoring will be * disabled. Otherwise, the scorer will only be set if it is a valid scorer application. - * @return true if the scorer was changed, or false if the package is not a valid scorer. + * @return true if the scorer was changed, or false if the package is not a valid scorer or + * a valid network recommendation provider exists. + * @deprecated Scorers are now selected from a configured list. */ + @Deprecated public boolean setActiveScorer(String packageName) { - String oldPackageName = Settings.Global.getString(mContext.getContentResolver(), - Settings.Global.NETWORK_SCORER_APP); - if (TextUtils.equals(oldPackageName, packageName)) { - // No change. - return true; - } - - Log.i(TAG, "Changing network scorer from " + oldPackageName + " to " + packageName); - - if (packageName == null) { - Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.NETWORK_SCORER_APP, null); - return true; - } else { - // We only make the change if the new package is valid. - if (getScorer(packageName) != null) { - Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.NETWORK_SCORER_APP, packageName); - return true; - } else { - Log.w(TAG, "Requested network scorer is not valid: " + packageName); - return false; - } - } + return false; } /** Determine whether the application with the given UID is the enabled scorer. */ @@ -230,7 +231,7 @@ public class NetworkScorerAppManager { if (defaultApp == null) { return false; } - if (callingUid != defaultApp.mPackageUid) { + if (callingUid != defaultApp.packageUid) { return false; } // To be extra safe, ensure the caller holds the SCORE_NETWORKS permission. It always @@ -239,17 +240,9 @@ public class NetworkScorerAppManager { PackageManager.PERMISSION_GRANTED; } - /** Returns the {@link NetworkScorerAppData} for the given app, or null if it's not a scorer. */ - public NetworkScorerAppData getScorer(String packageName) { - if (TextUtils.isEmpty(packageName)) { - return null; - } - Collection<NetworkScorerAppData> applications = getAllValidScorers(); - for (NetworkScorerAppData app : applications) { - if (packageName.equals(app.mPackageName)) { - return app; - } - } - return null; + private boolean isNetworkRecommendationsDisabled() { + final ContentResolver cr = mContext.getContentResolver(); + // A value of 1 indicates enabled. + return Settings.Global.getInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) != 1; } } diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index f081e937d776..fe9563d6d325 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -52,6 +52,17 @@ public class NetworkUtils { public native static void attachRaFilter(FileDescriptor fd, int packetType) throws SocketException; /** + * Attaches a socket filter that accepts L2-L4 signaling traffic required for IP connectivity. + * + * This includes: all ARP, ICMPv6 RS/RA/NS/NA messages, and DHCPv4 exchanges. + * + * @param fd the socket's {@link FileDescriptor}. + * @param packetType the hardware address type, one of ARPHRD_*. + */ + public native static void attachControlPacketFilter(FileDescriptor fd, int packetType) + throws SocketException; + + /** * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements. * @param fd the socket's {@link FileDescriptor}. * @param ifIndex the interface index. diff --git a/core/java/android/net/RecommendationRequest.java b/core/java/android/net/RecommendationRequest.java index 05ca1aa437f9..a96f90d57746 100644 --- a/core/java/android/net/RecommendationRequest.java +++ b/core/java/android/net/RecommendationRequest.java @@ -105,7 +105,16 @@ public final class RecommendationRequest implements Parcelable { } protected RecommendationRequest(Parcel in) { - mScanResults = (ScanResult[]) in.readParcelableArray(ScanResult.class.getClassLoader()); + final int resultCount = in.readInt(); + if (resultCount > 0) { + mScanResults = new ScanResult[resultCount]; + for (int i = 0; i < resultCount; i++) { + mScanResults[i] = in.readParcelable(ScanResult.class.getClassLoader()); + } + } else { + mScanResults = null; + } + mCurrentSelectedConfig = in.readParcelable(WifiConfiguration.class.getClassLoader()); mRequiredCapabilities = in.readParcelable(NetworkCapabilities.class.getClassLoader()); } @@ -117,7 +126,14 @@ public final class RecommendationRequest implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelableArray(mScanResults, flags); + if (mScanResults != null) { + dest.writeInt(mScanResults.length); + for (int i = 0; i < mScanResults.length; i++) { + dest.writeParcelable(mScanResults[i], flags); + } + } else { + dest.writeInt(0); + } dest.writeParcelable(mCurrentSelectedConfig, flags); dest.writeParcelable(mRequiredCapabilities, flags); } diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java index 0f3f957ca9bc..cf81e9191cca 100644 --- a/core/java/android/net/ScoredNetwork.java +++ b/core/java/android/net/ScoredNetwork.java @@ -104,7 +104,7 @@ public class ScoredNetwork implements Parcelable { * metered. */ public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve, boolean meteredHint) { - this(networkKey, rssiCurve, false /* meteredHint */, null /* attributes */); + this(networkKey, rssiCurve, meteredHint, null /* attributes */); } /** diff --git a/core/java/android/net/metrics/ConnectStats.java b/core/java/android/net/metrics/ConnectStats.java new file mode 100644 index 000000000000..3114ff81180d --- /dev/null +++ b/core/java/android/net/metrics/ConnectStats.java @@ -0,0 +1,123 @@ +/* + * 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.net.metrics; + +import android.system.OsConstants; +import android.util.IntArray; +import android.util.SparseIntArray; +import com.android.internal.util.TokenBucket; +import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.ConnectStatistics; +import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.Pair; + +/** + * A class that aggregates connect() statistics and helps build + * IpConnectivityLogClass.ConnectStatistics instances. + * + * {@hide} + */ +public class ConnectStats { + private final static int EALREADY = OsConstants.EALREADY; + private final static int EINPROGRESS = OsConstants.EINPROGRESS; + + /** How many events resulted in a given errno. */ + private final SparseIntArray mErrnos = new SparseIntArray(); + /** Latencies of blocking connects. TODO: add non-blocking connects latencies. */ + private final IntArray mLatencies = new IntArray(); + /** TokenBucket for rate limiting latency recording. */ + private final TokenBucket mLatencyTb; + /** Maximum number of latency values recorded. */ + private final int mMaxLatencyRecords; + /** Total count of successful connects. */ + private int mConnectCount = 0; + /** Total count of successful connects with IPv6 socket address. */ + private int mIpv6ConnectCount = 0; + + public ConnectStats(TokenBucket tb, int maxLatencyRecords) { + mLatencyTb = tb; + mMaxLatencyRecords = maxLatencyRecords; + } + + public ConnectStatistics toProto() { + ConnectStatistics stats = new ConnectStatistics(); + stats.connectCount = mConnectCount; + stats.ipv6AddrCount = mIpv6ConnectCount; + stats.latenciesMs = mLatencies.toArray(); + stats.errnosCounters = toPairArrays(mErrnos); + return stats; + } + + public void addEvent(int errno, int latencyMs, String ipAddr) { + if (isSuccess(errno)) { + countConnect(ipAddr); + countLatency(errno, latencyMs); + } else { + countError(errno); + } + } + + private void countConnect(String ipAddr) { + mConnectCount++; + if (isIPv6(ipAddr)) mIpv6ConnectCount++; + } + + private void countLatency(int errno, int ms) { + if (isNonBlocking(errno)) { + // Ignore connect() on non-blocking sockets + return; + } + if (!mLatencyTb.get()) { + // Rate limited + return; + } + if (mLatencies.size() >= mMaxLatencyRecords) { + // Hard limit the total number of latency measurements. + return; + } + mLatencies.add(ms); + } + + private void countError(int errno) { + final int newcount = mErrnos.get(errno, 0) + 1; + mErrnos.put(errno, newcount); + } + + private static boolean isSuccess(int errno) { + return (errno == 0) || isNonBlocking(errno); + } + + private static boolean isNonBlocking(int errno) { + // On non-blocking TCP sockets, connect() immediately returns EINPROGRESS. + // On non-blocking TCP sockets that are connecting, connect() immediately returns EALREADY. + return (errno == EINPROGRESS) || (errno == EALREADY); + } + + private static boolean isIPv6(String ipAddr) { + return ipAddr.contains(":"); + } + + private static Pair[] toPairArrays(SparseIntArray counts) { + final int s = counts.size(); + Pair[] pairs = new Pair[s]; + for (int i = 0; i < s; i++) { + Pair p = new Pair(); + p.key = counts.keyAt(i); + p.value = counts.valueAt(i); + pairs[i] = p; + } + return pairs; + } +} diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 3d5d900e4b43..e5aeb4b2139d 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -182,7 +182,7 @@ public abstract class BatteryStats implements Parcelable { * New in version 19: * - Wakelock data (wl) gets current and max times. */ - static final String CHECKIN_VERSION = "19"; + static final String CHECKIN_VERSION = "20"; /** * Old version, we hit 9 and ran out of room, need to remove. diff --git a/core/java/android/os/DropBoxManager.aidl b/core/java/android/os/DropBoxManager.aidl index 6474ec20a1d0..241e93bfd9d0 100644 --- a/core/java/android/os/DropBoxManager.aidl +++ b/core/java/android/os/DropBoxManager.aidl @@ -16,4 +16,4 @@ package android.os; -parcelable DropBoxManager.Entry; +parcelable DropBoxManager.Entry cpp_header "android/os/DropBoxManager.h"; diff --git a/core/java/android/os/IIncidentManager.aidl b/core/java/android/os/IIncidentManager.aidl new file mode 100644 index 000000000000..1a76648c2565 --- /dev/null +++ b/core/java/android/os/IIncidentManager.aidl @@ -0,0 +1,49 @@ +/** + * 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.os; + +import android.os.IIncidentReportCompletedListener; +import android.os.IIncidentReportStatusListener; +import android.os.IncidentReportArgs; + +/** + * Binder interface to report system health incidents. + * {@hide} + */ +oneway interface IIncidentManager { + + /** + * Takes a report with the given args, reporting status to the optional listener. + * + * When the report is completed, the system report listener will be notified. + */ + void reportIncident(in IncidentReportArgs args); + + /** + * Takes a report with the given args, reporting status to the optional listener. + * + * When the report is completed, the system report listener will be notified. + */ + void reportIncidentToStream(in IncidentReportArgs args, + @nullable IIncidentReportStatusListener listener, + FileDescriptor stream); + + /** + * Tell the incident daemon that the android system server is up and running. + */ + void systemRunning(); +} diff --git a/core/java/android/os/IIncidentReportCompletedListener.aidl b/core/java/android/os/IIncidentReportCompletedListener.aidl new file mode 100644 index 000000000000..2d66bf635a2a --- /dev/null +++ b/core/java/android/os/IIncidentReportCompletedListener.aidl @@ -0,0 +1,32 @@ +/** + * 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.os; + +/** + * Listener for incident report status + * + * {@hide} + */ +oneway interface IIncidentReportCompletedListener { + /** + * Called when there has been an incident report. + * + * The system service implementing this method should delete or move the file + * after it is finished with it. + */ + void onIncidentReport(String filename); +} diff --git a/core/java/android/os/IIncidentReportStatusListener.aidl b/core/java/android/os/IIncidentReportStatusListener.aidl new file mode 100644 index 000000000000..7be2ac8eabe2 --- /dev/null +++ b/core/java/android/os/IIncidentReportStatusListener.aidl @@ -0,0 +1,34 @@ +/** + * 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.os; + +/** + * Listener for incident report status + * + * {@hide} + */ +oneway interface IIncidentReportStatusListener { + const int STATUS_STARTING = 1; + const int STATUS_FINISHED = 2; + + void onReportStarted(); + + void onReportSectionStatus(int section, int status); + + void onReportFinished(); + void onReportFailed(); +} diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java new file mode 100644 index 000000000000..976d59472e69 --- /dev/null +++ b/core/java/android/os/IncidentManager.java @@ -0,0 +1,114 @@ +/* + * 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.os; + +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.content.Context; +import android.os.IIncidentManager; +import android.os.ServiceManager; +import android.provider.Settings; +import android.util.Slog; + +/** + * Class to take an incident report. + * + * @hide + */ +@SystemApi +@TestApi +public class IncidentManager { + private static final String TAG = "incident"; + + private Context mContext; + + /** + * @hide + */ + public IncidentManager(Context context) { + mContext = context; + } + + /** + * Take an incident report and put it in dropbox. + */ + public void reportIncident(IncidentReportArgs args) { + final IIncidentManager service = IIncidentManager.Stub.asInterface( + ServiceManager.getService("incident")); + if (service == null) { + Slog.e(TAG, "reportIncident can't find incident binder service"); + return; + } + + try { + service.reportIncident(args); + } catch (RemoteException ex) { + Slog.e(TAG, "reportIncident failed", ex); + } + } + + /** + * Convenience method to trigger an incident report and put it in dropbox. + * <p> + * The fields that are reported will be looked up in the system setting named by + * the settingName parameter. The setting must match one of these patterns: + * The string "disabled": The report will not be taken. + * The string "all": The report will taken with all sections. + * The string "none": The report will taken with no sections, but with the header. + * A comma separated list of field numbers: The report will have these fields. + * <p> + * The header parameter will be added as a header for the incident report. Fill in a + * {@link android.util.proto.ProtoOutputStream ProtoOutputStream}, and then call the + * {@link android.util.proto.ProtoOutputStream#bytes bytes()} method to retrieve + * the encoded data for the header. + */ + public void reportIncident(String settingName, byte[] headerProto) { + // Sections + String setting = Settings.System.getString(mContext.getContentResolver(), settingName); + IncidentReportArgs args; + try { + args = IncidentReportArgs.parseSetting(setting); + } catch (IllegalArgumentException ex) { + Slog.w(TAG, "Bad value for incident report setting '" + settingName + "'", ex); + return; + } + if (args == null) { + Slog.i(TAG, "Incident report requested but disabled: " + settingName); + return; + } + + // Header + args.addHeader(headerProto); + + // Look up the service + final IIncidentManager service = IIncidentManager.Stub.asInterface( + ServiceManager.getService("incident")); + if (service == null) { + Slog.e(TAG, "reportIncident can't find incident binder service"); + return; + } + + // Call the service + Slog.i(TAG, "Taking incident report: " + settingName); + try { + service.reportIncident(args); + } catch (RemoteException ex) { + Slog.e(TAG, "reportIncident failed", ex); + } + } +} + diff --git a/core/java/android/os/IncidentReportArgs.aidl b/core/java/android/os/IncidentReportArgs.aidl new file mode 100644 index 000000000000..bbddf59d9206 --- /dev/null +++ b/core/java/android/os/IncidentReportArgs.aidl @@ -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. + */ + +package android.os; + +parcelable IncidentReportArgs cpp_header "android/os/IncidentReportArgs.h"; + diff --git a/core/java/android/os/IncidentReportArgs.java b/core/java/android/os/IncidentReportArgs.java new file mode 100644 index 000000000000..ce2ae1071d01 --- /dev/null +++ b/core/java/android/os/IncidentReportArgs.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2005 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; + +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.IntArray; + +import java.util.ArrayList; + +/** + * The arguments for an incident report. + * {@hide} + */ +@SystemApi +@TestApi +public final class IncidentReportArgs implements Parcelable { + + private final IntArray mSections = new IntArray(); + private final ArrayList<byte[]> mHeaders = new ArrayList<byte[]>(); + private boolean mAll; + + /** + * Construct an incident report args with no fields. + */ + public IncidentReportArgs() { + } + + /** + * Construct an incdent report args from the given parcel. + */ + public IncidentReportArgs(Parcel in) { + readFromParcel(in); + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mAll ? 1 : 0); + + int N = mSections.size(); + out.writeInt(N); + for (int i=0; i<N; i++) { + out.writeInt(mSections.get(i)); + } + + N = mHeaders.size(); + out.writeInt(N); + for (int i=0; i<N; i++) { + out.writeByteArray(mHeaders.get(i)); + } + } + + public void readFromParcel(Parcel in) { + mAll = in.readInt() != 0; + + mSections.clear(); + int N = in.readInt(); + for (int i=0; i<N; i++) { + mSections.add(in.readInt()); + } + + mHeaders.clear(); + N = in.readInt(); + for (int i=0; i<N; i++) { + mHeaders.add(in.createByteArray()); + } + } + + public static final Parcelable.Creator<IncidentReportArgs> CREATOR + = new Parcelable.Creator<IncidentReportArgs>() { + public IncidentReportArgs createFromParcel(Parcel in) { + return new IncidentReportArgs(in); + } + + public IncidentReportArgs[] newArray(int size) { + return new IncidentReportArgs[size]; + } + }; + + /** + * Print this report as a string. + */ + public String toString() { + final StringBuilder sb = new StringBuilder("Incident("); + if (mAll) { + sb.append("all"); + } else { + final int N = mSections.size(); + if (N > 0) { + sb.append(mSections.get(0)); + } + for (int i=1; i<N; i++) { + sb.append(" "); + sb.append(mSections.get(i)); + } + } + sb.append(", "); + sb.append(mHeaders.size()); + sb.append(" headers)"); + return sb.toString(); + } + + /** + * Set this incident report to include all fields. + */ + public void setAll(boolean all) { + mAll = all; + if (all) { + mSections.clear(); + } + } + + /** + * Add this section to the incident report. + */ + public void addSection(int section) { + if (!mAll) { + mSections.add(section); + } + } + + /** + * Returns whether the incident report will include all fields. + */ + public boolean isAll() { + return mAll; + } + + /** + * Returns whether this section will be included in the incident report. + */ + public boolean containsSection(int section) { + return mAll || mSections.indexOf(section) >= 0; + } + + public int sectionCount() { + return mSections.size(); + } + + public void addHeader(byte[] header) { + mHeaders.add(header); + } + + /** + * Parses an incident report config as described in the system setting. + * + * @see IncidentManager#reportIncident + */ + public static IncidentReportArgs parseSetting(String setting) + throws IllegalArgumentException { + if (setting == null || setting.length() == 0) { + return null; + } + setting = setting.trim(); + if (setting.length() == 0 || "disabled".equals(setting)) { + return null; + } + + final IncidentReportArgs args = new IncidentReportArgs(); + + if ("all".equals(setting)) { + args.setAll(true); + return args; + } else if ("none".equals(setting)) { + return args; + } + + final String[] splits = setting.split(","); + final int N = splits.length; + for (int i=0; i<N; i++) { + final String str = splits[i].trim(); + if (str.length() == 0) { + continue; + } + int section; + try { + section = Integer.parseInt(str); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("Malformed setting. Bad integer at section" + + " index " + i + ": section='" + str + "' setting='" + setting + "'"); + } + if (section < 1) { + throw new IllegalArgumentException("Malformed setting. Illegal section at" + + " index " + i + ": section='" + str + "' setting='" + setting + "'"); + } + args.addSection(section); + } + + return args; + } +} + diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 8d4d0a558334..9d04929b2c8f 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -421,6 +421,12 @@ public final class PowerManager { public static final String REBOOT_SAFE_MODE = "safemode"; /** + * The 'reason' value used when rebooting the device without turning on the screen. + * @hide + */ + public static final String REBOOT_QUIESCENT = "quiescent"; + + /** * The value to pass as the 'reason' argument to android_reboot(). * @hide */ diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 4eee8541f3bb..9cd1a4246a58 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -398,6 +398,10 @@ public class Process { * make easily identifyable processes even if you are using the same base * <var>processClass</var> to start them. * + * When invokeWith is not null, the process will be started as a fresh app + * and not a zygote fork. Note that this is only allowed for uid 0 or when + * debugFlags contains DEBUG_ENABLE_DEBUGGER. + * * @param processClass The class to use as the process's main entry * point. * @param niceName A more readable name to use for the process. @@ -410,6 +414,7 @@ public class Process { * @param abi non-null the ABI this app should be started with. * @param instructionSet null-ok the instruction set to use. * @param appDataDir null-ok the data directory of the app. + * @param invokeWith null-ok the command to invoke with. * @param zygoteArgs Additional arguments to supply to the zygote process. * * @return An object that describes the result of the attempt to start the process. @@ -426,10 +431,11 @@ public class Process { String abi, String instructionSet, String appDataDir, + String invokeWith, String[] zygoteArgs) { return zygoteProcess.start(processClass, niceName, uid, gid, gids, debugFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, zygoteArgs); } /** @hide */ @@ -442,10 +448,11 @@ public class Process { String abi, String instructionSet, String appDataDir, + String invokeWith, String[] zygoteArgs) { return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids, debugFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, zygoteArgs); } /** diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index 6a751e808d4b..78820b537606 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -86,7 +86,7 @@ public class SystemProperties { */ public static String get(String key) { if (key.length() > PROP_NAME_MAX) { - throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX); + throw newKeyTooLargeException(key); } if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get(key); @@ -99,7 +99,7 @@ public class SystemProperties { */ public static String get(String key, String def) { if (key.length() > PROP_NAME_MAX) { - throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX); + throw newKeyTooLargeException(key); } if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get(key, def); @@ -115,7 +115,7 @@ public class SystemProperties { */ public static int getInt(String key, int def) { if (key.length() > PROP_NAME_MAX) { - throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX); + throw newKeyTooLargeException(key); } if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get_int(key, def); @@ -131,7 +131,7 @@ public class SystemProperties { */ public static long getLong(String key, long def) { if (key.length() > PROP_NAME_MAX) { - throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX); + throw newKeyTooLargeException(key); } if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get_long(key, def); @@ -152,7 +152,7 @@ public class SystemProperties { */ public static boolean getBoolean(String key, boolean def) { if (key.length() > PROP_NAME_MAX) { - throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX); + throw newKeyTooLargeException(key); } if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get_boolean(key, def); @@ -165,11 +165,10 @@ public class SystemProperties { */ public static void set(String key, String val) { if (key.length() > PROP_NAME_MAX) { - throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX); + throw newKeyTooLargeException(key); } if (val != null && val.length() > PROP_VALUE_MAX) { - throw new IllegalArgumentException("val.length > " + - PROP_VALUE_MAX); + throw newValueTooLargeException(key, val); } if (TRACK_KEY_ACCESS) onKeyAccess(key); native_set(key, val); @@ -197,6 +196,16 @@ public class SystemProperties { } } + private static IllegalArgumentException newKeyTooLargeException(String key) { + return new IllegalArgumentException("system property key '" + key + "' is longer than " + + PROP_NAME_MAX + " characters"); + } + + private static IllegalArgumentException newValueTooLargeException(String key, String value) { + return new IllegalArgumentException("value of system property '" + key + "' is longer than " + + PROP_VALUE_MAX + " characters: " + value); + } + /* * Notifies listeners that a system property has changed */ diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index 7e8cc0b8d754..6c01b3681244 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -85,6 +85,8 @@ public final class Trace { public static final long TRACE_TAG_DATABASE = 1L << 20; /** @hide */ public static final long TRACE_TAG_NETWORK = 1L << 21; + /** @hide */ + public static final long TRACE_TAG_ADB = 1L << 22; private static final long TRACE_TAG_NOT_READY = 1L << 63; private static final int MAX_SECTION_NAME_LEN = 127; diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 0d3b32858f85..0a32f0dd3720 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -253,6 +253,20 @@ public class UserManager { public static final String DISALLOW_REMOVE_USER = "no_remove_user"; /** + * Specifies if managed profiles of this user can be removed, other than by its profile owner. + * The default value is <code>false</code>. + * <p> + * This restriction can only be set by device owners. + * + * <p>Key for user restrictions. + * <p>Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_REMOVE_MANAGED_PROFILE = "no_remove_managed_profile"; + + /** * Specifies if a user is disallowed from enabling or * accessing debugging features. The default value is <code>false</code>. * @@ -322,8 +336,8 @@ public class UserManager { public static final String DISALLOW_FACTORY_RESET = "no_factory_reset"; /** - * Specifies if a user is disallowed from adding new users and - * profiles. This can only be set by device owners and profile owners on the primary user. + * Specifies if a user is disallowed from adding new users. This can only be set by device + * owners and profile owners on the primary user. * The default value is <code>false</code>. * <p>This restriction has no effect on secondary users and managed profiles since only the * primary user can add other users. @@ -337,6 +351,20 @@ public class UserManager { public static final String DISALLOW_ADD_USER = "no_add_user"; /** + * Specifies if a user is disallowed from adding managed profiles. + * <p>The default value for an unmanaged user is <code>false</code>. + * For users with a device owner set, the default is <code>true</code> + * <p>This restriction can only be set by device owners. + * + * <p>Key for user restrictions. + * <p>Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile"; + + /** * Specifies if a user is disallowed from disabling application * verification. The default value is <code>false</code>. * @@ -634,11 +662,14 @@ public class UserManager { * <code>false</code>. Setting this restriction has no effect if the bootloader is already * unlocked. * + * <p>Not for use by third-party applications. + * * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() * @hide */ + @SystemApi public static final String DISALLOW_OEM_UNLOCK = "no_oem_unlock"; /** @@ -1403,7 +1434,7 @@ public class UserManager { /** * Similar to {@link #createProfileForUser(String, int, int, String[])} - * except bypassing the checking of {@link UserManager#DISALLOW_ADD_USER}. + * except bypassing the checking of {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE}. * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * * @see #createProfileForUser(String, int, int, String[]) diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java index 1447e7d3fa63..466a7e35be68 100644 --- a/core/java/android/os/UserManagerInternal.java +++ b/core/java/android/os/UserManagerInternal.java @@ -120,7 +120,8 @@ public abstract class UserManagerInternal { public abstract void onEphemeralUserStop(int userId); /** - * Same as UserManager.createUser(), but bypasses the check for DISALLOW_ADD_USER. + * Same as UserManager.createUser(), but bypasses the check for + * {@link UserManager#DISALLOW_ADD_USER} and {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE} * * <p>Called by the {@link com.android.server.devicepolicy.DevicePolicyManagerService} when * createAndManageUser is called by the device owner. @@ -129,7 +130,8 @@ public abstract class UserManagerInternal { /** * Same as {@link UserManager#removeUser(int userHandle)}, but bypasses the check for - * {@link UserManager#DISALLOW_REMOVE_USER} and does not require the + * {@link UserManager#DISALLOW_REMOVE_USER} and + * {@link UserManager#DISALLOW_REMOVE_MANAGED_PROFILE} and does not require the * {@link android.Manifest.permission#MANAGE_USERS} permission. */ public abstract boolean removeUserEvenWhenDisallowed(int userId); diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index c45fe5a61852..5ac33a1768c2 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -170,6 +170,10 @@ public class ZygoteProcess { * make easily identifyable processes even if you are using the same base * <var>processClass</var> to start them. * + * When invokeWith is not null, the process will be started as a fresh app + * and not a zygote fork. Note that this is only allowed for uid 0 or when + * debugFlags contains DEBUG_ENABLE_DEBUGGER. + * * @param processClass The class to use as the process's main entry * point. * @param niceName A more readable name to use for the process. @@ -182,6 +186,7 @@ public class ZygoteProcess { * @param abi non-null the ABI this app should be started with. * @param instructionSet null-ok the instruction set to use. * @param appDataDir null-ok the data directory of the app. + * @param invokeWith null-ok the command to invoke with. * @param zygoteArgs Additional arguments to supply to the zygote process. * * @return An object that describes the result of the attempt to start the process. @@ -196,11 +201,12 @@ public class ZygoteProcess { String abi, String instructionSet, String appDataDir, + String invokeWith, String[] zygoteArgs) { try { return startViaZygote(processClass, niceName, uid, gid, gids, debugFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); @@ -330,6 +336,7 @@ public class ZygoteProcess { String abi, String instructionSet, String appDataDir, + String invokeWith, String[] extraArgs) throws ZygoteStartFailedEx { ArrayList<String> argsForZygote = new ArrayList<String>(); @@ -407,6 +414,11 @@ public class ZygoteProcess { argsForZygote.add("--app-data-dir=" + appDataDir); } + if (invokeWith != null) { + argsForZygote.add("--invoke-with"); + argsForZygote.add(invokeWith); + } + argsForZygote.add(processClass); if (extraArgs != null) { diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java index 5f27e3430bc4..a7941c7ebeeb 100644 --- a/core/java/android/service/autofill/AutoFillService.java +++ b/core/java/android/service/autofill/AutoFillService.java @@ -15,6 +15,11 @@ */ package android.service.autofill; +import static android.service.voice.VoiceInteractionSession.KEY_FLAGS; +import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE; +import static android.view.View.ASSIST_FLAG_SANITIZED_TEXT; +import static android.view.View.ASSIST_FLAG_NON_SANITIZED_TEXT; + import android.annotation.SdkConstant; import android.app.Activity; import android.app.Service; @@ -26,13 +31,14 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; -import android.service.voice.VoiceInteractionSession; import android.util.Log; import com.android.internal.os.HandlerCaller; import com.android.internal.os.IResultReceiver; import com.android.internal.os.SomeArgs; +// TODO(b/33197203): improve javadoc (class and methods) + /** * Top-level service of the current auto-fill service for a given user. * @@ -52,6 +58,11 @@ public abstract class AutoFillService extends Service { @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = "android.service.autofill.AutoFillService"; + // Bundle keys. + /** @hide */ + public static final String KEY_CALLBACK = "callback"; + + // Handler messages. private static final int MSG_CONNECT = 1; private static final int MSG_AUTO_FILL_ACTIVITY = 2; private static final int MSG_DISCONNECT = 3; @@ -59,14 +70,12 @@ public abstract class AutoFillService extends Service { private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() { @Override public void send(int resultCode, Bundle resultData) throws RemoteException { - final AssistStructure structure = resultData - .getParcelable(VoiceInteractionSession.KEY_STRUCTURE); - - final IBinder binder = resultData - .getBinder(VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK); + final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE); + final IBinder binder = resultData.getBinder(KEY_CALLBACK); + final int flags = resultData.getInt(KEY_FLAGS, 0); mHandlerCaller - .obtainMessageOO(MSG_AUTO_FILL_ACTIVITY, structure, binder).sendToTarget(); + .obtainMessageIOO(MSG_AUTO_FILL_ACTIVITY, flags, structure, binder).sendToTarget(); } }; @@ -100,7 +109,8 @@ public abstract class AutoFillService extends Service { final SomeArgs args = (SomeArgs) msg.obj; final AssistStructure structure = (AssistStructure) args.arg1; final IBinder binder = (IBinder) args.arg2; - requestAutoFill(structure, binder); + final int flags = msg.arg1; + requestAutoFill(structure, flags, binder); break; } case MSG_DISCONNECT: { onDisconnected(); @@ -145,19 +155,46 @@ public abstract class AutoFillService extends Service { } /** - * Handles an auto-fill request. + * Called when user requests service to auto-fill an {@link Activity}. * * @param structure {@link Activity}'s view structure . + * @param data bundle with optional parameters (currently none) which is passed along on + * subsequent calls (so it can be used by the service to share data). * @param cancellationSignal signal for observing cancel requests. - * @param callback object used to fulllfill the request. */ public abstract void onFillRequest(AssistStructure structure, - CancellationSignal cancellationSignal, FillCallback callback); + Bundle data, CancellationSignal cancellationSignal, FillCallback callback); - private void requestAutoFill(AssistStructure structure, IBinder binder) { - final FillCallback callback = new FillCallback(binder); - // TODO: hook up the cancelationSignal - onFillRequest(structure, new CancellationSignal(), callback); + /** + * Called when user requests service to save the fields of an {@link Activity}. + * + * @param structure {@link Activity}'s view structure. + * @param data same bundle passed to + * {@link #onFillRequest(AssistStructure, Bundle, CancellationSignal, FillCallback)}; + * might also contain with optional parameters (currently none). + * @param cancellationSignal signal for observing cancel requests. + * @param callback object used to notify the result of the request. + */ + public abstract void onSaveRequest(AssistStructure structure, + Bundle data, CancellationSignal cancellationSignal, SaveCallback callback); + + private void requestAutoFill(AssistStructure structure, int flags, IBinder binder) { + // TODO(b/33197203): pass the Bundle received from mAssistReceiver instead? + final Bundle data = new Bundle(); + switch (flags) { + case ASSIST_FLAG_SANITIZED_TEXT: + final FillCallback fillCallback = new FillCallback(binder); + // TODO(b/33197203): hook up the cancelationSignal + onFillRequest(structure, data, new CancellationSignal(), fillCallback); + break; + case ASSIST_FLAG_NON_SANITIZED_TEXT: + final SaveCallback saveCallback = new SaveCallback(binder); + // TODO(b/33197203): hook up the cancelationSignal + onSaveRequest(structure, null, new CancellationSignal(), saveCallback); + break; + default: + Log.w(TAG, "invalid flag on requestAutoFill(): " + flags); + } } /** diff --git a/core/java/android/service/autofill/FillCallback.java b/core/java/android/service/autofill/FillCallback.java index 23084409569f..3284b90304f5 100644 --- a/core/java/android/service/autofill/FillCallback.java +++ b/core/java/android/service/autofill/FillCallback.java @@ -17,7 +17,6 @@ package android.service.autofill; import static android.service.autofill.AutoFillService.DEBUG; -import static android.service.autofill.AutoFillService.TAG; import android.app.Activity; import android.app.assist.AssistStructure.ViewNode; @@ -38,6 +37,8 @@ import java.util.List; */ public final class FillCallback { + private static final String TAG = "FillCallback"; + private final IAutoFillCallback mCallback; /** @hide */ @@ -62,6 +63,13 @@ public final class FillCallback { } } + /** + * Notifies the {@link Activity} that the auto-fill request failed. + * + * @param message error message to be displayed. + * + * @throws RuntimeException if an error occurred while notifying the activity. + */ public void onFailure(CharSequence message) { if (DEBUG) Log.d(TAG, "onFailure(): message=" + message); diff --git a/core/java/android/service/autofill/IAutoFillManagerService.aidl b/core/java/android/service/autofill/IAutoFillManagerService.aidl index 76a25614f8ec..f1251c036469 100644 --- a/core/java/android/service/autofill/IAutoFillManagerService.aidl +++ b/core/java/android/service/autofill/IAutoFillManagerService.aidl @@ -25,11 +25,5 @@ import android.os.Bundle; */ oneway interface IAutoFillManagerService { - /** - * Request auto-fill on the top activity of a given user. - * - * @param userId user handle. - * @param activityToken optional token of activity that needs to be on top. - */ - void requestAutoFill(int userId, IBinder activityToken); + void requestAutoFill(IBinder activityToken, int userId, int flags); } diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java new file mode 100644 index 000000000000..4dc7392a963c --- /dev/null +++ b/core/java/android/service/autofill/SaveCallback.java @@ -0,0 +1,79 @@ +/* + * 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.service.autofill; + +import static android.service.autofill.AutoFillService.DEBUG; + +import android.app.Activity; +import android.app.assist.AssistStructure.ViewNode; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.util.Preconditions; + +/** + * Handles save requests from the {@link AutoFillService} into the {@link Activity} being + * auto-filled. + */ +public final class SaveCallback { + + private static final String TAG = "SaveCallback"; + + private final IAutoFillCallback mCallback; + + /** @hide */ + SaveCallback(IBinder binder) { + mCallback = IAutoFillCallback.Stub.asInterface(binder); + } + + /** + * Notifies the {@link Activity} that the save request succeeded. + * + * @param ids ids ({@link ViewNode#getAutoFillId()}) of the fields that were saved. + * + * @throws RuntimeException if an error occurred while saving the data. + */ + public void onSuccess(int[] ids) { + Preconditions.checkArgument(ids != null, "ids cannot be null"); + + Preconditions.checkArgument(ids.length > 0, "ids cannot be empty"); + + if (DEBUG) Log.d(TAG, "onSuccess(): ids=" + ids.length); + + // TODO(b/33197203): display which ids were saved + } + + /** + * Notifies the {@link Activity} that the save request failed. + * + * @param message error message to be displayed. + * + * @throws RuntimeException if an error occurred while notifying the activity. + */ + public void onFailure(CharSequence message) { + if (DEBUG) Log.d(TAG, "onFailure(): message=" + message); + + Preconditions.checkArgument(message != null, "message cannot be null"); + + try { + mCallback.showError(message.toString()); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } +} diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index 4e00c643d749..51ba8c728fd6 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -16,7 +16,10 @@ package android.service.notification; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SdkConstant; +import android.app.NotificationChannel; import android.content.Context; import android.content.Intent; import android.os.Handler; @@ -27,6 +30,7 @@ import android.os.RemoteException; import android.util.Log; import com.android.internal.os.SomeArgs; +import java.util.ArrayList; import java.util.List; /** @@ -79,9 +83,10 @@ public abstract class NotificationAssistantService extends NotificationListenerS public final void adjustNotification(Adjustment adjustment) { if (!isBound()) return; try { - getNotificationInterface().applyAdjustmentFromAssistantService(mWrapper, adjustment); + getNotificationInterface().applyAdjustmentFromAssistant(mWrapper, adjustment); } catch (android.os.RemoteException ex) { Log.v(TAG, "Unable to contact notification manager", ex); + throw ex.rethrowFromSystemServer(); } } @@ -95,12 +100,77 @@ public abstract class NotificationAssistantService extends NotificationListenerS public final void adjustNotifications(List<Adjustment> adjustments) { if (!isBound()) return; try { - getNotificationInterface().applyAdjustmentsFromAssistantService(mWrapper, adjustments); + getNotificationInterface().applyAdjustmentsFromAssistant(mWrapper, adjustments); } catch (android.os.RemoteException ex) { Log.v(TAG, "Unable to contact notification manager", ex); + throw ex.rethrowFromSystemServer(); } } + /** + * Creates a notification channel that notifications can be posted to for a given package. + * + * @param pkg The package to create a channel for. + * @param channel the channel to attempt to create. + */ + public void createNotificationChannel(@NonNull String pkg, + @NonNull NotificationChannel channel) { + if (!isBound()) return; + try { + getNotificationInterface().createNotificationChannelFromAssistant( + mWrapper, pkg, channel); + } catch (RemoteException e) { + Log.v(TAG, "Unable to contact notification manager", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Updates a notification channel for a given package. + * + * @param pkg The package to the channel belongs to. + * @param channel the channel to attempt to update. + */ + public void updateNotificationChannel(@NonNull String pkg, + @NonNull NotificationChannel channel) { + if (!isBound()) return; + try { + getNotificationInterface().updateNotificationChannelFromAssistant( + mWrapper, pkg, channel); + } catch (RemoteException e) { + Log.v(TAG, "Unable to contact notification manager", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns all notification channels belonging to the given package. + */ + public List<NotificationChannel> getNotificationChannels(@NonNull String pkg) { + if (!isBound()) return null; + try { + return getNotificationInterface().getNotificationChannelsFromAssistant( + mWrapper, pkg).getList(); + } catch (RemoteException e) { + Log.v(TAG, "Unable to contact notification manager", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Deletes the given notification channel. + */ + public void deleteNotificationChannel(@NonNull String pkg, @NonNull String channelId) { + if (!isBound()) return; + try { + getNotificationInterface().deleteNotificationChannelFromAssistant( + mWrapper, pkg, channelId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private class NotificationAssistantServiceWrapper extends NotificationListenerWrapper { @Override public void onNotificationEnqueued(IStatusBarNotificationHolder sbnHolder, diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index b0b206566a43..6276af398c43 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -246,7 +246,7 @@ public class StatusBarNotification implements Parcelable { } /** - * Returns a userHandle for the instance of the app that posted this notification. + * Returns a userid for whom this notification is intended. * * @deprecated Use {@link #getUser()} instead. */ diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 12aed251904f..48f3ac3f06f7 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -120,7 +120,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall /** @hide */ public static final String KEY_RECEIVER_EXTRAS = "receiverExtras"; /** @hide */ - public static final String KEY_AUTO_FILL_CALLBACK = "autoFillCallback"; + public static final String KEY_FLAGS = "flags"; final Context mContext; final HandlerCaller mHandlerCaller; diff --git a/core/java/android/text/AndroidCharacter.java b/core/java/android/text/AndroidCharacter.java index b150b6ea992f..c5f1a01f5927 100644 --- a/core/java/android/text/AndroidCharacter.java +++ b/core/java/android/text/AndroidCharacter.java @@ -17,9 +17,11 @@ package android.text; /** - * AndroidCharacter exposes some character properties that are not - * easily accessed from java.lang.Character. + * AndroidCharacter exposes some character properties that used to be not + * easily accessed from java.lang.Character, but are now available in ICU. + * @deprecated Use various methods from {@link android.icu.lang.UCharacter}, instead. */ +@Deprecated public class AndroidCharacter { public static final int EAST_ASIAN_WIDTH_NEUTRAL = 0; diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index de8aa5af5ccc..c3aac74d0f32 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -40,8 +40,6 @@ import android.text.style.TypefaceSpan; import android.text.style.URLSpan; import android.text.style.UnderlineSpan; -import com.android.internal.util.ArrayUtils; - import org.ccil.cowan.tagsoup.HTMLSchema; import org.ccil.cowan.tagsoup.Parser; import org.xml.sax.Attributes; @@ -342,19 +340,10 @@ public class Html { } private static String getTextDirection(Spanned text, int start, int end) { - final int len = end - start; - final byte[] levels = ArrayUtils.newUnpaddedByteArray(len); - final char[] buffer = TextUtils.obtain(len); - TextUtils.getChars(text, start, end, buffer, 0); - - int paraDir = AndroidBidi.bidi(Layout.DIR_REQUEST_DEFAULT_LTR, buffer, levels, len, - false /* no info */); - switch(paraDir) { - case Layout.DIR_RIGHT_TO_LEFT: - return " dir=\"rtl\""; - case Layout.DIR_LEFT_TO_RIGHT: - default: - return " dir=\"ltr\""; + if (TextDirectionHeuristics.FIRSTSTRONG_LTR.isRtl(text, start, end - start)) { + return " dir=\"rtl\""; + } else { + return " dir=\"ltr\""; } } diff --git a/core/java/android/text/TextClassificationManager.java b/core/java/android/text/TextClassificationManager.java new file mode 100644 index 000000000000..d4548f04e0a0 --- /dev/null +++ b/core/java/android/text/TextClassificationManager.java @@ -0,0 +1,62 @@ +/* + * 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.text; + +import android.annotation.NonNull; + +import java.util.Collections; +import java.util.List; + +/** + * Interface to the text classification service. + * This class uses machine learning techniques to infer things about text. + * Unless otherwise stated, methods of this class are blocking operations and should most likely not + * be called on the UI thread. + * + * <p> You do not instantiate this class directly; instead, retrieve it through + * {@link android.content.Context#getSystemService}. + * + * The TextClassificationManager serves as the default TextAssistant if none has been set. + * @see android.app.Activity#setTextAssistant(TextAssistant). + */ +public final class TextClassificationManager implements TextAssistant { + // TODO: Consider not making this class implement TextAssistant. + + /** @hide */ + public TextClassificationManager() {} + + /** + * Returns information containing languages that were detected in the provided text. + * This is a blocking operation and should most likely not be called on the UI thread. + */ + public List<TextLanguage> detectLanguages(@NonNull CharSequence text) { + // TODO: Implement this using the cld3 library. + return Collections.emptyList(); + } + + @Override + public TextSelection suggestSelection( + @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex) { + // TODO: Implement. + return TextAssistant.NO_OP.suggestSelection(text, selectionStartIndex, selectionEndIndex); + } + + @Override + public void addLinks(@NonNull Spannable text, int linkMask) { + // TODO: Implement. + } +} diff --git a/core/java/android/text/TextLanguage.java b/core/java/android/text/TextLanguage.java new file mode 100644 index 000000000000..eb834f120dee --- /dev/null +++ b/core/java/android/text/TextLanguage.java @@ -0,0 +1,85 @@ +/* + * 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.text; + +import android.annotation.NonNull; + +import com.android.internal.util.Preconditions; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Specifies detected languages for a section of text indicated by a start and end index. + */ +public final class TextLanguage { + + private final int mStartIndex; + private final int mEndIndex; + private final Map<String, Float> mLanguageConfidence; + + /** + * Initializes a TextLanguage object. + * + * @param startIndex the start index of the detected languages in the text provided to generate + * this object. + * @param endIndex the end index of the detected languages in the text provided to generate this + * object. + * @param languageConfidence a map of detected language to confidence score. The language string + * is a BCP-47 language tag. + * @throws NullPointerException if languageConfidence is null or contains a null key or value. + */ + public TextLanguage(int startIndex, int endIndex, + @NonNull Map<String, Float> languageConfidence) { + mStartIndex = startIndex; + mEndIndex = endIndex; + + Map<String, Float> map = new LinkedHashMap<>(); + Preconditions.checkNotNull(languageConfidence).entrySet().stream() + .sorted(Map.Entry.comparingByValue()) + .forEach(entry -> map.put( + Preconditions.checkNotNull(entry.getKey()), + Preconditions.checkNotNull(entry.getValue()))); + mLanguageConfidence = Collections.unmodifiableMap(map); + } + + /** + * Returns the start index of the detected languages in the text provided to generate this + * object. + */ + public int getStartIndex() { + return mStartIndex; + } + + /** + * Returns the end index of the detected languages in the text provided to generate this object. + */ + public int getEndIndex() { + return mEndIndex; + } + + /** + * Returns an unmodifiable map of detected language to confidence score. The map entries are + * ordered from high confidence score (1) to low confidence score (0). The language string is a + * BCP-47 language tag. + */ + @NonNull + public Map<String, Float> getLanguageConfidence() { + return mLanguageConfidence; + } +} diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 6262fc2a4846..58bc9a79d577 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.PluralsRes; import android.content.Context; import android.content.res.Resources; +import android.icu.lang.UCharacter; import android.icu.util.ULocale; import android.os.Parcel; import android.os.Parcelable; @@ -545,9 +546,10 @@ public class TextUtils { } public char charAt(int off) { - return AndroidCharacter.getMirror(mSource.charAt(mEnd - 1 - off)); + return (char) UCharacter.getMirror(mSource.charAt(mEnd - 1 - off)); } + @SuppressWarnings("deprecation") public void getChars(int start, int end, char[] dest, int destoff) { TextUtils.getChars(mSource, start + mStart, end + mStart, dest, destoff); diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index f16e714d82d1..e19b2c71b841 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -19,6 +19,10 @@ package android.text.format; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.icu.text.MeasureFormat; +import android.icu.text.MeasureFormat.FormatWidth; +import android.icu.util.Measure; +import android.icu.util.MeasureUnit; import com.android.internal.R; @@ -351,26 +355,54 @@ public class DateUtils } /** - * Return given duration in a human-friendly format. For example, "4 - * minutes" or "1 second". Returns only largest meaningful unit of time, + * Returns the given duration in a human-friendly format. For example, + * "4 minutes" or "1 second". Returns only the largest meaningful unit of time, * from seconds up to hours. * * @hide */ public static CharSequence formatDuration(long millis) { - final Resources res = Resources.getSystem(); + return formatDuration(millis, LENGTH_LONG); + } + + /** + * Returns the given duration in a human-friendly format. For example, + * "4 minutes" or "1 second". Returns only the largest meaningful unit of time, + * from seconds up to hours. + * <p> + * You can use abbrev to specify a preference for abbreviations (but note that some + * locales may not have abbreviations). Use LENGTH_LONG for the full spelling (e.g. "2 hours"), + * LENGTH_SHORT for the abbreviated spelling if available (e.g. "2 hr"), and LENGTH_SHORTEST for + * the briefest form available (e.g. "2h"). + * @hide + */ + public static CharSequence formatDuration(long millis, int abbrev) { + final FormatWidth width; + switch (abbrev) { + case LENGTH_LONG: + width = FormatWidth.WIDE; + break; + case LENGTH_SHORT: + case LENGTH_SHORTER: + case LENGTH_MEDIUM: + width = FormatWidth.SHORT; + break; + case LENGTH_SHORTEST: + width = FormatWidth.NARROW; + break; + default: + width = FormatWidth.WIDE; + } + final MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(), width); if (millis >= HOUR_IN_MILLIS) { final int hours = (int) ((millis + 1800000) / HOUR_IN_MILLIS); - return res.getQuantityString( - com.android.internal.R.plurals.duration_hours, hours, hours); + return formatter.format(new Measure(hours, MeasureUnit.HOUR)); } else if (millis >= MINUTE_IN_MILLIS) { final int minutes = (int) ((millis + 30000) / MINUTE_IN_MILLIS); - return res.getQuantityString( - com.android.internal.R.plurals.duration_minutes, minutes, minutes); + return formatter.format(new Measure(minutes, MeasureUnit.MINUTE)); } else { final int seconds = (int) ((millis + 500) / SECOND_IN_MILLIS); - return res.getQuantityString( - com.android.internal.R.plurals.duration_seconds, seconds, seconds); + return formatter.format(new Measure(seconds, MeasureUnit.SECOND)); } } diff --git a/core/java/android/text/style/RasterizerSpan.java b/core/java/android/text/style/RasterizerSpan.java index cae96403b2e5..f0be50ab065c 100644 --- a/core/java/android/text/style/RasterizerSpan.java +++ b/core/java/android/text/style/RasterizerSpan.java @@ -19,6 +19,9 @@ package android.text.style; import android.graphics.Rasterizer; import android.text.TextPaint; +/** + * @removed Rasterizer is not supported for hw-accerlerated and PDF rendering + */ public class RasterizerSpan extends CharacterStyle implements UpdateAppearance { private Rasterizer mRasterizer; diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java index 6196a97024e6..99f6c2a9c58d 100644 --- a/core/java/android/util/EventLog.java +++ b/core/java/android/util/EventLog.java @@ -56,6 +56,7 @@ public class EventLog { /** A previously logged event read from the logs. Instances are thread safe. */ public static final class Event { private final ByteBuffer mBuffer; + private Exception mLastWtf; // Layout of event log entry received from Android logger. // see system/core/include/log/logger.h @@ -116,13 +117,19 @@ public class EventLog { offset = V1_PAYLOAD_START; } mBuffer.limit(offset + mBuffer.getShort(LENGTH_OFFSET)); + if ((offset + DATA_OFFSET) >= mBuffer.limit()) { + // no payload + return null; + } mBuffer.position(offset + DATA_OFFSET); // Just after the tag. return decodeObject(); } catch (IllegalArgumentException e) { Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e); + mLastWtf = e; return null; } catch (BufferUnderflowException e) { Log.wtf(TAG, "Truncated entry payload: tag=" + getTag(), e); + mLastWtf = e; return null; } } @@ -148,6 +155,7 @@ public class EventLog { return new String(mBuffer.array(), start, length, "UTF-8"); } catch (UnsupportedEncodingException e) { Log.wtf(TAG, "UTF-8 is not supported", e); + mLastWtf = e; return null; } @@ -173,6 +181,24 @@ public class EventLog { byte[] bytes = mBuffer.array(); return Arrays.copyOf(bytes, bytes.length); } + + /** + * Retreive the last WTF error generated by this object. + * @hide + */ + //VisibleForTesting + public Exception getLastError() { + return mLastWtf; + } + + /** + * Clear the error state for this object. + * @hide + */ + //VisibleForTesting + public void clearError() { + mLastWtf = null; + } } // We assume that the native methods deal with any concurrency issues. diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java index 106f1729569f..8f9ae0e3f48c 100644 --- a/core/java/android/view/DisplayListCanvas.java +++ b/core/java/android/view/DisplayListCanvas.java @@ -23,6 +23,7 @@ import android.graphics.CanvasProperty; import android.graphics.Paint; import android.util.Pools.SynchronizedPool; +import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; /** @@ -230,30 +231,37 @@ public final class DisplayListCanvas extends RecordingCanvas { } } + + // ------------------ Fast JNI ------------------------ + @FastNative + private static native void nCallDrawGLFunction(long renderer, + long drawGLFunction, Runnable releasedCallback); + + + // ------------------ Critical JNI ------------------------ + + @CriticalNative private static native long nCreateDisplayListCanvas(long node, int width, int height); - @FastNative + @CriticalNative private static native void nResetDisplayListCanvas(long canvas, long node, int width, int height); - @FastNative + @CriticalNative private static native int nGetMaximumTextureWidth(); - @FastNative + @CriticalNative private static native int nGetMaximumTextureHeight(); - @FastNative + @CriticalNative private static native void nInsertReorderBarrier(long renderer, boolean enableReorder); - @FastNative - private static native void nCallDrawGLFunction(long renderer, - long drawGLFunction, Runnable releasedCallback); - @FastNative + @CriticalNative private static native long nFinishRecording(long renderer); - @FastNative + @CriticalNative private static native void nDrawRenderNode(long renderer, long renderNode); - @FastNative + @CriticalNative private static native void nDrawLayer(long renderer, long layer); - @FastNative + @CriticalNative private static native void nDrawCircle(long renderer, long propCx, long propCy, long propRadius, long propPaint); - @FastNative + @CriticalNative private static native void nDrawRoundRect(long renderer, long propLeft, long propTop, long propRight, long propBottom, long propRx, long propRy, long propPaint); } diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java index d563f5110600..3f3d5190fac7 100644 --- a/core/java/android/view/FocusFinder.java +++ b/core/java/android/view/FocusFinder.java @@ -16,6 +16,8 @@ package android.view; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Rect; import android.util.ArrayMap; import android.util.SparseArray; @@ -24,6 +26,7 @@ import android.util.SparseBooleanArray; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.List; /** * The algorithm used for finding the next focusable view in a given direction @@ -102,6 +105,30 @@ public class FocusFinder { return next; } + /** + * Find the root of the next keyboard navigation cluster after the current one. + * @param root Thew view tree to look inside. Cannot be null + * @param currentCluster The starting point of the search. Null means the default cluster + * @param direction Direction to look + * @return The next cluster, or null if none exists + */ + public View findNextKeyboardNavigationCluster( + @NonNull ViewGroup root, @Nullable View currentCluster, int direction) { + View next = null; + + final ArrayList<View> clusters = mTempList; + try { + clusters.clear(); + root.addKeyboardNavigationClusters(clusters, direction); + if (!clusters.isEmpty()) { + next = findNextKeyboardNavigationCluster(root, currentCluster, clusters, direction); + } + } finally { + clusters.clear(); + } + return next; + } + private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) { // check for user specified next focus View userSetNextFocus = focused.findUserSetNextFocus(root, direction); @@ -170,6 +197,24 @@ public class FocusFinder { } } + private View findNextKeyboardNavigationCluster(ViewGroup root, View currentCluster, + List<View> clusters, int direction) { + final int count = clusters.size(); + + switch (direction) { + case View.FOCUS_FORWARD: + case View.FOCUS_DOWN: + case View.FOCUS_RIGHT: + return getNextKeyboardNavigationCluster(root, currentCluster, clusters, count); + case View.FOCUS_BACKWARD: + case View.FOCUS_UP: + case View.FOCUS_LEFT: + return getPreviousKeyboardNavigationCluster(root, currentCluster, clusters, count); + default: + throw new IllegalArgumentException("Unknown direction: " + direction); + } + } + private View findNextFocusInRelativeDirection(ArrayList<View> focusables, ViewGroup root, View focused, Rect focusedRect, int direction) { try { @@ -270,6 +315,45 @@ public class FocusFinder { return null; } + private static View getNextKeyboardNavigationCluster(ViewGroup root, View currentCluster, + List<View> clusters, int count) { + if (currentCluster == null) { + // The current cluster is the default one. + // The next cluster after the default one is the first one. + // Note that the caller guarantees that 'clusters' is not empty. + return clusters.get(0); + } + + final int position = clusters.lastIndexOf(currentCluster); + if (position >= 0 && position + 1 < count) { + // Return the next non-default cluster if we can find it. + return clusters.get(position + 1); + } + + // The current cluster is the last one. The next one is the default one, i.e. the root. + return root; + } + + private static View getPreviousKeyboardNavigationCluster(ViewGroup root, View currentCluster, + List<View> clusters, int count) { + if (currentCluster == null) { + // The current cluster is the default one. + // The previous cluster before the default one is the last one. + // Note that the caller guarantees that 'clusters' is not empty. + return clusters.get(count - 1); + } + + final int position = clusters.indexOf(currentCluster); + if (position > 0) { + // Return the previous non-default cluster if we can find it. + return clusters.get(position - 1); + } + + // The current cluster is the first one. The previous one is the default one, i.e. the + // root. + return root; + } + /** * Is rect1 a better candidate than rect2 for a focus search in a particular * direction from a source rect? This is the core routine that determines diff --git a/core/java/android/view/IPinnedStackListener.aidl b/core/java/android/view/IPinnedStackListener.aidl index 3050dbba062f..3c348c50369f 100644 --- a/core/java/android/view/IPinnedStackListener.aidl +++ b/core/java/android/view/IPinnedStackListener.aidl @@ -16,13 +16,14 @@ package android.view; +import android.content.pm.ParceledListSlice; import android.view.IPinnedStackController; /** - * Listener for changes to the pinned stack made by the WindowManager. - * - * @hide - */ + * Listener for changes to the pinned stack made by the WindowManager. + * + * @hide + */ oneway interface IPinnedStackListener { /** @@ -36,4 +37,24 @@ oneway interface IPinnedStackListener { * is first registered to allow the listener to synchronized its state with the controller. */ void onBoundsChanged(boolean adjustedForIme); + + /** + * Called when window manager decides to adjust the minimized state, or when the listener + * is first registered to allow the listener to synchronized its state with the controller. + */ + void onMinimizedStateChanged(boolean isMinimized); + + /** + * Called when window manager decides to adjust the snap-to-edge state, which determines whether + * to snap only to the corners of the screen or to the closest edge. It is called when the + * listener is first registered to allow the listener to synchronized its state with the + * controller. + */ + void onSnapToEdgeStateChanged(boolean isSnapToEdge); + + /** + * Called when the set of actions for the current PiP activity changes, or when the listener + * is first registered to allow the listener to synchronized its state with the controller. + */ + void onActionsChanged(in ParceledListSlice actions); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index bd1ad8e01b3c..21875fe168bc 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -89,51 +89,33 @@ interface IWindowManager void addWindowToken(IBinder token, int type, int displayId); void removeWindowToken(IBinder token, int displayId); /** - * Adds an application token to the specified task Id. + * Creates the object representation for the application token in the window manager and adds it + * to the specified task Id. + * * @param addPos The position to add the token to in the task. * @param token The token to add. * @param taskId The Id of the task we are adding the token to. - * @param stackId Stack Id to create a new Task with the input task Id on - * if the task doesn't exist yet. * @param requestedOrientation Orientation to use. * @param fullscreen True if the application token is fullscreen. * @param showWhenLocked True if the application token should be shown when locked. - * @param userId Id of user to associate the token with. * @param configChanges Input configuration changes. * @param voiceInteraction True if the token is in voice interaction mode. * @param launchTaskBehind True if the token is been launched from behind. - * @param taskBounds Bounds to use when creating a new Task with the input task Id if - * the task doesn't exist yet. - * @param overrideConfig Override configuration that is being used with this task. - * @param taskResizeMode The resize mode of the task. * @param alwaysFocusable True if the app windows are always focusable regardless of the stack * they are in. - * @param homeTask True if this is the task. * @param targetSdkVersion The application's target SDK version - * @param isOnTopLauncher True if this task is an on-top launcher. */ - void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId, - int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId, - int configChanges, boolean voiceInteraction, boolean launchTaskBehind, - in Rect taskBounds, in Configuration overrideConfig, int taskResizeMode, - boolean alwaysFocusable, boolean homeTask, int targetSdkVersion, - int rotationAnimationHint, boolean isOnTopLauncher); + void addAppToken(int addPos, IApplicationToken token, int taskId, int requestedOrientation, + boolean fullscreen, boolean showWhenLocked, int configChanges, boolean voiceInteraction, + boolean launchTaskBehind, boolean alwaysFocusable, int targetSdkVersion, + int rotationAnimationHint); /** + * Adds an already existing application token on the window manager side to the input task id. * * @param token The token we are adding to the input task Id. * @param taskId The Id of the task we are adding the token to. - * @param stackId Stack Id to create a new Task with the input task Id on - * if the task doesn't exist yet. - * @param taskBounds Bounds to use when creating a new Task with the input task Id if - * the task doesn't exist yet. - * @param overrideConfig Override configuration that is being used with this task. - * @param taskResizeMode The resize mode of the task. - * @param homeTask True if this is the task. - * @param isOnTopLauncher True if this task is an on-top launcher. - */ - void setAppTask(IBinder token, int taskId, int stackId, in Rect taskBounds, - in Configuration overrideConfig, int taskResizeMode, boolean homeTask, - boolean isOnTopLauncher); + */ + void addAppToTask(IBinder token, int taskId); void setAppOrientation(IApplicationToken token, int requestedOrientation); int getAppOrientation(IApplicationToken token); void setFocusedApp(IBinder token, boolean moveFocusNow); diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java index 8eca43158ef2..fc666975ee25 100644 --- a/core/java/android/view/RenderNode.java +++ b/core/java/android/view/RenderNode.java @@ -24,6 +24,7 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.AnimatedVectorDrawable; +import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; import libcore.util.NativeAllocationRegistry; @@ -138,7 +139,9 @@ public class RenderNode { RenderNode.class.getClassLoader(), nGetNativeFinalizer(), 1024); } + // Note: written by native when display lists are detached private boolean mValid; + // Do not access directly unless you are ThreadedRenderer final long mNativeRenderNode; private final View mOwningView; @@ -792,13 +795,6 @@ public class RenderNode { return nGetDebugSize(mNativeRenderNode); } - /** - * Called by native when the passed displaylist is removed from the draw tree - */ - void onRenderNodeDetached() { - discardDisplayList(); - } - /////////////////////////////////////////////////////////////////////////// // Animations /////////////////////////////////////////////////////////////////////////// @@ -828,14 +824,13 @@ public class RenderNode { } /////////////////////////////////////////////////////////////////////////// - // Native methods + // Regular JNI methods /////////////////////////////////////////////////////////////////////////// // Intentionally not static because it acquires a reference to 'this' private native long nCreate(String name); private static native long nGetNativeFinalizer(); - private static native void nSetDisplayList(long renderNode, long newData); private static native void nOutput(long renderNode); private static native int nGetDebugSize(long renderNode); private static native void nRequestPositionUpdates(long renderNode, SurfaceView callback); @@ -845,132 +840,141 @@ public class RenderNode { private static native void nAddAnimator(long renderNode, long animatorPtr); private static native void nEndAllAnimators(long renderNode); + + /////////////////////////////////////////////////////////////////////////// + // @FastNative methods + /////////////////////////////////////////////////////////////////////////// + + @FastNative + private static native void nSetDisplayList(long renderNode, long newData); + + /////////////////////////////////////////////////////////////////////////// - // Fast native methods + // @CriticalNative methods /////////////////////////////////////////////////////////////////////////// // Matrix - @FastNative + @CriticalNative private static native void nGetTransformMatrix(long renderNode, long nativeMatrix); - @FastNative + @CriticalNative private static native void nGetInverseTransformMatrix(long renderNode, long nativeMatrix); - @FastNative + @CriticalNative private static native boolean nHasIdentityMatrix(long renderNode); // Properties - @FastNative + @CriticalNative private static native boolean nOffsetTopAndBottom(long renderNode, int offset); - @FastNative + @CriticalNative private static native boolean nOffsetLeftAndRight(long renderNode, int offset); - @FastNative + @CriticalNative private static native boolean nSetLeftTopRightBottom(long renderNode, int left, int top, int right, int bottom); - @FastNative + @CriticalNative private static native boolean nSetBottom(long renderNode, int bottom); - @FastNative + @CriticalNative private static native boolean nSetRight(long renderNode, int right); - @FastNative + @CriticalNative private static native boolean nSetTop(long renderNode, int top); - @FastNative + @CriticalNative private static native boolean nSetLeft(long renderNode, int left); - @FastNative + @CriticalNative private static native boolean nSetCameraDistance(long renderNode, float distance); - @FastNative + @CriticalNative private static native boolean nSetPivotY(long renderNode, float pivotY); - @FastNative + @CriticalNative private static native boolean nSetPivotX(long renderNode, float pivotX); - @FastNative + @CriticalNative private static native boolean nSetLayerType(long renderNode, int layerType); - @FastNative + @CriticalNative private static native boolean nSetLayerPaint(long renderNode, long paint); - @FastNative + @CriticalNative private static native boolean nSetClipToBounds(long renderNode, boolean clipToBounds); - @FastNative + @CriticalNative private static native boolean nSetClipBounds(long renderNode, int left, int top, int right, int bottom); - @FastNative + @CriticalNative private static native boolean nSetClipBoundsEmpty(long renderNode); - @FastNative + @CriticalNative private static native boolean nSetProjectBackwards(long renderNode, boolean shouldProject); - @FastNative + @CriticalNative private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldRecieve); - @FastNative + @CriticalNative private static native boolean nSetOutlineRoundRect(long renderNode, int left, int top, int right, int bottom, float radius, float alpha); - @FastNative + @CriticalNative private static native boolean nSetOutlineConvexPath(long renderNode, long nativePath, float alpha); - @FastNative + @CriticalNative private static native boolean nSetOutlineEmpty(long renderNode); - @FastNative + @CriticalNative private static native boolean nSetOutlineNone(long renderNode); - @FastNative + @CriticalNative private static native boolean nHasShadow(long renderNode); - @FastNative + @CriticalNative private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline); - @FastNative + @CriticalNative private static native boolean nSetRevealClip(long renderNode, boolean shouldClip, float x, float y, float radius); - @FastNative + @CriticalNative private static native boolean nSetAlpha(long renderNode, float alpha); - @FastNative + @CriticalNative private static native boolean nSetHasOverlappingRendering(long renderNode, boolean hasOverlappingRendering); - @FastNative + @CriticalNative private static native boolean nSetElevation(long renderNode, float lift); - @FastNative + @CriticalNative private static native boolean nSetTranslationX(long renderNode, float translationX); - @FastNative + @CriticalNative private static native boolean nSetTranslationY(long renderNode, float translationY); - @FastNative + @CriticalNative private static native boolean nSetTranslationZ(long renderNode, float translationZ); - @FastNative + @CriticalNative private static native boolean nSetRotation(long renderNode, float rotation); - @FastNative + @CriticalNative private static native boolean nSetRotationX(long renderNode, float rotationX); - @FastNative + @CriticalNative private static native boolean nSetRotationY(long renderNode, float rotationY); - @FastNative + @CriticalNative private static native boolean nSetScaleX(long renderNode, float scaleX); - @FastNative + @CriticalNative private static native boolean nSetScaleY(long renderNode, float scaleY); - @FastNative + @CriticalNative private static native boolean nSetStaticMatrix(long renderNode, long nativeMatrix); - @FastNative + @CriticalNative private static native boolean nSetAnimationMatrix(long renderNode, long animationMatrix); - @FastNative + @CriticalNative private static native boolean nHasOverlappingRendering(long renderNode); - @FastNative + @CriticalNative private static native boolean nGetClipToOutline(long renderNode); - @FastNative + @CriticalNative private static native float nGetAlpha(long renderNode); - @FastNative + @CriticalNative private static native float nGetCameraDistance(long renderNode); - @FastNative + @CriticalNative private static native float nGetScaleX(long renderNode); - @FastNative + @CriticalNative private static native float nGetScaleY(long renderNode); - @FastNative + @CriticalNative private static native float nGetElevation(long renderNode); - @FastNative + @CriticalNative private static native float nGetTranslationX(long renderNode); - @FastNative + @CriticalNative private static native float nGetTranslationY(long renderNode); - @FastNative + @CriticalNative private static native float nGetTranslationZ(long renderNode); - @FastNative + @CriticalNative private static native float nGetRotation(long renderNode); - @FastNative + @CriticalNative private static native float nGetRotationX(long renderNode); - @FastNative + @CriticalNative private static native float nGetRotationY(long renderNode); - @FastNative + @CriticalNative private static native boolean nIsPivotExplicitlySet(long renderNode); - @FastNative + @CriticalNative private static native float nGetPivotX(long renderNode); - @FastNative + @CriticalNative private static native float nGetPivotY(long renderNode); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 316b12338bc2..12658bd8f0ce 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -128,6 +128,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -2474,7 +2475,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 1 PFLAG3_SCROLL_INDICATOR_START * 1 PFLAG3_SCROLL_INDICATOR_END * 1 PFLAG3_ASSIST_BLOCKED - * xxxxxxxx * NO LONGER NEEDED, SHOULD BE REUSED * + * 1 PFLAG3_CLUSTER + * 1 PFLAG3_SECTION + * xxxxxx * NO LONGER NEEDED, SHOULD BE REUSED * * 1 PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE * 1 PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED * 1 PFLAG3_TEMPORARY_DETACH @@ -2673,6 +2676,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback, static final int PFLAG3_ASSIST_BLOCKED = 0x4000; /** + * Flag indicating that the view is a root of a keyboard navigation cluster. + * + * @see #isKeyboardNavigationCluster() + * @see #setKeyboardNavigationCluster(boolean) + */ + private static final int PFLAG3_CLUSTER = 0x8000; + + /** + * Flag indicating that the view is a root of a keyboard navigation section. + * + * @see #isKeyboardNavigationSection() + * @see #setKeyboardNavigationSection(boolean) + */ + private static final int PFLAG3_SECTION = 0x10000; + + /** * Whether this view has rendered elements that overlap (see {@link * #hasOverlappingRendering()}, {@link #forceHasOverlappingRendering(boolean)}, and * {@link #getHasOverlappingRendering()} ). The value in this bit is only valid when @@ -3740,6 +3759,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ int mNextFocusForwardId = View.NO_ID; + /** + * User-specified next keyboard navigation cluster. + */ + int mNextClusterForwardId = View.NO_ID; + + /** + * User-specified next keyboard navigation section. + */ + int mNextSectionForwardId = View.NO_ID; + private CheckForLongPress mPendingCheckForLongPress; private CheckForTap mPendingCheckForTap = null; private PerformClick mPerformClick; @@ -3964,6 +3993,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int mLayerType = LAYER_TYPE_NONE; Paint mLayerPaint; + + /** + * <p>When setting a {@link android.app.assist.AssistStructure}, its nodes should not contain + * PII (Personally Identifiable Information). + */ + // TODO(b/33197203) (b/33269702): improve documentation: mention all cases, show examples, etc. + public static final int ASSIST_FLAG_SANITIZED_TEXT = 0x1; + + /** + * <p>When setting a {@link android.app.assist.AssistStructure}, its nodes should contain all + * type of data, even sensitive PII (Personally Identifiable Information) like passwords or + * credit card numbers. + */ + public static final int ASSIST_FLAG_NON_SANITIZED_TEXT = 0x2; + /** * Set to true when drawing cache is enabled and cannot be created. * @@ -4516,6 +4560,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case R.styleable.View_nextFocusForward: mNextFocusForwardId = a.getResourceId(attr, View.NO_ID); break; + case R.styleable.View_nextClusterForward: + mNextClusterForwardId = a.getResourceId(attr, View.NO_ID); + break; + case R.styleable.View_nextSectionForward: + mNextSectionForwardId = a.getResourceId(attr, View.NO_ID); + break; case R.styleable.View_minWidth: mMinWidth = a.getDimensionPixelSize(attr, 0); break; @@ -4652,10 +4702,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, forceHasOverlappingRendering(a.getBoolean(attr, true)); } break; - case R.styleable.View_tooltip: setTooltip(a.getText(attr)); break; + case R.styleable.View_keyboardNavigationCluster: + if (a.peekValue(attr) != null) { + setKeyboardNavigationCluster(a.getBoolean(attr, true)); + } + break; + case R.styleable.View_keyboardNavigationSection: + if (a.peekValue(attr) != null) { + setKeyboardNavigationSection(a.getBoolean(attr, true)); + } + break; } } @@ -6781,8 +6840,35 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}. * @param structure Fill in with structured view data. The default implementation * fills in all data that can be inferred from the view itself. + * + * @deprecated As of API O sub-classes should override + * {@link #onProvideStructure(ViewStructure, int)} instead. */ + // TODO(b/33197203): set proper API above + @Deprecated public void onProvideStructure(ViewStructure structure) { + onProvideStructure(structure, 0); + } + + /** + * Called when assist structure is being retrieved from a view as part of + * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData} or as part + * of an auto-fill request. + * + * <p>The default implementation fills in all data that can be inferred from the view itself. + * + * <p>The structure must be filled according to the request type, which is set in the + * {@code flags} parameter - see the documentation on each flag for more details. + * + * @param structure Fill in with structured view data. The default implementation + * fills in all data that can be inferred from the view itself. + * @param flags optional flags (see {@link #ASSIST_FLAG_SANITIZED_TEXT} and + * {@link #ASSIST_FLAG_NON_SANITIZED_TEXT} for more info). + */ + public void onProvideStructure(ViewStructure structure, int flags) { + boolean forAutoFill = (flags + & (View.ASSIST_FLAG_SANITIZED_TEXT + | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0; final int id = mID; if (id > 0 && (id&0xff000000) != 0 && (id&0x00ff0000) != 0 && (id&0x0000ffff) != 0) { @@ -6800,9 +6886,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, structure.setId(id, null, null, null); } - // The auto-fill id needs to be unique, but its value doesn't matter, so it's better to - // reuse the accessibility id to save space. - structure.setAutoFillId(getAccessibilityViewId()); + if (forAutoFill) { + // The auto-fill id needs to be unique, but its value doesn't matter, so it's better to + // reuse the accessibility id to save space. + structure.setAutoFillId(getAccessibilityViewId()); + } structure.setDimens(mLeft, mTop, mScrollX, mScrollY, mRight - mLeft, mBottom - mTop); if (!hasIdentityMatrix()) { @@ -6852,20 +6940,52 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * uses {@link #getAccessibilityNodeProvider()} to try to generate this from the * view's virtual accessibility nodes, if any. You can override this for a more * optimal implementation providing this data. + * + * @deprecated As of API O, sub-classes should override + * {@link #onProvideVirtualStructure(ViewStructure, int)} instead. */ + // TODO(b/33197203): set proper API above + @Deprecated public void onProvideVirtualStructure(ViewStructure structure) { + onProvideVirtualStructure(structure, 0); + } + + /** + * Called when assist structure is being retrieved from a view as part of + * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData} or as part + * of an auto-fill request to generate additional virtual structure under this view. + * + * <p>The defaullt implementation uses {@link #getAccessibilityNodeProvider()} to try to + * generate this from the view's virtual accessibility nodes, if any. You can override this + * for a more optimal implementation providing this data. + * + * <p>The structure must be filled according to the request type, which is set in the + * {@code flags} parameter - see the documentation on each flag for more details. + * + * @param structure Fill in with structured view data. + * @param flags optional flags (see {@link #ASSIST_FLAG_SANITIZED_TEXT} and + * {@link #ASSIST_FLAG_NON_SANITIZED_TEXT} for more info). + */ + public void onProvideVirtualStructure(ViewStructure structure, int flags) { + boolean sanitize = (flags & View.ASSIST_FLAG_SANITIZED_TEXT) != 0; + + if (sanitize) { + // TODO(b/33197203): change populateVirtualStructure so it sanitizes data in this case. + return; + } + AccessibilityNodeProvider provider = getAccessibilityNodeProvider(); if (provider != null) { AccessibilityNodeInfo info = createAccessibilityNodeInfo(); structure.setChildCount(1); ViewStructure root = structure.newChild(0); - populateVirtualStructure(root, provider, info); + populateVirtualStructure(root, provider, info, flags); info.recycle(); } } private void populateVirtualStructure(ViewStructure structure, - AccessibilityNodeProvider provider, AccessibilityNodeInfo info) { + AccessibilityNodeProvider provider, AccessibilityNodeInfo info, int flags) { structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()), null, null, null); Rect rect = structure.getTempRect(); @@ -6914,7 +7034,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityNodeInfo cinfo = provider.createAccessibilityNodeInfo( AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i))); ViewStructure child = structure.newChild(i); - populateVirtualStructure(child, provider, cinfo); + populateVirtualStructure(child, provider, cinfo, flags); cinfo.recycle(); } } @@ -6924,11 +7044,38 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Dispatch creation of {@link ViewStructure} down the hierarchy. The default * implementation calls {@link #onProvideStructure} and * {@link #onProvideVirtualStructure}. + * + * @deprecated As of API O, sub-classes should override + * {@link #dispatchProvideStructure(ViewStructure, int)} instead. */ + // TODO(b/33197203): set proper API above + @Deprecated public void dispatchProvideStructure(ViewStructure structure) { - if (!isAssistBlocked()) { - onProvideStructure(structure); - onProvideVirtualStructure(structure); + dispatchProvideStructure(structure, 0); + } + + /** + * Dispatch creation of {@link ViewStructure} down the hierarchy. + * + * <p>The structure must be filled according to the request type, which is set in the + * {@code flags} parameter - see the documentation on each flag for more details. + * + * <p>The default implementation calls {@link #onProvideStructure(ViewStructure, int)} and + * {@link #onProvideVirtualStructure(ViewStructure, int)}. + * + * @param structure Fill in with structured view data. + * @param flags optional flags (see {@link #ASSIST_FLAG_SANITIZED_TEXT} and + * {@link #ASSIST_FLAG_NON_SANITIZED_TEXT} for more info). + */ + public void dispatchProvideStructure(ViewStructure structure, int flags) { + boolean forAutoFill = (flags + & (View.ASSIST_FLAG_SANITIZED_TEXT + | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0; + + boolean blocked = forAutoFill ? isAutoFillBlocked() : isAssistBlocked(); + if (!blocked) { + onProvideStructure(structure, flags); + onProvideVirtualStructure(structure, flags); } else { structure.setClassName(getAccessibilityClassName().toString()); structure.setAssistBlocked(true); @@ -7749,6 +7896,50 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Gets the id of the root of the next keyboard navigation cluster. + * @return The next keyboard navigation cluster ID, or {@link #NO_ID} if the framework should + * decide automatically. + * + * @attr ref android.R.styleable#View_nextClusterForward + */ + public int getNextClusterForwardId() { + return mNextClusterForwardId; + } + + /** + * Sets the id of the view to use as the root of the next keyboard navigation cluster. + * @param nextClusterForwardId The next cluster ID, or {@link #NO_ID} if the framework should + * decide automatically. + * + * @attr ref android.R.styleable#View_nextClusterForward + */ + public void setNextClusterForwardId(int nextClusterForwardId) { + mNextClusterForwardId = nextClusterForwardId; + } + + /** + * Gets the id of the root of the next keyboard navigation section. + * @return The next keyboard navigation section ID, or {@link #NO_ID} if the framework should + * decide automatically. + * + * @attr ref android.R.styleable#View_nextSectionForward + */ + public int getNextSectionForwardId() { + return mNextSectionForwardId; + } + + /** + * Sets the id of the view to use as the root of the next keyboard navigation section. + * @param nextSectionForwardId The next section ID, or {@link #NO_ID} if the framework should + * decide automatically. + * + * @attr ref android.R.styleable#View_nextSectionForward + */ + public void setNextSectionForwardId(int nextSectionForwardId) { + mNextSectionForwardId = nextSectionForwardId; + } + + /** * Returns the visibility of this view and all of its ancestors * * @return True if this view and all of its ancestors are {@link #VISIBLE} @@ -8667,6 +8858,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * @hide + * Indicates whether this view will participate in data collection through + * {@link ViewStructure} for auto-fill purposes. + * + * <p>If {@code true}, it will not provide any data for itself or its children. + * <p>If {@code false}, the normal data collection will be allowed. + * + * @return Returns {@code false} if assist data collection for auto-fill is not blocked, + * else {@code true}. + * + * TODO(b/33197203): update / remove javadoc tags below + * @see #setAssistBlocked(boolean) + * @attr ref android.R.styleable#View_assistBlocked + */ + public boolean isAutoFillBlocked() { + return false; // TODO(b/33197203): properly implement it + } + + /** + * @hide * Controls whether assist data collection from this view and its children is enabled * (that is, whether {@link #onProvideStructure} and * {@link #onProvideVirtualStructure} will be called). The default value is false, @@ -8825,6 +9035,76 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Returns whether this View is a root of a keyboard navigation cluster. + * + * @return True if this view is a root of a cluster, or false otherwise. + * @attr ref android.R.styleable#View_keyboardNavigationCluster + */ + @ViewDebug.ExportedProperty(category = "keyboardNavigationCluster") + public final boolean isKeyboardNavigationCluster() { + return (mPrivateFlags3 & PFLAG3_CLUSTER) != 0; + } + + /** + * Set whether this view is a root of a keyboard navigation cluster. + * + * @param isCluster If true, this view is a root of a cluster. + * + * @attr ref android.R.styleable#View_keyboardNavigationCluster + */ + public void setKeyboardNavigationCluster(boolean isCluster) { + if (isCluster) { + mPrivateFlags3 |= PFLAG3_CLUSTER; + } else { + mPrivateFlags3 &= ~PFLAG3_CLUSTER; + } + } + + /** + * Returns whether this View is a root of a keyboard navigation section. + * + * @return True if this view is a root of a section, or false otherwise. + * @attr ref android.R.styleable#View_keyboardNavigationSection + */ + @ViewDebug.ExportedProperty(category = "keyboardNavigationSection") + public final boolean isKeyboardNavigationSection() { + return (mPrivateFlags3 & PFLAG3_SECTION) != 0; + } + + /** + * Set whether this view is a root of a keyboard navigation section. + * + * @param isSection If true, this view is a root of a section. + * + * @attr ref android.R.styleable#View_keyboardNavigationSection + */ + public void setKeyboardNavigationSection(boolean isSection) { + if (isSection) { + mPrivateFlags3 |= PFLAG3_SECTION; + } else { + mPrivateFlags3 &= ~PFLAG3_SECTION; + } + } + + /** + * Find the nearest keyboard navigation cluster in the specified direction. + * This does not actually give focus to that cluster. + * + * @param direction Direction to look + * + * @return The nearest keyboard navigation cluster in the specified direction, or null if none + * can be found + */ + public View keyboardNavigationClusterSearch(int direction) { + if (mParent != null) { + final View currentCluster = isKeyboardNavigationCluster() ? this : null; + return mParent.keyboardNavigationClusterSearch(currentCluster, direction); + } else { + return null; + } + } + + /** * This method is the last chance for the focused view and its ancestors to * respond to an arrow key. This is called when the focused view did not * consume the key internally, nor could the view system find a new view in @@ -8947,6 +9227,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Adds any keyboard navigation cluster roots that are descendants of this view (possibly + * including this view if it is a cluster root itself) to views. + * + * @param views Cluster roots found so far + * @param direction Direction to look + */ + public void addKeyboardNavigationClusters(@NonNull Collection<View> views, int direction) { + if (!isKeyboardNavigationCluster()) { + return; + } + views.add(this); + } + + /** * Finds the Views that contain given text. The containment is case insensitive. * The search is performed by either the text that the View renders or the content * description that describes the view for accessibility purposes and the view does @@ -13780,11 +14074,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, receiver.damageInParent(); } } - - // Damage the entire IsolatedZVolume receiving this view's shadow. - if (isHardwareAccelerated() && getZ() != 0) { - damageShadowReceiver(); - } } } @@ -13812,23 +14101,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Damage area of the screen that can be covered by this View's shadow. - * - * This method will guarantee that any changes to shadows cast by a View - * are damaged on the screen for future redraw. - */ - private void damageShadowReceiver() { - final AttachInfo ai = mAttachInfo; - if (ai != null) { - ViewParent p = getParent(); - if (p != null && p instanceof ViewGroup) { - final ViewGroup vg = (ViewGroup) p; - vg.damageInParent(); - } - } - } - - /** * Quick invalidation for View property changes (alpha, translationXY, etc.). We don't want to * set any flags or handle all of the cases handled by the default invalidation methods. * Instead, we just want to schedule a traversal in ViewRootImpl with the appropriate @@ -13858,9 +14130,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { damageInParent(); } - if (isHardwareAccelerated() && invalidateParent && getZ() != 0) { - damageShadowReceiver(); - } } /** diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 0f8200d794ff..7340cf712ca0 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -59,6 +59,7 @@ import com.android.internal.R; import com.android.internal.util.Predicate; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -868,10 +869,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ @Override public View focusSearch(View focused, int direction) { - if (isRootNamespace()) { + if (isRootNamespace() + || isKeyboardNavigationCluster() + && (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD)) { // root namespace means we should consider ourselves the top of the // tree for focus searching; otherwise we could be focus searching - // into other tabs. see LocalActivityManager and TabHost for more info + // into other tabs. see LocalActivityManager and TabHost for more info. + // Cluster's root works same way for the forward and backward navigation. return FocusFinder.getInstance().findNextFocus(this, focused, direction); } else if (mParent != null) { return mParent.focusSearch(focused, direction); @@ -880,6 +884,23 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override + public View keyboardNavigationClusterSearch(View currentCluster, int direction) { + if (isKeyboardNavigationCluster()) { + currentCluster = this; + } + if (isRootNamespace()) { + // root namespace means we should consider ourselves the top of the + // tree for cluster searching; otherwise we could be focus searching + // into other tabs. see LocalActivityManager and TabHost for more info + return FocusFinder.getInstance().findNextKeyboardNavigationCluster( + this, currentCluster, direction); + } else if (mParent != null) { + return mParent.keyboardNavigationClusterSearch(currentCluster, direction); + } + return null; + } + + @Override public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { return false; } @@ -1086,6 +1107,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { + if (isKeyboardNavigationCluster() + && (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD) && !hasFocus()) { + // A cluster cannot be focus-entered from outside using forward/backward navigation. + return; + } + final int focusableCount = views.size(); final int descendantFocusability = getDescendantFocusability(); @@ -1118,6 +1145,32 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + @Override + public void addKeyboardNavigationClusters(Collection<View> views, int direction) { + final int focusableCount = views.size(); + + super.addKeyboardNavigationClusters(views, direction); + + if (focusableCount != views.size()) { + // No need to look for clusters inside a cluster. + return; + } + + if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) { + return; + } + + final int count = mChildrenCount; + final View[] children = mChildren; + + for (int i = 0; i < count; i++) { + final View child = children[i]; + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { + child.addKeyboardNavigationClusters(views, direction); + } + } + } + /** * Set whether this ViewGroup should ignore focus requests for itself and its children. * If this option is enabled and the ViewGroup or a descendant currently has focus, focus @@ -2982,7 +3035,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final View[] children = mChildren; for (int i = index; i != end; i += increment) { View child = children[i]; - if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE + && !child.isKeyboardNavigationCluster()) { if (child.requestFocus(direction, previouslyFocusedRect)) { return true; } @@ -3083,14 +3137,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Dispatch creation of {@link ViewStructure} down the hierarchy. This implementation - * adds in all child views of the view group, in addition to calling the default View - * implementation. + * {@inheritDoc} + * + * <p>This implementation adds in all child views of the view group, in addition to calling the + * default {@link View} implementation. */ @Override - public void dispatchProvideStructure(ViewStructure structure) { - super.dispatchProvideStructure(structure); - if (!isAssistBlocked()) { + public void dispatchProvideStructure(ViewStructure structure, int flags) { + super.dispatchProvideStructure(structure, flags); + + boolean forAutoFill = (flags + & (View.ASSIST_FLAG_SANITIZED_TEXT + | View.ASSIST_FLAG_NON_SANITIZED_TEXT)) != 0; + + boolean blocked = forAutoFill ? isAutoFillBlocked() : isAssistBlocked(); + + if (!blocked) { if (structure.getChildCount() == 0) { final int childrenCount = getChildCount(); if (childrenCount > 0) { @@ -3151,7 +3213,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); final ViewStructure cstructure = structure.newChild(i); - child.dispatchProvideStructure(cstructure); + + // Must explicitly check which recursive method to call because child might + // not be overriding the new, flags-based version + if (flags == 0) { + child.dispatchProvideStructure(cstructure); + } else { + child.dispatchProvideStructure(cstructure, flags); + } } if (preorderedList != null) preorderedList.clear(); } diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index 849c8b93fd6b..79b05cdb6e50 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -147,6 +147,19 @@ public interface ViewParent { public View focusSearch(View v, int direction); /** + * Find the nearest keyboard navigation cluster in the specified direction. + * This does not actually give focus to that cluster. + * + * @param currentCluster The starting point of the search. Null means the current cluster is not + * found yet + * @param direction Direction to look + * + * @return The nearest keyboard navigation cluster in the specified direction, or null if none + * can be found + */ + View keyboardNavigationClusterSearch(View currentCluster, int direction); + + /** * Change the z order of the child so it's on top of all other children. * This ordering change may affect layout, if this container * uses an order-dependent layout scheme (e.g., LinearLayout). Prior diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index e030e767732e..2cebeedda762 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4324,6 +4324,87 @@ public final class ViewRootImpl implements ViewParent, super.onDeliverToNext(q); } + private boolean performFocusNavigation(KeyEvent event) { + int direction = 0; + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (event.hasNoModifiers()) { + direction = View.FOCUS_LEFT; + } + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (event.hasNoModifiers()) { + direction = View.FOCUS_RIGHT; + } + break; + case KeyEvent.KEYCODE_DPAD_UP: + if (event.hasNoModifiers()) { + direction = View.FOCUS_UP; + } + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (event.hasNoModifiers()) { + direction = View.FOCUS_DOWN; + } + break; + case KeyEvent.KEYCODE_TAB: + if (event.hasNoModifiers()) { + direction = View.FOCUS_FORWARD; + } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) { + direction = View.FOCUS_BACKWARD; + } + break; + } + if (direction != 0) { + View focused = mView.findFocus(); + if (focused != null) { + View v = focused.focusSearch(direction); + if (v != null && v != focused) { + // do the math the get the interesting rect + // of previous focused into the coord system of + // newly focused view + focused.getFocusedRect(mTempRect); + if (mView instanceof ViewGroup) { + ((ViewGroup) mView).offsetDescendantRectToMyCoords( + focused, mTempRect); + ((ViewGroup) mView).offsetRectIntoDescendantCoords( + v, mTempRect); + } + if (v.requestFocus(direction, mTempRect)) { + playSoundEffect(SoundEffectConstants + .getContantForFocusDirection(direction)); + return true; + } + } + + // Give the focused view a last chance to handle the dpad key. + if (mView.dispatchUnhandledMove(focused, direction)) { + return true; + } + } else { + // find the best view to give focus to in this non-touch-mode with no-focus + View v = focusSearch(null, direction); + if (v != null && v.requestFocus(direction)) { + return true; + } + } + } + return false; + } + + private boolean performClusterNavigation(int direction) { + final View focused = mView.findFocus(); + final View cluster = focused != null + ? focused.keyboardNavigationClusterSearch(direction) + : keyboardNavigationClusterSearch(null, direction); + + if (cluster != null && cluster.requestFocus()) { + return true; + } + + return false; + } + private int processKeyEvent(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; @@ -4336,11 +4417,26 @@ public final class ViewRootImpl implements ViewParent, return FINISH_NOT_HANDLED; } + int clusterNavigationDirection = 0; + + if (event.getAction() == KeyEvent.ACTION_DOWN && event.isCtrlPressed()) { + final int character = + event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_CTRL_MASK); + if (character == '+') { + clusterNavigationDirection = View.FOCUS_FORWARD; + } + + if (character == '_') { + clusterNavigationDirection = View.FOCUS_BACKWARD; + } + } + // If the Control modifier is held, try to interpret the key as a shortcut. if (event.getAction() == KeyEvent.ACTION_DOWN && event.isCtrlPressed() && event.getRepeatCount() == 0 - && !KeyEvent.isModifierKey(event.getKeyCode())) { + && !KeyEvent.isModifierKey(event.getKeyCode()) + && clusterNavigationDirection == 0) { if (mView.dispatchKeyShortcutEvent(event)) { return FINISH_HANDLED; } @@ -4359,68 +4455,13 @@ public final class ViewRootImpl implements ViewParent, // Handle automatic focus changes. if (event.getAction() == KeyEvent.ACTION_DOWN) { - int direction = 0; - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_DPAD_LEFT: - if (event.hasNoModifiers()) { - direction = View.FOCUS_LEFT; - } - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (event.hasNoModifiers()) { - direction = View.FOCUS_RIGHT; - } - break; - case KeyEvent.KEYCODE_DPAD_UP: - if (event.hasNoModifiers()) { - direction = View.FOCUS_UP; - } - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - if (event.hasNoModifiers()) { - direction = View.FOCUS_DOWN; - } - break; - case KeyEvent.KEYCODE_TAB: - if (event.hasNoModifiers()) { - direction = View.FOCUS_FORWARD; - } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) { - direction = View.FOCUS_BACKWARD; - } - break; - } - if (direction != 0) { - View focused = mView.findFocus(); - if (focused != null) { - View v = focused.focusSearch(direction); - if (v != null && v != focused) { - // do the math the get the interesting rect - // of previous focused into the coord system of - // newly focused view - focused.getFocusedRect(mTempRect); - if (mView instanceof ViewGroup) { - ((ViewGroup) mView).offsetDescendantRectToMyCoords( - focused, mTempRect); - ((ViewGroup) mView).offsetRectIntoDescendantCoords( - v, mTempRect); - } - if (v.requestFocus(direction, mTempRect)) { - playSoundEffect(SoundEffectConstants - .getContantForFocusDirection(direction)); - return FINISH_HANDLED; - } - } - - // Give the focused view a last chance to handle the dpad key. - if (mView.dispatchUnhandledMove(focused, direction)) { - return FINISH_HANDLED; - } - } else { - // find the best view to give focus to in this non-touch-mode with no-focus - View v = focusSearch(null, direction); - if (v != null && v.requestFocus(direction)) { - return FINISH_HANDLED; - } + if (clusterNavigationDirection != 0) { + if (performClusterNavigation(clusterNavigationDirection)) { + return FINISH_HANDLED; + } + } else { + if (performFocusNavigation(event)) { + return FINISH_HANDLED; } } } @@ -5842,6 +5883,19 @@ public final class ViewRootImpl implements ViewParent, return FocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction); } + /** + * {@inheritDoc} + */ + @Override + public View keyboardNavigationClusterSearch(View currentCluster, int direction) { + checkThread(); + if (!(mView instanceof ViewGroup)) { + return null; + } + return FocusFinder.getInstance().findNextKeyboardNavigationCluster( + (ViewGroup) mView, currentCluster, direction); + } + public void debug() { mView.debug(); } diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index 7dd2fc23642d..e4d4e7b8b8c5 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -385,6 +385,14 @@ public final class ViewTreeObserver { } } + if (observer.mOnDrawListeners != null) { + if (mOnDrawListeners != null) { + mOnDrawListeners.addAll(observer.mOnDrawListeners); + } else { + mOnDrawListeners = observer.mOnDrawListeners; + } + } + if (observer.mOnTouchModeChangeListeners != null) { if (mOnTouchModeChangeListeners != null) { mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners); diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 7c1bcee6c8ad..8bc988d8de1a 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -274,6 +274,7 @@ public abstract class Window { private TypedArray mWindowStyle; private Callback mCallback; private OnWindowDismissedCallback mOnWindowDismissedCallback; + private OnWindowSwipeDismissedCallback mOnWindowSwipeDismissedCallback; private WindowControllerCallback mWindowControllerCallback; private OnRestrictedCaptionAreaChangedListener mOnRestrictedCaptionAreaChangedListener; private Rect mRestrictedCaptionAreaRect; @@ -587,6 +588,18 @@ public abstract class Window { } /** @hide */ + public interface OnWindowSwipeDismissedCallback { + /** + * Called when a window is swipe dismissed. This informs the callback that the + * window is gone, and it should finish itself. + * @param finishTask True if the task should also be finished. + * @param suppressWindowTransition True if the resulting exit and enter window transition + * animations should be suppressed. + */ + void onWindowSwipeDismissed(); + } + + /** @hide */ public interface WindowControllerCallback { /** * Moves the activity from @@ -880,6 +893,18 @@ public abstract class Window { } /** @hide */ + public final void setOnWindowSwipeDismissedCallback(OnWindowSwipeDismissedCallback sdcb) { + mOnWindowSwipeDismissedCallback = sdcb; + } + + /** @hide */ + public final void dispatchOnWindowSwipeDismissed() { + if (mOnWindowSwipeDismissedCallback != null) { + mOnWindowSwipeDismissedCallback.onWindowSwipeDismissed(); + } + } + + /** @hide */ public final void setWindowControllerCallback(WindowControllerCallback wccb) { mWindowControllerCallback = wccb; } diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 8084195468c5..13d5b856ef1e 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -350,7 +350,18 @@ public final class AccessibilityManager { return; } if (!mIsEnabled) { - throw new IllegalStateException("Accessibility off. Did you forget to check that?"); + Looper myLooper = Looper.myLooper(); + if (myLooper == Looper.getMainLooper()) { + throw new IllegalStateException( + "Accessibility off. Did you forget to check that?"); + } else { + // If we're not running on the thread with the main looper, it's possible for + // the state of accessibility to change between checking isEnabled and + // calling this method. So just log the error rather than throwing the + // exception. + Log.e(LOG_TAG, "Interrupt called with accessibility disabled"); + return; + } } userId = mUserId; } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index b840f4a94e0d..8ecc42de12df 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2504,6 +2504,7 @@ public class WebView extends AbsoluteLayout return mProvider.getViewDelegate().shouldDelayChildPressedState(); } + @Override public CharSequence getAccessibilityClassName() { return WebView.class.getName(); } @@ -2513,6 +2514,11 @@ public class WebView extends AbsoluteLayout mProvider.getViewDelegate().onProvideVirtualStructure(structure); } + @Override + public void onProvideVirtualStructure(ViewStructure structure, int flags) { + mProvider.getViewDelegate().onProvideVirtualStructure(structure, flags); + } + /** @hide */ @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java index 95ec1795c443..7b951806f097 100644 --- a/core/java/android/webkit/WebViewProvider.java +++ b/core/java/android/webkit/WebViewProvider.java @@ -311,6 +311,11 @@ public interface WebViewProvider { public void onProvideVirtualStructure(android.view.ViewStructure structure); + @SuppressWarnings("unused") + public default void onProvideVirtualStructure(android.view.ViewStructure structure, + int flags) { + } + public AccessibilityNodeProvider getAccessibilityNodeProvider(); public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info); diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java index b9224f36a922..444ebc520cb7 100644 --- a/core/java/android/widget/Chronometer.java +++ b/core/java/android/widget/Chronometer.java @@ -17,8 +17,11 @@ package android.widget; import android.content.Context; -import android.content.res.Resources; import android.content.res.TypedArray; +import android.icu.text.MeasureFormat; +import android.icu.text.MeasureFormat.FormatWidth; +import android.icu.util.Measure; +import android.icu.util.MeasureUnit; import android.os.SystemClock; import android.text.format.DateUtils; import android.util.AttributeSet; @@ -28,6 +31,7 @@ import android.widget.RemoteViews.RemoteView; import com.android.internal.R; +import java.util.ArrayList; import java.util.Formatter; import java.util.IllegalFormatException; import java.util.Locale; @@ -329,9 +333,6 @@ public class Chronometer extends TextView { private static final int MIN_IN_SEC = 60; private static final int HOUR_IN_SEC = MIN_IN_SEC*60; private static String formatDuration(long ms) { - final Resources res = Resources.getSystem(); - final StringBuilder text = new StringBuilder(); - int duration = (int) (ms / DateUtils.SECOND_IN_MILLIS); if (duration < 0) { duration = -duration; @@ -348,31 +349,19 @@ public class Chronometer extends TextView { m = duration / MIN_IN_SEC; duration -= m * MIN_IN_SEC; } - int s = duration; - - try { - if (h > 0) { - text.append(res.getQuantityString( - com.android.internal.R.plurals.duration_hours, h, h)); - } - if (m > 0) { - if (text.length() > 0) { - text.append(' '); - } - text.append(res.getQuantityString( - com.android.internal.R.plurals.duration_minutes, m, m)); - } + final int s = duration; - if (text.length() > 0) { - text.append(' '); - } - text.append(res.getQuantityString( - com.android.internal.R.plurals.duration_seconds, s, s)); - } catch (Resources.NotFoundException e) { - // Ignore; plurals throws an exception for an untranslated quantity for a given locale. - return null; + final ArrayList<Measure> measures = new ArrayList<Measure>(); + if (h > 0) { + measures.add(new Measure(h, MeasureUnit.HOUR)); } - return text.toString(); + if (m > 0) { + measures.add(new Measure(m, MeasureUnit.MINUTE)); + } + measures.add(new Measure(s, MeasureUnit.SECOND)); + + return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE) + .formatMeasures((Measure[]) measures.toArray()); } @Override diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 5fa1d2b42515..46324a3807e7 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -42,6 +42,7 @@ import android.transition.TransitionSet; import android.util.AttributeSet; import android.view.Gravity; import android.view.KeyEvent; +import android.view.KeyboardShortcutGroup; import android.view.MotionEvent; import android.view.View; import android.view.View.OnAttachStateChangeListener; @@ -57,6 +58,7 @@ import android.view.WindowManager.LayoutParams; import com.android.internal.R; import java.lang.ref.WeakReference; +import java.util.List; /** * <p> @@ -139,6 +141,12 @@ public class PopupWindow { private Context mContext; private WindowManager mWindowManager; + /** + * Keeps track of popup's parent's decor view. This is needed to dispatch + * requestKeyboardShortcuts to the owning Activity. + */ + private WeakReference<View> mParentRootView; + private boolean mIsShowing; private boolean mIsTransitioningToDismiss; private boolean mIsDropdown; @@ -1119,6 +1127,7 @@ public class PopupWindow { * @param y the popup's y location offset */ public void showAtLocation(View parent, int gravity, int x, int y) { + mParentRootView = new WeakReference<>(parent.getRootView()); showAtLocation(parent.getWindowToken(), gravity, x, y); } @@ -2225,6 +2234,7 @@ public class PopupWindow { mAnchor = new WeakReference<>(anchor); mAnchorRoot = new WeakReference<>(anchorRoot); mIsAnchorRootAttached = anchorRoot.isAttachedToWindow(); + mParentRootView = mAnchorRoot; mAnchorXoff = xoff; mAnchorYoff = yoff; @@ -2422,6 +2432,16 @@ public class PopupWindow { TransitionManager.endTransitions(PopupDecorView.this); } }; + + @Override + public void requestKeyboardShortcuts(List<KeyboardShortcutGroup> list, int deviceId) { + if (mParentRootView != null) { + View parentRoot = mParentRootView.get(); + if (parentRoot != null) { + parentRoot.requestKeyboardShortcuts(list, deviceId); + } + } + } } private class PopupBackgroundView extends FrameLayout { diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index fcc1667d8043..e629df9bd3df 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -1404,8 +1404,10 @@ public class Switch extends CompoundButton { } @Override - public void onProvideStructure(ViewStructure structure) { - super.onProvideStructure(structure); + public void onProvideStructure(ViewStructure structure, int flags) { + super.onProvideStructure(structure, flags); + + // NOTE: current there is no difference for Assist (flags=0) or AutoFill (flags>0); CharSequence switchText = isChecked() ? mTextOn : mTextOff; if (!TextUtils.isEmpty(switchText)) { CharSequence oldText = structure.getText(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 44655f18f3eb..1961bf6b7e15 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -77,6 +77,7 @@ import android.text.Spanned; import android.text.SpannedString; import android.text.StaticLayout; import android.text.TextAssistant; +import android.text.TextClassificationManager; import android.text.TextDirectionHeuristic; import android.text.TextDirectionHeuristics; import android.text.TextPaint; @@ -680,8 +681,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1; // Contains the sorted set of desired text sizes in pixels to pick from when auto-sizing text. private int[] mAutoSizeTextSizesInPx; - // Specifies if the current TextView needs to be auto-sized. - private boolean mNeedsTextAutoResize = false; /** * Kick-start the font cache for the zygote process (to pay the cost of @@ -1561,7 +1560,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } Arrays.sort(mAutoSizeTextSizesInPx); - mNeedsTextAutoResize = true; break; default: throw new IllegalArgumentException( @@ -7524,7 +7522,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener scrollTo(0, 0); } - if (mNeedsTextAutoResize) { + if (isAutoSizeEnabled()) { // Call auto-size after the width and height have been calculated. autoSizeText(); } @@ -7536,20 +7534,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Automatically computes and sets the text size. */ private void autoSizeText() { - synchronized (TEMP_RECTF) { - TEMP_RECTF.setEmpty(); - final int maxWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); - final int maxHeight = getMeasuredHeight() - getPaddingBottom() - getPaddingTop(); + final int maxWidth = getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight(); + final int maxHeight = getMeasuredHeight() - getTotalPaddingBottom() - getTotalPaddingTop(); - if (maxWidth <= 0 || maxHeight <= 0) { - return; - } + if (maxWidth <= 0 || maxHeight <= 0) { + return; + } + synchronized (TEMP_RECTF) { + TEMP_RECTF.setEmpty(); TEMP_RECTF.right = maxWidth; TEMP_RECTF.bottom = maxHeight; final float textSize = findLargestTextSizeWhichFits(TEMP_RECTF); - setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); - mNeedsTextAutoResize = false; + if (textSize != getTextSize()) { + setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); + } } } @@ -7594,13 +7593,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if ((mLayout instanceof BoringLayout) && BoringLayout.isBoring( text, mTempTextPaint, getTextDirectionHeuristic(), mBoring) != null) { - return mTempTextPaint.getFontSpacing() + getPaddingTop() + getPaddingBottom() - <= availableSpace.bottom - && mTempTextPaint.measureText(text, 0, text.length()) - + getPaddingLeft() + getPaddingRight() <= availableSpace.right; + return mTempTextPaint.getFontSpacing() <= availableSpace.bottom + && mTempTextPaint.measureText(text, 0, text.length()) <= availableSpace.right; } else { StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(text, 0, text.length(), - mTempTextPaint, getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); + mTempTextPaint, + getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight()); layoutBuilder.setAlignment(getLayoutAlignment()); layoutBuilder.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()); layoutBuilder.setIncludePad(true); @@ -9270,7 +9268,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * @return {@code true} if this TextView supports autosizing text to fit within its container. + * @return {@code true} if this widget supports auto-sizing text and has been configured to + * auto-size. + */ + private boolean isAutoSizeEnabled() { + return supportsAutoSizeText() && mAutoSizeType != AUTO_SIZE_TYPE_NONE; + } + + /** + * @return {@code true} if this TextView supports auto-sizing text to fit within its container. * @hide */ protected boolean supportsAutoSizeText() { @@ -9385,11 +9391,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } @Override - public void onProvideStructure(ViewStructure structure) { - super.onProvideStructure(structure); + public void onProvideStructure(ViewStructure structure, int flags) { + super.onProvideStructure(structure, flags); + + final boolean forAutoFillSave = + (flags & ASSIST_FLAG_NON_SANITIZED_TEXT) != 0; final boolean isPassword = hasPasswordTransformationMethod() || isPasswordInputType(getInputType()); - if (!isPassword) { + if (!isPassword || forAutoFillSave) { if (mLayout == null) { assumeLayout(); } @@ -10027,8 +10036,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mTextAssistant = ((Activity) mContext).getTextAssistant(); } else { // The context of this TextView should be an Activity. If it is not and no - // text assistant has been set, return a NO_OP TextAssistant. - mTextAssistant = TextAssistant.NO_OP; + // text assistant has been set, return the TextClassificationManager. + mTextAssistant = mContext.getSystemService(TextClassificationManager.class); } return mTextAssistant; } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index d8f7907524ee..c314cae2f55c 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -19,6 +19,7 @@ package com.android.internal.app; import android.animation.ObjectAnimator; import android.annotation.NonNull; import android.app.Activity; +import android.app.usage.UsageStatsManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -299,6 +300,11 @@ public class ChooserActivity extends ResolverActivity { || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) { result = Intent.createChooser(result, getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE)); + + // Don't auto-launch single intents if the intent is being forwarded. This is done + // because automatically launching a resolving application as a response to the user + // action of switching accounts is pretty unexpected. + result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false); } return result; } @@ -398,6 +404,7 @@ public class ChooserActivity extends ResolverActivity { } } } + updateChooserCounts(target, mContentType); return super.onTargetSelected(target, alwaysCheck); } @@ -542,20 +549,46 @@ public class ChooserActivity extends ResolverActivity { // Do nothing. We'll send the voice stuff ourselves. } + void updateChooserCounts(TargetInfo info, String annotation) { + if (info != null) { + UsageStatsManager usageStatsManager = + (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); + if (usageStatsManager == null) { + if (DEBUG) { + Log.d(TAG, "Can not start UsageStatsManager"); + } + return; + } + final ResolveInfo ri = info.getResolveInfo(); + if (ri != null && ri.activityInfo != null) { + usageStatsManager.reportChooserSelection(ri.activityInfo.packageName, getUserId(), + annotation, null, info.getResolvedIntent().getAction()); + if (DEBUG) { + Log.d(TAG, "ResolveInfo Package is" + ri.activityInfo.packageName); + } + } else if(DEBUG) { + Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo"); + } + } + } + void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) { if (mRefinementResultReceiver != null) { mRefinementResultReceiver.destroy(); mRefinementResultReceiver = null; } - if (selectedTarget == null) { Log.e(TAG, "Refinement result intent did not match any known targets; canceling"); } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) { Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget + " cannot match refined source intent " + matchingIntent); - } else if (super.onTargetSelected(selectedTarget.cloneFilledIn(matchingIntent, 0), false)) { - finish(); - return; + } else { + TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0); + if (super.onTargetSelected(clonedTarget, false)) { + updateChooserCounts(clonedTarget, mContentType); + finish(); + return; + } } onRefinementCanceled(); } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index dd8ef180e19c..c516b5cd7c3a 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -26,6 +26,7 @@ import android.app.VoiceInteractor.PickOptionRequest.Option; import android.app.VoiceInteractor.Prompt; import android.content.pm.ComponentInfo; import android.os.AsyncTask; +import android.os.RemoteException; import android.provider.MediaStore; import android.provider.Settings; import android.text.TextUtils; @@ -53,7 +54,6 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.PatternMatcher; -import android.os.RemoteException; import android.os.StrictMode; import android.os.UserHandle; import android.os.UserManager; @@ -114,6 +114,7 @@ public class ResolverActivity extends Activity { private ComponentName[] mFilteredComponents; protected ResolverDrawerLayout mResolverDrawerLayout; + protected String mContentType; private boolean mRegistered; private final PackageMonitor mPackageMonitor = new PackageMonitor() { @@ -270,6 +271,7 @@ public class ResolverActivity extends Activity { final String referrerPackage = getReferrerPackageName(); mResolverComparator = new ResolverComparator(this, getTargetIntent(), referrerPackage); + mContentType = mResolverComparator.mContentType; if (configureContentView(mIntents, initialIntents, rList, alwaysUseOption)) { return; diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java index 4d4c7ceb1655..75be906397a7 100644 --- a/core/java/com/android/internal/app/ResolverComparator.java +++ b/core/java/com/android/internal/app/ResolverComparator.java @@ -63,6 +63,8 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { private final long mSinceTime; private final LinkedHashMap<ComponentName, ScoredTarget> mScoredTargets = new LinkedHashMap<>(); private final String mReferrerPackage; + public String mContentType; + private String mAction; public ResolverComparator(Context context, Intent intent, String referrerPackage) { mCollator = Collator.getInstance(context.getResources().getConfiguration().locale); @@ -76,6 +78,8 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { mCurrentTime = System.currentTimeMillis(); mSinceTime = mCurrentTime - USAGE_STATS_PERIOD; mStats = mUsm.queryAndAggregateUsageStats(mSinceTime, mCurrentTime); + mContentType = intent.getType(); + mAction = intent.getAction(); } public void compute(List<ResolvedComponentInfo> targets) { @@ -86,6 +90,7 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { long mostRecentlyUsedTime = recentSinceTime + 1; long mostTimeSpent = 1; int mostLaunched = 1; + int mostSelected = 1; for (ResolvedComponentInfo target : targets) { final ScoredTarget scoredTarget @@ -114,6 +119,25 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { if (launched > mostLaunched) { mostLaunched = launched; } + // TODO(kanlig): get and combine counts of categories. + + int selected = 0; + if (pkStats.mChooserCounts != null && mAction != null + && pkStats.mChooserCounts.get(mAction) != null) { + selected = pkStats.mChooserCounts.get(mAction).getOrDefault(mContentType, 0); + } + if (DEBUG) { + if (mAction == null) { + Log.d(TAG, "Action type is null"); + } else { + Log.d(TAG, "Chooser Count of " + mAction + ":" + + target.name.getPackageName() + " is " + Integer.toString(selected)); + } + } + scoredTarget.chooserCount = selected; + if (selected > mostSelected) { + mostSelected = selected; + } } } @@ -190,7 +214,15 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { lhs.activityInfo.packageName, lhs.activityInfo.name)); final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName( rhs.activityInfo.packageName, rhs.activityInfo.name)); - final float diff = rhsTarget.score - lhsTarget.score; + + final int chooserCountDiff = Long.compare( + rhsTarget.chooserCount, lhsTarget.chooserCount); + + if (chooserCountDiff != 0) { + return chooserCountDiff > 0 ? 1 : -1; + } + + final int diff = Float.compare(rhsTarget.score, lhsTarget.score); if (diff != 0) { return diff > 0 ? 1 : -1; @@ -220,6 +252,7 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { public long lastTimeUsed; public long timeSpent; public long launchCount; + public long chooserCount; public ScoredTarget(ComponentInfo ci) { componentInfo = ci; @@ -232,6 +265,7 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { + " lastTimeUsed: " + lastTimeUsed + " timeSpent: " + timeSpent + " launchCount: " + launchCount + + " chooserCount: " + chooserCount + "}"; } } diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index f479f4feca35..83b7d2f948f8 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -76,6 +76,7 @@ public class NativeLibraryHelper { final long[] apkHandles; final boolean multiArch; final boolean extractNativeLibs; + final boolean debuggable; public static Handle create(File packageFile) throws IOException { try { @@ -89,15 +90,17 @@ public class NativeLibraryHelper { public static Handle create(Package pkg) throws IOException { return create(pkg.getAllCodePaths(), (pkg.applicationInfo.flags & ApplicationInfo.FLAG_MULTIARCH) != 0, - (pkg.applicationInfo.flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) != 0); + (pkg.applicationInfo.flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) != 0, + (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); } public static Handle create(PackageLite lite) throws IOException { - return create(lite.getAllCodePaths(), lite.multiArch, lite.extractNativeLibs); + return create(lite.getAllCodePaths(), lite.multiArch, lite.extractNativeLibs, + lite.debuggable); } private static Handle create(List<String> codePaths, boolean multiArch, - boolean extractNativeLibs) throws IOException { + boolean extractNativeLibs, boolean debuggable) throws IOException { final int size = codePaths.size(); final long[] apkHandles = new long[size]; for (int i = 0; i < size; i++) { @@ -112,13 +115,15 @@ public class NativeLibraryHelper { } } - return new Handle(apkHandles, multiArch, extractNativeLibs); + return new Handle(apkHandles, multiArch, extractNativeLibs, debuggable); } - Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs) { + Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs, + boolean debuggable) { this.apkHandles = apkHandles; this.multiArch = multiArch; this.extractNativeLibs = extractNativeLibs; + this.debuggable = debuggable; mGuard.open("close"); } @@ -149,15 +154,17 @@ public class NativeLibraryHelper { private static native long nativeOpenApk(String path); private static native void nativeClose(long handle); - private static native long nativeSumNativeBinaries(long handle, String cpuAbi); + private static native long nativeSumNativeBinaries(long handle, String cpuAbi, + boolean debuggable); private native static int nativeCopyNativeBinaries(long handle, String sharedLibraryPath, - String abiToCopy, boolean extractNativeLibs, boolean hasNativeBridge); + String abiToCopy, boolean extractNativeLibs, boolean hasNativeBridge, + boolean debuggable); private static long sumNativeBinaries(Handle handle, String abi) { long sum = 0; for (long apkHandle : handle.apkHandles) { - sum += nativeSumNativeBinaries(apkHandle, abi); + sum += nativeSumNativeBinaries(apkHandle, abi, handle.debuggable); } return sum; } @@ -173,7 +180,7 @@ public class NativeLibraryHelper { public static int copyNativeBinaries(Handle handle, File sharedLibraryDir, String abi) { for (long apkHandle : handle.apkHandles) { int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi, - handle.extractNativeLibs, HAS_NATIVE_BRIDGE); + handle.extractNativeLibs, HAS_NATIVE_BRIDGE, handle.debuggable); if (res != INSTALL_SUCCEEDED) { return res; } @@ -191,7 +198,7 @@ public class NativeLibraryHelper { public static int findSupportedAbi(Handle handle, String[] supportedAbis) { int finalRes = NO_NATIVE_LIBRARIES; for (long apkHandle : handle.apkHandles) { - final int res = nativeFindSupportedAbi(apkHandle, supportedAbis); + final int res = nativeFindSupportedAbi(apkHandle, supportedAbis, handle.debuggable); if (res == NO_NATIVE_LIBRARIES) { // No native code, keep looking through all APKs. } else if (res == INSTALL_FAILED_NO_MATCHING_ABIS) { @@ -213,7 +220,8 @@ public class NativeLibraryHelper { return finalRes; } - private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis); + private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis, + boolean debuggable); // Convenience method to call removeNativeBinariesFromDirLI(File) public static void removeNativeBinariesLI(String nativeLibraryPath) { diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java index 98d87d349de7..eec3cb0be11f 100644 --- a/core/java/com/android/internal/content/PackageHelper.java +++ b/core/java/com/android/internal/content/PackageHelper.java @@ -16,8 +16,6 @@ package com.android.internal.content; -import static android.net.TrafficStats.MB_IN_BYTES; - import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -38,7 +36,7 @@ import android.provider.Settings; import android.util.ArraySet; import android.util.Log; -import libcore.io.IoUtils; +import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.FileOutputStream; @@ -50,6 +48,11 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; +import libcore.io.IoUtils; + +import static android.net.TrafficStats.MB_IN_BYTES; +import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL; + /** * Constants used internally between the PackageManager * and media container service transports. @@ -74,6 +77,8 @@ public class PackageHelper { public static final int APP_INSTALL_INTERNAL = 1; public static final int APP_INSTALL_EXTERNAL = 2; + private static TestableInterface sDefaultTestableInterface = null; + public static IStorageManager getStorageManager() throws RemoteException { IBinder service = ServiceManager.getService("mount"); if (service != null) { @@ -338,6 +343,65 @@ public class PackageHelper { } /** + * A group of external dependencies used in + * {@link #resolveInstallVolume(Context, String, int, long)}. It can be backed by real values + * from the system or mocked ones for testing purposes. + */ + public static abstract class TestableInterface { + abstract public StorageManager getStorageManager(Context context); + abstract public boolean getForceAllowOnExternalSetting(Context context); + abstract public boolean getAllow3rdPartyOnInternalConfig(Context context); + abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName); + abstract public File getDataDirectory(); + + public boolean fitsOnInternalStorage(Context context, long sizeBytes) { + StorageManager storage = getStorageManager(context); + File target = getDataDirectory(); + return (sizeBytes <= storage.getStorageBytesUntilLow(target)); + } + } + + private synchronized static TestableInterface getDefaultTestableInterface() { + if (sDefaultTestableInterface == null) { + sDefaultTestableInterface = new TestableInterface() { + @Override + public StorageManager getStorageManager(Context context) { + return context.getSystemService(StorageManager.class); + } + + @Override + public boolean getForceAllowOnExternalSetting(Context context) { + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0; + } + + @Override + public boolean getAllow3rdPartyOnInternalConfig(Context context) { + return context.getResources().getBoolean( + com.android.internal.R.bool.config_allow3rdPartyAppOnInternal); + } + + @Override + public ApplicationInfo getExistingAppInfo(Context context, String packageName) { + ApplicationInfo existingInfo = null; + try { + existingInfo = context.getPackageManager().getApplicationInfo(packageName, + PackageManager.MATCH_ANY_USER); + } catch (NameNotFoundException ignored) { + } + return existingInfo; + } + + @Override + public File getDataDirectory() { + return Environment.getDataDirectory(); + } + }; + } + return sDefaultTestableInterface; + } + + /** * Given a requested {@link PackageInfo#installLocation} and calculated * install size, pick the actual volume to install the app. Only considers * internal and private volumes, and prefers to keep an existing package on @@ -348,25 +412,44 @@ public class PackageHelper { */ public static String resolveInstallVolume(Context context, String packageName, int installLocation, long sizeBytes) throws IOException { - final boolean forceAllowOnExternal = Settings.Global.getInt( - context.getContentResolver(), Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0; + TestableInterface testableInterface = getDefaultTestableInterface(); + return resolveInstallVolume(context, packageName, + installLocation, sizeBytes, testableInterface); + } + + @VisibleForTesting + public static String resolveInstallVolume(Context context, String packageName, + int installLocation, long sizeBytes, TestableInterface testInterface) + throws IOException { + final boolean forceAllowOnExternal = testInterface.getForceAllowOnExternalSetting(context); + final boolean allow3rdPartyOnInternal = + testInterface.getAllow3rdPartyOnInternalConfig(context); // TODO: handle existing apps installed in ASEC; currently assumes // they'll end up back on internal storage - ApplicationInfo existingInfo = null; - try { - existingInfo = context.getPackageManager().getApplicationInfo(packageName, - PackageManager.MATCH_ANY_USER); - } catch (NameNotFoundException ignored) { - } + ApplicationInfo existingInfo = testInterface.getExistingAppInfo(context, packageName); - final StorageManager storageManager = context.getSystemService(StorageManager.class); - final boolean fitsOnInternal = fitsOnInternal(context, sizeBytes); + final boolean fitsOnInternal = testInterface.fitsOnInternalStorage(context, sizeBytes); + final StorageManager storageManager = + testInterface.getStorageManager(context); + + // System apps always forced to internal storage + if (existingInfo != null && existingInfo.isSystemApp()) { + if (fitsOnInternal) { + return StorageManager.UUID_PRIVATE_INTERNAL; + } else { + throw new IOException("Not enough space on existing volume " + + existingInfo.volumeUuid + " for system app " + packageName + " upgrade"); + } + } + // Now deal with non-system apps. final ArraySet<String> allCandidates = new ArraySet<>(); VolumeInfo bestCandidate = null; long bestCandidateAvailBytes = Long.MIN_VALUE; for (VolumeInfo vol : storageManager.getVolumes()) { - if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) { + boolean isInternalStorage = ID_PRIVATE_INTERNAL.equals(vol.id); + if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable() + && (!isInternalStorage || allow3rdPartyOnInternal)) { final long availBytes = storageManager.getStorageBytesUntilLow(new File(vol.path)); if (availBytes >= sizeBytes) { allCandidates.add(vol.fsUuid); @@ -378,11 +461,6 @@ public class PackageHelper { } } - // System apps always forced to internal storage - if (existingInfo != null && existingInfo.isSystemApp()) { - installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY; - } - // If app expresses strong desire for internal storage, honor it if (!forceAllowOnExternal && installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { @@ -391,6 +469,11 @@ public class PackageHelper { throw new IOException("Cannot automatically move " + packageName + " from " + existingInfo.volumeUuid + " to internal storage"); } + + if (!allow3rdPartyOnInternal) { + throw new IOException("Not allowed to install non-system apps on internal storage"); + } + if (fitsOnInternal) { return StorageManager.UUID_PRIVATE_INTERNAL; } else { @@ -411,14 +494,13 @@ public class PackageHelper { } } - // We're left with either preferring external or auto, so just pick + // We're left with new installations with either preferring external or auto, so just pick // volume with most space if (bestCandidate != null) { return bestCandidate.fsUuid; - } else if (fitsOnInternal) { - return StorageManager.UUID_PRIVATE_INTERNAL; } else { - throw new IOException("No special requests, but no room anywhere"); + throw new IOException("No special requests, but no room on allowed volumes. " + + " allow3rdPartyOnInternal? " + allow3rdPartyOnInternal); } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index b60e6b5ee3c1..6a5bbccb48f0 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -111,7 +111,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 150 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 151 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS = 2000; @@ -1603,7 +1603,7 @@ public class BatteryStatsImpl extends BatteryStats { @Override public void writeToParcel(Parcel out, long elapsedRealtimeUs) { super.writeToParcel(out, elapsedRealtimeUs); - out.writeLong(mMaxDurationMs); + out.writeLong(getMaxDurationMsLocked(elapsedRealtimeUs / 1000)); } /** @@ -1616,7 +1616,7 @@ public class BatteryStatsImpl extends BatteryStats { @Override public void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) { super.writeSummaryFromParcelLocked(out, elapsedRealtimeUs); - out.writeLong(mMaxDurationMs); + out.writeLong(getMaxDurationMsLocked(elapsedRealtimeUs / 1000)); } /** @@ -1640,7 +1640,7 @@ public class BatteryStatsImpl extends BatteryStats { public void onTimeStarted(long elapsedRealtimeUs, long baseUptime, long baseRealtime) { super.onTimeStarted(elapsedRealtimeUs, baseUptime, baseRealtime); if (mNesting > 0) { - mStartTimeMs = mTimeBase.getRealtime(mClocks.elapsedRealtime()*1000) / 1000; + mStartTimeMs = baseRealtime / 1000; } } @@ -1650,10 +1650,11 @@ public class BatteryStatsImpl extends BatteryStats { * If the timer is running, add the duration into mCurrentDurationMs. */ @Override - public void onTimeStopped(long elapsedRealtimeUs, long baseUptime, long baseRealtime) { - super.onTimeStopped(elapsedRealtimeUs, baseUptime, baseRealtime); + public void onTimeStopped(long elapsedRealtimeUs, long baseUptime, long baseRealtimeUs) { + super.onTimeStopped(elapsedRealtimeUs, baseUptime, baseRealtimeUs); if (mNesting > 0) { - mCurrentDurationMs += (elapsedRealtimeUs / 1000) - mStartTimeMs; + // baseRealtimeUs has already been converted to the timebase's realtime. + mCurrentDurationMs += (baseRealtimeUs / 1000) - mStartTimeMs; } mStartTimeMs = -1; } @@ -1668,7 +1669,7 @@ public class BatteryStatsImpl extends BatteryStats { super.startRunningLocked(elapsedRealtimeMs); if (mNesting == 1 && mTimeBase.isRunning()) { // Just started - mStartTimeMs = mTimeBase.getRealtime(mClocks.elapsedRealtime()*1000) / 1000; + mStartTimeMs = mTimeBase.getRealtime(elapsedRealtimeMs * 1000) / 1000; } } @@ -1680,8 +1681,7 @@ public class BatteryStatsImpl extends BatteryStats { */ @Override public void stopRunningLocked(long elapsedRealtimeMs) { - super.stopRunningLocked(elapsedRealtimeMs); - if (mNesting == 0) { + if (mNesting == 1) { final long durationMs = getCurrentDurationMsLocked(elapsedRealtimeMs); if (durationMs > mMaxDurationMs) { mMaxDurationMs = durationMs; @@ -1689,6 +1689,9 @@ public class BatteryStatsImpl extends BatteryStats { mStartTimeMs = -1; mCurrentDurationMs = 0; } + // super method decrements mNesting, which getCurrentDurationMsLocked relies on, + // so call super.stopRunningLocked after calling getCurrentDurationMsLocked. + super.stopRunningLocked(elapsedRealtimeMs); } @Override @@ -1730,11 +1733,9 @@ public class BatteryStatsImpl extends BatteryStats { @Override public long getCurrentDurationMsLocked(long elapsedRealtimeMs) { long durationMs = mCurrentDurationMs; - if (mNesting > 0) { - if (mTimeBase.isRunning()) { - durationMs += (mTimeBase.getRealtime(elapsedRealtimeMs*1000)/1000) - - mStartTimeMs; - } + if (mNesting > 0 && mTimeBase.isRunning()) { + durationMs += (mTimeBase.getRealtime(elapsedRealtimeMs*1000)/1000) + - mStartTimeMs; } return durationMs; } @@ -2896,8 +2897,22 @@ public class BatteryStatsImpl extends BatteryStats { mHistoryLastWritten.setTo(mHistoryLastLastWritten); } + boolean recordResetDueToOverflow = false; final int dataSize = mHistoryBuffer.dataSize(); - if (dataSize >= MAX_HISTORY_BUFFER) { + if (dataSize >= MAX_MAX_HISTORY_BUFFER*3) { + // Clients can't deal with history buffers this large. This only + // really happens when the device is on charger and interacted with + // for long periods of time, like in retail mode. Since the device is + // most likely charged, when unplugged, stats would have reset anyways. + // Reset the stats and mark that we overflowed. + // b/32540341 + resetAllStatsLocked(); + + // Mark that we want to set *OVERFLOW* event and the RESET:START + // events. + recordResetDueToOverflow = true; + + } else if (dataSize >= MAX_HISTORY_BUFFER) { if (!mHistoryOverflow) { mHistoryOverflow = true; addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur); @@ -2943,9 +2958,12 @@ public class BatteryStatsImpl extends BatteryStats { return; } - if (dataSize == 0) { + if (dataSize == 0 || recordResetDueToOverflow) { // The history is currently empty; we need it to start with a time stamp. cur.currentTime = System.currentTimeMillis(); + if (recordResetDueToOverflow) { + addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_OVERFLOW, cur); + } addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_RESET, cur); } addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur); diff --git a/core/java/com/android/internal/os/IDropBoxManagerService.aidl b/core/java/com/android/internal/os/IDropBoxManagerService.aidl index d067926a096c..d16579c03d7a 100644 --- a/core/java/com/android/internal/os/IDropBoxManagerService.aidl +++ b/core/java/com/android/internal/os/IDropBoxManagerService.aidl @@ -17,7 +17,6 @@ package com.android.internal.os; import android.os.DropBoxManager; -import android.os.ParcelFileDescriptor; /** * "Backend" interface used by {@link android.os.DropBoxManager} to talk to the diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java index d968e3c939ab..a8a55499f5f3 100644 --- a/core/java/com/android/internal/os/WebViewZygoteInit.java +++ b/core/java/com/android/internal/os/WebViewZygoteInit.java @@ -62,6 +62,9 @@ class WebViewZygoteInit { ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader( packagePath, libsPath); + // Add the APK to the Zygote's list of allowed files for children. + Zygote.nativeAllowFileAcrossFork(packagePath); + // Once we have the classloader, look up the WebViewFactoryProvider implementation and // call preloadInZygote() on it to give it the opportunity to preload the native library // and perform any other initialisation work that should be shared among the children. diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index fc0ccb75de95..293de3d71332 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -153,6 +153,11 @@ public final class Zygote { int[][] rlimits, long permittedCapabilities, long effectiveCapabilities); /** + * Lets children of the zygote inherit open file descriptors to this path. + */ + native protected static void nativeAllowFileAcrossFork(String path); + + /** * Zygote unmount storage space on initializing. * This method is called once. */ diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 7edc938c3742..39cb464de56c 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -695,9 +695,11 @@ class ZygoteConnection { throws ZygoteSecurityException { int peerUid = peer.getUid(); - if (args.invokeWith != null && peerUid != 0) { - throw new ZygoteSecurityException("Peer is not permitted to specify " - + "an explicit invoke-with wrapper command"); + if (args.invokeWith != null && peerUid != 0 && + (args.debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) == 0) { + throw new ZygoteSecurityException("Peer is permitted to specify an" + + "explicit invoke-with wrapper command only for debuggable" + + "applications."); } } diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index ec068a32f814..e68ebc4342fd 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -2997,6 +2997,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { swipeDismiss.setOnDismissedListener(new SwipeDismissLayout.OnDismissedListener() { @Override public void onDismissed(SwipeDismissLayout layout) { + dispatchOnWindowSwipeDismissed(); dispatchOnWindowDismissed(false /*finishTask*/, true /*suppressWindowTransition*/); } }); diff --git a/core/java/com/android/internal/util/TokenBucket.java b/core/java/com/android/internal/util/TokenBucket.java index effb82ba7a2d..a163ceb5f8f8 100644 --- a/core/java/com/android/internal/util/TokenBucket.java +++ b/core/java/com/android/internal/util/TokenBucket.java @@ -33,6 +33,8 @@ import static com.android.internal.util.Preconditions.checkArgumentPositive; * The available amount of tokens is computed lazily when the bucket state is inspected. * Therefore it is purely synchronous and does not involve any asynchronous activity. * It is not synchronized in any way and not a thread-safe object. + * + * {@hide} */ public class TokenBucket { diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 63b700bde76b..2a8077cf3f70 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -590,8 +590,6 @@ public class LockPatternUtils { setCredentialRequiredToDecrypt(false); } - getDevicePolicyManager().setActivePasswordState(new PasswordMetrics(), userHandle); - onAfterChangingPassword(userHandle); } @@ -644,6 +642,7 @@ public class LockPatternUtils { + MIN_LOCK_PATTERN_SIZE + " dots long."); } + setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId); getLockSettings().setLockPattern(patternToString(pattern), savedPattern, userId); DevicePolicyManager dpm = getDevicePolicyManager(); @@ -659,10 +658,6 @@ public class LockPatternUtils { } setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId); - - setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId); - dpm.setActivePasswordState(new PasswordMetrics( - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern.size()), userId); onAfterChangingPassword(userId); } catch (RemoteException re) { Log.e(TAG, "Couldn't save lock pattern " + re); @@ -775,10 +770,9 @@ public class LockPatternUtils { + "of length " + MIN_LOCK_PASSWORD_SIZE); } + final int computedQuality = PasswordMetrics.computeForPassword(password).quality; + setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle); getLockSettings().setLockPassword(password, savedPassword, userHandle); - getLockSettings().setSeparateProfileChallengeEnabled(userHandle, true, null); - final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password); - final int computedQuality = metrics.quality; // Update the device encryption password. if (userHandle == UserHandle.USER_SYSTEM @@ -796,15 +790,6 @@ public class LockPatternUtils { } } - setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle); - if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { - metrics.quality = Math.max(quality, metrics.quality); - dpm.setActivePasswordState(metrics, userHandle); - } else { - // The password is not anything. - dpm.setActivePasswordState(new PasswordMetrics(), userHandle); - } - // Add the password to the password history. We assume all // password hashes have the same length for simplicity of implementation. String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle); diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java index 83b49eb47e56..b5d62689d34e 100644 --- a/core/java/com/android/internal/widget/SwipeDismissLayout.java +++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java @@ -339,18 +339,16 @@ public class SwipeDismissLayout extends FrameLayout { mVelocityTracker.addMovement(ev); mVelocityTracker.computeCurrentVelocity(1000); if (!mDismissed) { - - if (deltaX > (getWidth() * DISMISS_MIN_DRAG_WIDTH_RATIO) && - ev.getRawX() >= mLastX) { + if ((deltaX > (getWidth() * DISMISS_MIN_DRAG_WIDTH_RATIO) && + ev.getRawX() >= mLastX) + || mVelocityTracker.getXVelocity() >= mMinFlingVelocity) { mDismissed = true; } } // Check if the user tried to undo this. if (mDismissed && mSwiping) { - // Check if the user's finger is actually back - if (deltaX < (getWidth() * DISMISS_MIN_DRAG_WIDTH_RATIO) || - // or user is flinging back left - mVelocityTracker.getXVelocity() < -mMinFlingVelocity) { + // Check if the user's finger is actually flinging back to left + if (mVelocityTracker.getXVelocity() < -mMinFlingVelocity) { mDismissed = false; } } diff --git a/core/jni/Android.mk b/core/jni/Android.mk index e447dd25aa80..35550579243c 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -136,7 +136,6 @@ LOCAL_SRC_FILES:= \ android/graphics/PathEffect.cpp \ android/graphics/Picture.cpp \ android/graphics/BitmapRegionDecoder.cpp \ - android/graphics/Rasterizer.cpp \ android/graphics/Region.cpp \ android/graphics/Shader.cpp \ android/graphics/SurfaceTexture.cpp \ @@ -189,8 +188,10 @@ LOCAL_SRC_FILES:= \ com_android_internal_util_VirtualRefBasePtr.cpp \ com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp \ hwbinder/EphemeralStorage.cpp \ + fd_utils.cpp \ LOCAL_C_INCLUDES += \ + $(LOCAL_PATH)/include \ $(JNI_H_INCLUDE) \ $(LOCAL_PATH)/android/graphics \ $(LOCAL_PATH)/../../libs/hwui \ @@ -284,8 +285,10 @@ LOCAL_SHARED_LIBRARIES += \ # <bionic_tls.h> in com_google_android_gles_jni_GLImpl.cpp LOCAL_C_INCLUDES += bionic/libc/private +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include + # AndroidRuntime.h depends on nativehelper/jni.h -LOCAL_EXPORT_C_INCLUDE_DIRS := libnativehelper/include +LOCAL_EXPORT_C_INCLUDE_DIRS += libnativehelper/include LOCAL_MODULE:= libandroid_runtime diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 1f810ac203ad..d47c6b72ec0f 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -121,13 +121,11 @@ extern int register_android_graphics_CanvasProperty(JNIEnv* env); extern int register_android_graphics_ColorFilter(JNIEnv* env); extern int register_android_graphics_DrawFilter(JNIEnv* env); extern int register_android_graphics_FontFamily(JNIEnv* env); -extern int register_android_graphics_LayerRasterizer(JNIEnv*); extern int register_android_graphics_Matrix(JNIEnv* env); extern int register_android_graphics_Paint(JNIEnv* env); extern int register_android_graphics_Path(JNIEnv* env); extern int register_android_graphics_PathMeasure(JNIEnv* env); extern int register_android_graphics_Picture(JNIEnv*); -extern int register_android_graphics_Rasterizer(JNIEnv* env); extern int register_android_graphics_Region(JNIEnv* env); extern int register_android_graphics_SurfaceTexture(JNIEnv* env); extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env); @@ -1332,7 +1330,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_DrawFilter), REG_JNI(register_android_graphics_FontFamily), REG_JNI(register_android_graphics_Interpolator), - REG_JNI(register_android_graphics_LayerRasterizer), REG_JNI(register_android_graphics_MaskFilter), REG_JNI(register_android_graphics_Matrix), REG_JNI(register_android_graphics_Movie), @@ -1342,7 +1339,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_PathMeasure), REG_JNI(register_android_graphics_PathEffect), REG_JNI(register_android_graphics_Picture), - REG_JNI(register_android_graphics_Rasterizer), REG_JNI(register_android_graphics_Region), REG_JNI(register_android_graphics_Shader), REG_JNI(register_android_graphics_SurfaceTexture), diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 178e07342555..8f74bf8a4b6f 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -5,7 +5,11 @@ #include "SkPixelRef.h" #include "SkImageEncoder.h" #include "SkImageInfo.h" +#include "SkColor.h" #include "SkColorPriv.h" +#include "SkHalf.h" +#include "SkPM4f.h" +#include "SkPM4fPriv.h" #include "GraphicsJNI.h" #include "SkDither.h" #include "SkUnPreMultiply.h" @@ -232,6 +236,28 @@ using namespace android::bitmap; typedef void (*FromColorProc)(void* dst, const SkColor src[], int width, int x, int y); +static void FromColor_F16(void* dst, const SkColor src[], int width, + int, int) { + uint64_t* d = (uint64_t*)dst; + + for (int i = 0; i < width; i++) { + *d++ = SkColor4f::FromColor(*src++).premul().toF16(); + } +} + +static void FromColor_F16_Raw(void* dst, const SkColor src[], int width, + int, int) { + uint64_t* d = (uint64_t*)dst; + + for (int i = 0; i < width; i++) { + const float* color = SkColor4f::FromColor(*src++).vec(); + uint16_t* scratch = reinterpret_cast<uint16_t*>(d++); + for (int i = 0; i < 4; ++i) { + scratch[i] = SkFloatToHalf(color[i]); + } + } +} + static void FromColor_D32(void* dst, const SkColor src[], int width, int, int) { SkPMColor* d = (SkPMColor*)dst; @@ -321,6 +347,8 @@ static FromColorProc ChooseFromColorProc(const SkBitmap& bitmap) { return FromColor_D565; case kAlpha_8_SkColorType: return FromColor_DA8; + case kRGBA_F16_SkColorType: + return bitmap.alphaType() == kPremul_SkAlphaType ? FromColor_F16 : FromColor_F16_Raw; default: break; } @@ -351,8 +379,7 @@ bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors, int srcOffset, int dstBitmap.notifyPixelsChanged(); - env->ReleaseIntArrayElements(srcColors, const_cast<jint*>(array), - JNI_ABORT); + env->ReleaseIntArrayElements(srcColors, const_cast<jint*>(array), JNI_ABORT); return true; } @@ -361,6 +388,24 @@ bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors, int srcOffset, int typedef void (*ToColorProc)(SkColor dst[], const void* src, int width, SkColorTable*); +static void ToColor_F16_Alpha(SkColor dst[], const void* src, int width, + SkColorTable*) { + SkASSERT(width > 0); + uint64_t* s = (uint64_t*)src; + do { + *dst++ = SkPM4f::FromF16((const uint16_t*) s++).unpremul().toSkColor(); + } while (--width != 0); +} + +static void ToColor_F16_Raw(SkColor dst[], const void* src, int width, + SkColorTable*) { + SkASSERT(width > 0); + uint64_t* s = (uint64_t*)src; + do { + *dst++ = Sk4f_toS32(swizzle_rb(SkHalfToFloat_finite_ftz(*s++))); + } while (--width != 0); +} + static void ToColor_S32_Alpha(SkColor dst[], const void* src, int width, SkColorTable*) { SkASSERT(width > 0); @@ -520,6 +565,17 @@ static ToColorProc ChooseToColorProc(const SkBitmap& src) { } case kAlpha_8_SkColorType: return ToColor_SA8; + case kRGBA_F16_SkColorType: + switch (src.alphaType()) { + case kOpaque_SkAlphaType: + return ToColor_F16_Raw; + case kPremul_SkAlphaType: + return ToColor_F16_Alpha; + case kUnpremul_SkAlphaType: + return ToColor_F16_Raw; + default: + return NULL; + } default: break; } @@ -554,7 +610,7 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, SkBitmap bitmap; bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType, - GraphicsJNI::defaultColorSpace())); + GraphicsJNI::colorSpaceForType(colorType))); sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap, NULL); if (!nativeBitmap) { @@ -562,8 +618,7 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, } if (jColors != NULL) { - GraphicsJNI::SetPixels(env, jColors, offset, stride, - 0, 0, width, height, bitmap); + GraphicsJNI::SetPixels(env, jColors, offset, stride, 0, 0, width, height, bitmap); } return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable)); @@ -573,6 +628,14 @@ static jobject Bitmap_copy(JNIEnv* env, jobject, jlong srcHandle, jint dstConfigHandle, jboolean isMutable) { SkBitmap src; reinterpret_cast<BitmapWrapper*>(srcHandle)->getSkBitmap(&src); + if (dstConfigHandle == GraphicsJNI::hardwareLegacyBitmapConfig()) { + sk_sp<Bitmap> bitmap(Bitmap::allocateHardwareBitmap(src)); + if (!bitmap.get()) { + return NULL; + } + return createBitmap(env, bitmap.release(), kBitmapCreateFlag_None); + } + SkColorType dstCT = GraphicsJNI::legacyBitmapConfigToColorType(dstConfigHandle); SkBitmap result; HeapAllocator allocator; @@ -712,6 +775,9 @@ static jint Bitmap_rowBytes(JNIEnv* env, jobject, jlong bitmapHandle) { static jint Bitmap_config(JNIEnv* env, jobject, jlong bitmapHandle) { LocalScopedBitmap bitmap(bitmapHandle); + if (bitmap->bitmap().isHardware()) { + return GraphicsJNI::hardwareLegacyBitmapConfig(); + } return GraphicsJNI::colorTypeToLegacyBitmapConfig(bitmap->info().colorType()); } @@ -787,6 +853,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { const int density = p->readInt32(); if (kN32_SkColorType != colorType && + kRGBA_F16_SkColorType != colorType && kRGB_565_SkColorType != colorType && kARGB_4444_SkColorType != colorType && kIndex_8_SkColorType != colorType && @@ -797,8 +864,15 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { std::unique_ptr<SkBitmap> bitmap(new SkBitmap); - if (!bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType, - isSRGB ? SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named) : nullptr), rowBytes)) { + sk_sp<SkColorSpace> colorSpace; + if (kRGBA_F16_SkColorType == colorType) { + colorSpace = SkColorSpace::MakeNamed(SkColorSpace::kSRGBLinear_Named); + } else { + colorSpace = isSRGB ? SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named) : nullptr; + } + + if (!bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType, colorSpace), + rowBytes)) { return NULL; } @@ -1124,17 +1198,27 @@ static void Bitmap_copyPixelsFromBuffer(JNIEnv* env, jobject, } } -static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, - jlong bm1Handle) { +static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, jlong bm1Handle) { SkBitmap bm0; SkBitmap bm1; - reinterpret_cast<BitmapWrapper*>(bm0Handle)->getSkBitmap(&bm0); - reinterpret_cast<BitmapWrapper*>(bm1Handle)->getSkBitmap(&bm1); - if (bm0.width() != bm1.width() || - bm0.height() != bm1.height() || - bm0.colorType() != bm1.colorType() || - bm0.alphaType() != bm1.alphaType() || - bm0.colorSpace() != bm1.colorSpace()) { + + LocalScopedBitmap bitmap0(bm0Handle); + LocalScopedBitmap bitmap1(bm1Handle); + + // Paying the price for making Hardware Bitmap as Config: + // later check for colorType will pass successfully, + // because Hardware Config internally may be RGBA8888 or smth like that. + if (bitmap0->bitmap().isHardware() != bitmap1->bitmap().isHardware()) { + return JNI_FALSE; + } + + bitmap0->bitmap().getSkBitmap(&bm0); + bitmap1->bitmap().getSkBitmap(&bm1); + if (bm0.width() != bm1.width() + || bm0.height() != bm1.height() + || bm0.colorType() != bm1.colorType() + || bm0.alphaType() != bm1.alphaType() + || !SkColorSpace::Equals(bm0.colorSpace(), bm1.colorSpace())) { return JNI_FALSE; } diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index e86acc4b200c..724fccc2e91b 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -276,10 +276,15 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding } } + if (isMutable && isHardware) { + doThrowIAE(env, "Bitmaps with Config.HARWARE are always immutable"); + return nullObjectReturn("Cannot create mutable hardware bitmap"); + } + // Create the codec. NinePatchPeeker peeker; - std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(streamDeleter.release(), - &peeker)); + std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream( + streamDeleter.release(), &peeker)); if (!codec.get()) { return nullObjectReturn("SkAndroidCodec::NewFromStream returned null"); } @@ -390,9 +395,12 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied); const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), - decodeColorType, alphaType, GraphicsJNI::defaultColorSpace()); + decodeColorType, alphaType); - SkImageInfo bitmapInfo = decodeInfo; + // We always decode to sRGB, but only mark the bitmap with a color space if linear + // blending is enabled. + SkImageInfo bitmapInfo = decodeInfo.makeColorSpace( + GraphicsJNI::colorSpaceForType(decodeColorType)); if (decodeColorType == kGray_8_SkColorType) { // The legacy implementation of BitmapFactory used kAlpha8 for // grayscale images (before kGray8 existed). While the codec diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index e740428131be..6f97c600a1cc 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -297,13 +297,16 @@ enum LegacyBitmapConfig { kRGB_565_LegacyBitmapConfig = 3, kARGB_4444_LegacyBitmapConfig = 4, kARGB_8888_LegacyBitmapConfig = 5, - kHardware_LegacyBitmapConfig = 6, + kRGBA_16F_LegacyBitmapConfig = 6, + kHardware_LegacyBitmapConfig = 7, kLastEnum_LegacyBitmapConfig = kHardware_LegacyBitmapConfig }; jint GraphicsJNI::colorTypeToLegacyBitmapConfig(SkColorType colorType) { switch (colorType) { + case kRGBA_F16_SkColorType: + return kRGBA_16F_LegacyBitmapConfig; case kN32_SkColorType: return kARGB_8888_LegacyBitmapConfig; case kARGB_4444_SkColorType: @@ -329,6 +332,7 @@ SkColorType GraphicsJNI::legacyBitmapConfigToColorType(jint legacyConfig) { kRGB_565_SkColorType, kARGB_4444_SkColorType, kN32_SkColorType, + kRGBA_F16_SkColorType, kN32_SkColorType }; @@ -366,6 +370,10 @@ bool GraphicsJNI::isHardwareConfig(JNIEnv* env, jobject jconfig) { return c == kHardware_LegacyBitmapConfig; } +jint GraphicsJNI::hardwareLegacyBitmapConfig() { + return kHardware_LegacyBitmapConfig; +} + android::Canvas* GraphicsJNI::getNativeCanvas(JNIEnv* env, jobject canvas) { SkASSERT(env); SkASSERT(canvas); @@ -454,6 +462,19 @@ sk_sp<SkColorSpace> GraphicsJNI::defaultColorSpace() { #endif } +sk_sp<SkColorSpace> GraphicsJNI::linearColorSpace() { + return SkColorSpace::MakeNamed(SkColorSpace::kSRGBLinear_Named); +} + +sk_sp<SkColorSpace> GraphicsJNI::colorSpaceForType(SkColorType type) { + switch (type) { + case kRGBA_F16_SkColorType: + return linearColorSpace(); + default: + return defaultColorSpace(); + } +} + /////////////////////////////////////////////////////////////////////////////// bool HeapAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { mStorage = android::Bitmap::allocateHeapBitmap(bitmap, ctable); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index ced9939774a9..508c9ffd0f20 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -69,6 +69,7 @@ public: static SkColorType getNativeBitmapColorType(JNIEnv*, jobject jconfig); static bool isHardwareConfig(JNIEnv* env, jobject jconfig); + static jint hardwareLegacyBitmapConfig(); static jobject createRegion(JNIEnv* env, SkRegion* region); @@ -94,6 +95,8 @@ public: const SkBitmap& dstBitmap); static sk_sp<SkColorSpace> defaultColorSpace(); + static sk_sp<SkColorSpace> linearColorSpace(); + static sk_sp<SkColorSpace> colorSpaceForType(SkColorType type); }; class HeapAllocator : public SkBRDAllocator { diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 34568391167d..dfe809dd7d58 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -30,7 +30,6 @@ #include "SkMaskFilter.h" #include "SkPath.h" #include "SkPathEffect.h" -#include "SkRasterizer.h" #include "SkShader.h" #include "SkBlendMode.h" #include "unicode/uloc.h" @@ -848,12 +847,6 @@ namespace PaintGlue { return 0; } - static jlong setRasterizer(jlong objHandle, jlong rasterizerHandle) { - Paint* obj = reinterpret_cast<Paint*>(objHandle); - obj->setRasterizer(GraphicsJNI::refNativeRasterizer(rasterizerHandle)); - return reinterpret_cast<jlong>(obj->getRasterizer()); - } - static jint getTextAlign(jlong objHandle) { Paint* obj = reinterpret_cast<Paint*>(objHandle); return static_cast<jint>(obj->getTextAlign()); @@ -1038,7 +1031,6 @@ static const JNINativeMethod methods[] = { {"nSetPathEffect","(JJ)J", (void*) PaintGlue::setPathEffect}, {"nSetMaskFilter","(JJ)J", (void*) PaintGlue::setMaskFilter}, {"nSetTypeface","(JJ)J", (void*) PaintGlue::setTypeface}, - {"nSetRasterizer","(JJ)J", (void*) PaintGlue::setRasterizer}, {"nGetTextAlign","(J)I", (void*) PaintGlue::getTextAlign}, {"nSetTextAlign","(JI)V", (void*) PaintGlue::setTextAlign}, {"nSetTextLocalesByMinikinLangListId","(JI)V", diff --git a/core/jni/android/graphics/Path.cpp b/core/jni/android/graphics/Path.cpp index ab393f2bc1f1..292454bd0875 100644 --- a/core/jni/android/graphics/Path.cpp +++ b/core/jni/android/graphics/Path.cpp @@ -36,121 +36,88 @@ namespace android { class SkPathGlue { public: - static void finalizer(JNIEnv* env, jobject clazz, jlong objHandle) { - SkPath* obj = reinterpret_cast<SkPath*>(objHandle); - // Purge entries from the HWUI path cache if this path's data is unique - if (obj->unique() && android::uirenderer::Caches::hasInstance()) { - android::uirenderer::Caches::getInstance().pathCache.removeDeferred(obj); - } - delete obj; - } + // ---------------- Regular JNI ----------------------------- - static jlong init1(JNIEnv* env, jobject clazz) { + static jlong init(JNIEnv* env, jclass clazz) { return reinterpret_cast<jlong>(new SkPath()); } - static jlong init2(JNIEnv* env, jobject clazz, jlong valHandle) { + static jlong init_Path(JNIEnv* env, jclass clazz, jlong valHandle) { SkPath* val = reinterpret_cast<SkPath*>(valHandle); return reinterpret_cast<jlong>(new SkPath(*val)); } - static void reset(JNIEnv* env, jobject clazz, jlong objHandle) { + static void finalize(JNIEnv* env, jclass clazz, jlong objHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); - obj->reset(); - } - - static void rewind(JNIEnv* env, jobject clazz, jlong objHandle) { - SkPath* obj = reinterpret_cast<SkPath*>(objHandle); - obj->rewind(); + // Purge entries from the HWUI path cache if this path's data is unique + if (obj->unique() && android::uirenderer::Caches::hasInstance()) { + android::uirenderer::Caches::getInstance().pathCache.removeDeferred(obj); + } + delete obj; } - static void assign(JNIEnv* env, jobject clazz, jlong dstHandle, jlong srcHandle) { + static void set(JNIEnv* env, jclass clazz, jlong dstHandle, jlong srcHandle) { SkPath* dst = reinterpret_cast<SkPath*>(dstHandle); const SkPath* src = reinterpret_cast<SkPath*>(srcHandle); *dst = *src; } - static jboolean isConvex(JNIEnv* env, jobject clazz, jlong objHandle) { - SkPath* obj = reinterpret_cast<SkPath*>(objHandle); - return obj->isConvex(); - } - - static jint getFillType(JNIEnv* env, jobject clazz, jlong objHandle) { - SkPath* obj = reinterpret_cast<SkPath*>(objHandle); - return obj->getFillType(); - } - - static void setFillType(JNIEnv* env, jobject clazz, jlong pathHandle, jint ftHandle) { - SkPath* path = reinterpret_cast<SkPath*>(pathHandle); - SkPath::FillType ft = static_cast<SkPath::FillType>(ftHandle); - path->setFillType(ft); - } - - static jboolean isEmpty(JNIEnv* env, jobject clazz, jlong objHandle) { - SkPath* obj = reinterpret_cast<SkPath*>(objHandle); - return obj->isEmpty(); - } - - static jboolean isRect(JNIEnv* env, jobject clazz, jlong objHandle, jobject jrect) { - SkRect rect; - SkPath* obj = reinterpret_cast<SkPath*>(objHandle); - jboolean result = obj->isRect(&rect); - GraphicsJNI::rect_to_jrectf(rect, env, jrect); - return result; - } - - static void computeBounds(JNIEnv* env, jobject clazz, jlong objHandle, jobject jbounds) { + static void computeBounds(JNIEnv* env, jclass clazz, jlong objHandle, jobject jbounds) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); const SkRect& bounds = obj->getBounds(); GraphicsJNI::rect_to_jrectf(bounds, env, jbounds); } - static void incReserve(JNIEnv* env, jobject clazz, jlong objHandle, jint extraPtCount) { + static void incReserve(JNIEnv* env, jclass clazz, jlong objHandle, jint extraPtCount) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->incReserve(extraPtCount); } - static void moveTo__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x, jfloat y) { + static void moveTo__FF(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x, jfloat y) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->moveTo(x, y); } - static void rMoveTo(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy) { + static void rMoveTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat dx, jfloat dy) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->rMoveTo(dx, dy); } - static void lineTo__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x, jfloat y) { + static void lineTo__FF(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x, jfloat y) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->lineTo(x, y); } - static void rLineTo(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy) { + static void rLineTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat dx, jfloat dy) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->rLineTo(dx, dy); } - static void quadTo__FFFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x1, jfloat y1, jfloat x2, jfloat y2) { + static void quadTo__FFFF(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x1, jfloat y1, + jfloat x2, jfloat y2) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->quadTo(x1, y1, x2, y2); } - static void rQuadTo(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx1, jfloat dy1, jfloat dx2, jfloat dy2) { + static void rQuadTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat dx1, jfloat dy1, + jfloat dx2, jfloat dy2) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->rQuadTo(dx1, dy1, dx2, dy2); } - static void cubicTo__FFFFFF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x1, jfloat y1, jfloat x2, jfloat y2, jfloat x3, jfloat y3) { + static void cubicTo__FFFFFF(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x1, jfloat y1, + jfloat x2, jfloat y2, jfloat x3, jfloat y3) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->cubicTo(x1, y1, x2, y2, x3, y3); } - static void rCubicTo(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x1, jfloat y1, jfloat x2, jfloat y2, jfloat x3, jfloat y3) { + static void rCubicTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x1, jfloat y1, + jfloat x2, jfloat y2, jfloat x3, jfloat y3) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->rCubicTo(x1, y1, x2, y2, x3, y3); } - static void arcTo(JNIEnv* env, jobject clazz, jlong objHandle, jfloat left, jfloat top, + static void arcTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat startAngle, jfloat sweepAngle, jboolean forceMoveTo) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); @@ -158,19 +125,19 @@ public: obj->arcTo(oval, startAngle, sweepAngle, forceMoveTo); } - static void close(JNIEnv* env, jobject clazz, jlong objHandle) { + static void close(JNIEnv* env, jclass clazz, jlong objHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->close(); } - static void addRect(JNIEnv* env, jobject clazz, jlong objHandle, + static void addRect(JNIEnv* env, jclass clazz, jlong objHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, jint dirHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle); obj->addRect(left, top, right, bottom, dir); } - static void addOval(JNIEnv* env, jobject clazz, jlong objHandle, + static void addOval(JNIEnv* env, jclass clazz, jlong objHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, jint dirHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle); @@ -178,20 +145,21 @@ public: obj->addOval(oval, dir); } - static void addCircle(JNIEnv* env, jobject clazz, jlong objHandle, jfloat x, jfloat y, jfloat radius, jint dirHandle) { + static void addCircle(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x, jfloat y, + jfloat radius, jint dirHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle); obj->addCircle(x, y, radius, dir); } - static void addArc(JNIEnv* env, jobject clazz, jlong objHandle, jfloat left, jfloat top, + static void addArc(JNIEnv* env, jclass clazz, jlong objHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat startAngle, jfloat sweepAngle) { SkRect oval = SkRect::MakeLTRB(left, top, right, bottom); SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->addArc(oval, startAngle, sweepAngle); } - static void addRoundRectXY(JNIEnv* env, jobject clazz, jlong objHandle, jfloat left, jfloat top, + static void addRoundRectXY(JNIEnv* env, jclass clazz, jlong objHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat rx, jfloat ry, jint dirHandle) { SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); SkPath* obj = reinterpret_cast<SkPath*>(objHandle); @@ -199,8 +167,8 @@ public: obj->addRoundRect(rect, rx, ry, dir); } - static void addRoundRect8(JNIEnv* env, jobject, jlong objHandle, jfloat left, jfloat top, - jfloat right, jfloat bottom, jfloatArray array, jint dirHandle) { + static void addRoundRect8(JNIEnv* env, jclass clazz, jlong objHandle, jfloat left, jfloat top, + jfloat right, jfloat bottom, jfloatArray array, jint dirHandle) { SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkPath::Direction dir = static_cast<SkPath::Direction>(dirHandle); @@ -213,49 +181,53 @@ public: obj->addRoundRect(rect, src, dir); } - static void addPath__PathFF(JNIEnv* env, jobject clazz, jlong objHandle, jlong srcHandle, jfloat dx, jfloat dy) { + static void addPath__PathFF(JNIEnv* env, jclass clazz, jlong objHandle, jlong srcHandle, + jfloat dx, jfloat dy) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkPath* src = reinterpret_cast<SkPath*>(srcHandle); obj->addPath(*src, dx, dy); } - static void addPath__Path(JNIEnv* env, jobject clazz, jlong objHandle, jlong srcHandle) { + static void addPath__Path(JNIEnv* env, jclass clazz, jlong objHandle, jlong srcHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkPath* src = reinterpret_cast<SkPath*>(srcHandle); obj->addPath(*src); } - static void addPath__PathMatrix(JNIEnv* env, jobject clazz, jlong objHandle, jlong srcHandle, jlong matrixHandle) { + static void addPath__PathMatrix(JNIEnv* env, jclass clazz, jlong objHandle, jlong srcHandle, + jlong matrixHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkPath* src = reinterpret_cast<SkPath*>(srcHandle); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); obj->addPath(*src, *matrix); } - static void offset__FF(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy) { + static void offset__FF(JNIEnv* env, jclass clazz, jlong objHandle, jfloat dx, jfloat dy) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->offset(dx, dy); } - static void setLastPoint(JNIEnv* env, jobject clazz, jlong objHandle, jfloat dx, jfloat dy) { + static void setLastPoint(JNIEnv* env, jclass clazz, jlong objHandle, jfloat dx, jfloat dy) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); obj->setLastPt(dx, dy); } - static void transform__MatrixPath(JNIEnv* env, jobject clazz, jlong objHandle, jlong matrixHandle, jlong dstHandle) { + static void transform__MatrixPath(JNIEnv* env, jclass clazz, jlong objHandle, jlong matrixHandle, + jlong dstHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); SkPath* dst = reinterpret_cast<SkPath*>(dstHandle); obj->transform(*matrix, dst); } - static void transform__Matrix(JNIEnv* env, jobject clazz, jlong objHandle, jlong matrixHandle) { + static void transform__Matrix(JNIEnv* env, jclass clazz, jlong objHandle, jlong matrixHandle) { SkPath* obj = reinterpret_cast<SkPath*>(objHandle); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); obj->transform(*matrix); } - static jboolean op(JNIEnv* env, jobject clazz, jlong p1Handle, jlong p2Handle, jint opHandle, jlong rHandle) { + static jboolean op(JNIEnv* env, jclass clazz, jlong p1Handle, jlong p2Handle, jint opHandle, + jlong rHandle) { SkPath* p1 = reinterpret_cast<SkPath*>(p1Handle); SkPath* p2 = reinterpret_cast<SkPath*>(p2Handle); SkPathOp op = static_cast<SkPathOp>(opHandle); @@ -416,8 +388,8 @@ public: // Note that more than one point may have the same length along the path in // the case of a move. // NULL can be returned if the Path is empty. - static jfloatArray approximate(JNIEnv* env, jclass, jlong pathHandle, float acceptableError) - { + static jfloatArray approximate(JNIEnv* env, jclass clazz, jlong pathHandle, + float acceptableError) { SkPath* path = reinterpret_cast<SkPath*>(pathHandle); SkASSERT(path); SkPath::Iter pathIter(*path, false); @@ -467,47 +439,94 @@ public: delete[] approximation; return result; } + + // ---------------- @FastNative ----------------------------- + + static jboolean isRect(JNIEnv* env, jclass clazz, jlong objHandle, jobject jrect) { + SkRect rect; + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + jboolean result = obj->isRect(&rect); + GraphicsJNI::rect_to_jrectf(rect, env, jrect); + return result; + } + + // ---------------- @CriticalNative ------------------------- + + static void reset(jlong objHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->reset(); + } + + static void rewind(jlong objHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + obj->rewind(); + } + + static jboolean isEmpty(jlong objHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + return obj->isEmpty(); + } + + static jboolean isConvex(jlong objHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + return obj->isConvex(); + } + + static jint getFillType(jlong objHandle) { + SkPath* obj = reinterpret_cast<SkPath*>(objHandle); + return obj->getFillType(); + } + + static void setFillType(jlong pathHandle, jint ftHandle) {; + SkPath* path = reinterpret_cast<SkPath*>(pathHandle); + SkPath::FillType ft = static_cast<SkPath::FillType>(ftHandle); + path->setFillType(ft); + } }; static const JNINativeMethod methods[] = { - {"finalizer", "(J)V", (void*) SkPathGlue::finalizer}, - {"init1","()J", (void*) SkPathGlue::init1}, - {"init2","(J)J", (void*) SkPathGlue::init2}, - {"native_reset","(J)V", (void*) SkPathGlue::reset}, - {"native_rewind","(J)V", (void*) SkPathGlue::rewind}, - {"native_set","(JJ)V", (void*) SkPathGlue::assign}, - {"native_isConvex","(J)Z", (void*) SkPathGlue::isConvex}, - {"native_getFillType","(J)I", (void*) SkPathGlue::getFillType}, - {"native_setFillType","(JI)V", (void*) SkPathGlue::setFillType}, - {"native_isEmpty","(J)Z", (void*) SkPathGlue::isEmpty}, - {"native_isRect","(JLandroid/graphics/RectF;)Z", (void*) SkPathGlue::isRect}, - {"native_computeBounds","(JLandroid/graphics/RectF;)V", (void*) SkPathGlue::computeBounds}, - {"native_incReserve","(JI)V", (void*) SkPathGlue::incReserve}, - {"native_moveTo","(JFF)V", (void*) SkPathGlue::moveTo__FF}, - {"native_rMoveTo","(JFF)V", (void*) SkPathGlue::rMoveTo}, - {"native_lineTo","(JFF)V", (void*) SkPathGlue::lineTo__FF}, - {"native_rLineTo","(JFF)V", (void*) SkPathGlue::rLineTo}, - {"native_quadTo","(JFFFF)V", (void*) SkPathGlue::quadTo__FFFF}, - {"native_rQuadTo","(JFFFF)V", (void*) SkPathGlue::rQuadTo}, - {"native_cubicTo","(JFFFFFF)V", (void*) SkPathGlue::cubicTo__FFFFFF}, - {"native_rCubicTo","(JFFFFFF)V", (void*) SkPathGlue::rCubicTo}, - {"native_arcTo","(JFFFFFFZ)V", (void*) SkPathGlue::arcTo}, - {"native_close","(J)V", (void*) SkPathGlue::close}, - {"native_addRect","(JFFFFI)V", (void*) SkPathGlue::addRect}, - {"native_addOval","(JFFFFI)V", (void*) SkPathGlue::addOval}, - {"native_addCircle","(JFFFI)V", (void*) SkPathGlue::addCircle}, - {"native_addArc","(JFFFFFF)V", (void*) SkPathGlue::addArc}, - {"native_addRoundRect","(JFFFFFFI)V", (void*) SkPathGlue::addRoundRectXY}, - {"native_addRoundRect","(JFFFF[FI)V", (void*) SkPathGlue::addRoundRect8}, - {"native_addPath","(JJFF)V", (void*) SkPathGlue::addPath__PathFF}, - {"native_addPath","(JJ)V", (void*) SkPathGlue::addPath__Path}, - {"native_addPath","(JJJ)V", (void*) SkPathGlue::addPath__PathMatrix}, - {"native_offset","(JFF)V", (void*) SkPathGlue::offset__FF}, - {"native_setLastPoint","(JFF)V", (void*) SkPathGlue::setLastPoint}, - {"native_transform","(JJJ)V", (void*) SkPathGlue::transform__MatrixPath}, - {"native_transform","(JJ)V", (void*) SkPathGlue::transform__Matrix}, - {"native_op","(JJIJ)Z", (void*) SkPathGlue::op}, - {"native_approximate", "(JF)[F", (void*) SkPathGlue::approximate}, + {"nInit","()J", (void*) SkPathGlue::init}, + {"nInit","(J)J", (void*) SkPathGlue::init_Path}, + {"nFinalize", "(J)V", (void*) SkPathGlue::finalize}, + {"nSet","(JJ)V", (void*) SkPathGlue::set}, + {"nComputeBounds","(JLandroid/graphics/RectF;)V", (void*) SkPathGlue::computeBounds}, + {"nIncReserve","(JI)V", (void*) SkPathGlue::incReserve}, + {"nMoveTo","(JFF)V", (void*) SkPathGlue::moveTo__FF}, + {"nRMoveTo","(JFF)V", (void*) SkPathGlue::rMoveTo}, + {"nLineTo","(JFF)V", (void*) SkPathGlue::lineTo__FF}, + {"nRLineTo","(JFF)V", (void*) SkPathGlue::rLineTo}, + {"nQuadTo","(JFFFF)V", (void*) SkPathGlue::quadTo__FFFF}, + {"nRQuadTo","(JFFFF)V", (void*) SkPathGlue::rQuadTo}, + {"nCubicTo","(JFFFFFF)V", (void*) SkPathGlue::cubicTo__FFFFFF}, + {"nRCubicTo","(JFFFFFF)V", (void*) SkPathGlue::rCubicTo}, + {"nArcTo","(JFFFFFFZ)V", (void*) SkPathGlue::arcTo}, + {"nClose","(J)V", (void*) SkPathGlue::close}, + {"nAddRect","(JFFFFI)V", (void*) SkPathGlue::addRect}, + {"nAddOval","(JFFFFI)V", (void*) SkPathGlue::addOval}, + {"nAddCircle","(JFFFI)V", (void*) SkPathGlue::addCircle}, + {"nAddArc","(JFFFFFF)V", (void*) SkPathGlue::addArc}, + {"nAddRoundRect","(JFFFFFFI)V", (void*) SkPathGlue::addRoundRectXY}, + {"nAddRoundRect","(JFFFF[FI)V", (void*) SkPathGlue::addRoundRect8}, + {"nAddPath","(JJFF)V", (void*) SkPathGlue::addPath__PathFF}, + {"nAddPath","(JJ)V", (void*) SkPathGlue::addPath__Path}, + {"nAddPath","(JJJ)V", (void*) SkPathGlue::addPath__PathMatrix}, + {"nOffset","(JFF)V", (void*) SkPathGlue::offset__FF}, + {"nSetLastPoint","(JFF)V", (void*) SkPathGlue::setLastPoint}, + {"nTransform","(JJJ)V", (void*) SkPathGlue::transform__MatrixPath}, + {"nTransform","(JJ)V", (void*) SkPathGlue::transform__Matrix}, + {"nOp","(JJIJ)Z", (void*) SkPathGlue::op}, + {"nApproximate", "(JF)[F", (void*) SkPathGlue::approximate}, + + // ------- @FastNative below here ---------------------- + {"nIsRect","(JLandroid/graphics/RectF;)Z", (void*) SkPathGlue::isRect}, + + // ------- @CriticalNative below here ------------------ + {"nReset","(J)V", (void*) SkPathGlue::reset}, + {"nRewind","(J)V", (void*) SkPathGlue::rewind}, + {"nIsEmpty","(J)Z", (void*) SkPathGlue::isEmpty}, + {"nIsConvex","(J)Z", (void*) SkPathGlue::isConvex}, + {"nGetFillType","(J)I", (void*) SkPathGlue::getFillType}, + {"nSetFillType","(JI)V", (void*) SkPathGlue::setFillType}, }; int register_android_graphics_Path(JNIEnv* env) { diff --git a/core/jni/android/graphics/Rasterizer.cpp b/core/jni/android/graphics/Rasterizer.cpp deleted file mode 100644 index f4094985177c..000000000000 --- a/core/jni/android/graphics/Rasterizer.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/* libs/android_runtime/android/graphics/Rasterizer.cpp -** -** Copyright 2006, 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. -*/ - -// This file was generated from the C++ include file: SkRasterizer.h -// Any changes made to this file will be discarded by the build. -// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, -// or one of the auxilary file specifications in device/tools/gluemaker. - -#include "jni.h" -#include "GraphicsJNI.h" -#include "SkLayerRasterizer.h" -#include "core_jni_helpers.h" - -#include <hwui/Paint.h> - -// Rasterizer.java holds a pointer (jlong) to this guy -class NativeRasterizer { -public: - NativeRasterizer() {} - virtual ~NativeRasterizer() {} - - // Can return NULL, or a ref to the skia rasterizer. - virtual sk_sp<SkRasterizer> refRasterizer() { return NULL; } -}; - -class NativeLayerRasterizer : public NativeRasterizer { -public: - SkLayerRasterizer::Builder fBuilder; - - virtual sk_sp<SkRasterizer> refRasterizer() { - return fBuilder.snapshot(); - } -}; - -sk_sp<SkRasterizer> GraphicsJNI::refNativeRasterizer(jlong rasterizerHandle) { - NativeRasterizer* nr = reinterpret_cast<NativeRasterizer*>(rasterizerHandle); - return nr ? nr->refRasterizer() : NULL; -} - -/////////////////////////////////////////////////////////////////////////////// - -namespace android { - -class SkRasterizerGlue { -public: - static void finalizer(JNIEnv* env, jobject clazz, jlong objHandle) { - delete reinterpret_cast<NativeRasterizer *>(objHandle); - } -}; - -static const JNINativeMethod gRasterizerMethods[] = { - {"finalizer", "(J)V", (void*) SkRasterizerGlue::finalizer} -}; - -int register_android_graphics_Rasterizer(JNIEnv* env) { - return RegisterMethodsOrDie(env, "android/graphics/Rasterizer", gRasterizerMethods, - NELEM(gRasterizerMethods)); -} - -class SkLayerRasterizerGlue { -public: - static jlong create(JNIEnv* env, jobject) { - return reinterpret_cast<jlong>(new NativeLayerRasterizer); - } - - static void addLayer(JNIEnv* env, jobject, jlong layerHandle, jlong paintHandle, jfloat dx, jfloat dy) { - NativeLayerRasterizer* nr = reinterpret_cast<NativeLayerRasterizer *>(layerHandle); - const Paint* paint = reinterpret_cast<Paint *>(paintHandle); - SkASSERT(nr); - SkASSERT(paint); - nr->fBuilder.addLayer(*paint, dx, dy); - } -}; - -static const JNINativeMethod gLayerRasterizerMethods[] = { - { "nativeConstructor", "()J", (void*)SkLayerRasterizerGlue::create }, - { "nativeAddLayer", "(JJFF)V", (void*)SkLayerRasterizerGlue::addLayer } -}; - -int register_android_graphics_LayerRasterizer(JNIEnv* env) -{ - return RegisterMethodsOrDie(env, "android/graphics/LayerRasterizer", - gLayerRasterizerMethods, NELEM(gLayerRasterizerMethods)); -} - -} - diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp index a31bd8066138..be9449b6eb5c 100644 --- a/core/jni/android_graphics_Canvas.cpp +++ b/core/jni/android_graphics_Canvas.cpp @@ -185,12 +185,12 @@ static jboolean quickRejectPath(JNIEnv* env, jobject, jlong canvasHandle, jlong // SkRegion::Op and SkClipOp are numerically identical, so we can freely cast // from one to the other (though SkClipOp is destined to become a strict subset) -static_assert(SkRegion::kDifference_Op == static_cast<SkRegion::Op>(kDifference_SkClipOp), ""); -static_assert(SkRegion::kIntersect_Op == static_cast<SkRegion::Op>(kIntersect_SkClipOp), ""); -static_assert(SkRegion::kUnion_Op == static_cast<SkRegion::Op>(kUnion_SkClipOp), ""); -static_assert(SkRegion::kXOR_Op == static_cast<SkRegion::Op>(kXOR_SkClipOp), ""); -static_assert(SkRegion::kReverseDifference_Op == static_cast<SkRegion::Op>(kReverseDifference_SkClipOp), ""); -static_assert(SkRegion::kReplace_Op == static_cast<SkRegion::Op>(kReplace_SkClipOp), ""); +static_assert(SkRegion::kDifference_Op == static_cast<SkRegion::Op>(SkClipOp::kDifference), ""); +static_assert(SkRegion::kIntersect_Op == static_cast<SkRegion::Op>(SkClipOp::kIntersect), ""); +static_assert(SkRegion::kUnion_Op == static_cast<SkRegion::Op>(SkClipOp::kUnion), ""); +static_assert(SkRegion::kXOR_Op == static_cast<SkRegion::Op>(SkClipOp::kXOR), ""); +static_assert(SkRegion::kReverseDifference_Op == static_cast<SkRegion::Op>(SkClipOp::kReverseDifference), ""); +static_assert(SkRegion::kReplace_Op == static_cast<SkRegion::Op>(SkClipOp::kReplace), ""); static SkClipOp opHandleToClipOp(jint opHandle) { // The opHandle is defined in Canvas.java to be Region::Op diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 26a2cf01266c..3e99521f8ad4 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -44,28 +44,33 @@ int ifc_disable(const char *ifname); namespace android { +static const uint32_t kEtherTypeOffset = offsetof(ether_header, ether_type); +static const uint32_t kEtherHeaderLen = sizeof(ether_header); +static const uint32_t kIPv4Protocol = kEtherHeaderLen + offsetof(iphdr, protocol); +static const uint32_t kIPv4FlagsOffset = kEtherHeaderLen + offsetof(iphdr, frag_off); +static const uint32_t kIPv6NextHeader = kEtherHeaderLen + offsetof(ip6_hdr, ip6_nxt); +static const uint32_t kIPv6PayloadStart = kEtherHeaderLen + sizeof(ip6_hdr); +static const uint32_t kICMPv6TypeOffset = kIPv6PayloadStart + offsetof(icmp6_hdr, icmp6_type); +static const uint32_t kUDPSrcPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, source); +static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest); static const uint16_t kDhcpClientPort = 68; static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd) { - uint32_t ip_offset = sizeof(ether_header); - uint32_t proto_offset = ip_offset + offsetof(iphdr, protocol); - uint32_t flags_offset = ip_offset + offsetof(iphdr, frag_off); - uint32_t dport_indirect_offset = ip_offset + offsetof(udphdr, dest); struct sock_filter filter_code[] = { // Check the protocol is UDP. - BPF_STMT(BPF_LD | BPF_B | BPF_ABS, proto_offset), + BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv4Protocol), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_UDP, 0, 6), // Check this is not a fragment. - BPF_STMT(BPF_LD | BPF_H | BPF_ABS, flags_offset), - BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, 0x1fff, 4, 0), + BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kIPv4FlagsOffset), + BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, IP_OFFMASK, 4, 0), // Get the IP header length. - BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, ip_offset), + BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, kEtherHeaderLen), // Check the destination port. - BPF_STMT(BPF_LD | BPF_H | BPF_IND, dport_indirect_offset), + BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPDstPortIndirectOffset), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 0, 1), // Accept or reject. @@ -93,17 +98,13 @@ static void android_net_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject return; } - uint32_t ipv6_offset = sizeof(ether_header); - uint32_t ipv6_next_header_offset = ipv6_offset + offsetof(ip6_hdr, ip6_nxt); - uint32_t icmp6_offset = ipv6_offset + sizeof(ip6_hdr); - uint32_t icmp6_type_offset = icmp6_offset + offsetof(icmp6_hdr, icmp6_type); struct sock_filter filter_code[] = { // Check IPv6 Next Header is ICMPv6. - BPF_STMT(BPF_LD | BPF_B | BPF_ABS, ipv6_next_header_offset), + BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv6NextHeader), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3), // Check ICMPv6 type is Router Advertisement. - BPF_STMT(BPF_LD | BPF_B | BPF_ABS, icmp6_type_offset), + BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 0, 1), // Accept or reject. @@ -122,6 +123,81 @@ static void android_net_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject } } +// TODO: Move all this filter code into libnetutils. +static void android_net_utils_attachControlPacketFilter( + JNIEnv *env, jobject clazz, jobject javaFd, jint hardwareAddressType) { + if (hardwareAddressType != ARPHRD_ETHER) { + jniThrowExceptionFmt(env, "java/net/SocketException", + "attachControlPacketFilter only supports ARPHRD_ETHER"); + return; + } + + // Capture all: + // - ARPs + // - DHCPv4 packets + // - Router Advertisements & Solicitations + // - Neighbor Advertisements & Solicitations + // + // tcpdump: + // arp or + // '(ip and udp port 68)' or + // '(icmp6 and ip6[40] >= 133 and ip6[40] <= 136)' + struct sock_filter filter_code[] = { + // Load the link layer next payload field. + BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kEtherTypeOffset), + + // Accept all ARP. + // TODO: Figure out how to better filter ARPs on noisy networks. + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_ARP, 16, 0), + + // If IPv4: + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IP, 0, 9), + + // Check the protocol is UDP. + BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv4Protocol), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_UDP, 0, 14), + + // Check this is not a fragment. + BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kIPv4FlagsOffset), + BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, IP_OFFMASK, 12, 0), + + // Get the IP header length. + BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, kEtherHeaderLen), + + // Check the source port. + BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPSrcPortIndirectOffset), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 8, 0), + + // Check the destination port. + BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPDstPortIndirectOffset), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 6, 7), + + // IPv6 ... + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IPV6, 0, 6), + // ... check IPv6 Next Header is ICMPv6 (ignore fragments), ... + BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv6NextHeader), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 4), + // ... and check the ICMPv6 type is one of RS/RA/NS/NA. + BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset), + BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, ND_ROUTER_SOLICIT, 0, 2), + BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, ND_NEIGHBOR_ADVERT, 1, 0), + + // Accept or reject. + BPF_STMT(BPF_RET | BPF_K, 0xffff), + BPF_STMT(BPF_RET | BPF_K, 0) + }; + struct sock_fprog filter = { + sizeof(filter_code) / sizeof(filter_code[0]), + filter_code, + }; + + int fd = jniGetFDFromFileDescriptor(env, javaFd); + if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) { + jniThrowExceptionFmt(env, "java/net/SocketException", + "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno)); + } +} + static void android_net_utils_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd, jint ifIndex) { @@ -263,6 +339,7 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess }, { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter }, { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter }, + { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter }, { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket }, }; diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp index 740b24d645a8..2ae4a17c00a6 100644 --- a/core/jni/android_os_HwBinder.cpp +++ b/core/jni/android_os_HwBinder.cpp @@ -26,10 +26,11 @@ #include <JNIHelp.h> #include <android/hidl/manager/1.0/IServiceManager.h> #include <android/hidl/base/1.0/IBase.h> -#include <android/hidl/base/1.0/IHwBase.h> +#include <android/hidl/base/1.0/BpBase.h> #include <android_runtime/AndroidRuntime.h> #include <hidl/ServiceManagement.h> #include <hidl/Status.h> +#include <hidl/HidlTransportSupport.h> #include <hwbinder/ProcessState.h> #include <nativehelper/ScopedLocalRef.h> @@ -241,14 +242,8 @@ static void JHwBinder_native_registerService( using android::hidl::manager::V1_0::IServiceManager; sp<hardware::IBinder> binder = JHwBinder::GetNativeContext(env, thiz); - - sp<hidl::base::V1_0::IBase> base = hidl::base::V1_0::IHwBase::asInterface(binder); - if (base.get() == nullptr) { - LOG(ERROR) << "IBinder object cannot be casted to the base interface."; - signalExceptionForError(env, UNKNOWN_ERROR); - return; - } - + /* TODO(b/33440494) this is not right */ + sp<hidl::base::V1_0::IBase> base = new hidl::base::V1_0::BpBase(binder); bool ok = hardware::defaultServiceManager()->add( interfaceChain, serviceName, @@ -300,7 +295,7 @@ static jobject JHwBinder_native_getService( serviceName, [&service](sp<hidl::base::V1_0::IBase> out) { service = hardware::toBinder< - hidl::base::V1_0::IBase, hidl::base::V1_0::IHwBase + hidl::base::V1_0::IBase, hidl::base::V1_0::BpBase >(out); }); diff --git a/core/jni/android_view_DisplayListCanvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp index 74c073f84165..2c4771c18d0f 100644 --- a/core/jni/android_view_DisplayListCanvas.cpp +++ b/core/jni/android_view_DisplayListCanvas.cpp @@ -87,19 +87,17 @@ private: sp<InvokeRunnableMessage> mMessage; }; -// ---------------------------------------------------------------------------- -// Setup -// ---------------------------------------------------------------------------- -static void android_view_DisplayListCanvas_insertReorderBarrier(JNIEnv* env, jobject clazz, - jlong canvasPtr, jboolean reorderEnable) { - Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); - canvas->insertReorderBarrier(reorderEnable); +// ---------------- Regular JNI ----------------------------- + +static void +android_app_ActivityThread_dumpGraphics(JNIEnv* env, jobject clazz, jobject javaFileDescriptor) { + int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor); + android::uirenderer::renderthread::RenderProxy::dumpGraphicsMemory(fd); } -// ---------------------------------------------------------------------------- -// Functor -// ---------------------------------------------------------------------------- + +// ---------------- @FastNative ----------------------------- static void android_view_DisplayListCanvas_callDrawGLFunction(JNIEnv* env, jobject clazz, jlong canvasPtr, jlong functorPtr, jobject releasedCallback) { @@ -112,102 +110,81 @@ static void android_view_DisplayListCanvas_callDrawGLFunction(JNIEnv* env, jobje canvas->callDrawGLFunction(functor, bridge.get()); } -// ---------------------------------------------------------------------------- -// Misc -// ---------------------------------------------------------------------------- -static jint android_view_DisplayListCanvas_getMaxTextureWidth(JNIEnv* env, jobject clazz) { +// ---------------- @CriticalNative ------------------------- + +static jlong android_view_DisplayListCanvas_createDisplayListCanvas(jlong renderNodePtr, + jint width, jint height) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return reinterpret_cast<jlong>(Canvas::create_recording_canvas(width, height, renderNode)); +} + +static void android_view_DisplayListCanvas_resetDisplayListCanvas(jlong canvasPtr, + jlong renderNodePtr, jint width, jint height) { + Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + canvas->resetRecording(width, height, renderNode); +} + +static jint android_view_DisplayListCanvas_getMaxTextureWidth() { if (!Caches::hasInstance()) { android::uirenderer::renderthread::RenderProxy::staticFence(); } return Caches::getInstance().maxTextureSize; } -static jint android_view_DisplayListCanvas_getMaxTextureHeight(JNIEnv* env, jobject clazz) { +static jint android_view_DisplayListCanvas_getMaxTextureHeight() { if (!Caches::hasInstance()) { android::uirenderer::renderthread::RenderProxy::staticFence(); } return Caches::getInstance().maxTextureSize; } -// ---------------------------------------------------------------------------- -// Drawing -// ---------------------------------------------------------------------------- - -static void android_view_DisplayListCanvas_drawRoundRectProps(JNIEnv* env, jobject clazz, - jlong canvasPtr, jlong leftPropPtr, jlong topPropPtr, jlong rightPropPtr, - jlong bottomPropPtr, jlong rxPropPtr, jlong ryPropPtr, jlong paintPropPtr) { +static void android_view_DisplayListCanvas_insertReorderBarrier(jlong canvasPtr, + jboolean reorderEnable) { Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); - CanvasPropertyPrimitive* leftProp = reinterpret_cast<CanvasPropertyPrimitive*>(leftPropPtr); - CanvasPropertyPrimitive* topProp = reinterpret_cast<CanvasPropertyPrimitive*>(topPropPtr); - CanvasPropertyPrimitive* rightProp = reinterpret_cast<CanvasPropertyPrimitive*>(rightPropPtr); - CanvasPropertyPrimitive* bottomProp = reinterpret_cast<CanvasPropertyPrimitive*>(bottomPropPtr); - CanvasPropertyPrimitive* rxProp = reinterpret_cast<CanvasPropertyPrimitive*>(rxPropPtr); - CanvasPropertyPrimitive* ryProp = reinterpret_cast<CanvasPropertyPrimitive*>(ryPropPtr); - CanvasPropertyPaint* paintProp = reinterpret_cast<CanvasPropertyPaint*>(paintPropPtr); - canvas->drawRoundRect(leftProp, topProp, rightProp, bottomProp, rxProp, ryProp, paintProp); + canvas->insertReorderBarrier(reorderEnable); } -static void android_view_DisplayListCanvas_drawCircleProps(JNIEnv* env, jobject clazz, - jlong canvasPtr, jlong xPropPtr, jlong yPropPtr, jlong radiusPropPtr, jlong paintPropPtr) { - Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); - CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr); - CanvasPropertyPrimitive* yProp = reinterpret_cast<CanvasPropertyPrimitive*>(yPropPtr); - CanvasPropertyPrimitive* radiusProp = reinterpret_cast<CanvasPropertyPrimitive*>(radiusPropPtr); - CanvasPropertyPaint* paintProp = reinterpret_cast<CanvasPropertyPaint*>(paintPropPtr); - canvas->drawCircle(xProp, yProp, radiusProp, paintProp); -} - -// ---------------------------------------------------------------------------- -// Display lists -// ---------------------------------------------------------------------------- - -static jlong android_view_DisplayListCanvas_finishRecording(JNIEnv* env, - jobject clazz, jlong canvasPtr) { +static jlong android_view_DisplayListCanvas_finishRecording(jlong canvasPtr) { Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); return reinterpret_cast<jlong>(canvas->finishRecording()); } -static jlong android_view_DisplayListCanvas_createDisplayListCanvas(JNIEnv* env, jobject clazz, - jlong renderNodePtr, jint width, jint height) { - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - return reinterpret_cast<jlong>(Canvas::create_recording_canvas(width, height, renderNode)); -} - -static void android_view_DisplayListCanvas_resetDisplayListCanvas(JNIEnv* env, jobject clazz, - jlong canvasPtr, jlong renderNodePtr, jint width, jint height) { - Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - canvas->resetRecording(width, height, renderNode); -} - - -static void android_view_DisplayListCanvas_drawRenderNode(JNIEnv* env, - jobject clazz, jlong canvasPtr, jlong renderNodePtr) { +static void android_view_DisplayListCanvas_drawRenderNode(jlong canvasPtr, jlong renderNodePtr) { Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); canvas->drawRenderNode(renderNode); } -// ---------------------------------------------------------------------------- -// Layers -// ---------------------------------------------------------------------------- - -static void android_view_DisplayListCanvas_drawLayer(JNIEnv* env, jobject clazz, - jlong canvasPtr, jlong layerPtr) { +static void android_view_DisplayListCanvas_drawLayer(jlong canvasPtr, jlong layerPtr) { Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr); canvas->drawLayer(layer); } -// ---------------------------------------------------------------------------- -// Logging -// ---------------------------------------------------------------------------- +static void android_view_DisplayListCanvas_drawRoundRectProps(jlong canvasPtr, + jlong leftPropPtr, jlong topPropPtr, jlong rightPropPtr, jlong bottomPropPtr, + jlong rxPropPtr, jlong ryPropPtr, jlong paintPropPtr) { + Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); + CanvasPropertyPrimitive* leftProp = reinterpret_cast<CanvasPropertyPrimitive*>(leftPropPtr); + CanvasPropertyPrimitive* topProp = reinterpret_cast<CanvasPropertyPrimitive*>(topPropPtr); + CanvasPropertyPrimitive* rightProp = reinterpret_cast<CanvasPropertyPrimitive*>(rightPropPtr); + CanvasPropertyPrimitive* bottomProp = reinterpret_cast<CanvasPropertyPrimitive*>(bottomPropPtr); + CanvasPropertyPrimitive* rxProp = reinterpret_cast<CanvasPropertyPrimitive*>(rxPropPtr); + CanvasPropertyPrimitive* ryProp = reinterpret_cast<CanvasPropertyPrimitive*>(ryPropPtr); + CanvasPropertyPaint* paintProp = reinterpret_cast<CanvasPropertyPaint*>(paintPropPtr); + canvas->drawRoundRect(leftProp, topProp, rightProp, bottomProp, rxProp, ryProp, paintProp); +} -static void -android_app_ActivityThread_dumpGraphics(JNIEnv* env, jobject clazz, jobject javaFileDescriptor) { - int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor); - android::uirenderer::renderthread::RenderProxy::dumpGraphicsMemory(fd); +static void android_view_DisplayListCanvas_drawCircleProps(jlong canvasPtr, + jlong xPropPtr, jlong yPropPtr, jlong radiusPropPtr, jlong paintPropPtr) { + Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); + CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr); + CanvasPropertyPrimitive* yProp = reinterpret_cast<CanvasPropertyPrimitive*>(yPropPtr); + CanvasPropertyPrimitive* radiusProp = reinterpret_cast<CanvasPropertyPrimitive*>(radiusPropPtr); + CanvasPropertyPaint* paintProp = reinterpret_cast<CanvasPropertyPaint*>(paintPropPtr); + canvas->drawCircle(xProp, yProp, radiusProp, paintProp); } // ---------------------------------------------------------------------------- @@ -220,28 +197,25 @@ static JNINativeMethod gMethods[] = { // ------------ @FastNative ------------------ - { "nInsertReorderBarrier","(JZ)V", (void*) android_view_DisplayListCanvas_insertReorderBarrier }, - { "nCallDrawGLFunction", "(JJLjava/lang/Runnable;)V", (void*) android_view_DisplayListCanvas_callDrawGLFunction }, - { "nDrawRoundRect", "(JJJJJJJJ)V", (void*) android_view_DisplayListCanvas_drawRoundRectProps }, - { "nDrawCircle", "(JJJJJ)V", (void*) android_view_DisplayListCanvas_drawCircleProps }, - - { "nFinishRecording", "(J)J", (void*) android_view_DisplayListCanvas_finishRecording }, - { "nDrawRenderNode", "(JJ)V", (void*) android_view_DisplayListCanvas_drawRenderNode }, - + // ------------ @CriticalNative -------------- { "nCreateDisplayListCanvas", "(JII)J", (void*) android_view_DisplayListCanvas_createDisplayListCanvas }, - { "nResetDisplayListCanvas", "(JJII)V", (void*) android_view_DisplayListCanvas_resetDisplayListCanvas }, - - { "nDrawLayer", "(JJ)V", (void*) android_view_DisplayListCanvas_drawLayer }, - - { "nGetMaximumTextureWidth", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureWidth }, - { "nGetMaximumTextureHeight", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureHeight }, + { "nResetDisplayListCanvas", "(JJII)V", (void*) android_view_DisplayListCanvas_resetDisplayListCanvas }, + { "nGetMaximumTextureWidth", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureWidth }, + { "nGetMaximumTextureHeight", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureHeight }, + { "nInsertReorderBarrier", "(JZ)V", (void*) android_view_DisplayListCanvas_insertReorderBarrier }, + { "nFinishRecording", "(J)J", (void*) android_view_DisplayListCanvas_finishRecording }, + { "nDrawRenderNode", "(JJ)V", (void*) android_view_DisplayListCanvas_drawRenderNode }, + { "nDrawLayer", "(JJ)V", (void*) android_view_DisplayListCanvas_drawLayer }, + { "nDrawCircle", "(JJJJJ)V", (void*) android_view_DisplayListCanvas_drawCircleProps }, + { "nDrawRoundRect", "(JJJJJJJJ)V",(void*) android_view_DisplayListCanvas_drawRoundRectProps }, }; static JNINativeMethod gActivityThreadMethods[] = { - { "dumpGraphicsInfo", "(Ljava/io/FileDescriptor;)V", + // ------------ Regular JNI ------------------ + { "nDumpGraphicsInfo", "(Ljava/io/FileDescriptor;)V", (void*) android_app_ActivityThread_dumpGraphics } }; diff --git a/core/jni/android_view_GraphicBuffer.cpp b/core/jni/android_view_GraphicBuffer.cpp index 53c9ff04abbd..f18837f2f72a 100644 --- a/core/jni/android_view_GraphicBuffer.cpp +++ b/core/jni/android_view_GraphicBuffer.cpp @@ -142,6 +142,8 @@ static inline SkColorType convertPixelFormat(int32_t format) { return kN32_SkColorType; case PIXEL_FORMAT_RGBX_8888: return kN32_SkColorType; + case PIXEL_FORMAT_RGBA_FP16: + return kRGBA_F16_SkColorType; case PIXEL_FORMAT_RGB_565: return kRGB_565_SkColorType; default: @@ -197,7 +199,7 @@ static jboolean android_view_GraphicBuffer_lockCanvas(JNIEnv* env, jobject, Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas); nativeCanvas->setBitmap(bitmap); nativeCanvas->clipRect(rect.left, rect.top, rect.right, rect.bottom, - kIntersect_SkClipOp); + SkClipOp::kIntersect); if (dirtyRect) { INVOKEV(dirtyRect, gRectClassInfo.set, diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp index dd2a7a98b7a0..d75d5c179d3c 100644 --- a/core/jni/android_view_RenderNode.cpp +++ b/core/jni/android_view_RenderNode.cpp @@ -51,7 +51,7 @@ static JNIEnv* getenv(JavaVM* vm) { return env; } -static jmethodID gOnRenderNodeDetached; +static jfieldID gRenderNode_validFieldID; class RenderNodeContext : public VirtualLightRefBase { public: @@ -87,7 +87,10 @@ void onRenderNodeRemoved(JNIEnv* env, RenderNode* node) { node->setUserContext(nullptr); return; } - env->CallVoidMethod(jnode, gOnRenderNodeDetached); + + // Update the valid field, since native has already removed + // the staging DisplayList + env->SetBooleanField(jnode, gRenderNode_validFieldID, false); env->DeleteLocalRef(jnode); } @@ -95,14 +98,12 @@ void onRenderNodeRemoved(JNIEnv* env, RenderNode* node) { // DisplayList view properties // ---------------------------------------------------------------------------- -static void android_view_RenderNode_output(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static void android_view_RenderNode_output(JNIEnv* env, jobject clazz, jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->output(); } -static jint android_view_RenderNode_getDebugSize(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jint android_view_RenderNode_getDebugSize(JNIEnv* env, jobject clazz, jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->getDebugSize(); } @@ -153,59 +154,53 @@ static void android_view_RenderNode_setDisplayList(JNIEnv* env, // RenderProperties - setters // ---------------------------------------------------------------------------- -static jboolean android_view_RenderNode_setLayerType(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jint jlayerType) { +static jboolean android_view_RenderNode_setLayerType(jlong renderNodePtr, jint jlayerType) { LayerType layerType = static_cast<LayerType>(jlayerType); return SET_AND_DIRTY(mutateLayerProperties().setType, layerType, RenderNode::GENERIC); } -static jboolean android_view_RenderNode_setLayerPaint(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jlong paintPtr) { +static jboolean android_view_RenderNode_setLayerPaint(jlong renderNodePtr, jlong paintPtr) { Paint* paint = reinterpret_cast<Paint*>(paintPtr); return SET_AND_DIRTY(mutateLayerProperties().setFromPaint, paint, RenderNode::GENERIC); } -static jboolean android_view_RenderNode_setStaticMatrix(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jlong matrixPtr) { +static jboolean android_view_RenderNode_setStaticMatrix(jlong renderNodePtr, jlong matrixPtr) { SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); return SET_AND_DIRTY(setStaticMatrix, matrix, RenderNode::GENERIC); } -static jboolean android_view_RenderNode_setAnimationMatrix(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jlong matrixPtr) { +static jboolean android_view_RenderNode_setAnimationMatrix(jlong renderNodePtr, jlong matrixPtr) { SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); return SET_AND_DIRTY(setAnimationMatrix, matrix, RenderNode::GENERIC); } -static jboolean android_view_RenderNode_setClipToBounds(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jboolean clipToBounds) { +static jboolean android_view_RenderNode_setClipToBounds(jlong renderNodePtr, + jboolean clipToBounds) { return SET_AND_DIRTY(setClipToBounds, clipToBounds, RenderNode::GENERIC); } -static jboolean android_view_RenderNode_setClipBounds(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jint left, jint top, jint right, jint bottom) { +static jboolean android_view_RenderNode_setClipBounds(jlong renderNodePtr, + jint left, jint top, jint right, jint bottom) { android::uirenderer::Rect clipBounds(left, top, right, bottom); return SET_AND_DIRTY(setClipBounds, clipBounds, RenderNode::GENERIC); } -static jboolean android_view_RenderNode_setClipBoundsEmpty(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jboolean android_view_RenderNode_setClipBoundsEmpty(jlong renderNodePtr) { return SET_AND_DIRTY(setClipBoundsEmpty,, RenderNode::GENERIC); } -static jboolean android_view_RenderNode_setProjectBackwards(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jboolean shouldProject) { +static jboolean android_view_RenderNode_setProjectBackwards(jlong renderNodePtr, + jboolean shouldProject) { return SET_AND_DIRTY(setProjectBackwards, shouldProject, RenderNode::GENERIC); } -static jboolean android_view_RenderNode_setProjectionReceiver(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jboolean shouldRecieve) { +static jboolean android_view_RenderNode_setProjectionReceiver(jlong renderNodePtr, + jboolean shouldRecieve) { return SET_AND_DIRTY(setProjectionReceiver, shouldRecieve, RenderNode::GENERIC); } -static jboolean android_view_RenderNode_setOutlineRoundRect(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jint left, jint top, - jint right, jint bottom, jfloat radius, jfloat alpha) { +static jboolean android_view_RenderNode_setOutlineRoundRect(jlong renderNodePtr, + jint left, jint top, jint right, jint bottom, jfloat radius, jfloat alpha) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->mutateStagingProperties().mutableOutline().setRoundRect(left, top, right, bottom, radius, alpha); @@ -213,8 +208,8 @@ static jboolean android_view_RenderNode_setOutlineRoundRect(JNIEnv* env, return true; } -static jboolean android_view_RenderNode_setOutlineConvexPath(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jlong outlinePathPtr, jfloat alpha) { +static jboolean android_view_RenderNode_setOutlineConvexPath(jlong renderNodePtr, + jlong outlinePathPtr, jfloat alpha) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); SkPath* outlinePath = reinterpret_cast<SkPath*>(outlinePathPtr); renderNode->mutateStagingProperties().mutableOutline().setConvexPath(outlinePath, alpha); @@ -222,38 +217,34 @@ static jboolean android_view_RenderNode_setOutlineConvexPath(JNIEnv* env, return true; } -static jboolean android_view_RenderNode_setOutlineEmpty(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jboolean android_view_RenderNode_setOutlineEmpty(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->mutateStagingProperties().mutableOutline().setEmpty(); renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); return true; } -static jboolean android_view_RenderNode_setOutlineNone(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jboolean android_view_RenderNode_setOutlineNone(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->mutateStagingProperties().mutableOutline().setNone(); renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); return true; } -static jboolean android_view_RenderNode_hasShadow(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jboolean android_view_RenderNode_hasShadow(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().hasShadow(); } -static jboolean android_view_RenderNode_setClipToOutline(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jboolean clipToOutline) { +static jboolean android_view_RenderNode_setClipToOutline(jlong renderNodePtr, + jboolean clipToOutline) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->mutateStagingProperties().mutableOutline().setShouldClip(clipToOutline); renderNode->setPropertyFieldsDirty(RenderNode::GENERIC); return true; } -static jboolean android_view_RenderNode_setRevealClip(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jboolean shouldClip, +static jboolean android_view_RenderNode_setRevealClip(jlong renderNodePtr, jboolean shouldClip, jfloat x, jfloat y, jfloat radius) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->mutateStagingProperties().mutableRevealClip().set( @@ -262,100 +253,82 @@ static jboolean android_view_RenderNode_setRevealClip(JNIEnv* env, return true; } -static jboolean android_view_RenderNode_setAlpha(JNIEnv* env, - jobject clazz, jlong renderNodePtr, float alpha) { +static jboolean android_view_RenderNode_setAlpha(jlong renderNodePtr, float alpha) { return SET_AND_DIRTY(setAlpha, alpha, RenderNode::ALPHA); } -static jboolean android_view_RenderNode_setHasOverlappingRendering(JNIEnv* env, - jobject clazz, jlong renderNodePtr, bool hasOverlappingRendering) { +static jboolean android_view_RenderNode_setHasOverlappingRendering(jlong renderNodePtr, + bool hasOverlappingRendering) { return SET_AND_DIRTY(setHasOverlappingRendering, hasOverlappingRendering, RenderNode::GENERIC); } -static jboolean android_view_RenderNode_setElevation(JNIEnv* env, - jobject clazz, jlong renderNodePtr, float elevation) { +static jboolean android_view_RenderNode_setElevation(jlong renderNodePtr, float elevation) { return SET_AND_DIRTY(setElevation, elevation, RenderNode::Z); } -static jboolean android_view_RenderNode_setTranslationX(JNIEnv* env, - jobject clazz, jlong renderNodePtr, float tx) { +static jboolean android_view_RenderNode_setTranslationX(jlong renderNodePtr, float tx) { return SET_AND_DIRTY(setTranslationX, tx, RenderNode::TRANSLATION_X | RenderNode::X); } -static jboolean android_view_RenderNode_setTranslationY(JNIEnv* env, - jobject clazz, jlong renderNodePtr, float ty) { +static jboolean android_view_RenderNode_setTranslationY(jlong renderNodePtr, float ty) { return SET_AND_DIRTY(setTranslationY, ty, RenderNode::TRANSLATION_Y | RenderNode::Y); } -static jboolean android_view_RenderNode_setTranslationZ(JNIEnv* env, - jobject clazz, jlong renderNodePtr, float tz) { +static jboolean android_view_RenderNode_setTranslationZ(jlong renderNodePtr, float tz) { return SET_AND_DIRTY(setTranslationZ, tz, RenderNode::TRANSLATION_Z | RenderNode::Z); } -static jboolean android_view_RenderNode_setRotation(JNIEnv* env, - jobject clazz, jlong renderNodePtr, float rotation) { +static jboolean android_view_RenderNode_setRotation(jlong renderNodePtr, float rotation) { return SET_AND_DIRTY(setRotation, rotation, RenderNode::ROTATION); } -static jboolean android_view_RenderNode_setRotationX(JNIEnv* env, - jobject clazz, jlong renderNodePtr, float rx) { +static jboolean android_view_RenderNode_setRotationX(jlong renderNodePtr, float rx) { return SET_AND_DIRTY(setRotationX, rx, RenderNode::ROTATION_X); } -static jboolean android_view_RenderNode_setRotationY(JNIEnv* env, - jobject clazz, jlong renderNodePtr, float ry) { +static jboolean android_view_RenderNode_setRotationY(jlong renderNodePtr, float ry) { return SET_AND_DIRTY(setRotationY, ry, RenderNode::ROTATION_Y); } -static jboolean android_view_RenderNode_setScaleX(JNIEnv* env, - jobject clazz, jlong renderNodePtr, float sx) { +static jboolean android_view_RenderNode_setScaleX(jlong renderNodePtr, float sx) { return SET_AND_DIRTY(setScaleX, sx, RenderNode::SCALE_X); } -static jboolean android_view_RenderNode_setScaleY(JNIEnv* env, - jobject clazz, jlong renderNodePtr, float sy) { +static jboolean android_view_RenderNode_setScaleY(jlong renderNodePtr, float sy) { return SET_AND_DIRTY(setScaleY, sy, RenderNode::SCALE_Y); } -static jboolean android_view_RenderNode_setPivotX(JNIEnv* env, - jobject clazz, jlong renderNodePtr, float px) { +static jboolean android_view_RenderNode_setPivotX(jlong renderNodePtr, float px) { return SET_AND_DIRTY(setPivotX, px, RenderNode::GENERIC); } -static jboolean android_view_RenderNode_setPivotY(JNIEnv* env, - jobject clazz, jlong renderNodePtr, float py) { +static jboolean android_view_RenderNode_setPivotY(jlong renderNodePtr, float py) { return SET_AND_DIRTY(setPivotY, py, RenderNode::GENERIC); } -static jboolean android_view_RenderNode_setCameraDistance(JNIEnv* env, - jobject clazz, jlong renderNodePtr, float distance) { +static jboolean android_view_RenderNode_setCameraDistance(jlong renderNodePtr, float distance) { return SET_AND_DIRTY(setCameraDistance, distance, RenderNode::GENERIC); } -static jboolean android_view_RenderNode_setLeft(JNIEnv* env, - jobject clazz, jlong renderNodePtr, int left) { +static jboolean android_view_RenderNode_setLeft(jlong renderNodePtr, int left) { return SET_AND_DIRTY(setLeft, left, RenderNode::X); } -static jboolean android_view_RenderNode_setTop(JNIEnv* env, - jobject clazz, jlong renderNodePtr, int top) { +static jboolean android_view_RenderNode_setTop(jlong renderNodePtr, int top) { return SET_AND_DIRTY(setTop, top, RenderNode::Y); } -static jboolean android_view_RenderNode_setRight(JNIEnv* env, - jobject clazz, jlong renderNodePtr, int right) { +static jboolean android_view_RenderNode_setRight(jlong renderNodePtr, int right) { return SET_AND_DIRTY(setRight, right, RenderNode::X); } -static jboolean android_view_RenderNode_setBottom(JNIEnv* env, - jobject clazz, jlong renderNodePtr, int bottom) { +static jboolean android_view_RenderNode_setBottom(jlong renderNodePtr, int bottom) { return SET_AND_DIRTY(setBottom, bottom, RenderNode::Y); } -static jboolean android_view_RenderNode_setLeftTopRightBottom(JNIEnv* env, - jobject clazz, jlong renderNodePtr, int left, int top, - int right, int bottom) { +static jboolean android_view_RenderNode_setLeftTopRightBottom(jlong renderNodePtr, + int left, int top, int right, int bottom) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); if (renderNode->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom)) { renderNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); @@ -364,13 +337,11 @@ static jboolean android_view_RenderNode_setLeftTopRightBottom(JNIEnv* env, return false; } -static jboolean android_view_RenderNode_offsetLeftAndRight(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jint offset) { +static jboolean android_view_RenderNode_offsetLeftAndRight(jlong renderNodePtr, jint offset) { return SET_AND_DIRTY(offsetLeftRight, offset, RenderNode::X); } -static jboolean android_view_RenderNode_offsetTopAndBottom(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jint offset) { +static jboolean android_view_RenderNode_offsetTopAndBottom(jlong renderNodePtr, jint offset) { return SET_AND_DIRTY(offsetTopBottom, offset, RenderNode::Y); } @@ -378,92 +349,77 @@ static jboolean android_view_RenderNode_offsetTopAndBottom(JNIEnv* env, // RenderProperties - getters // ---------------------------------------------------------------------------- -static jboolean android_view_RenderNode_hasOverlappingRendering(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jboolean android_view_RenderNode_hasOverlappingRendering(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().hasOverlappingRendering(); } -static jboolean android_view_RenderNode_getClipToOutline(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jboolean android_view_RenderNode_getClipToOutline(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().getOutline().getShouldClip(); } -static jfloat android_view_RenderNode_getAlpha(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jfloat android_view_RenderNode_getAlpha(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().getAlpha(); } -static jfloat android_view_RenderNode_getCameraDistance(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jfloat android_view_RenderNode_getCameraDistance(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().getCameraDistance(); } -static jfloat android_view_RenderNode_getScaleX(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jfloat android_view_RenderNode_getScaleX(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().getScaleX(); } -static jfloat android_view_RenderNode_getScaleY(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jfloat android_view_RenderNode_getScaleY(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().getScaleY(); } -static jfloat android_view_RenderNode_getElevation(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jfloat android_view_RenderNode_getElevation(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().getElevation(); } -static jfloat android_view_RenderNode_getTranslationX(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jfloat android_view_RenderNode_getTranslationX(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().getTranslationX(); } -static jfloat android_view_RenderNode_getTranslationY(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jfloat android_view_RenderNode_getTranslationY(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().getTranslationY(); } -static jfloat android_view_RenderNode_getTranslationZ(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jfloat android_view_RenderNode_getTranslationZ(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().getTranslationZ(); } -static jfloat android_view_RenderNode_getRotation(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jfloat android_view_RenderNode_getRotation(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().getRotation(); } -static jfloat android_view_RenderNode_getRotationX(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jfloat android_view_RenderNode_getRotationX(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().getRotationX(); } -static jfloat android_view_RenderNode_getRotationY(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jfloat android_view_RenderNode_getRotationY(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().getRotationY(); } -static jboolean android_view_RenderNode_isPivotExplicitlySet(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jboolean android_view_RenderNode_isPivotExplicitlySet(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); return renderNode->stagingProperties().isPivotExplicitlySet(); } -static jboolean android_view_RenderNode_hasIdentityMatrix(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jboolean android_view_RenderNode_hasIdentityMatrix(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->mutateStagingProperties().updateMatrix(); return !renderNode->stagingProperties().hasTransformMatrix(); @@ -473,8 +429,7 @@ static jboolean android_view_RenderNode_hasIdentityMatrix(JNIEnv* env, // RenderProperties - computed getters // ---------------------------------------------------------------------------- -static void android_view_RenderNode_getTransformMatrix(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jlong outMatrixPtr) { +static void android_view_RenderNode_getTransformMatrix(jlong renderNodePtr, jlong outMatrixPtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); SkMatrix* outMatrix = reinterpret_cast<SkMatrix*>(outMatrixPtr); @@ -488,10 +443,10 @@ static void android_view_RenderNode_getTransformMatrix(JNIEnv* env, } } -static void android_view_RenderNode_getInverseTransformMatrix(JNIEnv* env, - jobject clazz, jlong renderNodePtr, jlong outMatrixPtr) { +static void android_view_RenderNode_getInverseTransformMatrix(jlong renderNodePtr, + jlong outMatrixPtr) { // load transform matrix - android_view_RenderNode_getTransformMatrix(env, clazz, renderNodePtr, outMatrixPtr); + android_view_RenderNode_getTransformMatrix(renderNodePtr, outMatrixPtr); SkMatrix* outMatrix = reinterpret_cast<SkMatrix*>(outMatrixPtr); // return it inverted @@ -501,15 +456,13 @@ static void android_view_RenderNode_getInverseTransformMatrix(JNIEnv* env, } } -static jfloat android_view_RenderNode_getPivotX(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jfloat android_view_RenderNode_getPivotX(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->mutateStagingProperties().updateMatrix(); return renderNode->stagingProperties().getPivotX(); } -static jfloat android_view_RenderNode_getPivotY(JNIEnv* env, - jobject clazz, jlong renderNodePtr) { +static jfloat android_view_RenderNode_getPivotY(jlong renderNodePtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); renderNode->mutateStagingProperties().updateMatrix(); return renderNode->stagingProperties().getPivotY(); @@ -519,8 +472,8 @@ static jfloat android_view_RenderNode_getPivotY(JNIEnv* env, // RenderProperties - Animations // ---------------------------------------------------------------------------- -static void android_view_RenderNode_addAnimator(JNIEnv* env, jobject clazz, - jlong renderNodePtr, jlong animatorPtr) { +static void android_view_RenderNode_addAnimator(JNIEnv* env, jobject clazz, jlong renderNodePtr, + jlong animatorPtr) { RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); RenderPropertyAnimator* animator = reinterpret_cast<RenderPropertyAnimator*>(animatorPtr); renderNode->addAnimator(animator); @@ -651,15 +604,22 @@ static const JNINativeMethod gMethods[] = { // ---------------------------------------------------------------------------- { "nCreate", "(Ljava/lang/String;)J", (void*) android_view_RenderNode_create }, { "nGetNativeFinalizer", "()J", (void*) android_view_RenderNode_getNativeFinalizer }, - { "nSetDisplayList", "(JJ)V", (void*) android_view_RenderNode_setDisplayList }, { "nOutput", "(J)V", (void*) android_view_RenderNode_output }, { "nGetDebugSize", "(J)I", (void*) android_view_RenderNode_getDebugSize }, { "nAddAnimator", "(JJ)V", (void*) android_view_RenderNode_addAnimator }, { "nEndAllAnimators", "(J)V", (void*) android_view_RenderNode_endAllAnimators }, { "nRequestPositionUpdates", "(JLandroid/view/SurfaceView;)V", (void*) android_view_RenderNode_requestPositionUpdates }, + { "nSetDisplayList", "(JJ)V", (void*) android_view_RenderNode_setDisplayList }, + + +// ---------------------------------------------------------------------------- +// Fast JNI via @CriticalNative annotation in RenderNode.java +// ---------------------------------------------------------------------------- + { "nSetDisplayList", "(JJ)V", (void*) android_view_RenderNode_setDisplayList }, + // ---------------------------------------------------------------------------- -// Fast JNI via @FastNative annotation in RenderNode.java +// Critical JNI via @CriticalNative annotation in RenderNode.java // ---------------------------------------------------------------------------- { "nSetLayerType", "(JI)Z", (void*) android_view_RenderNode_setLayerType }, { "nSetLayerPaint", "(JJ)Z", (void*) android_view_RenderNode_setLayerPaint }, @@ -732,8 +692,7 @@ int register_android_view_RenderNode(JNIEnv* env) { gSurfaceViewPositionLostMethod = GetMethodIDOrDie(env, clazz, "windowPositionLost_uiRtSync", "(J)V"); clazz = FindClassOrDie(env, "android/view/RenderNode"); - gOnRenderNodeDetached = GetMethodIDOrDie(env, clazz, - "onRenderNodeDetached", "()V"); + gRenderNode_validFieldID = GetFieldIDOrDie(env, clazz, "mValid", "Z"); return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); } diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 63997e5a82cf..96e6f819d592 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -168,6 +168,7 @@ PublicFormat android_view_Surface_mapHalFormatDataspaceToPublicFormat( switch(format) { case HAL_PIXEL_FORMAT_RGBA_8888: case HAL_PIXEL_FORMAT_RGBX_8888: + case HAL_PIXEL_FORMAT_RGBA_FP16: case HAL_PIXEL_FORMAT_RGB_888: case HAL_PIXEL_FORMAT_RGB_565: case HAL_PIXEL_FORMAT_Y8: @@ -283,6 +284,7 @@ static inline SkColorType convertPixelFormat(PixelFormat format) { switch (format) { case PIXEL_FORMAT_RGBX_8888: return kN32_SkColorType; case PIXEL_FORMAT_RGBA_8888: return kN32_SkColorType; + case PIXEL_FORMAT_RGBA_FP16: return kRGBA_F16_SkColorType; case PIXEL_FORMAT_RGB_565: return kRGB_565_SkColorType; default: return kUnknown_SkColorType; } @@ -339,7 +341,7 @@ static jlong nativeLockCanvas(JNIEnv* env, jclass clazz, if (dirtyRectPtr) { nativeCanvas->clipRect(dirtyRect.left, dirtyRect.top, - dirtyRect.right, dirtyRect.bottom, kIntersect_SkClipOp); + dirtyRect.right, dirtyRect.bottom, SkClipOp::kIntersect); } if (dirtyRectObj) { diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 5b8818171b6f..ed071cd0f45e 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -164,6 +164,11 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, alphaType = kPremul_SkAlphaType; break; } + case PIXEL_FORMAT_RGBA_FP16: { + colorType = kRGBA_F16_SkColorType; + alphaType = kPremul_SkAlphaType; + break; + } case PIXEL_FORMAT_RGB_565: { colorType = kRGB_565_SkColorType; alphaType = kOpaque_SkAlphaType; diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp index 351dce97754c..613e0402412a 100644 --- a/core/jni/android_view_TextureView.cpp +++ b/core/jni/android_view_TextureView.cpp @@ -83,6 +83,10 @@ static inline SkImageInfo convertPixelFormat(const ANativeWindow_Buffer& buffer) colorType = kN32_SkColorType; alphaType = kOpaque_SkAlphaType; break; + case WINDOW_FORMAT_RGBA_FP16: + colorType = kRGBA_F16_SkColorType; + alphaType = kPremul_SkAlphaType; + break; case WINDOW_FORMAT_RGB_565: colorType = kRGB_565_SkColorType; alphaType = kOpaque_SkAlphaType; @@ -167,7 +171,7 @@ static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject, Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas); nativeCanvas->setBitmap(bitmap); nativeCanvas->clipRect(rect.left, rect.top, rect.right, rect.bottom, - kIntersect_SkClipOp); + SkClipOp::kIntersect); if (dirtyRect) { INVOKEV(dirtyRect, gRectClassInfo.set, diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index 7e016576d70a..f8f9efe2804f 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -49,9 +49,6 @@ #define RS_BITCODE_SUFFIX ".bc" -#define GDBSERVER "gdbserver" -#define GDBSERVER_LEN (sizeof(GDBSERVER) - 1) - #define TMP_FILE_PATTERN "/tmp.XXXXXX" #define TMP_FILE_PATTERN_LEN (sizeof(TMP_FILE_PATTERN) - 1) @@ -246,7 +243,7 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr return INSTALL_FAILED_INTERNAL_ERROR; } - *(localFileName + nativeLibPath.size()) = '/'; + *(localTmpFileName + nativeLibPath.size()) = '/'; if (strlcpy(localTmpFileName + nativeLibPath.size(), TMP_FILE_PATTERN, TMP_FILE_PATTERN_LEN - nativeLibPath.size()) != TMP_FILE_PATTERN_LEN) { @@ -313,20 +310,20 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr */ class NativeLibrariesIterator { private: - NativeLibrariesIterator(ZipFileRO* zipFile, void* cookie) - : mZipFile(zipFile), mCookie(cookie), mLastSlash(NULL) { + NativeLibrariesIterator(ZipFileRO* zipFile, bool debuggable, void* cookie) + : mZipFile(zipFile), mDebuggable(debuggable), mCookie(cookie), mLastSlash(NULL) { fileName[0] = '\0'; } public: - static NativeLibrariesIterator* create(ZipFileRO* zipFile) { + static NativeLibrariesIterator* create(ZipFileRO* zipFile, bool debuggable) { void* cookie = NULL; // Do not specify a suffix to find both .so files and gdbserver. if (!zipFile->startIteration(&cookie, APK_LIB, NULL /* suffix */)) { return NULL; } - return new NativeLibrariesIterator(zipFile, cookie); + return new NativeLibrariesIterator(zipFile, debuggable, cookie); } ZipEntryRO next() { @@ -347,15 +344,8 @@ public: const char* lastSlash = strrchr(fileName, '/'); ALOG_ASSERT(lastSlash != NULL, "last slash was null somehow for %s\n", fileName); - // Exception: If we find the gdbserver binary, return it. - if (!strncmp(lastSlash + 1, GDBSERVER, GDBSERVER_LEN)) { - mLastSlash = lastSlash; - break; - } - - // Make sure the filename starts with lib and ends with ".so". - if (strncmp(fileName + fileNameLen - LIB_SUFFIX_LEN, LIB_SUFFIX, LIB_SUFFIX_LEN) - || strncmp(lastSlash, LIB_PREFIX, LIB_PREFIX_LEN)) { + // Skip directories. + if (*(lastSlash + 1) == 0) { continue; } @@ -364,6 +354,14 @@ public: continue; } + if (!mDebuggable) { + // Make sure the filename starts with lib and ends with ".so". + if (strncmp(fileName + fileNameLen - LIB_SUFFIX_LEN, LIB_SUFFIX, LIB_SUFFIX_LEN) + || strncmp(lastSlash, LIB_PREFIX, LIB_PREFIX_LEN)) { + continue; + } + } + mLastSlash = lastSlash; break; } @@ -386,19 +384,21 @@ private: char fileName[PATH_MAX]; ZipFileRO* const mZipFile; + const bool mDebuggable; void* mCookie; const char* mLastSlash; }; static install_status_t iterateOverNativeFiles(JNIEnv *env, jlong apkHandle, jstring javaCpuAbi, - iterFunc callFunc, void* callArg) { + jboolean debuggable, iterFunc callFunc, void* callArg) { ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle); if (zipFile == NULL) { return INSTALL_FAILED_INVALID_APK; } - std::unique_ptr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile)); + std::unique_ptr<NativeLibrariesIterator> it( + NativeLibrariesIterator::create(zipFile, debuggable)); if (it.get() == NULL) { return INSTALL_FAILED_INVALID_APK; } @@ -432,7 +432,8 @@ iterateOverNativeFiles(JNIEnv *env, jlong apkHandle, jstring javaCpuAbi, } -static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray) { +static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray, + jboolean debuggable) { const int numAbis = env->GetArrayLength(supportedAbisArray); Vector<ScopedUtfChars*> supportedAbis; @@ -446,7 +447,8 @@ static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supported return INSTALL_FAILED_INVALID_APK; } - std::unique_ptr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile)); + std::unique_ptr<NativeLibrariesIterator> it( + NativeLibrariesIterator::create(zipFile, debuggable)); if (it.get() == NULL) { return INSTALL_FAILED_INVALID_APK; } @@ -488,29 +490,29 @@ static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supported static jint com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, jclass clazz, jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi, - jboolean extractNativeLibs, jboolean hasNativeBridge) + jboolean extractNativeLibs, jboolean hasNativeBridge, jboolean debuggable) { void* args[] = { &javaNativeLibPath, &extractNativeLibs, &hasNativeBridge }; - return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi, + return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi, debuggable, copyFileIfChanged, reinterpret_cast<void*>(args)); } static jlong com_android_internal_content_NativeLibraryHelper_sumNativeBinaries(JNIEnv *env, jclass clazz, - jlong apkHandle, jstring javaCpuAbi) + jlong apkHandle, jstring javaCpuAbi, jboolean debuggable) { size_t totalSize = 0; - iterateOverNativeFiles(env, apkHandle, javaCpuAbi, sumFiles, &totalSize); + iterateOverNativeFiles(env, apkHandle, javaCpuAbi, debuggable, sumFiles, &totalSize); return totalSize; } static jint com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv *env, jclass clazz, - jlong apkHandle, jobjectArray javaCpuAbisToSearch) + jlong apkHandle, jobjectArray javaCpuAbisToSearch, jboolean debuggable) { - return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch); + return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch, debuggable); } enum bitcode_scan_result_t { @@ -569,13 +571,13 @@ static const JNINativeMethod gMethods[] = { "(J)V", (void *)com_android_internal_content_NativeLibraryHelper_close}, {"nativeCopyNativeBinaries", - "(JLjava/lang/String;Ljava/lang/String;ZZ)I", + "(JLjava/lang/String;Ljava/lang/String;ZZZ)I", (void *)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries}, {"nativeSumNativeBinaries", - "(JLjava/lang/String;)J", + "(JLjava/lang/String;Z)J", (void *)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries}, {"nativeFindSupportedAbi", - "(J[Ljava/lang/String;)I", + "(J[Ljava/lang/String;Z)I", (void *)com_android_internal_content_NativeLibraryHelper_findSupportedAbi}, {"hasRenderscriptBitcode", "(J)I", (void *)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode}, diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index da059e3bfe67..cc7b95894a1f 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -56,7 +56,7 @@ #include "ScopedLocalRef.h" #include "ScopedPrimitiveArray.h" #include "ScopedUtfChars.h" -#include "fd_utils-inl.h" +#include "fd_utils.h" #include "nativebridge/native_bridge.h" @@ -709,6 +709,16 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( return pid; } +static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork( + JNIEnv* env, jclass, jstring path) { + ScopedUtfChars path_native(env, path); + const char* path_cstr = path_native.c_str(); + if (!path_cstr) { + RuntimeAbort(env, __LINE__, "path_cstr == NULL"); + } + FileDescriptorWhitelist::Get()->Allow(path_cstr); +} + static void com_android_internal_os_Zygote_nativeUnmountStorageOnInit(JNIEnv* env, jclass) { // Zygote process unmount root storage space initially before every child processes are forked. // Every forked child processes (include SystemServer) only mount their own root storage space @@ -753,6 +763,8 @@ static const JNINativeMethod gMethods[] = { (void *) com_android_internal_os_Zygote_nativeForkAndSpecialize }, { "nativeForkSystemServer", "(II[II[[IJJ)I", (void *) com_android_internal_os_Zygote_nativeForkSystemServer }, + { "nativeAllowFileAcrossFork", "(Ljava/lang/String;)V", + (void *) com_android_internal_os_Zygote_nativeAllowFileAcrossFork }, { "nativeUnmountStorageOnInit", "()V", (void *) com_android_internal_os_Zygote_nativeUnmountStorageOnInit } }; diff --git a/core/jni/fd_utils-inl.h b/core/jni/fd_utils-inl.h deleted file mode 100644 index e270911de5e7..000000000000 --- a/core/jni/fd_utils-inl.h +++ /dev/null @@ -1,587 +0,0 @@ -/* - * 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 <string> -#include <unordered_map> -#include <set> -#include <vector> -#include <algorithm> - -#include <android-base/strings.h> -#include <dirent.h> -#include <fcntl.h> -#include <grp.h> -#include <inttypes.h> -#include <stdlib.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/un.h> -#include <unistd.h> - -#include <cutils/log.h> -#include "JNIHelp.h" -#include "ScopedPrimitiveArray.h" - -// Whitelist of open paths that the zygote is allowed to keep open. -// -// In addition to the paths listed here, all files ending with -// ".jar" under /system/framework" are whitelisted. See -// FileDescriptorInfo::IsWhitelisted for the canonical definition. -// -// If the whitelisted path is associated with a regular file or a -// character device, the file is reopened after a fork with the same -// offset and mode. If the whilelisted path is associated with a -// AF_UNIX socket, the socket will refer to /dev/null after each -// fork, and all operations on it will fail. -static const char* kPathWhitelist[] = { - "/dev/null", - "/dev/socket/zygote", - "/dev/socket/zygote_secondary", - "/dev/socket/webview_zygote", - "/sys/kernel/debug/tracing/trace_marker", - "/system/framework/framework-res.apk", - "/dev/urandom", - "/dev/ion", - "/dev/dri/renderD129", // Fixes b/31172436 -}; - -static const char* kFdPath = "/proc/self/fd"; - -// Keeps track of all relevant information (flags, offset etc.) of an -// open zygote file descriptor. -class FileDescriptorInfo { - public: - // Create a FileDescriptorInfo for a given file descriptor. Returns - // |NULL| if an error occurred. - static FileDescriptorInfo* createFromFd(int fd) { - struct stat f_stat; - // This should never happen; the zygote should always have the right set - // of permissions required to stat all its open files. - if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { - ALOGE("Unable to stat fd %d : %s", fd, strerror(errno)); - return NULL; - } - - if (S_ISSOCK(f_stat.st_mode)) { - std::string socket_name; - if (!GetSocketName(fd, &socket_name)) { - return NULL; - } - - if (!IsWhitelisted(socket_name)) { - ALOGE("Socket name not whitelisted : %s (fd=%d)", socket_name.c_str(), fd); - return NULL; - } - - return new FileDescriptorInfo(fd); - } - - // We only handle whitelisted regular files and character devices. Whitelisted - // character devices must provide a guarantee of sensible behaviour when - // reopened. - // - // S_ISDIR : Not supported. (We could if we wanted to, but it's unused). - // S_ISLINK : Not supported. - // S_ISBLK : Not supported. - // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate - // with the child process across forks but those should have been closed - // before we got to this point. - if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) { - ALOGE("Unsupported st_mode %d", f_stat.st_mode); - return NULL; - } - - std::string file_path; - if (!Readlink(fd, &file_path)) { - return NULL; - } - - if (!IsWhitelisted(file_path)) { - ALOGE("Not whitelisted : %s", file_path.c_str()); - return NULL; - } - - // File descriptor flags : currently on FD_CLOEXEC. We can set these - // using F_SETFD - we're single threaded at this point of execution so - // there won't be any races. - const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD)); - if (fd_flags == -1) { - ALOGE("Failed fcntl(%d, F_GETFD) : %s", fd, strerror(errno)); - return NULL; - } - - // File status flags : - // - File access mode : (O_RDONLY, O_WRONLY...) we'll pass these through - // to the open() call. - // - // - File creation flags : (O_CREAT, O_EXCL...) - there's not much we can - // do about these, since the file has already been created. We shall ignore - // them here. - // - // - Other flags : We'll have to set these via F_SETFL. On linux, F_SETFL - // can only set O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK. - // In particular, it can't set O_SYNC and O_DSYNC. We'll have to test for - // their presence and pass them in to open(). - int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL)); - if (fs_flags == -1) { - ALOGE("Failed fcntl(%d, F_GETFL) : %s", fd, strerror(errno)); - return NULL; - } - - // File offset : Ignore the offset for non seekable files. - const off_t offset = TEMP_FAILURE_RETRY(lseek64(fd, 0, SEEK_CUR)); - - // We pass the flags that open accepts to open, and use F_SETFL for - // the rest of them. - static const int kOpenFlags = (O_RDONLY | O_WRONLY | O_RDWR | O_DSYNC | O_SYNC); - int open_flags = fs_flags & (kOpenFlags); - fs_flags = fs_flags & (~(kOpenFlags)); - - return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset); - } - - // Checks whether the file descriptor associated with this object - // refers to the same description. - bool Restat() const { - struct stat f_stat; - if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { - return false; - } - - return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev; - } - - bool ReopenOrDetach() const { - if (is_sock) { - return DetachSocket(); - } - - // NOTE: This might happen if the file was unlinked after being opened. - // It's a common pattern in the case of temporary files and the like but - // we should not allow such usage from the zygote. - const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags)); - - if (new_fd == -1) { - ALOGE("Failed open(%s, %d) : %s", file_path.c_str(), open_flags, strerror(errno)); - return false; - } - - if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) { - close(new_fd); - ALOGE("Failed fcntl(%d, F_SETFD, %x) : %s", new_fd, fd_flags, strerror(errno)); - return false; - } - - if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) { - close(new_fd); - ALOGE("Failed fcntl(%d, F_SETFL, %x) : %s", new_fd, fs_flags, strerror(errno)); - return false; - } - - if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) { - close(new_fd); - ALOGE("Failed lseek64(%d, SEEK_SET) : %s", new_fd, strerror(errno)); - return false; - } - - if (TEMP_FAILURE_RETRY(dup2(new_fd, fd)) == -1) { - close(new_fd); - ALOGE("Failed dup2(%d, %d) : %s", fd, new_fd, strerror(errno)); - return false; - } - - close(new_fd); - - return true; - } - - const int fd; - const struct stat stat; - const std::string file_path; - const int open_flags; - const int fd_flags; - const int fs_flags; - const off_t offset; - const bool is_sock; - - private: - FileDescriptorInfo(int fd) : - fd(fd), - stat(), - open_flags(0), - fd_flags(0), - fs_flags(0), - offset(0), - is_sock(true) { - } - - FileDescriptorInfo(struct stat stat, const std::string& file_path, int fd, int open_flags, - int fd_flags, int fs_flags, off_t offset) : - fd(fd), - stat(stat), - file_path(file_path), - open_flags(open_flags), - fd_flags(fd_flags), - fs_flags(fs_flags), - offset(offset), - is_sock(false) { - } - - static bool StartsWith(const std::string& str, const std::string& prefix) { - return str.compare(0, prefix.size(), prefix) == 0; - } - - static bool EndsWith(const std::string& str, const std::string& suffix) { - if (suffix.size() > str.size()) { - return false; - } - - return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; - } - - // Returns true iff. a given path is whitelisted. A path is whitelisted - // if it belongs to the whitelist (see kPathWhitelist) or if it's a path - // under /system/framework that ends with ".jar" or if it is a system - // framework overlay. - static bool IsWhitelisted(const std::string& path) { - for (size_t i = 0; i < (sizeof(kPathWhitelist) / sizeof(kPathWhitelist[0])); ++i) { - if (kPathWhitelist[i] == path) { - return true; - } - } - - static const std::string kFrameworksPrefix = "/system/framework/"; - static const std::string kJarSuffix = ".jar"; - if (StartsWith(path, kFrameworksPrefix) && EndsWith(path, kJarSuffix)) { - return true; - } - - // Whitelist files needed for Runtime Resource Overlay, like these: - // /system/vendor/overlay/framework-res.apk - // /system/vendor/overlay-subdir/pg/framework-res.apk - // /vendor/overlay/framework-res.apk - // /vendor/overlay/PG/android-framework-runtime-resource-overlay.apk - // /data/resource-cache/system@vendor@overlay@framework-res.apk@idmap - // /data/resource-cache/system@vendor@overlay-subdir@pg@framework-res.apk@idmap - // See AssetManager.cpp for more details on overlay-subdir. - static const std::string kOverlayDir = "/system/vendor/overlay/"; - static const std::string kVendorOverlayDir = "/vendor/overlay"; - static const std::string kOverlaySubdir = "/system/vendor/overlay-subdir/"; - static const std::string kApkSuffix = ".apk"; - - if ((StartsWith(path, kOverlayDir) || StartsWith(path, kOverlaySubdir) - || StartsWith(path, kVendorOverlayDir)) - && EndsWith(path, kApkSuffix) - && path.find("/../") == std::string::npos) { - return true; - } - - static const std::string kOverlayIdmapPrefix = "/data/resource-cache/"; - static const std::string kOverlayIdmapSuffix = ".apk@idmap"; - if (StartsWith(path, kOverlayIdmapPrefix) && EndsWith(path, kOverlayIdmapSuffix) - && path.find("/../") == std::string::npos) { - return true; - } - - // All regular files that are placed under this path are whitelisted automatically. - static const std::string kZygoteWhitelistPath = "/vendor/zygote_whitelist/"; - if (StartsWith(path, kZygoteWhitelistPath) && path.find("/../") == std::string::npos) { - return true; - } - - return false; - } - - // TODO: Call android::base::Readlink instead of copying the code here. - static bool Readlink(const int fd, std::string* result) { - char path[64]; - snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); - - // Code copied from android::base::Readlink starts here : - - // Annoyingly, the readlink system call returns EINVAL for a zero-sized buffer, - // and truncates to whatever size you do supply, so it can't be used to query. - // We could call lstat first, but that would introduce a race condition that - // we couldn't detect. - // ext2 and ext4 both have PAGE_SIZE limitations, so we assume that here. - char buf[4096]; - ssize_t len = readlink(path, buf, sizeof(buf)); - if (len == -1) return false; - - result->assign(buf, len); - return true; - } - - // Returns the locally-bound name of the socket |fd|. Returns true - // iff. all of the following hold : - // - // - the socket's sa_family is AF_UNIX. - // - the length of the path is greater than zero (i.e, not an unnamed socket). - // - the first byte of the path isn't zero (i.e, not a socket with an abstract - // address). - static bool GetSocketName(const int fd, std::string* result) { - sockaddr_storage ss; - sockaddr* addr = reinterpret_cast<sockaddr*>(&ss); - socklen_t addr_len = sizeof(ss); - - if (TEMP_FAILURE_RETRY(getsockname(fd, addr, &addr_len)) == -1) { - ALOGE("Failed getsockname(%d) : %s", fd, strerror(errno)); - return false; - } - - if (addr->sa_family != AF_UNIX) { - ALOGE("Unsupported socket (fd=%d) with family %d", fd, addr->sa_family); - return false; - } - - const sockaddr_un* unix_addr = reinterpret_cast<const sockaddr_un*>(&ss); - - size_t path_len = addr_len - offsetof(struct sockaddr_un, sun_path); - // This is an unnamed local socket, we do not accept it. - if (path_len == 0) { - ALOGE("Unsupported AF_UNIX socket (fd=%d) with empty path.", fd); - return false; - } - - // This is a local socket with an abstract address, we do not accept it. - if (unix_addr->sun_path[0] == '\0') { - ALOGE("Unsupported AF_UNIX socket (fd=%d) with abstract address.", fd); - return false; - } - - // If we're here, sun_path must refer to a null terminated filesystem - // pathname (man 7 unix). Remove the terminator before assigning it to an - // std::string. - if (unix_addr->sun_path[path_len - 1] == '\0') { - --path_len; - } - - result->assign(unix_addr->sun_path, path_len); - return true; - } - - bool DetachSocket() const { - const int dev_null_fd = open("/dev/null", O_RDWR); - if (dev_null_fd < 0) { - ALOGE("Failed to open /dev/null : %s", strerror(errno)); - return false; - } - - if (dup2(dev_null_fd, fd) == -1) { - ALOGE("Failed dup2 on socket descriptor %d : %s", fd, strerror(errno)); - return false; - } - - if (close(dev_null_fd) == -1) { - ALOGE("Failed close(%d) : %s", dev_null_fd, strerror(errno)); - return false; - } - - return true; - } - - DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo); -}; - -// A FileDescriptorTable is a collection of FileDescriptorInfo objects -// keyed by their FDs. -class FileDescriptorTable { - public: - // Creates a new FileDescriptorTable. This function scans - // /proc/self/fd for the list of open file descriptors and collects - // information about them. Returns NULL if an error occurs. - static FileDescriptorTable* Create() { - DIR* d = opendir(kFdPath); - if (d == NULL) { - ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno)); - return NULL; - } - int dir_fd = dirfd(d); - dirent* e; - - std::unordered_map<int, FileDescriptorInfo*> open_fd_map; - while ((e = readdir(d)) != NULL) { - const int fd = ParseFd(e, dir_fd); - if (fd == -1) { - continue; - } - - FileDescriptorInfo* info = FileDescriptorInfo::createFromFd(fd); - if (info == NULL) { - if (closedir(d) == -1) { - ALOGE("Unable to close directory : %s", strerror(errno)); - } - return NULL; - } - open_fd_map[fd] = info; - } - - if (closedir(d) == -1) { - ALOGE("Unable to close directory : %s", strerror(errno)); - return NULL; - } - return new FileDescriptorTable(open_fd_map); - } - - bool Restat() { - std::set<int> open_fds; - - // First get the list of open descriptors. - DIR* d = opendir(kFdPath); - if (d == NULL) { - ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno)); - return false; - } - - int dir_fd = dirfd(d); - dirent* e; - while ((e = readdir(d)) != NULL) { - const int fd = ParseFd(e, dir_fd); - if (fd == -1) { - continue; - } - - open_fds.insert(fd); - } - - if (closedir(d) == -1) { - ALOGE("Unable to close directory : %s", strerror(errno)); - return false; - } - - return RestatInternal(open_fds); - } - - // Reopens all file descriptors that are contained in the table. Returns true - // if all descriptors were successfully re-opened or detached, and false if an - // error occurred. - bool ReopenOrDetach() { - std::unordered_map<int, FileDescriptorInfo*>::const_iterator it; - for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) { - const FileDescriptorInfo* info = it->second; - if (info == NULL || !info->ReopenOrDetach()) { - return false; - } - } - - return true; - } - - private: - FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map) - : open_fd_map_(map) { - } - - bool RestatInternal(std::set<int>& open_fds) { - bool error = false; - - // Iterate through the list of file descriptors we've already recorded - // and check whether : - // - // (a) they continue to be open. - // (b) they refer to the same file. - std::unordered_map<int, FileDescriptorInfo*>::iterator it = open_fd_map_.begin(); - while (it != open_fd_map_.end()) { - std::set<int>::const_iterator element = open_fds.find(it->first); - if (element == open_fds.end()) { - // The entry from the file descriptor table is no longer in the list - // of open files. We warn about this condition and remove it from - // the list of FDs under consideration. - // - // TODO(narayan): This will be an error in a future android release. - // error = true; - // ALOGW("Zygote closed file descriptor %d.", it->first); - it = open_fd_map_.erase(it); - } else { - // The entry from the file descriptor table is still open. Restat - // it and check whether it refers to the same file. - const bool same_file = it->second->Restat(); - if (!same_file) { - // The file descriptor refers to a different description. We must - // update our entry in the table. - delete it->second; - it->second = FileDescriptorInfo::createFromFd(*element); - if (it->second == NULL) { - // The descriptor no longer no longer refers to a whitelisted file. - // We flag an error and remove it from the list of files we're - // tracking. - error = true; - it = open_fd_map_.erase(it); - } else { - // Successfully restatted the file, move on to the next open FD. - ++it; - } - } else { - // It's the same file. Nothing to do here. Move on to the next open - // FD. - ++it; - } - - // Finally, remove the FD from the set of open_fds. We do this last because - // |element| will not remain valid after a call to erase. - open_fds.erase(element); - } - } - - if (open_fds.size() > 0) { - // The zygote has opened new file descriptors since our last inspection. - // We warn about this condition and add them to our table. - // - // TODO(narayan): This will be an error in a future android release. - // error = true; - // ALOGW("Zygote opened %zd new file descriptor(s).", open_fds.size()); - - // TODO(narayan): This code will be removed in a future android release. - std::set<int>::const_iterator it; - for (it = open_fds.begin(); it != open_fds.end(); ++it) { - const int fd = (*it); - FileDescriptorInfo* info = FileDescriptorInfo::createFromFd(fd); - if (info == NULL) { - // A newly opened file is not on the whitelist. Flag an error and - // continue. - error = true; - } else { - // Track the newly opened file. - open_fd_map_[fd] = info; - } - } - } - - return !error; - } - - static int ParseFd(dirent* e, int dir_fd) { - char* end; - const int fd = strtol(e->d_name, &end, 10); - if ((*end) != '\0') { - return -1; - } - - // Don't bother with the standard input/output/error, they're handled - // specially post-fork anyway. - if (fd <= STDERR_FILENO || fd == dir_fd) { - return -1; - } - - return fd; - } - - // Invariant: All values in this unordered_map are non-NULL. - std::unordered_map<int, FileDescriptorInfo*> open_fd_map_; - - DISALLOW_COPY_AND_ASSIGN(FileDescriptorTable); -}; diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp new file mode 100644 index 000000000000..969d336f3cad --- /dev/null +++ b/core/jni/fd_utils.cpp @@ -0,0 +1,559 @@ +/* + * 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 "fd_utils.h" + +#include <algorithm> + +#include <fcntl.h> +#include <grp.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/un.h> +#include <unistd.h> + +#include <android-base/strings.h> +#include <cutils/log.h> + +// Static whitelist of open paths that the zygote is allowed to keep open. +static const char* kPathWhitelist[] = { + "/dev/null", + "/dev/socket/zygote", + "/dev/socket/zygote_secondary", + "/dev/socket/webview_zygote", + "/sys/kernel/debug/tracing/trace_marker", + "/system/framework/framework-res.apk", + "/dev/urandom", + "/dev/ion", + "/dev/dri/renderD129", // Fixes b/31172436 +}; + +static const char kFdPath[] = "/proc/self/fd"; + +// static +FileDescriptorWhitelist* FileDescriptorWhitelist::Get() { + if (instance_ == nullptr) { + instance_ = new FileDescriptorWhitelist(); + } + return instance_; +} + +bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { + // Check the static whitelist path. + for (const auto& whitelist_path : kPathWhitelist) { + if (path == whitelist_path) + return true; + } + + // Check any paths added to the dynamic whitelist. + for (const auto& whitelist_path : whitelist_) { + if (path == whitelist_path) + return true; + } + + static const std::string kFrameworksPrefix = "/system/framework/"; + static const std::string kJarSuffix = ".jar"; + if (StartsWith(path, kFrameworksPrefix) && EndsWith(path, kJarSuffix)) { + return true; + } + + // Whitelist files needed for Runtime Resource Overlay, like these: + // /system/vendor/overlay/framework-res.apk + // /system/vendor/overlay-subdir/pg/framework-res.apk + // /vendor/overlay/framework-res.apk + // /vendor/overlay/PG/android-framework-runtime-resource-overlay.apk + // /data/resource-cache/system@vendor@overlay@framework-res.apk@idmap + // /data/resource-cache/system@vendor@overlay-subdir@pg@framework-res.apk@idmap + // See AssetManager.cpp for more details on overlay-subdir. + static const std::string kOverlayDir = "/system/vendor/overlay/"; + static const std::string kVendorOverlayDir = "/vendor/overlay"; + static const std::string kOverlaySubdir = "/system/vendor/overlay-subdir/"; + static const std::string kApkSuffix = ".apk"; + + if ((StartsWith(path, kOverlayDir) || StartsWith(path, kOverlaySubdir) + || StartsWith(path, kVendorOverlayDir)) + && EndsWith(path, kApkSuffix) + && path.find("/../") == std::string::npos) { + return true; + } + + static const std::string kOverlayIdmapPrefix = "/data/resource-cache/"; + static const std::string kOverlayIdmapSuffix = ".apk@idmap"; + if (StartsWith(path, kOverlayIdmapPrefix) && EndsWith(path, kOverlayIdmapSuffix) + && path.find("/../") == std::string::npos) { + return true; + } + + // All regular files that are placed under this path are whitelisted automatically. + static const std::string kZygoteWhitelistPath = "/vendor/zygote_whitelist/"; + if (StartsWith(path, kZygoteWhitelistPath) && path.find("/../") == std::string::npos) { + return true; + } + + return false; +} + +FileDescriptorWhitelist::FileDescriptorWhitelist() + : whitelist_() { +} + +// TODO: Call android::base::StartsWith instead of copying the code here. +// static +bool FileDescriptorWhitelist::StartsWith(const std::string& str, + const std::string& prefix) { + return str.compare(0, prefix.size(), prefix) == 0; +} + +// TODO: Call android::base::EndsWith instead of copying the code here. +// static +bool FileDescriptorWhitelist::EndsWith(const std::string& str, + const std::string& suffix) { + if (suffix.size() > str.size()) { + return false; + } + + return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +FileDescriptorWhitelist* FileDescriptorWhitelist::instance_ = nullptr; + +// static +FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd) { + struct stat f_stat; + // This should never happen; the zygote should always have the right set + // of permissions required to stat all its open files. + if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { + ALOGE("Unable to stat fd %d : %s", fd, strerror(errno)); + return NULL; + } + + const FileDescriptorWhitelist* whitelist = FileDescriptorWhitelist::Get(); + + if (S_ISSOCK(f_stat.st_mode)) { + std::string socket_name; + if (!GetSocketName(fd, &socket_name)) { + return NULL; + } + + if (!whitelist->IsAllowed(socket_name)) { + ALOGE("Socket name not whitelisted : %s (fd=%d)", socket_name.c_str(), fd); + return NULL; + } + + return new FileDescriptorInfo(fd); + } + + // We only handle whitelisted regular files and character devices. Whitelisted + // character devices must provide a guarantee of sensible behaviour when + // reopened. + // + // S_ISDIR : Not supported. (We could if we wanted to, but it's unused). + // S_ISLINK : Not supported. + // S_ISBLK : Not supported. + // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate + // with the child process across forks but those should have been closed + // before we got to this point. + if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) { + ALOGE("Unsupported st_mode %d", f_stat.st_mode); + return NULL; + } + + std::string file_path; + if (!Readlink(fd, &file_path)) { + return NULL; + } + + if (!whitelist->IsAllowed(file_path)) { + ALOGE("Not whitelisted : %s", file_path.c_str()); + return NULL; + } + + // File descriptor flags : currently on FD_CLOEXEC. We can set these + // using F_SETFD - we're single threaded at this point of execution so + // there won't be any races. + const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD)); + if (fd_flags == -1) { + ALOGE("Failed fcntl(%d, F_GETFD) : %s", fd, strerror(errno)); + return NULL; + } + + // File status flags : + // - File access mode : (O_RDONLY, O_WRONLY...) we'll pass these through + // to the open() call. + // + // - File creation flags : (O_CREAT, O_EXCL...) - there's not much we can + // do about these, since the file has already been created. We shall ignore + // them here. + // + // - Other flags : We'll have to set these via F_SETFL. On linux, F_SETFL + // can only set O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK. + // In particular, it can't set O_SYNC and O_DSYNC. We'll have to test for + // their presence and pass them in to open(). + int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL)); + if (fs_flags == -1) { + ALOGE("Failed fcntl(%d, F_GETFL) : %s", fd, strerror(errno)); + return NULL; + } + + // File offset : Ignore the offset for non seekable files. + const off_t offset = TEMP_FAILURE_RETRY(lseek64(fd, 0, SEEK_CUR)); + + // We pass the flags that open accepts to open, and use F_SETFL for + // the rest of them. + static const int kOpenFlags = (O_RDONLY | O_WRONLY | O_RDWR | O_DSYNC | O_SYNC); + int open_flags = fs_flags & (kOpenFlags); + fs_flags = fs_flags & (~(kOpenFlags)); + + return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset); +} + +bool FileDescriptorInfo::Restat() const { + struct stat f_stat; + if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { + return false; + } + + return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev; +} + +bool FileDescriptorInfo::ReopenOrDetach() const { + if (is_sock) { + return DetachSocket(); + } + + // NOTE: This might happen if the file was unlinked after being opened. + // It's a common pattern in the case of temporary files and the like but + // we should not allow such usage from the zygote. + const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags)); + + if (new_fd == -1) { + ALOGE("Failed open(%s, %d) : %s", file_path.c_str(), open_flags, strerror(errno)); + return false; + } + + if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) { + close(new_fd); + ALOGE("Failed fcntl(%d, F_SETFD, %x) : %s", new_fd, fd_flags, strerror(errno)); + return false; + } + + if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) { + close(new_fd); + ALOGE("Failed fcntl(%d, F_SETFL, %x) : %s", new_fd, fs_flags, strerror(errno)); + return false; + } + + if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) { + close(new_fd); + ALOGE("Failed lseek64(%d, SEEK_SET) : %s", new_fd, strerror(errno)); + return false; + } + + if (TEMP_FAILURE_RETRY(dup2(new_fd, fd)) == -1) { + close(new_fd); + ALOGE("Failed dup2(%d, %d) : %s", fd, new_fd, strerror(errno)); + return false; + } + + close(new_fd); + + return true; +} + +FileDescriptorInfo::FileDescriptorInfo(int fd) : + fd(fd), + stat(), + open_flags(0), + fd_flags(0), + fs_flags(0), + offset(0), + is_sock(true) { +} + +FileDescriptorInfo::FileDescriptorInfo(struct stat stat, const std::string& file_path, + int fd, int open_flags, int fd_flags, int fs_flags, + off_t offset) : + fd(fd), + stat(stat), + file_path(file_path), + open_flags(open_flags), + fd_flags(fd_flags), + fs_flags(fs_flags), + offset(offset), + is_sock(false) { +} + +// TODO: Call android::base::Readlink instead of copying the code here. +// static +bool FileDescriptorInfo::Readlink(const int fd, std::string* result) { + char path[64]; + snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); + + // Code copied from android::base::Readlink starts here : + + // Annoyingly, the readlink system call returns EINVAL for a zero-sized buffer, + // and truncates to whatever size you do supply, so it can't be used to query. + // We could call lstat first, but that would introduce a race condition that + // we couldn't detect. + // ext2 and ext4 both have PAGE_SIZE limitations, so we assume that here. + char buf[4096]; + ssize_t len = readlink(path, buf, sizeof(buf)); + if (len == -1) return false; + + result->assign(buf, len); + return true; +} + +// static +bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) { + sockaddr_storage ss; + sockaddr* addr = reinterpret_cast<sockaddr*>(&ss); + socklen_t addr_len = sizeof(ss); + + if (TEMP_FAILURE_RETRY(getsockname(fd, addr, &addr_len)) == -1) { + ALOGE("Failed getsockname(%d) : %s", fd, strerror(errno)); + return false; + } + + if (addr->sa_family != AF_UNIX) { + ALOGE("Unsupported socket (fd=%d) with family %d", fd, addr->sa_family); + return false; + } + + const sockaddr_un* unix_addr = reinterpret_cast<const sockaddr_un*>(&ss); + + size_t path_len = addr_len - offsetof(struct sockaddr_un, sun_path); + // This is an unnamed local socket, we do not accept it. + if (path_len == 0) { + ALOGE("Unsupported AF_UNIX socket (fd=%d) with empty path.", fd); + return false; + } + + // This is a local socket with an abstract address, we do not accept it. + if (unix_addr->sun_path[0] == '\0') { + ALOGE("Unsupported AF_UNIX socket (fd=%d) with abstract address.", fd); + return false; + } + + // If we're here, sun_path must refer to a null terminated filesystem + // pathname (man 7 unix). Remove the terminator before assigning it to an + // std::string. + if (unix_addr->sun_path[path_len - 1] == '\0') { + --path_len; + } + + result->assign(unix_addr->sun_path, path_len); + return true; +} + +bool FileDescriptorInfo::DetachSocket() const { + const int dev_null_fd = open("/dev/null", O_RDWR); + if (dev_null_fd < 0) { + ALOGE("Failed to open /dev/null : %s", strerror(errno)); + return false; + } + + if (dup2(dev_null_fd, fd) == -1) { + ALOGE("Failed dup2 on socket descriptor %d : %s", fd, strerror(errno)); + return false; + } + + if (close(dev_null_fd) == -1) { + ALOGE("Failed close(%d) : %s", dev_null_fd, strerror(errno)); + return false; + } + + return true; +} + +// static +FileDescriptorTable* FileDescriptorTable::Create() { + DIR* d = opendir(kFdPath); + if (d == NULL) { + ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno)); + return NULL; + } + int dir_fd = dirfd(d); + dirent* e; + + std::unordered_map<int, FileDescriptorInfo*> open_fd_map; + while ((e = readdir(d)) != NULL) { + const int fd = ParseFd(e, dir_fd); + if (fd == -1) { + continue; + } + + FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd); + if (info == NULL) { + if (closedir(d) == -1) { + ALOGE("Unable to close directory : %s", strerror(errno)); + } + return NULL; + } + open_fd_map[fd] = info; + } + + if (closedir(d) == -1) { + ALOGE("Unable to close directory : %s", strerror(errno)); + return NULL; + } + return new FileDescriptorTable(open_fd_map); +} + +bool FileDescriptorTable::Restat() { + std::set<int> open_fds; + + // First get the list of open descriptors. + DIR* d = opendir(kFdPath); + if (d == NULL) { + ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno)); + return false; + } + + int dir_fd = dirfd(d); + dirent* e; + while ((e = readdir(d)) != NULL) { + const int fd = ParseFd(e, dir_fd); + if (fd == -1) { + continue; + } + + open_fds.insert(fd); + } + + if (closedir(d) == -1) { + ALOGE("Unable to close directory : %s", strerror(errno)); + return false; + } + + return RestatInternal(open_fds); +} + +// Reopens all file descriptors that are contained in the table. Returns true +// if all descriptors were successfully re-opened or detached, and false if an +// error occurred. +bool FileDescriptorTable::ReopenOrDetach() { + std::unordered_map<int, FileDescriptorInfo*>::const_iterator it; + for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) { + const FileDescriptorInfo* info = it->second; + if (info == NULL || !info->ReopenOrDetach()) { + return false; + } + } + + return true; +} + +FileDescriptorTable::FileDescriptorTable( + const std::unordered_map<int, FileDescriptorInfo*>& map) + : open_fd_map_(map) { +} + +bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds) { + bool error = false; + + // Iterate through the list of file descriptors we've already recorded + // and check whether : + // + // (a) they continue to be open. + // (b) they refer to the same file. + std::unordered_map<int, FileDescriptorInfo*>::iterator it = open_fd_map_.begin(); + while (it != open_fd_map_.end()) { + std::set<int>::const_iterator element = open_fds.find(it->first); + if (element == open_fds.end()) { + // The entry from the file descriptor table is no longer in the list + // of open files. We warn about this condition and remove it from + // the list of FDs under consideration. + // + // TODO(narayan): This will be an error in a future android release. + // error = true; + // ALOGW("Zygote closed file descriptor %d.", it->first); + it = open_fd_map_.erase(it); + } else { + // The entry from the file descriptor table is still open. Restat + // it and check whether it refers to the same file. + const bool same_file = it->second->Restat(); + if (!same_file) { + // The file descriptor refers to a different description. We must + // update our entry in the table. + delete it->second; + it->second = FileDescriptorInfo::CreateFromFd(*element); + if (it->second == NULL) { + // The descriptor no longer no longer refers to a whitelisted file. + // We flag an error and remove it from the list of files we're + // tracking. + error = true; + it = open_fd_map_.erase(it); + } else { + // Successfully restatted the file, move on to the next open FD. + ++it; + } + } else { + // It's the same file. Nothing to do here. Move on to the next open + // FD. + ++it; + } + + // Finally, remove the FD from the set of open_fds. We do this last because + // |element| will not remain valid after a call to erase. + open_fds.erase(element); + } + } + + if (open_fds.size() > 0) { + // The zygote has opened new file descriptors since our last inspection. + // We warn about this condition and add them to our table. + // + // TODO(narayan): This will be an error in a future android release. + // error = true; + // ALOGW("Zygote opened %zd new file descriptor(s).", open_fds.size()); + + // TODO(narayan): This code will be removed in a future android release. + std::set<int>::const_iterator it; + for (it = open_fds.begin(); it != open_fds.end(); ++it) { + const int fd = (*it); + FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd); + if (info == NULL) { + // A newly opened file is not on the whitelist. Flag an error and + // continue. + error = true; + } else { + // Track the newly opened file. + open_fd_map_[fd] = info; + } + } + } + + return !error; +} + +// static +int FileDescriptorTable::ParseFd(dirent* e, int dir_fd) { + char* end; + const int fd = strtol(e->d_name, &end, 10); + if ((*end) != '\0') { + return -1; + } + + // Don't bother with the standard input/output/error, they're handled + // specially post-fork anyway. + if (fd <= STDERR_FILENO || fd == dir_fd) { + return -1; + } + + return fd; +} diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h new file mode 100644 index 000000000000..9e3afd910914 --- /dev/null +++ b/core/jni/fd_utils.h @@ -0,0 +1,147 @@ +/* + * 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. + */ + +#ifndef FRAMEWORKS_BASE_CORE_JNI_FD_UTILS_H_ +#define FRAMEWORKS_BASE_CORE_JNI_FD_UTILS_H_ + +#include <set> +#include <string> +#include <unordered_map> +#include <vector> + +#include <dirent.h> +#include <inttypes.h> +#include <sys/stat.h> + +#include <android-base/macros.h> + +// Whitelist of open paths that the zygote is allowed to keep open. +// +// In addition to the paths listed in kPathWhitelist in file_utils.cpp, and +// paths dynamically added with Allow(), all files ending with ".jar" +// under /system/framework" are whitelisted. See IsAllowed() for the canonical +// definition. +// +// If the whitelisted path is associated with a regular file or a +// character device, the file is reopened after a fork with the same +// offset and mode. If the whilelisted path is associated with a +// AF_UNIX socket, the socket will refer to /dev/null after each +// fork, and all operations on it will fail. +class FileDescriptorWhitelist { + public: + // Lazily creates the global whitelist. + static FileDescriptorWhitelist* Get(); + + // Adds a path to the whitelist. + void Allow(const std::string& path) { + whitelist_.push_back(path); + } + + // Returns true iff. a given path is whitelisted. A path is whitelisted + // if it belongs to the whitelist (see kPathWhitelist) or if it's a path + // under /system/framework that ends with ".jar" or if it is a system + // framework overlay. + bool IsAllowed(const std::string& path) const; + + private: + FileDescriptorWhitelist(); + + static bool StartsWith(const std::string& str, const std::string& prefix); + + static bool EndsWith(const std::string& str, const std::string& suffix); + + static FileDescriptorWhitelist* instance_; + + std::vector<std::string> whitelist_; + + DISALLOW_COPY_AND_ASSIGN(FileDescriptorWhitelist); +}; + +// Keeps track of all relevant information (flags, offset etc.) of an +// open zygote file descriptor. +class FileDescriptorInfo { + public: + // Create a FileDescriptorInfo for a given file descriptor. Returns + // |NULL| if an error occurred. + static FileDescriptorInfo* CreateFromFd(int fd); + + // Checks whether the file descriptor associated with this object + // refers to the same description. + bool Restat() const; + + bool ReopenOrDetach() const; + + const int fd; + const struct stat stat; + const std::string file_path; + const int open_flags; + const int fd_flags; + const int fs_flags; + const off_t offset; + const bool is_sock; + + private: + FileDescriptorInfo(int fd); + + FileDescriptorInfo(struct stat stat, const std::string& file_path, int fd, int open_flags, + int fd_flags, int fs_flags, off_t offset); + + static bool Readlink(const int fd, std::string* result); + + // Returns the locally-bound name of the socket |fd|. Returns true + // iff. all of the following hold : + // + // - the socket's sa_family is AF_UNIX. + // - the length of the path is greater than zero (i.e, not an unnamed socket). + // - the first byte of the path isn't zero (i.e, not a socket with an abstract + // address). + static bool GetSocketName(const int fd, std::string* result); + + bool DetachSocket() const; + + DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo); +}; + +// A FileDescriptorTable is a collection of FileDescriptorInfo objects +// keyed by their FDs. +class FileDescriptorTable { + public: + // Creates a new FileDescriptorTable. This function scans + // /proc/self/fd for the list of open file descriptors and collects + // information about them. Returns NULL if an error occurs. + static FileDescriptorTable* Create(); + + bool Restat(); + + // Reopens all file descriptors that are contained in the table. Returns true + // if all descriptors were successfully re-opened or detached, and false if an + // error occurred. + bool ReopenOrDetach(); + + private: + FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map); + + bool RestatInternal(std::set<int>& open_fds); + + static int ParseFd(dirent* e, int dir_fd); + + // Invariant: All values in this unordered_map are non-NULL. + std::unordered_map<int, FileDescriptorInfo*> open_fd_map_; + + DISALLOW_COPY_AND_ASSIGN(FileDescriptorTable); +}; + +#endif // FRAMEWORKS_BASE_CORE_JNI_FD_UTILS_H_ diff --git a/include/android_runtime/AndroidRuntime.h b/core/jni/include/android_runtime/AndroidRuntime.h index ed77d9aead08..c2189d4af86d 100644 --- a/include/android_runtime/AndroidRuntime.h +++ b/core/jni/include/android_runtime/AndroidRuntime.h @@ -98,7 +98,7 @@ public: * Called when the Java application exits to perform additional cleanup actions * before the process is terminated. */ - virtual void onExit(int code) { } + virtual void onExit(int /*code*/) { } /** create a new thread that is visible from Java */ static android_thread_id_t createJavaThread(const char* name, void (*start)(void *), diff --git a/include/android_runtime/Log.h b/core/jni/include/android_runtime/Log.h index aa6d202683cf..aa6d202683cf 100644 --- a/include/android_runtime/Log.h +++ b/core/jni/include/android_runtime/Log.h diff --git a/include/android_runtime/android_app_NativeActivity.h b/core/jni/include/android_runtime/android_app_NativeActivity.h index e096e9187bbc..e096e9187bbc 100644 --- a/include/android_runtime/android_app_NativeActivity.h +++ b/core/jni/include/android_runtime/android_app_NativeActivity.h diff --git a/include/android_runtime/android_content_res_Configuration.h b/core/jni/include/android_runtime/android_content_res_Configuration.h index 34c4439bbde8..34c4439bbde8 100644 --- a/include/android_runtime/android_content_res_Configuration.h +++ b/core/jni/include/android_runtime/android_content_res_Configuration.h diff --git a/include/android_runtime/android_graphics_SurfaceTexture.h b/core/jni/include/android_runtime/android_graphics_SurfaceTexture.h index c534d4bb9e0a..c534d4bb9e0a 100644 --- a/include/android_runtime/android_graphics_SurfaceTexture.h +++ b/core/jni/include/android_runtime/android_graphics_SurfaceTexture.h diff --git a/include/android_runtime/android_hardware_camera2_CameraMetadata.h b/core/jni/include/android_runtime/android_hardware_camera2_CameraMetadata.h index 3c76ca5d2f33..3c76ca5d2f33 100644 --- a/include/android_runtime/android_hardware_camera2_CameraMetadata.h +++ b/core/jni/include/android_runtime/android_hardware_camera2_CameraMetadata.h diff --git a/include/android_runtime/android_util_AssetManager.h b/core/jni/include/android_runtime/android_util_AssetManager.h index 8dd933707a6a..8dd933707a6a 100644 --- a/include/android_runtime/android_util_AssetManager.h +++ b/core/jni/include/android_runtime/android_util_AssetManager.h diff --git a/include/android_runtime/android_view_InputQueue.h b/core/jni/include/android_runtime/android_view_InputQueue.h index ed37b0aa5083..ed37b0aa5083 100644 --- a/include/android_runtime/android_view_InputQueue.h +++ b/core/jni/include/android_runtime/android_view_InputQueue.h diff --git a/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h index b1e552a810e8..1bc85212090a 100644 --- a/include/android_runtime/android_view_Surface.h +++ b/core/jni/include/android_runtime/android_view_Surface.h @@ -42,6 +42,7 @@ enum class PublicFormat { NV16 = 0x10, NV21 = 0x11, YUY2 = 0x14, + RGBA_FP16 = 0x16, RAW_SENSOR = 0x20, PRIVATE = 0x22, YUV_420_888 = 0x23, diff --git a/include/android_runtime/android_view_SurfaceSession.h b/core/jni/include/android_runtime/android_view_SurfaceSession.h index 3748f6c92ebf..3748f6c92ebf 100644 --- a/include/android_runtime/android_view_SurfaceSession.h +++ b/core/jni/include/android_runtime/android_view_SurfaceSession.h diff --git a/core/proto/android/content/component_name.proto b/core/proto/android/content/component_name.proto new file mode 100644 index 000000000000..7908af9d2a36 --- /dev/null +++ b/core/proto/android/content/component_name.proto @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2006 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. + */ + +syntax = "proto2"; + +option java_package = "android.content"; +option java_multiple_files = true; + +package android.content; + +/** + * An android.content.ComponentName object. + */ +message ComponentNameProto { + optional string package_name = 1; + optional string class_name = 2; +} + diff --git a/core/proto/android/content/configuration.proto b/core/proto/android/content/configuration.proto new file mode 100644 index 000000000000..683e7aacd3ad --- /dev/null +++ b/core/proto/android/content/configuration.proto @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2006 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. + */ + +syntax = "proto2"; + +option java_package = "android.content"; +option java_multiple_files = true; + +package android.content; + +import "frameworks/base/core/proto/android/content/locale.proto"; + +/** + * An android resource configuration. + */ +message ConfigurationProto { + optional float font_scale = 1; + optional uint32 mcc = 2; + optional uint32 mnc = 3; + repeated LocaleProto locales = 4; + optional uint32 screen_layout = 5; + optional uint32 touchscreen = 6; + optional uint32 keyboard_hidden = 7; + optional uint32 hard_keyboard_hidden = 8; + optional uint32 navigation = 9; + optional uint32 navigation_hidden = 10; + optional uint32 orientation = 11; + optional uint32 ui_mode = 12; + optional uint32 screen_width_dp = 13; + optional uint32 screen_height_dp = 14; + optional uint32 smallest_screen_width_dp = 15; + optional uint32 density_dpi = 16; +} + diff --git a/core/proto/android/content/locale.proto b/core/proto/android/content/locale.proto new file mode 100644 index 000000000000..55ce68eed15f --- /dev/null +++ b/core/proto/android/content/locale.proto @@ -0,0 +1,29 @@ +/* + * 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. + */ + +syntax = "proto2"; + +option java_package = "android.content"; +option java_multiple_files = true; + +package android.content; + +message LocaleProto { + optional string language = 1; + optional string country = 2; + optional string variant = 3; +} + diff --git a/core/proto/android/os/incident_proto.proto b/core/proto/android/os/incident_proto.proto new file mode 100644 index 000000000000..1708b8135567 --- /dev/null +++ b/core/proto/android/os/incident_proto.proto @@ -0,0 +1,52 @@ +/* + * 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. + */ + +syntax = "proto2"; + +option java_package = "android.os"; +option java_multiple_files = true; + +import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/service/fingerprint_proto.proto"; + +package android.os; + +message IncidentHeaderProto { + enum Cause { + CAUSE_UNKNOWN = 0; + CAUSE_USER = 1; + CAUSE_ANR = 2; + CAUSE_CRASH = 3; + } + + optional Cause cause = 1; +} + +message IncidentProto { + // Incident header + repeated IncidentHeaderProto header = 1; + + // Device information + //optional SystemProperties system_properties = 1000; + + // Linux services + //optional Procrank procrank = 2000; + //optional PageTypeInfo page_type_info = 2001; + //optional KernelWakeSources kernel_wake_sources = 2002; + + // System Services + optional android.service.fingerprint.FingerprintServiceDumpProto fingerprint = 3000; +} diff --git a/core/proto/android/service/fingerprint_proto.proto b/core/proto/android/service/fingerprint_proto.proto new file mode 100644 index 000000000000..b2c500029661 --- /dev/null +++ b/core/proto/android/service/fingerprint_proto.proto @@ -0,0 +1,56 @@ +/* + * 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. + */ + +syntax = "proto2"; + +package android.service.fingerprint; + +option java_multiple_files = true; + +message FingerprintServiceDumpProto { + // Each log may include multiple tuples of (user_id, num_fingerprints). + repeated FingerprintUserStatsProto users = 1; +} + +message FingerprintUserStatsProto { + // Should be 0, 10, 11, 12, etc. where 0 is the owner. + optional int32 user_id = 1; + + // The number of fingerprints registered to this user. + optional int32 num_fingerprints = 2; + + // Normal fingerprint authentications (e.g. lockscreen). + optional FingerprintActionStatsProto normal = 3; + + // Crypto authentications (e.g. to unlock password storage, make secure + // purchases, etc). + optional FingerprintActionStatsProto crypto = 4; +} + +message FingerprintActionStatsProto { + // Number of accepted fingerprints. + optional int32 accept = 1; + + // Number of rejected fingerprints. + optional int32 reject = 2; + + // Total number of acquisitions. Should be >= accept+reject due to poor + // image acquisition in some cases (too fast, too slow, dirty sensor, etc.) + optional int32 acquire = 3; + + // Total number of lockouts. + optional int32 lockout = 4; +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 40fd8813d768..218083f3b87d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -101,6 +101,7 @@ <protected-broadcast android:name="android.app.action.BUGREPORT_SHARING_DECLINED" /> <protected-broadcast android:name="android.app.action.BUGREPORT_FAILED" /> <protected-broadcast android:name="android.app.action.BUGREPORT_SHARE" /> + <protected-broadcast android:name="android.app.action.SHOW_DEVICE_MONITORING_DIALOG" /> <protected-broadcast android:name="android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" /> <protected-broadcast android:name="android.appwidget.action.APPWIDGET_DELETED" /> @@ -3393,7 +3394,10 @@ android:permission="android.permission.MASTER_CLEAR"> <intent-filter android:priority="100" > - <!-- For Checkin, Settings, etc.: action=MASTER_CLEAR --> + <!-- For Checkin, Settings, etc.: action=FACTORY_RESET --> + <action android:name="android.intent.action.FACTORY_RESET" /> + <!-- As above until all the references to the deprecated MASTER_CLEAR get updated to + FACTORY_RESET. --> <action android:name="android.intent.action.MASTER_CLEAR" /> <!-- MCS always uses REMOTE_INTENT: category=MASTER_CLEAR --> diff --git a/core/res/res/drawable/ic_qs_network_logging.xml b/core/res/res/drawable/ic_qs_network_logging.xml new file mode 100644 index 000000000000..9e08264156e6 --- /dev/null +++ b/core/res/res/drawable/ic_qs_network_logging.xml @@ -0,0 +1,29 @@ +<!-- +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. +--> + +<!-- STOPSHIP: Placeholder icon for network logging until the real icon is finalized--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="#4DFFFFFF" > + <path + android:fillColor="#FFFFFFFF" + android:pathData="M2,24v-4h12v4H2z M2,16v-4h20v4H2z M5,7 12,0 19,7 14,7 14,15 10,15 10,7z"/> + +</vector> diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index f9ae7cf2e705..b640d4d7a08a 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Laat \'n program toe om installasiesessies te lees. Dit laat dit toe om besonderhede van aktiewe pakketinstallasies te sien."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"versoek installeerpakkette"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Laat \'n program toe om te versoek dat pakkette geïnstalleer word."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"vra om batteryoptimerings te ignoreer"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Laat \'n program toe om toestemming te vra om batteryoptimerings vir daardie program ignoreer."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Klop twee keer vir zoembeheer"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Kon nie legstuk byvoeg nie."</string> <string name="ime_action_go" msgid="8320845651737369027">"Gaan"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (volgende wekker)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Totdat jy dit afskakel"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Totdat jy Moenie steur nie afskakel"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Vou in"</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index e24ffce8a75b..7786c9452905 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"መተግበሪያው የመጫን ክፍለ ጊዜዎችን እንዲያነብ ይፈቅድለታል። ይህም ስለ ገቢር የጥቅል ጭነቶች ዝርዝር መረጃን እንዲያይ ይፈቅድለታል።"</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"የጭነት ጥቅሎችን መጠየቅ"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"መተግበሪያ የጥቅሎች መጫንን እንዲጠይቅ ይፈቅዳል።"</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"የባትሪ ማትባቶችን ችላ ለማለት መጠየቅ"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"አንድ መተግበሪያ ለዚያ መተግበሪያ የባትሪ ማትባቶችን ችላ ለማለት እንዲጠይቅ ይፈቅድለታል።"</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"ለአጉላ መቆጣጠሪያ ሁለት ጊዜ ነካ አድርግ"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"ምግብር ማከል አልተቻለም።"</string> <string name="ime_action_go" msgid="8320845651737369027">"ሂድ"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"እስከ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ድረስ"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"እስከ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ቀጣይ ማንቂያ)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"ይህን እስኪያጠፉት ድረስ"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"አትረብሽን እስኪያጠፉ ድረስ"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"ሰብስብ"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index bd0a6580570c..8a02fad01be1 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -1312,10 +1312,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"للسماح لأحد التطبيقات بقراءة جلسات التثبيت. ويسمح لك هذا بالاطلاع على تفاصيل بشأن عمليات تثبيت الحزم النشطة."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"طلب حزم التثبيت"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"للسماح لتطبيق ما بطلب تثبيت الحزم."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"طلب تجاهل تحسينات البطارية"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"للسماح للتطبيق بطلب الإذن لتجاهل تحسينات البطارية في هذا التطبيق."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"اضغط مرتين للتحكم في التكبير/التصغير"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"تعذرت إضافة أداة."</string> <string name="ime_action_go" msgid="8320845651737369027">"تنفيذ"</string> @@ -1756,7 +1754,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"حتى <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"حتى <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (التنبيه التالي)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"لحين تعطيل هذا الإعداد"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"حتى يتم تعطيل \"الرجاء عدم الإزعاج\""</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"تصغير"</string> diff --git a/core/res/res/values-az-rAZ/strings.xml b/core/res/res/values-az-rAZ/strings.xml index 6322bfa12e36..2c55b7e8cf57 100644 --- a/core/res/res/values-az-rAZ/strings.xml +++ b/core/res/res/values-az-rAZ/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Tətbiqə quraşdırma sessiyalarını oxumağa yardım edir. Bu da aktiv paket quraşdırmaları haqqında məlumatları görməyə imkan verir."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"paketləri quraşdırma sorğusu"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Tətbiqə paketləri quraşdırma sorğusu göndərməyə icazə verir."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"batareya optimallaşdırmasını iqnor etmək üçün soruşun"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Tatareya optimallaşdırılmasını o tətbiq üçün iqnor edilməsinə icazə vermək məqsədilə soruşmağa tətbiqə icazə verilir."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Zoom kontrolu üçün iki dəfə toxunun"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Widget əlavə edilə bilmədi."</string> <string name="ime_action_go" msgid="8320845651737369027">"Get"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Saat <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> qədər"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> radəsinə qədər (növbəti siqnal)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Bunu söndürənə kimi"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"\"Narahat etməyin\" seçiminini deaktiv edənə kimi"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Dağıt"</string> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index b44b18061da0..48fefd2904d4 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -1237,10 +1237,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Dozvoljava aplikaciji da čita sesije instaliranja. To joj dozvoljava da vidi detalje o aktivnim instalacijama paketa."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"zahtevanje paketa za instaliranje"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Omogućava da aplikacija zahteva instalaciju paketa."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"traženje dozvole za ignorisanje optimizacija baterije"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Dozvoljava aplikaciji da traži dozvolu za ignorisanje optimizacija baterije za tu aplikaciju."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Dodirnite dvaput za kontrolu zumiranja"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Nije moguće dodati vidžet."</string> <string name="ime_action_go" msgid="8320845651737369027">"Idi"</string> @@ -1651,7 +1649,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (sledeći alarm)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Dok ne isključite"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Dok ne isključite režim Ne uznemiravaj"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Skupi"</string> diff --git a/core/res/res/values-be-rBY/strings.xml b/core/res/res/values-be-rBY/strings.xml index b035e9c469bd..adeeef39dfa1 100644 --- a/core/res/res/values-be-rBY/strings.xml +++ b/core/res/res/values-be-rBY/strings.xml @@ -1262,10 +1262,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Дазваляе праграме счытваць сеансы ўсталёўкі. Гэта дазваляе ёй праглядаць інфармацыю аб актыўных усталёўках пакета."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"запытваць усталёўку пакетаў"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Дазваляе праграме запытваць усталёўку пакетаў."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"запытваць дазвол на ігнараванне аптымізацыі акумулятара"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Дазваляе праграме запытваць дазвол на ігнараванне аптымізацыі акумулятара для гэтай праграмы."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Націсніце двойчы, каб кіраваць маштабаваннем"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Немагчыма дадаць віджэт."</string> <string name="ime_action_go" msgid="8320845651737369027">"Пачаць"</string> @@ -1686,7 +1684,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Да <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Да <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (наступны будзільнік)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Пакуль вы не выключыце гэта"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Пакуль вы не выключыце рэжым «Не турбаваць»"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Згарнуць"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 4caa90696edd..3359a1818eb1 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Разрешава на приложението да чете сесии за инсталиране. Това му позволява да вижда подробности за активните инсталирания на пакети."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"заявка на пакети за инсталиране"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Разрешава на приложението да заявява инсталиране на пакети."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"искане за пренебрегване на оптимизациите на батерията"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Разрешава на дадено приложение да иска разрешение за пренебрегване на свързаните с него оптимизации на батерията."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Докоснете двукратно за управление на промяната на мащаба"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Приспособлението не можа да бъде добавено."</string> <string name="ime_action_go" msgid="8320845651737369027">"Старт"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"До следващия будилник (<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Докато не изключите това"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Докато не изключите „Не безпокойте“"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Свиване"</string> diff --git a/core/res/res/values-bn-rBD/strings.xml b/core/res/res/values-bn-rBD/strings.xml index 0c73ecdccccb..7512bae2f17d 100644 --- a/core/res/res/values-bn-rBD/strings.xml +++ b/core/res/res/values-bn-rBD/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"কোনো অ্যাপ্লিকেশানকে সেশনগুলি পড়ার অনুমতি দেয়। এটি সক্রিয় প্যাকেজ ইনস্টলেশনের বিশদ বিবরণ দেখতে দেয়।"</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"প্যাকেজগুলি ইনস্টল করার অনুরোধ"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"একটি অ্যাপ্লিকেশানকে প্যাকেজগুলির ইনস্টল করার অনুরোধ জানাতে অনুমতি দেয়৷"</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"ব্যাটারি অপ্টিমাইজেশন উপেক্ষা করার জন্য অনুমতি চাওয়া"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"কোনো অ্যাপের জন্য ব্যাটারি অপ্টিমাইজেশন উপেক্ষা করতে সেটিকে অনুমতির চাওয়ার মঞ্জুরি দেয়৷"</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"জুম নিয়ন্ত্রণের জন্য দুবার আলতো চাপুন"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"উইজেট যোগ করা যায়নি৷"</string> <string name="ime_action_go" msgid="8320845651737369027">"যান"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> পর্যন্ত"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> পর্যন্ত (পরবর্তী অ্যালার্ম)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"আপনার দ্বারা এটি বন্ধ করা পর্যন্ত"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"যতক্ষণ না পর্যন্ত আপনি বিরক্ত করবেন না বন্ধ করছেন"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"সঙ্কুচিত করুন"</string> @@ -1687,5 +1686,5 @@ <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"এখনই পুনরায় সেট করুন"</string> <string name="suspended_widget_accessibility" msgid="6712143096475264190">"অক্ষম করা <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="conference_call" msgid="3751093130790472426">"কনফারেন্স কল"</string> - <string name="tooltip_popup_title" msgid="5253721848739260181">"সরঞ্জামটিপ"</string> + <string name="tooltip_popup_title" msgid="5253721848739260181">"টুলটিপ"</string> </resources> diff --git a/core/res/res/values-bs-rBA/strings.xml b/core/res/res/values-bs-rBA/strings.xml index f831874add13..c188416567b8 100644 --- a/core/res/res/values-bs-rBA/strings.xml +++ b/core/res/res/values-bs-rBA/strings.xml @@ -1239,10 +1239,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Dozvoljava aplikaciji da čita sesije instalacija. Ovim se aplikaciji omogućava da vidi detalje o aktivnim instalacijama paketa."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"zahtijevanje paketa za instaliranje"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Omogućava aplikaciji da zahtijeva instalaciju paket ā."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"traži zanemarivanje optimizacije baterije"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Omogućava aplikaciji da traži dozvolu za zanemarivanje optimizacije baterije za tu aplikaciju."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Dodirnite dvaput za kontrolu uvećanja"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Dodavanje vidžeta nije uspjelo."</string> <string name="ime_action_go" msgid="8320845651737369027">"Počni"</string> @@ -1653,7 +1651,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (sljedeći alarm)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Dok ovo ne isključite"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Dok ne isključite opciju Ne ometaj"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Skupi"</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 30d1706d9ecc..98a96db7e361 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Permet que una aplicació llegeixi les sessions d\'instal·lació i això permet veure detalls sobre les instal·lacions de paquet actives."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"sol·licitar la instal·lació de paquets"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Permet que una aplicació sol·liciti la instal·lació de paquets."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"Demanar permís per ignorar les optimitzacions de bateria"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Permet que una aplicació demani permís per ignorar les optimitzacions de bateria per a l\'aplicació."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Piqueu dos cops per controlar el zoom"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"No s\'ha pogut afegir el widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"Vés"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Fins a les <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Fins a les <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (propera alarma)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Fins que no ho desactivis"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Fins que desactivis el mode No molesteu"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Replega"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index d4ffbaac0ad5..57c4d06eecfd 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -1262,10 +1262,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Povoluje aplikaci číst instalační relace. Díky tomu můžete zobrazit podrobnosti o aktivních instalacích balíčku."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"odesílání žádostí o přístup k instalačním balíčkům"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Umožňuje aplikaci požádat o instalaci balíčků."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"požádat o ignorování optimalizace využití baterie"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Povoluje aplikaci požádat o oprávnění ignorovat optimalizaci využití baterie, která pro ni je nastavena."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Poklepáním můžete ovládat přiblížení"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Widget nelze přidat."</string> <string name="ime_action_go" msgid="8320845651737369027">"Přejít"</string> @@ -1686,7 +1684,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (příští budík)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Dokud tuto funkci nevypnete"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Dokud nevypnete režim Nerušit"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Sbalit"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 815c32492bb4..c730ce627525 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Tillader, at en applikation læser installationssessioner. Dermed kan applikationen se oplysninger om aktive pakkeinstallationer."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"anmod om installation af pakker"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Tillader, at en app anmoder om installation af pakker."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"bede om at ignorere batterioptimeringer"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Gør det muligt for en app at bede om tilladelse til at ignorere batterioptimeringer for den pågældende app."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Tryk to gange for zoomkontrol"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Widget kunne ikke tilføjes."</string> <string name="ime_action_go" msgid="8320845651737369027">"Gå"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Indtil <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Indtil <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (næste alarm)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Indtil du slår denne indstilling fra"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Indtil du slår \"Forstyr ikke\" fra"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Skjul"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index c72916086917..167d199c2111 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Ermöglicht der App, Installationssitzungen zu lesen. Dadurch kann sie Details aktiver Paketinstallationen abrufen."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"Installation von Paketen anfordern"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Ermöglicht der App, die Installation von Paketen anzufordern"</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"fragen, ob Akku-Leistungsoptimierungen ignoriert werden können"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Erlaubt einer App, nach der Berechtigung zum Ignorieren der Akku-Leistungsoptimierungen zu fragen."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Für Zoomeinstellung zweimal berühren"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Widget konnte nicht hinzugefügt werden."</string> <string name="ime_action_go" msgid="8320845651737369027">"Los"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Bis <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Bis <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (nächste Weckzeit)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Bis zur Deaktivierung"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Bis zur Deaktivierung von \"Nicht stören\""</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Minimieren"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 4f0d2f1e8474..c477aecb9dd1 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Επιτρέπει σε μια εφαρμογή την ανάγνωση των περιόδων σύνδεσης εγκατάστασης. Αυτό της επιτρέπει να βλέπει λεπτομέρειες σχετικά με τις εγκαταστάσεις του ενεργού πακέτου."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"ζητά πακέτα εγκατάστασης"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Επιτρέπει σε μια εφαρμογή να ζητά εγκατάσταση πακέτων."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"αίτημα αγνόησης βελτιστοποιήσεων μπαταρίας"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Επιτρέπει σε μια εφαρμογή να ζητήσει άδεια για την αγνόηση βελτιστοποιήσεων της μπαταρίας για τη συγκεκριμένη εφαρμογή."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Πατήστε δύο φορές για έλεγχο εστίασης"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Δεν ήταν δυνατή η προσθήκη του γραφικού στοιχείου."</string> <string name="ime_action_go" msgid="8320845651737369027">"Μετάβαση"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Έως τις <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Μέχρι τις <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (επόμενο ξυπνητήρι)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Μέχρι να το απενεργοποιήσετε"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Μέχρι να απενεργοποιήσετε τη ρύθμιση \"Μην ενοχλείτε\""</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Σύμπτυξη"</string> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index a85976d14da6..50989b4f2b37 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Allows an application to read install sessions. This allows it to see details about active package installations."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"request install packages"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Allows an application to request installation of packages."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"ask to ignore battery optimisations"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Allows an app to ask for permission to ignore battery optimisations for that app."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Tap twice for zoom control"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Couldn\'t add widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"Go"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (next alarm)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Until you turn this off"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Until you turn off Do Not Disturb"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Collapse"</string> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index a85976d14da6..50989b4f2b37 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Allows an application to read install sessions. This allows it to see details about active package installations."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"request install packages"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Allows an application to request installation of packages."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"ask to ignore battery optimisations"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Allows an app to ask for permission to ignore battery optimisations for that app."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Tap twice for zoom control"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Couldn\'t add widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"Go"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (next alarm)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Until you turn this off"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Until you turn off Do Not Disturb"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Collapse"</string> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index a85976d14da6..50989b4f2b37 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Allows an application to read install sessions. This allows it to see details about active package installations."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"request install packages"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Allows an application to request installation of packages."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"ask to ignore battery optimisations"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Allows an app to ask for permission to ignore battery optimisations for that app."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Tap twice for zoom control"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Couldn\'t add widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"Go"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (next alarm)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Until you turn this off"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Until you turn off Do Not Disturb"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Collapse"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 19d00d09cb46..347a33138903 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Permite que una aplicación lea sesiones de instalación. Esto le permite ver detalles acerca de instalaciones de paquetes activas."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"solicitar la instalación de paquetes"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Permite que una aplicación solicite la instalación de paquetes."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"solicitar permiso para ignorar las optimizaciones de la batería"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Permite que una app solicite permiso para ignorar las optimizaciones de la batería."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Presiona dos veces para obtener el control del zoom"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"No se pudo agregar el widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"Ir"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Hasta la(s) <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Hasta la hora <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próxima alarma)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Hasta que lo desactives"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Hasta que desactives No molestar"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Contraer"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 487cf7432e97..0d624c718389 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Permite que una aplicación consulte sesiones de instalación para ver detalles sobre instalaciones de paquetes activos."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"solicitar instalación de paquetes"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Permite a una aplicación solicitar la instalación de paquetes."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"solicitar permiso para ignorar las optimizaciones de la batería"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Permite que una aplicación solicite permiso para ignorar las optimizaciones de la batería."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Da dos toques para acceder al control de zoom."</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"No se ha podido añadir el widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"Ir"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Hasta las <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Hasta las <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próxima alarma)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Hasta desactivar esta opción"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Hasta que desactives la opción No molestar"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Contraer"</string> diff --git a/core/res/res/values-et-rEE/strings.xml b/core/res/res/values-et-rEE/strings.xml index 7f46309bdaa1..a6c6cc9ad658 100644 --- a/core/res/res/values-et-rEE/strings.xml +++ b/core/res/res/values-et-rEE/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Lubab rakendusel lugeda installiseansse. See võimaldab näha aktiivse paketi installimise üksikasju."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"installipakettide taotlemine"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Võimaldab rakendusel pakettide installimist taotleda."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"küsida luba aku optimeerimise eiramiseks"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Lubab rakendusel küsida luba rakenduse aku optimeerimise eiramiseks."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Suumi kasutamiseks koputage kaks korda"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Vidinat ei saanud lisada."</string> <string name="ime_action_go" msgid="8320845651737369027">"Mine"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Kuni <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Kuni <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (järgmine äratus)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Kuni lülitate selle välja"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Kuni lülitate välja valiku Mitte segada"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Ahendamine"</string> diff --git a/core/res/res/values-eu-rES/strings.xml b/core/res/res/values-eu-rES/strings.xml index 2490b2142b98..c2f6d7a8520d 100644 --- a/core/res/res/values-eu-rES/strings.xml +++ b/core/res/res/values-eu-rES/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Instalazio-saioak irakurtzea baimentzen die aplikazioei. Horrela, pakete-instalazio aktiboei buruzko xehetasunak ikus ditzakete."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"Eskatu instalazio-paketeak"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Paketeak instalatzeko eskatzea baimentzen die aplikazioei."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"Eskatu bateriaren optimizazioei ez ikusi egitea"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Bateriaren optimizazioei ez ikusi egiteko baimena eskatzea baimentzen die aplikazioei."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Sakatu birritan zooma kontrolatzeko"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Ezin izan da widgeta gehitu."</string> <string name="ime_action_go" msgid="8320845651737369027">"Joan"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> arte"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> arte (hurrengo alarma)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Zuk desaktibatu arte"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"\"Ez molestatu\" desaktibatzen duzun arte"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Tolestu"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 63c0d2ab1d16..8585bff8cbef 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"به برنامه اجازه میدهد جلسات نصب را بخواند. این کار به برنامه اجازه میدهد جزئیات نصبهای بسته فعال را ببیند."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"درخواست نصب بسته"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"به برنامه اجازه میدهد درخواست نصب بستهبندی کند."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"درخواست نادیدهگرفتن بهینهسازی باتری"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"به یک برنامه اجازه میدهد جهت نادیده گرفتن بهینهسازی باتری برای خود مجوز درخواست کند."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"برای کنترل بزرگنمایی، دو بار ضربه بزنید"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"افزودن ابزارک انجام نشد."</string> <string name="ime_action_go" msgid="8320845651737369027">"برو"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"تا <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"تا <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (هشدار بعدی)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"تا وقتی آن را خاموش کنید"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"تا زمانی که «مزاحم نشوید» را خاموش کنید"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"کوچک کردن"</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 9e9d3d19468b..4b4631096aa4 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Sallii sovelluksen lukea asennusistuntoja. Toiminto sallii sovelluksen lukea aktiivisten asennuspakettien tietoja."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"pyytää asennuspaketteja"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Antaa sovelluksen pyytää pakettien asennusta."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"Lupa ohittaa akun optimoinnit"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Sallii sovelluksen pyytää lupaa ohittaa tietyn sovelluksen akun optimoinnit."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Hallitse zoomausta napauttamalla kahdesti"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Widgetin lisääminen epäonnistui."</string> <string name="ime_action_go" msgid="8320845651737369027">"Siirry"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Kunnes kello on <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> asti (seuraava hälytys)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Kunnes poistat tämän käytöstä"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Kunnes poistat Varattu-tilan käytöstä."</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Kutista"</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index 15aaf84685fd..d7a1bd6b7ba8 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Permet à une application d\'accéder aux sessions d\'installation. Cela lui permet de consulter les détails relatifs à l\'installation des paquets actifs."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"demander l\'installation de paquets"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Permet à une application de demander l\'installation de paquets."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"demander d\'ignorer les optimisations de la pile"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Permet à une application de demander la permission d\'ignorer les optimisations de la pile."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Appuyer deux fois pour régler le zoom"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Impossible d\'ajouter le widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"Aller"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (alarme suivante)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Jusqu\'à la désactivation"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Jusqu\'à ce que vous désactiviez le mode « Ne pas déranger »"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Réduire"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index b8fe1e0a8fd0..af291d4a83ae 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Permet à une application d\'accéder aux sessions d\'installation. Cela lui permet de consulter les détails relatifs à l\'installation des packages actifs."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"demander l\'installation de packages"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Permet à une application de demander l\'installation de packages."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"demander à ignorer les optimisations de batterie"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Autorise une application à demander l\'autorisation d\'ignorer les optimisations de batterie pour cette application."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Appuyer deux fois pour régler le zoom"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Impossible d\'ajouter le widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"OK"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (alarme suivante)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Jusqu\'à la désactivation"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Jusqu\'à ce que vous désactiviez la fonctionnalité \"Ne pas déranger\""</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Réduire"</string> diff --git a/core/res/res/values-gl-rES/strings.xml b/core/res/res/values-gl-rES/strings.xml index b3fbfe807a47..27acc354d70a 100644 --- a/core/res/res/values-gl-rES/strings.xml +++ b/core/res/res/values-gl-rES/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Permite que unha aplicación consulte as sesións de instalación. Desta forma, pode ver os detalles acerca das instalacións de paquetes activas."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"solicitar instalación de paquetes"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Permite a unha aplicación solicitar a instalación dos paquetes."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"pedir que se ignore a optimización da batería"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Fai que unha aplicación poida solicitar permiso para ignorar as optimizacións da batería."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Toca dúas veces para controlar o zoom"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Non se puido engadir o widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"Ir"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Ata as <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Ata as <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próxima alarma)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Ata que desactives isto"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Ata que desactives o modo Non molestar"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Contraer"</string> diff --git a/core/res/res/values-gu-rIN/strings.xml b/core/res/res/values-gu-rIN/strings.xml index d8578c910f0c..a36079fb0889 100644 --- a/core/res/res/values-gu-rIN/strings.xml +++ b/core/res/res/values-gu-rIN/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"એપ્લિકેશનને ઇન્સ્ટોલ સત્રોને વાંચવાની મંજૂરી આપે છે. આ તેને સક્રિય પૅકેજ ઇન્સ્ટોલેશન્સ વિશે વિગતો જોવાની મંજૂરી આપે છે."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"પૅકેજેસ ઇન્સ્ટૉલ કરવાની વિનંતી કરો"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"એપ્લિકેશનને પૅકેજેસના ઇન્સ્ટોલેશનની વિનંતી કરવાની મંજૂરી આપો."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"બૅટરી ઓપ્ટિમાઇઝેશન્સને અવગણવા માટે પૂછો"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"ઍપ્લિકેશનને તે ઍપ્લિકેશન માટે બૅટરી ઓપ્ટિમાઇઝેશન્સને અવગણવાની પરવાનગી આપવા માટે પૂછવાની મંજૂરી આપે છે."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"ઝૂમ નિયંત્રણ માટે બેવાર ટૅપ કરો"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"વિજેટ ઉમેરી શકાયું નથી."</string> <string name="ime_action_go" msgid="8320845651737369027">"જાઓ"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> સુધી"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (આગલા એલાર્મ) સુધી"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"તમે આ બંધ ન કરો ત્યાં સુધી"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"તમે ખલેલ પાડશો નહીં બંધ ન કરો ત્યાં સુધી"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"સંકુચિત કરો"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index c2f35f91b49c..f58b293be76f 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"ऐप्लिकेशन को इंस्टॉल सत्रों को पढ़ने देती है. इससे उसे सक्रिय पैकेज इंस्टॉलेशन के बारे में विवरण देखने की अनुमति मिल जाती है."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"पैकेज इंस्टॉल करने का अनुरोध करें"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"किसी ऐप्लिकेशन को पैकेज इंस्टॉल करने के अनुरोध की अनुमति देता है."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"बैटरी ऑप्टिमाइज़ेशन पर ध्यान ना देने के लिए पूछें"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"किसी ऐप्लिकेशन को उस ऐप्लिकेशन के लिए बैटरी ऑप्टिमाइज़ेशन पर ध्यान ना देने की अनुमति के लिए पूछने देता है."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"ज़ूम नियंत्रण के लिए दो बार टैप करें"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"विजेट नहीं जोड़ा जा सका."</string> <string name="ime_action_go" msgid="8320845651737369027">"जाएं"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> तक"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (अगले अलार्म) तक"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"जब तक आप इसे बंद नहीं कर देते"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"जब तक कि आप परेशान ना करें को बंद नहीं कर देते"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"संक्षिप्त करें"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 7106e358991b..a4fc7c4cad83 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1237,10 +1237,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Omogućuje aplikaciji čitanje sesija instaliranja. Aplikacija može vidjeti pojedinosti o aktivnim instaliranjima paketa."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"zahtijevati instaliranje paketa"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Aplikaciji omogućuje zahtijevanje instaliranja paketa."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"tražiti zanemarivanje optimizacija baterije"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Aplikaciji omogućuje da traži dopuštenje za zanemarivanje optimizacija baterije za tu aplikaciju."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Dvaput dotaknite za upravljanje zumiranjem"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Widget nije moguće dodati."</string> <string name="ime_action_go" msgid="8320845651737369027">"Idi"</string> @@ -1651,7 +1649,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (sljedeći alarm)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Dok ne isključite"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Dok ne isključite \"Ne uznemiravaj\""</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Sažmi"</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index 6acbcb69e2d2..0823d483e184 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Engedélyezi az alkalmazásnak a telepítési munkamenetek olvasását. Ezáltal részleteket kaphat az egyes csomagok éppen folyamatban lévő telepítéséről."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"telepítőcsomagok kérése"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Lehetővé teszi az alkalmazás számára csomagok telepítésének kérését."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"Akkumulátoroptimalizálási beállítások mellőzésének kérése"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Az alkalmazás engedélyt kérhet az akkumulátoroptimalizálási beállítások mellőzésére."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Érintse meg kétszer a nagyítás beállításához"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Nem sikerült hozzáadni a modult."</string> <string name="ime_action_go" msgid="8320845651737369027">"Ugrás"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Eddig: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Eddig: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ez a következő ébresztés)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Amíg ki nem kapcsolja ezt"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Amíg ki nem kapcsolja a „Ne zavarjanak” lehetőséget"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Összecsukás"</string> diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml index 85fb492dcd17..0f88ee2c27d6 100644 --- a/core/res/res/values-hy-rAM/strings.xml +++ b/core/res/res/values-hy-rAM/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Ծրագրին թույլ է տալիս կարդալ տեղադրման աշխատաշրջանները: Սա թույլ է տալիս տեղեկանալ փաթեթների ակտիվ տեղադրումների մանրամասներին:"</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"պահանջել տեղադրման փաթեթներ"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Թույլ է տալիս հավելվածին պահանջել փաթեթների տեղադրումը:"</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"հայցել մարտկոցի օպտիմալացումն անտեսելու թույլտվություն"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Հավելվածին հնարավորություն է տալիս հայցելու թույլտվություն՝ տվյալ հավելվածի համար մարտկոցի օպտիմալացումն անտեսելու համար:"</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Հպեք երկու անգամ` խոշորացման վերահսկման համար"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Չհաջողվեց վիջեթ ավելացնել:"</string> <string name="ime_action_go" msgid="8320845651737369027">"Առաջ"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Մինչև <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Մինչև ժ. <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>-ը (հաջորդ զարթուցիչը)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Քանի դեռ չեք անջատել"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Մինչև չանջատեք «Չանհանգստացնել» գործառույթը"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Թաքցնել"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 343191c0cf54..75907a99c19d 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Memungkinkan aplikasi membaca sesi pemasangan. Tindakan ini memungkinkannya melihat detail tentang pemasangan paket aktif."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"minta pasang paket"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Mengizinkan aplikasi meminta pemasangan paket."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"meminta mengabaikan pengoptimalan baterai"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Mengizinkan aplikasi meminta izin untuk mengabaikan pengoptimalan baterai bagi aplikasi tersebut."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Ketuk dua kali untuk kontrol perbesar/perkecil"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Tidak dapat menambahkan widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"Buka"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Hingga <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Hingga <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (alarm berikutnya)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Hingga Anda menonaktifkan ini"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Hingga Anda menonaktifkan status Jangan Ganggu"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Ciutkan"</string> diff --git a/core/res/res/values-is-rIS/strings.xml b/core/res/res/values-is-rIS/strings.xml index a8753013b8fb..4226a86aecad 100644 --- a/core/res/res/values-is-rIS/strings.xml +++ b/core/res/res/values-is-rIS/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Leyfir forriti að lesa uppsetningarlotur. Þetta gerir því kleift að sjá upplýsingar um virkar pakkauppsetningar."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"fara fram á uppsetningu pakka"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Leyfir forriti að fara fram á uppsetningu pakka."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"biðja um að hunsa rafhlöðusparnað"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Gerir forriti kleift að biðja um heimild til að hunsa rafhlöðusparnað fyrir forritið."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Ýttu tvisvar til að opna aðdráttarstýringar"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Ekki tókst að bæta græju við."</string> <string name="ime_action_go" msgid="8320845651737369027">"Áfram"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Til <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Þangað til <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (næsta viðvörun)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Þar til þú slekkur á þessu"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Þar til þú slekkur á „Ónáðið ekki“"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Minnka"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 7bae091feb84..e54ed1932166 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Consente a un\'applicazione di leggere le sessioni di installazione. L\'app può conoscere i dettagli sulle installazioni di pacchetti attive."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"richiesta di pacchetti di installazione"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Consente a un\'applicazione di richiedere l\'installazione di pacchetti."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"chiedi di ignorare le ottimizzazioni della batteria"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Consente a un\'app di chiedere l\'autorizzazione a ignorare le ottimizzazioni della batteria per quell\'app."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Tocca due volte per il comando dello zoom"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Aggiunta del widget non riuscita."</string> <string name="ime_action_go" msgid="8320845651737369027">"Vai"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Fino a <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Fino a <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (prossima sveglia)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Fino alla disattivazione"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Fino alla disattivazione di Non disturbare"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Comprimi"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index c79f88917068..d0d8e064c8cf 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1262,10 +1262,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"מאפשר לאפליקציה לקרוא הפעלות התקנה. הרשאה זו מאפשרת לה לראות פרטים על התקנות פעילות של חבילות."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"בקשה להתקנת חבילות"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"מתיר לאפליקציה לבקש התקנה של חבילות."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"בקשה להתעלם מאופטימיזציות של הסוללה"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"מאפשר לאפליקציה לבקש רשות להתעלם מאופטימיזציות של הסוללה לאפליקציה הזו."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"הקש פעמיים לבקרת מרחק מתצוגה"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"לא ניתן להוסיף widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"התחל"</string> @@ -1686,7 +1684,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"עד <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"עד <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ההתראה הבאה)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"עד שתכבה"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"עד שתכבה את \'נא לא להפריע\'"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"כווץ"</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index f2e927e4c279..cf8052ee9332 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"インストールセッションの読み取りをアプリに許可します。これにより、アプリはアクティブパッケージのインストールに関する詳細情報を参照できるようになります。"</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"インストールパッケージのリクエスト"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"パッケージのインストールをリクエストすることをアプリケーションに許可します。"</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"電池の最適化を無視するかどうかの確認"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"電池の最適化の無視についてアプリが確認することを許可します。"</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"ダブルタップでズームします"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"ウィジェットを追加できませんでした。"</string> <string name="ime_action_go" msgid="8320845651737369027">"移動"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>まで"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>(次のアラーム)まで"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"ユーザーがOFFにするまで"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"マナーモードを OFF にするまで"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"折りたたむ"</string> diff --git a/core/res/res/values-ka-rGE/strings.xml b/core/res/res/values-ka-rGE/strings.xml index dfdc182cace9..2b6ee7f8a859 100644 --- a/core/res/res/values-ka-rGE/strings.xml +++ b/core/res/res/values-ka-rGE/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"საშუალებას აძლევს აპლიკაციას წაიკითხოს ინსტალაციის სესიები. ამით მას საშუალება აქვს იხილოს პაკეტის აქტიური ინსტალაციები."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"პაკეტების ინსტალაციის მოთხოვნა"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"აპლიკაციას შეეძლება მოითხოვოს პაკეტების ინსტალაცია."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"ბატარეის ოპტიმიზაციის იგნორირების მოთხოვნა"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"საშუალებას მისცემს აპს, მოითხოვოს მასთან დაკავშირებული ბატარეის ოპტიმიზაციის იგნორირების ნებართვა."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"მასშტაბის ცვლილებისთვის შეეხეთ ორჯერ"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"ვერ დაემატა ვიჯეტი."</string> <string name="ime_action_go" msgid="8320845651737369027">"გადასვლა"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>-მდე"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>-მდე (შემდეგი მაღვიძარა)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"სანამ ამას გამორთავდეთ"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"სანამ გამორთავთ „არ შემაწუხოთ“ ფუნქციას"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"აკეცვა"</string> diff --git a/core/res/res/values-kk-rKZ/strings.xml b/core/res/res/values-kk-rKZ/strings.xml index bd4e171f0ce0..ad9060c9944b 100644 --- a/core/res/res/values-kk-rKZ/strings.xml +++ b/core/res/res/values-kk-rKZ/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Қолданбаға орнату сеанстарын оқуға рұқсат етеді. Бұл оған белсенді бума орнатулары туралы мәліметтерді көруге рұқсат етеді."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"орнату бумаларын сұрау"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Қолданбаның бумаларды орнатуға рұқсат сұрауына мүмкіндік береді."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"батареяны оңтайландыру әрекетін елемеуді сұрау"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Қолданба батареяны оңтайландыру әрекетін елемеуді сұрай алады."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Масштабтау параметрін басқару үшін екі рет түртіңіз"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Виджетті қосу."</string> <string name="ime_action_go" msgid="8320845651737369027">"Өту"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> дейін"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> дейін (келесі дабыл)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Сіз осыны өшіргенше"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Өшірмейінше мазаламау"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Тасалау"</string> diff --git a/core/res/res/values-km-rKH/strings.xml b/core/res/res/values-km-rKH/strings.xml index 49d3a64d70eb..e53164a01323 100644 --- a/core/res/res/values-km-rKH/strings.xml +++ b/core/res/res/values-km-rKH/strings.xml @@ -1214,10 +1214,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"ឲ្យកម្មវិធីអានសម័យដំឡើង។ វាអនុញ្ញាតឲ្យឃើញព័ត៌មានលម្អិតអំពីការដំឡើងកញ្ចប់សកម្ម។"</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"ស្នើសុំកញ្ចប់ដំឡើង"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"អនុញ្ញាតឲ្យកម្មវិធីស្នើសុំដំឡើងកញ្ចប់ (ឯកសារ/មាតិកា)។"</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"ស្នើឲ្យមិនអើពើចំពោះការបង្កើនប្រសិទ្ធភាពថ្ម"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"អនុញ្ញាតឲ្យកម្មវិធីស្នើសុំការអនុញ្ញាត ដើម្បីមិនអើពើចំពោះការបង្កើនប្រសិទ្ធភាពថ្ម។"</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"ប៉ះ ពីរដងដើម្បីពិនិត្យការពង្រីក"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"មិនអាចបន្ថែមធាតុក្រាហ្វិក។"</string> <string name="ime_action_go" msgid="8320845651737369027">"ទៅ"</string> @@ -1618,7 +1616,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"រហូតដល់ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"រហូតដល់ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ម៉ោងរោទិ៍បន្ទាប់)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"រហូតដល់ពេលអ្នកបិទវា"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"រហូតទាល់តែអ្នកបិទ កុំរំខាន"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"បង្រួម"</string> diff --git a/core/res/res/values-kn-rIN/strings.xml b/core/res/res/values-kn-rIN/strings.xml index bfd69d6a2986..534267200167 100644 --- a/core/res/res/values-kn-rIN/strings.xml +++ b/core/res/res/values-kn-rIN/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"ಸ್ಥಾಪಿತ ಸೆಷನ್ಗಳನ್ನು ಓದಲು ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಅನುಮತಿಸುತ್ತದೆ. ಸಕ್ರಿಯ ಪ್ಯಾಕೇಜ್ ಸ್ಥಾಪನೆಗಳ ಕುರಿತು ವಿವರಣೆಗಳನ್ನು ವೀಕ್ಷಿಸಲು ಇದು ಅನುಮತಿಸುತ್ತದೆ."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"ಸ್ಥಾಪನೆ ಪ್ಯಾಕೇಜ್ಗಳನ್ನು ವಿನಂತಿಸಿ"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"ಪ್ಯಾಕೇಜ್ಗಳ ಸ್ಥಾಪನೆಯನ್ನು ವಿನಂತಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"ಬ್ಯಾಟರಿ ಆಪ್ಟಿಮೈಸೇಶನ್ಗಳನ್ನು ಕಡೆಗಣಿಸಲು ಕೇಳಿ"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"ಈ ಅಪ್ಲಿಕೇಶನ್ಗೆ ಬ್ಯಾಟರಿ ಆಪ್ಟಿಮೈಸೇಶನ್ಗಳನ್ನು ಕಡೆಗಣಿಸುವುದಕ್ಕೆ ಅನುಮತಿಯನ್ನು ಕೇಳಲು ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಅನುಮತಿಸುತ್ತದೆ."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"ಝೂಮ್ ನಿಯಂತ್ರಿಸಲು ಎರಡು ಬಾರಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"ವಿಜೆಟ್ ಸೇರಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ."</string> <string name="ime_action_go" msgid="8320845651737369027">"ಹೋಗು"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ವರೆಗೆ"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ವರೆಗೆ (ಮುಂದಿನ ಅಲಾರಮ್)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"ನೀವಿದನ್ನು ಆಫ್ ಮಾಡುವವರೆಗೆ"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"ನೀವು ಆಫ್ ಮಾಡುವವರೆಗೂ ಅಡಚಣೆ ಮಾಡಬೇಡಿ"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"ಸಂಕುಚಿಸು"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index e81de032e1f6..11f26eb29c28 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -248,9 +248,9 @@ <string name="permgrouplab_location" msgid="7275582855722310164">"위치"</string> <string name="permgroupdesc_location" msgid="1346617465127855033">"이 기기의 위치정보에 액세스"</string> <string name="permgrouplab_calendar" msgid="5863508437783683902">"캘린더"</string> - <string name="permgroupdesc_calendar" msgid="3889615280211184106">"일정에 접근할 수 있도록"</string> + <string name="permgroupdesc_calendar" msgid="3889615280211184106">"캘린더에 액세스"</string> <string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string> - <string name="permgroupdesc_sms" msgid="4656988620100940350">"문자 메시지를 보내고 확인할 수 있도록"</string> + <string name="permgroupdesc_sms" msgid="4656988620100940350">"SMS 메시지 전송 및 보기"</string> <string name="permgrouplab_storage" msgid="1971118770546336966">"저장"</string> <string name="permgroupdesc_storage" msgid="637758554581589203">"기기 사진, 미디어, 파일 액세스"</string> <string name="permgrouplab_microphone" msgid="171539900250043464">"마이크"</string> @@ -258,7 +258,7 @@ <string name="permgrouplab_camera" msgid="4820372495894586615">"카메라"</string> <string name="permgroupdesc_camera" msgid="3250611594678347720">"사진 및 동영상 촬영"</string> <string name="permgrouplab_phone" msgid="5229115638567440675">"전화"</string> - <string name="permgroupdesc_phone" msgid="6234224354060641055">"통화 상태를 관리하거나 전화를 걸 수 있도록"</string> + <string name="permgroupdesc_phone" msgid="6234224354060641055">"전화 걸기 및 관리"</string> <string name="permgrouplab_sensors" msgid="416037179223226722">"신체 센서"</string> <string name="permgroupdesc_sensors" msgid="7147968539346634043">"생체 신호에 관한 센서 데이터에 액세스"</string> <string name="capability_title_canRetrieveWindowContent" msgid="3901717936930170320">"창 콘텐츠 가져오기"</string> @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"애플리케이션의 설치 세션 읽기를 허용하면, 활성 패키지 설치에 대한 세부 정보를 볼 수 있습니다."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"패키지 설치 요청"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"애플리케이션이 패키지 설치를 요청하도록 허용합니다."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"배터리 최적화를 무시하도록 요청"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"앱에서 배터리 최적화를 무시할 수 있는 권한을 요청할 수 있도록 허용합니다."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"확대/축소하려면 두 번 탭하세요."</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"위젯을 추가할 수 없습니다."</string> <string name="ime_action_go" msgid="8320845651737369027">"이동"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>까지"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>(다음 알람)까지"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"이 기능을 사용 중지할 때까지"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"알림 일시중지 기능을 사용 중지할 때까지"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"접기"</string> diff --git a/core/res/res/values-ky-rKG/strings.xml b/core/res/res/values-ky-rKG/strings.xml index d4fa9d8e2bdd..86bcef323b2e 100644 --- a/core/res/res/values-ky-rKG/strings.xml +++ b/core/res/res/values-ky-rKG/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Колдонмого орнотуу сеанстарын окуу мүмкүнчүлүгүн берет. Ушуну менен, ал жигердүү топтом орнотууларынын чоо-жайын көрө алат."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"орнотуу топтомдорун суроо"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Колдонмо топтомдорду орнотууга уруксат сурай алат."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"батареянын кубатын көп керектей берсин"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Колдонмо батареянын кубатын керектегенден мурун уруксат суралсын."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Масштабдын параметрлерин өзгөртүү үчүн бул жерди эки жолу басыңыз."</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Виджетти кошуу мүмкүн болбоду."</string> <string name="ime_action_go" msgid="8320845651737369027">"Өтүү"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> чейин"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> чейин (кийинки ойготкуч)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Бул өчүрүлгөнгө чейин"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"\"Тынчымды алба\" режими өчүрүлгөнгө чейин"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Жыйнап коюу"</string> diff --git a/core/res/res/values-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml index 7e0f79cac3a7..9e15571ac1ea 100644 --- a/core/res/res/values-lo-rLA/strings.xml +++ b/core/res/res/values-lo-rLA/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນອ່ານເຊດຊັນການຕິດຕັ້ງໄດ້. ນີ້ຈະອະນຸຍາດໃຫ້ມັນເບິ່ງເຫັນລາຍລະອຽດກ່ຽວກັບການຕິດຕັ້ງແພັກເກດທີ່ເຮັດວຽກຢູ່ໄດ້."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"ຂໍຕິດຕັ້ງແພັກເກດ"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນຂອງການຕິດຕັ້ງແພັກເກດ."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"ຖາມເພື່ອໃຫ້ເພີກເສີຍການປັບແຕ່ງແບັດເຕີຣີ"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"ອະນຸຍາດໃຫ້ແອັບຖາມສິດອະນຸຍາດເພື່ອເພີກເສີຍຕໍ່ການປັບແຕ່ງແບັດເຕີຣີສຳລັບແອັບນັ້ນ."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"ແຕະສອງເທື່ອເພື່ອຄວບຄຸມການຊູມ"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"ບໍ່ສາມາດເພີ່ມວິດເຈັດໄດ້."</string> <string name="ime_action_go" msgid="8320845651737369027">"ໄປ"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"ຈົນຮອດ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"ຈົນກ່ວາ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ສັນຍານເຕືອນຕໍ່ໄປ)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"ຈົນກວ່າທ່ານຈະປິດ"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"ຈົນກ່ວາທ່ານປິດຫ້າມລົບກວນ"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"ຫຍໍ້"</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 310057099413..3591ea406b11 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -1262,10 +1262,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Leidžiama programai skaityti diegimo seansus. Leidžiama peržiūrėti išsamią aktyvių paketų diegimo informaciją."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"pateikti užklausą dėl diegimo paketų"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Programai leidžiama pateikti užklausą dėl paketų diegimo."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"prašyti nepaisyti akumuliatoriaus optimizavimo nustatymų"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Programai leidžiama prašyti leidimo nepaisyti tai programai skirto akumuliatoriaus optimizavimo nustatymų."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Bakstelėkite du kartus, kad valdytumėte mastelio keitimą"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Nepavyko pridėti."</string> <string name="ime_action_go" msgid="8320845651737369027">"Pradėti"</string> @@ -1686,7 +1684,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Iki <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Iki <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (kitas signalas)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Kol išjungsite"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Kol neišjungsite režimo „Netrukdyti“"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Sutraukti"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 5393e493834e..2f2399bdeef7 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -1237,10 +1237,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Ļauj lietojumprogrammai lasīt instalēšanas sesijas. Tādējādi lietojumprogrammai ir pieejama informācija par aktīvajām pakotņu instalācijām."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"Pieprasīt pakotņu instalēšanu"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Ļauj lietojumprogrammai pieprasīt pakotņu instalēšanu."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"Lūgt akumulatora optimizācijas ignorēšanu"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Ļauj lietotnei lūgt atļauju ignorēt akumulatora optimizāciju šai lietotnei."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Pieskarieties divreiz, lai kontrolētu tālummaiņu."</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Nevarēja pievienot logrīku."</string> <string name="ime_action_go" msgid="8320845651737369027">"Doties uz"</string> @@ -1651,7 +1649,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Līdz <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Līdz plkst. <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (nākamais signāls)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Līdz brīdim, kad izslēgsiet"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Līdz izslēgsiet statusu “Netraucēt”"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Sakļaut"</string> diff --git a/core/res/res/values-mk-rMK/strings.xml b/core/res/res/values-mk-rMK/strings.xml index f0c9a8d391a6..2a4cd9d7a619 100644 --- a/core/res/res/values-mk-rMK/strings.xml +++ b/core/res/res/values-mk-rMK/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Дозволува апликација да чита сесии на инсталирање. Тоа овозможува апликацијата да гледа детали за активни инсталации на пакет."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"барање пакети за инсталирање"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Дозволува апликацијата да бара инсталација на пакети."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"прашај дали да се игнорираат оптимизациите на батеријата"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Овозможува апликацијата да побара дозвола за игнорирање на оптимизациите на батеријата за таа апликација."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Допрете двапати за контрола на зумот"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Не можеше да се додаде виџет."</string> <string name="ime_action_go" msgid="8320845651737369027">"Оди"</string> @@ -1618,7 +1616,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (следниот аларм)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Додека не го исклучите"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Додека не го исклучите Не вознемирувај"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Собери"</string> diff --git a/core/res/res/values-ml-rIN/strings.xml b/core/res/res/values-ml-rIN/strings.xml index a1e7214444e3..0c7eff1cd262 100644 --- a/core/res/res/values-ml-rIN/strings.xml +++ b/core/res/res/values-ml-rIN/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"ഇൻസ്റ്റാൾ ചെയ്ത സെഷനുകൾ റീഡുചെയ്യുന്നതിന് ഒരു അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു. സജീവ പാക്കേജ് ഇൻസ്റ്റാളേഷനുകളെക്കുറിച്ചുള്ള വിശദാംശങ്ങൾ കാണുന്നതിന് ഇത് അനുവദിക്കുന്നു."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"പാക്കേജുകൾ ഇൻസ്റ്റാൾ ചെയ്യാൻ അഭ്യർത്ഥിക്കുക"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"പാക്കേജുകളുടെ ഇൻസ്റ്റാളേഷൻ അഭ്യർത്ഥിക്കാൻ ഒരു അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"ബാറ്ററി ഒപ്റ്റിമൈസേഷനുകൾ അവഗണിക്കാൻ ആവശ്യപ്പെടുക"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"ആപ്പിന് വേണ്ടിയുള്ള ബാറ്ററി ഒപ്റ്റിമൈസേഷനുകളെ അവഗണിക്കാനുള്ള അനുമതി ചോദിക്കുന്നതിന് ആപ്പിനെ അനുവദിക്കുന്നു."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"സൂം നിയന്ത്രണം ലഭിക്കാൻ രണ്ടുതവണ ടാപ്പുചെയ്യുക"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"വിജറ്റ് ചേർക്കാനായില്ല."</string> <string name="ime_action_go" msgid="8320845651737369027">"പോവുക"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> വരെ"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> വരെ (അടുത്ത അലാറം)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"നിങ്ങൾ ഇത് ഓഫാക്കും വരെ"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"\'ശല്ല്യപ്പെടുത്തരുത്\' ഓഫാക്കുന്നതുവരെ"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"ചുരുക്കുക"</string> diff --git a/core/res/res/values-mn-rMN/strings.xml b/core/res/res/values-mn-rMN/strings.xml index 3f9618c224c6..73a8be0c4a83 100644 --- a/core/res/res/values-mn-rMN/strings.xml +++ b/core/res/res/values-mn-rMN/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Аппликешн-д суулгах сешн уншихыг зөвшөөрнө. Энэ нь идэвхтэй багцуудыг суулгалтын талаар дэлгэрэнгүй мэдээллийг үзэх боломж олгоно."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"багц суулгахыг хүсэх"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Аппликейшн нь багц суулгахыг хүсэх боломжтой."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"батерейны оновчлол алгасахыг асуух"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Тухайн аппaaс батерейны оновчлол алгасах зөвшөөрөл асуухыг зөвшөөрдөг."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Өсгөх контрол дээр хоёр удаа товшино уу"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Виджет нэмж чадсангүй."</string> <string name="ime_action_go" msgid="8320845651737369027">"Очих"</string> @@ -1614,7 +1612,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> хүртэл"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> хүртэл (дараагийн сэрүүлэг)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Таныг унтраах хүртэл"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"\"Бүү саад бол\"-ыг унтраах хүртэл"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Хумих"</string> diff --git a/core/res/res/values-mr-rIN/strings.xml b/core/res/res/values-mr-rIN/strings.xml index d5487e157cbd..3228337e0b4e 100644 --- a/core/res/res/values-mr-rIN/strings.xml +++ b/core/res/res/values-mr-rIN/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"अनुप्रयोगास स्थापना सत्र वाचण्याची अनुमती देते. हे सक्रिय पॅकेज स्थापनांविषयी तपशील पाहाण्याची यास अनुमती देते."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"पॅकेज स्थापित करण्यासाठी विनंती करा"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"पॅकेजच्या स्थापना करण्यासाठी अनुप्रयोगास अनुमती देते."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"बॅटरी ऑप्टिमायझेशन दुर्लक्षित करण्यास सांगा"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"त्या अॅपसाठी बॅटरी ऑप्टिमायझेशन दुर्लक्षित करण्यासाठी अॅपला परवानगी मागण्याची अनुमती देते."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"झूम नियंत्रणासाठी दोनदा टॅप करा"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"विजेट जोडू शकलो नाही."</string> <string name="ime_action_go" msgid="8320845651737369027">"जा"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> पर्यंत"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> पर्यंत (पुढील अलार्म)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"आपण हे बंद करेपर्यंत"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"आपण बंद करेपर्यंत व्यत्यय आणू नका"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"संक्षिप्त करा"</string> diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml index 1ef64935f64d..bd28553f9254 100644 --- a/core/res/res/values-ms-rMY/strings.xml +++ b/core/res/res/values-ms-rMY/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Membenarkan aplikasi membaca sesi pemasangan Ini membenarkan apl melihat butiran mengenai pemasangan pakej yang aktif."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"minta pakej pemasangan"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Membenarkan aplikasi meminta pemasangan pakej."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"minta kebenaran untuk mengabaikan pengoptimuman bateri"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Membenarkan apl meminta kebenaran untuk mengabaikan pengoptimuman bateri untuk apl itu."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Ketik dua kali untuk mendapatkan kawalan zum"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Tidak dapat menambahkan widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"Pergi"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Sehingga <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Sehingga <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (penggera akan datang)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Sehingga anda matikan"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Hingga anda mematikan Jangan Ganggu"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Runtuhkan"</string> diff --git a/core/res/res/values-my-rMM/strings.xml b/core/res/res/values-my-rMM/strings.xml index 2117c560c881..489a85a65088 100644 --- a/core/res/res/values-my-rMM/strings.xml +++ b/core/res/res/values-my-rMM/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"အပလီကေးရှင်းအား တပ်ဆင်ရေး ချိတ်ဆက်မှုများကို ဖတ်ခွင့်ပြုသည်။ ၎င်းသည် ဖွင့်သုံးနေသည့် အထုပ်အား တပ်ဆင်မှုဆိုင်ရာ အသေးိစတ်များကို ကြည့်ရှုခွင့် ပြုသည်။"</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"တပ်ဆင်ရေး အထုပ်များကို တောင်းဆိုပါ"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"ပက်ကေ့များ သွင်းယူခြင်းအတွက် တောင်းဆိုရန် အပလီကေးရှင်းအား ခွင့်ပြုပါ"</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"ဘက်ထရီ ပိုမိုကောင်းမွန်အောင် ပြုလုပ်ခြင်းကို လျစ်လျူရှုရန် တောင်းဆိုပါ"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"ဘက်ထရီ ပိုမိုကောင်းမွန်အောင် ပြုလုပ်ခြင်းကို လျစ်လျူရှုရန်အတွက် ခွင့်ပြုချက်တောင်းရန် အက်ပ်ကို ခွင့်ပြုပါ။"</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"ဇူးမ်အသုံးပြုရန် နှစ်ချက်တို့ပါ"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"ဝဒ်ဂျက်ထည့်လို့ မရပါ"</string> <string name="ime_action_go" msgid="8320845651737369027">"သွားပါ"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>အထိ"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> အထိ (လာမည့် နှိုးစက်)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"သင်က ဒါကို ပိတ်မပစ်သည့် အထိ"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"မနှောင့်ယှက်ရန် ကိုသင်ပိတ်သည်အထိ"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"ခေါက်ရန်"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index f2b11c92cc8b..34909e172554 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Tillater en app å lese installeringsøkter. Dette gjør det mulig for den å se detaljer om aktive pakkeinstallasjoner."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"be om installasjon av pakker"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Lar apper be om installasjon av pakker."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"be om å ignorere batterioptimaliseringer"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Gjør det mulig for apper å be om tillatelse til å ignorere batterioptimaliseringer for disse appene."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Trykk to ganger for zoomkontroll"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Kunne ikke legge til modulen."</string> <string name="ime_action_go" msgid="8320845651737369027">"Utfør"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Til <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Til <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (neste alarm)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Inntil du slår av funksjonen"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Inntil du slår av Ikke forstyrr"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Skjul"</string> diff --git a/core/res/res/values-ne-rNP/strings.xml b/core/res/res/values-ne-rNP/strings.xml index 15f78793768b..5df851f724d9 100644 --- a/core/res/res/values-ne-rNP/strings.xml +++ b/core/res/res/values-ne-rNP/strings.xml @@ -1218,10 +1218,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"स्थापित सत्र पढ्न अनुप्रयोगलाई अनुमति दिनुहोस्। यसले सक्रिय प्याकेज प्रतिष्ठानहरू बारेमा विवरण हेर्ने अनुमति दिन्छ।"</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"स्थापना प्याकेजहरू अनुरोध गर्नुहोस्"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"प्याकेजहरूको स्थापना अनुरोध गर्न अनुप्रयोगलाई अनुमति दिन्छ।"</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"ब्याट्री सम्बन्धी अनुकूलनहरूलाई बेवास्ता गर्न सोध्नुहोस्"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"कुनै अनुप्रयोगलाई त्यसका ब्याट्री सम्बन्धी अनुकूलनहरूलाई बेवास्ता गर्नका लागि अनुमति माग्न दिन्छ।"</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"जुम नियन्त्रणको लागि दुई चोटि ट्याप गर्नुहोस्"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"विजेट थप गर्न सकिँदैन।"</string> <string name="ime_action_go" msgid="8320845651737369027">"जानुहोस्"</string> @@ -1622,7 +1620,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> सम्म"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (अर्को अलार्म) सम्म"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"तपाईँले यसलाई बन्द नगरेसम्म"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"तपाईँले बन्द नगरे सम्म बाधा नपुर्याउँनुहोस्"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"संक्षिप्त पार्नुहोस्"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 9112a8caa9b2..caa8058020cb 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Hiermee wordt een app toegestaan installatiesessies te lezen. Zo kan de app informatie bekijken over actieve pakketinstallaties."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"installatiepakketten aanvragen"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Hiermee kan een app installatie van pakketten aanvragen."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"vragen om batterijoptimalisatie te negeren"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Hiermee kan een app toestemming vragen om batterijoptimalisatie voor die app te negeren."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Tik twee keer voor zoomregeling"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Kan widget niet toevoegen."</string> <string name="ime_action_go" msgid="8320845651737369027">"Ga"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (volgend alarm)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Totdat u dit uitschakelt"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Totdat u \'Niet storen\' uitschakelt"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Samenvouwen"</string> diff --git a/core/res/res/values-pa-rIN/strings.xml b/core/res/res/values-pa-rIN/strings.xml index a3628cc39406..e88aa87a28dc 100644 --- a/core/res/res/values-pa-rIN/strings.xml +++ b/core/res/res/values-pa-rIN/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"ਇੱਕ ਐਪਲੀਕੇਸ਼ਨ ਨੂੰ ਇੰਸਟੌਲ ਸੈਸ਼ਨ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਇਸਨੂੰ ਸਕਿਰਿਆ ਪੈਕੇਜ ਇੰਸਟੌਲੇਸ਼ਨਾਂ ਬਾਰੇ ਵੇਰਵੇ ਦੇਖਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"ਪੈਕੇਜ ਸਥਾਪਿਤ ਕਰਨ ਦੀ ਬੇਨਤੀ ਕਰੋ"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"ਪੈਕੇਜ ਦੀ ਸਥਾਪਨਾ ਦੀ ਬੇਨਤੀ ਕਰਨ ਲਈ ਐਪਲੀਕੇਸ਼ਨ ਨੂੰ ਅਨੁਮਤੀ ਦਿੰਦਾ ਹੈ"</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"ਬੈਟਰੀ ਸੁਯੋਗਤਾਵਾਂ ਨੂੰ ਅਣਡਿੱਠ ਕਰਨ ਲਈ ਪੁੱਛੋ"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"ਕਿਸੇ ਐਪ ਨੂੰ ਉਸ ਵਾਸਤੇ ਬੈਟਰੀ ਸੁਯੋਗਤਾਵਾਂ ਨੂੰ ਅਣਡਿੱਠ ਕਰਨ ਲਈ ਇਜਾਜ਼ਤ ਵਾਸਤੇ ਪੁੱਛਣ ਲਈ ਆਗਿਆ ਦਿੰਦੀ ਹੈ।"</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"ਜ਼ੂਮ ਕੰਟਰੋਲ ਲਈ ਦੋ ਵਾਰ ਟੈਪ ਕਰੋ"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"ਵਿਜੇਟ ਨਹੀਂ ਜੋੜ ਸਕਿਆ।"</string> <string name="ime_action_go" msgid="8320845651737369027">"ਜਾਓ"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ਤੱਕ"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ਤੱਕ (ਅਗਲਾ ਅਲਾਰਮ)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਇਸਨੂੰ ਬੰਦ ਨਹੀਂ ਕਰਦੇ ਹੋ"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਪਰੇਸ਼ਾਨ ਨਾ ਕਰੋ ਨੂੰ ਬੰਦ ਨਹੀਂ ਕਰਦੇ ਹੋ"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"ਨਸ਼ਟ ਕਰੋ"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index bdd601a5954e..13ea8d366890 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -1262,10 +1262,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Pozwala aplikacji odczytywać sesje instalacji. Umożliwia to jej na poznanie szczegółów aktywnych instalacji pakietów."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"żądanie instalacji pakietów"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Zezwala aplikacji żądanie instalacji pakietów."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"Prośba o ignorowanie optymalizacji wykorzystania baterii"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Zezwala aplikacji na proszenie o uprawnienia do ignorowania optymalizacji wykorzystania baterii w przypadku danej aplikacji."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Dotknij dwukrotnie, aby sterować powiększeniem"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Nie można dodać widżetu."</string> <string name="ime_action_go" msgid="8320845651737369027">"OK"</string> @@ -1686,7 +1684,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (następny alarm)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Dopóki nie wyłączysz"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Do wyłączenia Nie przeszkadzać"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Zwiń"</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index 40bb94a1ee30..c0d53f294e6d 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Permite que um app leia sessões de instalação. Isso permite que ele veja detalhes sobre as instalações de pacote ativas."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"solicitar pacotes de instalação"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Permite que um app solicite a instalação de pacotes."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"solicitar que as otimizações de bateria sejam ignoradas"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Permite que um app peça permissão para ignorar as otimizações de bateria para esse app."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Toque duas vezes para ter controle do zoom"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Não foi possível adicionar widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"Ir"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Até às <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Até <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próximo alarme)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Até você desativar"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Até que você desative \"Não perturbe\""</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Recolher"</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 6fdaa19f4653..f6dcb87e8a80 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Permite que uma aplicação leia sessões de instalação. Isto permite que veja detalhes acerca de instalações de pacotes ativas."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"solicitar pacotes de instalação"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Permite que uma aplicação solicite a instalação de pacotes."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"pedir para ignorar as otimizações da bateria"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Permite que uma aplicação solicite autorização para ignorar as otimizações da bateria para a mesma."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Tocar duas vezes para controlar o zoom"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Não foi possível adicionar widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"Ir"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Até às <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Até <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próximo alarme)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Até que o utilizador desative"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Até desativar Não incomodar"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Reduzir"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 40bb94a1ee30..c0d53f294e6d 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Permite que um app leia sessões de instalação. Isso permite que ele veja detalhes sobre as instalações de pacote ativas."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"solicitar pacotes de instalação"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Permite que um app solicite a instalação de pacotes."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"solicitar que as otimizações de bateria sejam ignoradas"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Permite que um app peça permissão para ignorar as otimizações de bateria para esse app."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Toque duas vezes para ter controle do zoom"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Não foi possível adicionar widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"Ir"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Até às <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Até <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próximo alarme)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Até você desativar"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Até que você desative \"Não perturbe\""</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Recolher"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index e732022b9948..05025c37e2c9 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -1237,10 +1237,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Permite unei aplicații accesul la citirea sesiunilor de instalare. Aceasta poate vedea detalii despre instalările de pachete active."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"să solicite pachete de instalare"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Permite unei aplicații să solicite instalarea pachetelor."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"să solicite ignorarea optimizărilor bateriei"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Permite unei aplicații să solicite permisiunea de a ignora optimizările bateriei pentru aplicația respectivă."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Apăsați de două ori pentru a controla mărirea/micșorarea"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Nu s-a putut adăuga widgetul."</string> <string name="ime_action_go" msgid="8320845651737369027">"Accesați"</string> @@ -1651,7 +1649,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Până la <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Până la <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (următoarea alarmă)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Până la dezactivare"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Până când dezactivați „Nu deranja”"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Restrângeți"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 9c45d89156c6..f83a8787f1aa 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -1262,10 +1262,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Чтение данных текущих сеансов установки пакетов."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"Запрос пакетов установки"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Приложение сможет запрашивать разрешения на установку пакетов."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"Без ограничения расхода батареи"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Разрешает приложению игнорировать ограничение на расход заряда батареи."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Нажмите дважды для изменения масштаба"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Не удалось добавить виджет."</string> <string name="ime_action_go" msgid="8320845651737369027">"Выбрать"</string> @@ -1686,7 +1684,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (будильник)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Пока я не отключу"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Пока вы не отключите режим \"Не беспокоить\""</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Свернуть"</string> diff --git a/core/res/res/values-si-rLK/strings.xml b/core/res/res/values-si-rLK/strings.xml index 87a277b63354..5911e5eff2bb 100644 --- a/core/res/res/values-si-rLK/strings.xml +++ b/core/res/res/values-si-rLK/strings.xml @@ -269,8 +269,8 @@ <string name="capability_desc_canRequestEnhancedWebAccessibility" msgid="7881063961507511765">"යෙදුම් අන්තර්ගතයට ප්රවේශ්යතාවය වැඩිවන ලෙස සකස් කිරීමට ඇතැම් විට ස්ක්රිප්ට් ස්ථාපනය කර ඇත."</string> <string name="capability_title_canRequestFilterKeyEvents" msgid="2103440391902412174">"ඔබ ටයිප් කළ පෙළ බලන්න"</string> <string name="capability_desc_canRequestFilterKeyEvents" msgid="7463135292204152818">"ණයවරපත් අංක සහ මුරපද වැනි පුද්ගලික දත්ත ඇතුළත් වේ."</string> - <string name="capability_title_canControlMagnification" msgid="3593493281059424855">"සංදර්ශන විශාලන මට්ටම පාලනය කිරීම"</string> - <string name="capability_desc_canControlMagnification" msgid="4791858203568383773">"සංදර්ශනයේ විශාලන මට්ටම සහ පිහිටීම පාලනය කිරීම."</string> + <string name="capability_title_canControlMagnification" msgid="3593493281059424855">"සංදර්ශනයේ විශාලනය පාලනය කරන්න"</string> + <string name="capability_desc_canControlMagnification" msgid="4791858203568383773">"සංදර්ශනයේ විශාලන මට්ටම සහ පිහිටීම පාලනය කරන්න."</string> <string name="capability_title_canPerformGestures" msgid="7418984730362576862">"අභින සිදු කරන්න"</string> <string name="capability_desc_canPerformGestures" msgid="8296373021636981249">"තට්ටු කිරීමට, ස්වයිප් කිරීමට, පින්ච් කිරීමට, සහ වෙනත් අභින සිදු කිරීමට හැකිය."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"තත්ව තීරුව අබල කරන්න හෝ වෙනස් කරන්න"</string> @@ -459,13 +459,13 @@ <string name="permlab_readSyncSettings" msgid="6201810008230503052">"සමමුහුර්ත සැකසීම් කියවන්න"</string> <string name="permdesc_readSyncSettings" msgid="2706745674569678644">"ගිණුම සඳහා සමමුහුර්ත සැකසීම් කියවීමට යෙදුමට අවසර දෙන්න. උදාහරණයක් ලෙස, ගිණුමක් සමඟ පුද්ගල යෙදුම සමමුහුර්ත දැයි මෙයට හඳුනා ගත හැක."</string> <string name="permlab_writeSyncSettings" msgid="5408694875793945314">"සමමුහුර්ත කිරීම සක්රිය කරන්න සහ අක්රිය කරන්න"</string> - <string name="permdesc_writeSyncSettings" msgid="8956262591306369868">"ගිණුම සඳහා සමමුහුර්ත සැකසීම් වෙනස් කිරීමට යෙදුමට අවසර දෙන්න. උදාහරණයක් ලෙස, ගිණුම සමඟ පුද්ගල යෙදුම සමමුහුර්ත කිරීම සක්රිය කිරීමට භාවිත කල හැක."</string> + <string name="permdesc_writeSyncSettings" msgid="8956262591306369868">"ගිණුමක් සඳහා සමමුහුර්ත සැකසීම් විකරණය කිරීමට යෙදුමකට ඉඩ දෙයි. උදාහරණයක් ලෙස, ගිණුමක් සමඟ පුද්ගල යෙදුම සමමුහුර්ත කිරීම සබල කිරීමට මෙය භාවිත කළ හැක."</string> <string name="permlab_readSyncStats" msgid="7396577451360202448">"සමමුහුර්ත කිරීමේ සංඛ්යාන කියවීම"</string> <string name="permdesc_readSyncStats" msgid="1510143761757606156">"සමමුහුර්ත කිරීමේ සිදුවීම් ඉතිහාසය සහ කෙතරම් දත්ත සමමුහුර්ත වී ඇතිදැයි ඇතුලත් ගිණුම සඳහා සමමුහුර්ත කිරීමේ සංඛ්යාන කියවීමට යෙදුමට අවසර දෙන්න."</string> <string name="permlab_sdcardRead" product="nosdcard" msgid="367275095159405468">"ඔබගේ USB ආචයනය හි අන්තර්ගතය කියවන්න"</string> <string name="permlab_sdcardRead" product="default" msgid="2188156462934977940">"ඔබගේ SD කාඩ් පතෙහි අන්තර්ගතය කියවන්න"</string> <string name="permdesc_sdcardRead" product="nosdcard" msgid="3446988712598386079">"යෙදුමට ඔබගේ USB ආචයනය අන්තර්ගතය කියවීමට අවසර දෙන්න."</string> - <string name="permdesc_sdcardRead" product="default" msgid="2607362473654975411">"යෙදුමට ඔබගේ SD කාඩ් පතින් අන්තර්ගත කියවීමට අවසර දෙන්න."</string> + <string name="permdesc_sdcardRead" product="default" msgid="2607362473654975411">"යෙදුමට ඔබගේ SD කාඩ්පතේ අන්තර්ගතය කියවීමට ඉඩ දෙයි."</string> <string name="permlab_sdcardWrite" product="nosdcard" msgid="8485979062254666748">"ඔබගේ USB ආචයනයේ අන්තර්ගත වෙනස් කිරීම හෝ මැකීම"</string> <string name="permlab_sdcardWrite" product="default" msgid="8805693630050458763">"ඔබගේ SD පතේ අන්තර්ගත වෙනස් කිරීම හෝ මැකීම"</string> <string name="permdesc_sdcardWrite" product="nosdcard" msgid="6175406299445710888">"USB ආචයනය වෙත ලිවීමට යෙදුමට අවසර දෙන්න."</string> @@ -739,7 +739,7 @@ <!-- String.format failed for translation --> <!-- no translation found for keyguard_accessibility_widget_changed (5678624624681400191) --> <skip /> - <string name="keyguard_accessibility_add_widget" msgid="8273277058724924654">"විජටය එකතු කරන්න."</string> + <string name="keyguard_accessibility_add_widget" msgid="8273277058724924654">"විජටය එක් කරන්න."</string> <string name="keyguard_accessibility_widget_empty_slot" msgid="1281505703307930757">"හිස්"</string> <string name="keyguard_accessibility_unlock_area_expanded" msgid="2278106022311170299">"අගුළු අරින ප්රදේශය විදහා ඇත."</string> <string name="keyguard_accessibility_unlock_area_collapsed" msgid="6366992066936076396">"අගුළු අරින ප්රදේශය හැකිලී ඇත."</string> @@ -952,7 +952,7 @@ <string name="undo" msgid="7905788502491742328">"අස් කරන්න"</string> <string name="redo" msgid="7759464876566803888">"යළි කරන්න"</string> <string name="textSelectionCABTitle" msgid="5236850394370820357">"පෙළ තේරීම"</string> - <string name="addToDictionary" msgid="4352161534510057874">"ශබ්ද කෝෂයට එකතු කරන්න"</string> + <string name="addToDictionary" msgid="4352161534510057874">"ශබ්ද කෝෂයට එක් කරන්න"</string> <string name="deleteText" msgid="6979668428458199034">"මකන්න"</string> <string name="inputMethod" msgid="1653630062304567879">"ආදාන ක්රමය"</string> <string name="editTextMenuTitle" msgid="4909135564941815494">"පෙළ ක්රියාවන්"</string> @@ -1214,10 +1214,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"ස්ථාපන සැසිය කියවීමට යෙදුමට ඉඩ දෙන්න. සක්රිය පැකේජ ස්ථාපනය පිළිබඳ විස්තර බැලීමට එයට මෙයින් ඉඩ දෙයි."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"ස්ථාපන පැකේජ ඉල්ලීම"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"ස්ථාපන පැකේජ ඉල්ලීමට යෙදුමකට අවසර දීම."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"බැටරි ප්රශස්තකරණ නොසලකා හැරීමට ඉල්ලන්න"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"යෙදුමකට එම යෙදුම සඳහා බැටරි ප්රශස්තකරණ නොසලකා හැරීමට අවසර ඉල්ලීමට ඉඩ දෙයි."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"විශාලන පාලක සඳහා දෙවතාවක් තට්ටු කරන්න"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"විජටය එකතු කිරීමට නොහැකි විය."</string> <string name="ime_action_go" msgid="8320845651737369027">"යන්න"</string> @@ -1292,7 +1290,7 @@ <string name="sync_undo_deletes" msgid="2941317360600338602">"මැකීම් අස් කරන්න"</string> <string name="sync_do_nothing" msgid="3743764740430821845">"දැනට කිසිවක් නොකරන්න"</string> <string name="choose_account_label" msgid="5655203089746423927">"ගිණුමක් තෝරන්න"</string> - <string name="add_account_label" msgid="2935267344849993553">"ගිණුමක් එකතු කරන්න"</string> + <string name="add_account_label" msgid="2935267344849993553">"ගිණුමක් එක් කරන්න"</string> <string name="add_account_button_label" msgid="3611982894853435874">"ගිණුමක් එක් කරන්න"</string> <string name="number_picker_increment_button" msgid="2412072272832284313">"වැඩි කරන්න"</string> <string name="number_picker_decrement_button" msgid="476050778386779067">"අඩු කරන්න"</string> @@ -1618,7 +1616,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> තෙක්"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> තෙක් (ඊළඟ එලාමය)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"ඔබ මෙය ක්රියාවිරහිත කරන තුරු"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"බාධා නොකරන්න ඔබ අක්රිය කරන තුරු"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"හකුළන්න"</string> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 7811a469d3c6..9a41b072cdd8 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -1262,10 +1262,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Toto povolenie umožňuje aplikácii čítať relácie inštalácií a zobraziť tak podrobnosti o aktívnych inštaláciách balíkov."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"odosielanie žiadostí o inštaláciu balíkov"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Umožňuje aplikácii vyžiadať inštaláciu balíkov."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"požiadať o ignorovanie optimalizácií výdrže batérie"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Umožňuje aplikácii požiadať o povolenie ignorovať optimalizácie výdrže batérie pre danú aplikáciu."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Dvojitým klepnutím môžete ovládať priblíženie"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Miniaplikáciu sa nepodarilo pridať."</string> <string name="ime_action_go" msgid="8320845651737369027">"Hľadať"</string> @@ -1686,7 +1684,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ďalší budík)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Kým túto funkciu nevypnete"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Dokým nevypnete stav Nerušiť"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Zbaliť"</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 5cdbefb5cea1..e6b08e9e7603 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -1262,10 +1262,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Aplikaciji omogoča branje sej namestitev. Tako lahko bere podrobnosti o aktivnih namestitvah paketov."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"zahtevanje paketov za namestitev"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Aplikaciji omogoča zahtevanje namestitve paketov."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"Dovoljenje za prezrtje optimizacij akumulatorja"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Aplikaciji dovoljuje, da vpraša za dovoljenje, ali naj prezre optimizacije akumulatorja."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Tapnite dvakrat za nadzor povečave/pomanjšave"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Pripomočka ni bilo mogoče dodati."</string> <string name="ime_action_go" msgid="8320845651737369027">"Pojdi"</string> @@ -1686,7 +1684,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (naslednji alarm)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Dokler tega ne izklopite"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Dokler ne izklopite načina »ne moti«"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Strni"</string> diff --git a/core/res/res/values-sq-rAL/strings.xml b/core/res/res/values-sq-rAL/strings.xml index ea33ffff5d19..688d14b1b5de 100644 --- a/core/res/res/values-sq-rAL/strings.xml +++ b/core/res/res/values-sq-rAL/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Lejon një aplikacion të lexojë sesionet e instalimit. Kjo e lejon atë të shohë detaje rreth instalimeve të paketave aktive."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"kërko paketat e instalimit"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Lejon që një aplikacion të kërkojë instalimin e paketave."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"kërko të shpërfillësh optimizimet e baterisë"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Lejon që një aplikacion të kërkojë leje për të shpërfillur optimizimet e baterisë për atë aplikacion."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Trokit dy herë për të kontrolluar zmadhimin"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Nuk mundi të shtonte miniaplikacion."</string> <string name="ime_action_go" msgid="8320845651737369027">"Shko"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Deri në <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Deri në <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (alarmi tjetër)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Deri sa ta çaktivizosh këtë"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Deri sa të çaktivizosh gjendjen \"Mos shqetëso\""</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Shpalos"</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 19a114003533..1ab6bb3a7c34 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -1237,10 +1237,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Дозвољава апликацији да чита сесије инсталирања. То јој дозвољава да види детаље о активним инсталацијама пакета."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"захтевање пакета за инсталирање"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Омогућава да апликација захтева инсталацију пакета."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"тражење дозволе за игнорисање оптимизација батерије"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Дозвољава апликацији да тражи дозволу за игнорисање оптимизација батерије за ту апликацију."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Додирните двапут за контролу зумирања"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Није могуће додати виџет."</string> <string name="ime_action_go" msgid="8320845651737369027">"Иди"</string> @@ -1651,7 +1649,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (следећи аларм)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Док не искључите"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Док не искључите режим Не узнемиравај"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Скупи"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 3a4fee388d3c..b4d7f2704d79 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Tillåt appen att läsa installationssessioner. Det ger den tillgång till uppgifter om aktiva paketinstallationer."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"begära installationspaket"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Tillåter att en app begär paketinstallation."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"får be om tillstånd att ignorera batterioptimering"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Appen får be om tillstånd att ignorera batterioptimering."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Peka två gånger för zoomkontroll"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Det gick inte att lägga till widgeten."</string> <string name="ime_action_go" msgid="8320845651737369027">"Kör"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Till kl. <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Till <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (nästa alarm)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Tills du inaktiverar detta"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Tills du inaktiverar Stör ej"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Komprimera"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 4b75ff1ede74..5183c87568d4 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -1210,10 +1210,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Huruhusu programu kusoma vipindi vya kusanikisha. Hii huiruhusu kuona maelezo kuhusu usanikishaji wa programu unaoendelea."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"omba ruhusa ya kusakinisha vifurushi"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Huruhusu programu kuomba idhini ya kusakinisha vifurushi."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"omba kupuuza uimarishji wa betri"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Huruhusu programu kuomba ruhusa ya kupuuza uimarishaji wa betri katika programu yako."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Gonga mara mbili kwa udhibiti wa kuza"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Haikuweza kuongeza wijeti."</string> <string name="ime_action_go" msgid="8320845651737369027">"Nenda"</string> @@ -1614,7 +1612,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Hadi <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Mpaka <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (kengele inayofuata)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Hadi utakapozima hili"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Hadi utakapozima Usinisumbue"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Kunja"</string> diff --git a/core/res/res/values-sw900dp/dimens.xml b/core/res/res/values-sw900dp/dimens.xml new file mode 100644 index 000000000000..11092b2cb9e9 --- /dev/null +++ b/core/res/res/values-sw900dp/dimens.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. +*/ +--> +<resources> + + <!-- Height of the bottom navigation / system bar. --> + <dimen name="navigation_bar_height">56dp</dimen> + + <!-- Height of the bottom navigation bar in landscape; often + the same as @dimen/navigation_bar_height --> + <dimen name="navigation_bar_height_landscape">56dp</dimen> + +</resources> diff --git a/core/res/res/values-ta-rIN/strings.xml b/core/res/res/values-ta-rIN/strings.xml index 6a54f828cdc1..1dfcfb275f3d 100644 --- a/core/res/res/values-ta-rIN/strings.xml +++ b/core/res/res/values-ta-rIN/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"நிறுவல் அமர்வுகளைப் படிக்க, பயன்பாட்டை அனுமதிக்கிறது. இது செயல்படும் தொகுப்பு நிறுவல்களைப் பற்றிய விவரங்களைப் பார்க்க அனுமதிக்கிறது."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"நிறுவல் தொகுப்புகளைக் கோருதல்"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"தொகுப்புகளின் நிறுவலைக் கோர, பயன்பாட்டை அனுமதிக்கும்."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"பேட்டரி மேம்படுத்தல்களைப் புறக்கணிப்பதற்கான அனுமதியைக் கோரு"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"பயன்பாட்டிற்கான பேட்டரி மேம்படுத்தல்களைப் புறக்கணிப்பதற்கான அனுமதியைக் கோர, பயன்பாட்டை அனுமதிக்கும்."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"அளவை மாற்றுவதற்கான கட்டுப்பாட்டிற்கு, இருமுறை தட்டவும்"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"விட்ஜெட்டைச் சேர்க்க முடியவில்லை."</string> <string name="ime_action_go" msgid="8320845651737369027">"செல்"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> வரை"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> மணி (அடுத்த அலாரம்) வரை"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"இதை முடக்கும்வரை"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"தொந்தரவு செய்ய வேண்டாம் என்பதை முடக்கும் வரை"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"சுருக்கு"</string> diff --git a/core/res/res/values-te-rIN/strings.xml b/core/res/res/values-te-rIN/strings.xml index 49b509c38b56..9a3bfb1c7af4 100644 --- a/core/res/res/values-te-rIN/strings.xml +++ b/core/res/res/values-te-rIN/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"ఇన్స్టాల్ సెషన్లను చదవడానికి అనువర్తనాన్ని అనుమతిస్తుంది. ఇది సక్రియ ప్యాకేజీ ఇన్స్టాలేషన్ల గురించి వివరాలను చూడటానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"ఇన్స్టాల్ ప్యాకేజీలను అభ్యర్థించడం"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"ప్యాకేజీల ఇన్స్టాలేషన్ అభ్యర్థించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"బ్యాటరీ అనుకూలీకరణలను విస్మరించడానికి అడగాలి"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"ఆ అనువర్తనం కోసం బ్యాటరీ అనుకూలీకరణలు విస్మరించేలా అనుమతి కోరడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"జూమ్ నియంత్రణ కోసం రెండుసార్లు నొక్కండి"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"విడ్జెట్ను జోడించడం సాధ్యపడలేదు."</string> <string name="ime_action_go" msgid="8320845651737369027">"వెళ్లు"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> వరకు"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (తదుపరి అలారం) వరకు"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"మీరు దీన్ని ఆఫ్ చేసే వరకు"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"మీరు అంతరాయం కలిగించవద్దు ఎంపిక ఆఫ్ చేసే వరకు"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"కుదించండి"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index c4d72ab0371d..25fd417c028d 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"อนุญาตให้แอปพลิเคชันอ่านเซสชันการติดตั้ง ซึ่งจะอนุญาตให้อ่านรายละเอียดเกี่ยวกับการติดตั้งแพ็กเกจที่ใช้งาน"</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"ขอติดตั้งแพ็กเกจ"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"อนุญาตให้แอปพลิเคชันขอการติดตั้งแพ็กเกจ"</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"ขอเพิกเฉยต่อการเพิ่มประสิทธิภาพแบตเตอรี่"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"อนุญาตให้แอปขอสิทธิ์เพิกเฉยต่อการเพิ่มประสิทธิภาพแบตเตอรี่สำหรับแอปนั้น"</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"แตะสองครั้งเพื่อควบคุมการซูม"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"ไม่สามารถเพิ่มวิดเจ็ต"</string> <string name="ime_action_go" msgid="8320845651737369027">"ไป"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"จนถึงเวลา <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"จนถึงเวลา <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (การปลุกครั้งถัดไป)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"จนกว่าคุณจะปิดฟังก์ชันนี้"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"จนกว่าคุณจะปิดห้ามรบกวน"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"ยุบ"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index a4e1b26e1504..51d22f34e6a6 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Pinapayagan ang isang application na magbasa ng mga session ng pag-install. Nagbibigay-daan ito upang makita ang mga detalye tungkol sa mga aktibong pag-install ng package."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"humiling ng mga package sa pag-install"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Pinapayagan ang isang application na hilingin ang pag-install ng mga package."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"hilingin na balewalain ang mga pag-optimize ng baterya"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Pinapayagang humingi ng pahintulot ang isang app na balewalain ang mga pag-optimize ng baterya para sa app na iyon."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Tapikin ng dalawang beses para sa pagkontrol ng zoom"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Hindi maidagdag ang widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"Pumunta"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Hanggang <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Hanggang <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (susunod na alarm)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Hanggang sa i-off mo ito"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Hanggang sa i-off mo ang Huwag Istorbohin"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"I-collapse"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index eacf16e3cb22..86b2ab614ab2 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Bir uygulamanın yükleme oturumlarını okumasına izin verir. Bu, etkin paket yüklemeleriyle ilgili ayrıntıların görülmesine olanak tanır."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"paket yükleme isteğinde bulunma"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Uygulamaya, paketleri yükleme isteğinde bulunma izni verir."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"pil optimizasyonlarını göz ardı etme izni iste"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Bir uygulamanın, kendisi için pil optimizasyonlarını göz ardı etme izni istemesine olanak sağlar."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Zum denetimi için iki kez dokun"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Widget eklenemedi."</string> <string name="ime_action_go" msgid="8320845651737369027">"Git"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Şu saate kadar: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (sonraki alarma) saatine kadar"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Siz bunu kapatana kadar"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Rahatsız Etmeyin ayarını kapatana kadar"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Daralt"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 4dbeb0df11c8..ce55856ffaea 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -1262,10 +1262,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Дозволяє додатку читати дані сеансів встановлення. Додаток може бачити деталі про активні встановлення пакетів."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"запитувати дані про пакети встановлення"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Додаток зможе надсилати запити на встановлення пакетів."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"запитувати дозвіл ігнорувати оптимізацію використання заряду акумулятора"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Додаток зможе запитувати дозвіл ігнорувати оптимізацію використання заряду акумулятора."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Двічі натис. для кер. масшт."</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Не вдалося додати віджет."</string> <string name="ime_action_go" msgid="8320845651737369027">"Йти"</string> @@ -1686,7 +1684,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (наступний будильник)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Доки ви не вимкнете"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Доки ввімкнено режим \"Не турбувати\""</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Згорнути"</string> diff --git a/core/res/res/values-ur-rPK/strings.xml b/core/res/res/values-ur-rPK/strings.xml index 82253ac7a157..c71617a93897 100644 --- a/core/res/res/values-ur-rPK/strings.xml +++ b/core/res/res/values-ur-rPK/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"ایک ایپلیکیشن کو انسٹال سیشنز پڑھنے کی اجازت دیتا ہے۔ یہ اسے فعال پیکیج انسٹالیشنز کے بارے میں تفصیلات دیکھنے کی اجازت دیتا ہے۔"</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"پیکجز انسٹال کرنے کی درخواست کریں"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"ایک ایپلیکیشن کو پیکجز انسٹال کرنے کی اجازت دیتی ہے۔"</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"بیٹری کی بہتریاں نظر انداز کرنے کا پوچھیں"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"اس ایپ کیلئے ایک ایپ کو بیٹری کی کارکردگی بہتر بنانے کو نظر انداز کرنے کی اجازت دیں۔"</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"زوم کنٹرول کیلئے دوبار تھپتھپائیں"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"ویجٹس کو شامل نہیں کرسکا۔"</string> <string name="ime_action_go" msgid="8320845651737369027">"جائیں"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> تک"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> تک (اگلا الارم)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"جب تک آپ اسے آف نہ کر دیں"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"جب تک آپ ڈسڑب نہ کریں کو آف نہیں کر دیتے"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"سکیڑیں"</string> diff --git a/core/res/res/values-uz-rUZ/strings.xml b/core/res/res/values-uz-rUZ/strings.xml index f44a6c9f42fc..b3a248d6ce01 100644 --- a/core/res/res/values-uz-rUZ/strings.xml +++ b/core/res/res/values-uz-rUZ/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Ilovaga o‘rnatilgan seanslarni o‘qish uchun ruxsat beradi. Bu unga faol paket o‘rnatmalari haqidagi ma’lumotlarni ko‘rish imkonini beradi."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"paketlarni o‘rnatish so‘rovini yuborish"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Ilovaga paketlarni o‘rnatish so‘rovini yuborish imkonini beradi."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"batareya quvvatidan xohlagancha foydalanishni so‘rash"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Ilovaga batareya quvvatidan xohlagancha foydalanish uchun ruxsat so‘rashga imkon beradi."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Ko‘lamini o‘zgartirish uchun ikki marta bosing"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Vidjet qo‘shilmadi."</string> <string name="ime_action_go" msgid="8320845651737369027">"O‘tish"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> gacha"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> gacha (keyingi signal)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Men o‘chirmaguncha"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"“Bezovta qilinmasin” rejimi o‘chirilmaguncha"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Yig‘ish"</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 21ad11c45d69..8c760736f1e7 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Cho phép ứng dụng đọc phiên cài đặt. Thao tác này sẽ cho phép ứng dụng xem chi tiết về gói cài đặt đang hoạt động."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"yêu cầu gói cài đặt"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Cho phép ứng dụng yêu cầu cài đặt gói."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"hỏi để bỏ qua tối ưu hóa pin"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Cho phép ứng dụng hỏi quyền để bỏ qua tối ưu hóa pin cho ứng dụng đó."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Nhấn hai lần để kiểm soát thu phóng"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Không thể thêm tiện ích."</string> <string name="ime_action_go" msgid="8320845651737369027">"Đến"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Cho đến <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Cho tới <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (cảnh báo tiếp theo)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Cho đến khi bạn tắt tính năng này"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Cho đến khi bạn tắt Đừng làm phiền"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Thu gọn"</string> diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml index d13d15468825..ac7b236ba981 100644 --- a/core/res/res/values-watch/config.xml +++ b/core/res/res/values-watch/config.xml @@ -65,4 +65,5 @@ <!-- The small screens of watch devices makes multi-window support undesireable. --> <bool name="config_supportsMultiWindow">false</bool> + <bool name="config_supportsSplitScreenMultiWindow">false</bool> </resources> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 68fec510f4a1..1534e3055887 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"允许应用读取安装会话。这样,应用将可以查看有关当前软件包安装的详情。"</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"请求安装文件包"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"允许应用请求安装文件包。"</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"请求忽略电池优化"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"允许应用请求相应的权限,以便忽略针对该应用的电池优化。"</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"双击可以进行缩放控制"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"无法添加小部件。"</string> <string name="ime_action_go" msgid="8320845651737369027">"开始"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"到<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"直到<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>(闹钟下次响铃时)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"直到您将其关闭"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"直到您关闭“勿扰”模式"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"收起"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index d6b4cf715c4f..57e901d2ed33 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"允許應用程式讀取安裝工作階段。應用程式將可查看目前安裝套裝的詳細資料。"</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"要求安裝套件"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"允許應用程式要求安裝套件"</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"要求忽略電池優化"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"允許應用程式要求就該應用程式忽略電池優化。"</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"輕觸兩下控制縮放"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"無法新增小工具。"</string> <string name="ime_action_go" msgid="8320845651737369027">"開始"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"完成時間:<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"直至<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (下一次響鬧)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"直至您關閉這項設定"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"直至您關閉「請勿騷擾」功能"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"收合"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 74b149d1e385..d3bbf67c27ca 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"允許應用程式讀取安裝工作階段。應用程式將可查看目前的套件安裝詳細資料。"</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"要求安裝套件"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"允許應用程式要求安裝套件。"</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"要求忽略電池效能最佳化設定"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"允許應用程式要求權限,以便忽略針對該應用程式的電池效能最佳化設定。"</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"點兩下以進行縮放控制"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"無法新增小工具。"</string> <string name="ime_action_go" msgid="8320845651737369027">"開始"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"結束時間:<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"到<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> 為止 (下一個鬧鐘)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"手動關閉這項設定前一律啟用"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"直到您關閉「零打擾」模式"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g>/<xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"收合"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index d36b7fb8334d..455dc240125b 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -1212,10 +1212,8 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Ivumela uhlelo lokusebenza ukufunda izikhathi. Lokhu kuzolivumela ukubona imininingwane mayelana nokufaka kwephakethi esebenzayo."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"cela amaphakheji wokufaka"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Ivumela uhlelo lokusebenza ukucela ukufakwa kwamaphakheji."</string> - <!-- no translation found for permlab_requestIgnoreBatteryOptimizations (8021256345643918264) --> - <skip /> - <!-- no translation found for permdesc_requestIgnoreBatteryOptimizations (8359147856007447638) --> - <skip /> + <string name="permlab_requestIgnoreBatteryOptimizations" msgid="8021256345643918264">"cela ukuziba ukulungiselelwa kwebhethri"</string> + <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="8359147856007447638">"Ivumela uhlelo lokusebenza ukuthi licele imvume yokuziba ukulungiselela ibhethri yalolo hlelo lokusebenza."</string> <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Thepha kabili ukuthola ukulawula ukusondeza"</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Yehlulekile ukwengeza i-widget."</string> <string name="ime_action_go" msgid="8320845651737369027">"Iya"</string> @@ -1616,7 +1614,8 @@ </plurals> <string name="zen_mode_until" msgid="7336308492289875088">"Kuze kube ngu-<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string> <string name="zen_mode_alarm" msgid="9128205721301330797">"Kuze kube ngu-<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (i-alamu elandelayo)"</string> - <string name="zen_mode_forever" msgid="7420011936770086993">"Uze uvale lokhu"</string> + <!-- no translation found for zen_mode_forever (1916263162129197274) --> + <skip /> <string name="zen_mode_forever_dnd" msgid="3792132696572189081">"Uze uvale ungaphazamisi"</string> <string name="zen_mode_rule_name_combination" msgid="191109939968076477">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string> <string name="toolbar_collapse_description" msgid="2821479483960330739">"Goqa"</string> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 8c64484969bb..aa050deeb948 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2887,6 +2887,28 @@ <!-- Defines text displayed in a small popup window on hover or long press. --> <attr name="tooltip" format="string" localization="suggested" /> + <!-- Whether this view is a root of a keyboard navigation cluster. + See {@link android.view.View#setKeyboardNavigationCluster(boolean)}. --> + <attr name="keyboardNavigationCluster" format="boolean" /> + + <!-- Whether this view is a root of a keyboard navigation section. + See {@link android.view.View#setKeyboardNavigationSection(boolean)}. --> + <attr name="keyboardNavigationSection" format="boolean" /> + + <!-- Defines the next keyboard navigation cluster. + + If the reference refers to a view that does not exist or is part + of a hierarchy that is invisible, a {@link java.lang.RuntimeException} + will result when the reference is accessed.--> + <attr name="nextClusterForward" format="reference"/> + + <!-- Defines the next keyboard navigation section. + + If the reference refers to a view that does not exist or is part + of a hierarchy that is invisible, a {@link java.lang.RuntimeException} + will result when the reference is accessed.--> + <attr name="nextSectionForward" format="reference"/> + </declare-styleable> <!-- Attributes that can be assigned to a tag for a particular View. --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 9e75ff9d7b59..5967c692fb94 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1267,7 +1267,8 @@ This may be empty if network scoring and recommending isn't supported. --> <string-array name="config_networkRecommendationPackageNames" translatable="false"> - <!-- Add packages here --> + <!-- The standard AOSP network recommendation provider --> + <item>com.android.networkrecommendation</item> </string-array> <!-- Whether to enable Hardware FLP overlay which allows Hardware FLP to be @@ -2568,6 +2569,9 @@ E.g. freeform, split-screen, picture-in-picture. --> <bool name="config_supportsMultiWindow">true</bool> + <!-- True if the device supports split screen as a form of multi-window. --> + <bool name="config_supportsSplitScreenMultiWindow">true</bool> + <!-- True if the device requires AppWidgetService even if it does not have the PackageManager.FEATURE_APP_WIDGETS feature --> <bool name="config_enableAppWidgetService">false</bool> @@ -2610,6 +2614,9 @@ <!-- Component that is the default launcher when demo mode is enabled. --> <string name="config_demoModeLauncherComponent">com.android.retaildemo/.DemoPlayer</string> + <!-- Hashed password (SHA-256) used to restrict demo mode operation --> + <string name="config_demoModePassword" translatable="false"></string> + <!-- Flag indicating whether round icons should be parsed from the application manifest. --> <bool name="config_useRoundIcon">false</bool> @@ -2687,4 +2694,7 @@ user-set value if toggled by settings so the "Transition animation scale" setting should also be hidden if intended to be permanent. --> <item name="config_appTransitionAnimationDurationScaleDefault" format="float" type="dimen">1.0</item> + + <!-- Flag indicates that whether non-system apps can be installed on internal storage. --> + <bool name="config_allow3rdPartyAppOnInternal">true</bool> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 37c4fd1eb62b..4604f3f748b1 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2776,6 +2776,10 @@ <public name="paddingHorizontal" /> <public name="paddingVertical" /> <public name="visibleToEphemeral" /> + <public name="keyboardNavigationCluster" /> + <public name="keyboardNavigationSection" /> + <public name="nextClusterForward" /> + <public name="nextSectionForward" /> </public-group> <public-group type="style" first-id="0x010302e0"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 87f38c1e9991..880944e659e6 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -103,6 +103,8 @@ <!-- Displayed when the user dialed an MMI code whose function could not be performed because FDN is enabled. This will be displayed in a toast. --> <string name="mmiFdnError">Operation is restricted to fixed dialing numbers only.</string> + <!-- Displayed when a carrier does not support call forwarding queries when roaming. --> + <string name="mmiErrorWhileRoaming">Can not change call forwarding settings from your phone while you are roaming.</string> <!-- Displayed when a phone feature such as call barring was activated. --> <string name="serviceEnabled">Service was enabled.</string> @@ -204,6 +206,12 @@ <!-- Displayed to tell the user that all service is blocked by access control. --> <string name="RestrictedOnAll">All voice/data/SMS services are blocked.</string> + <!-- Displayed to tell the user that they should switch their network preference. --> + <string name="NetworkPreferenceSwitchTitle">Can\u2019t reach network</string> + <!-- Displayed to tell the user that they should switch their network preference. --> + <string name="NetworkPreferenceSwitchSummary">To improve reception, try changing the type selected at Settings > Cellular networks > Preferred network type."</string> + + <!-- Displayed to tell the user that peer changed TTY mode --> <string name="peerTtyModeFull">Peer requested TTY Mode FULL</string> <string name="peerTtyModeHco">Peer requested TTY Mode HCO</string> @@ -384,6 +392,13 @@ This indicates that a work profile has been deleted. [CHAR LIMIT=NONE]--> <string name="work_profile_deleted_description_dpm_wipe">Your work profile is no longer available on this device.</string> + <!-- Content title for a notification. This indicates that network logging was activated by + a device owner. [CHAR LIMIT=NONE]--> + <string name="network_logging_notification_title">Network traffic is being monitored</string> + <!-- Content text for a notification. Tapping opens a dialog with more information on network + logging. [CHAR LIMIT=NONE]--> + <string name="network_logging_notification_text">Tap for more details</string> + <!-- Factory reset warning dialog strings--> <skip /> <!-- Shows up in the dialog's title to warn about an impeding factory reset. [CHAR LIMIT=NONE] --> <string name="factory_reset_warning">Your device will be erased</string> @@ -741,17 +756,11 @@ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_readSms">read your text messages (SMS or MMS)</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_readSms" product="tablet">Allows the app to read SMS - messages stored on your tablet or SIM card. This allows the app to read all - SMS messages, regardless of content or confidentiality.</string> + <string name="permdesc_readSms" product="tablet">This app can read all SMS (text) messages stored on your tablet.</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_readSms" product="tv">Allows the app to read SMS - messages stored on your TV or SIM card. This allows the app to read all - SMS messages, regardless of content or confidentiality.</string> + <string name="permdesc_readSms" product="tv">This app can read all SMS (text) messages stored on your TV.</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_readSms" product="default">Allows the app to read SMS - messages stored on your phone or SIM card. This allows the app to read all - SMS messages, regardless of content or confidentiality.</string> + <string name="permdesc_readSms" product="default">This app can read all SMS (text) messages stored on your phone.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_receiveWapPush">receive text messages (WAP)</string> @@ -793,12 +802,9 @@ running.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permlab_systemAlertWindow">draw over other apps</string> + <string name="permlab_systemAlertWindow">This app can appear on top of other apps</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_systemAlertWindow">Allows the app to draw on top of other - applications or parts of the user interface. They may interfere with your - use of the interface in any application, or change what you think you are - seeing in other applications.</string> + <string name="permdesc_systemAlertWindow">This app can appear on top of other apps or other parts of the screen. This may interfere with normal app usage and change the way that other apps appear.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_persistentActivity">make app always run</string> @@ -904,20 +910,7 @@ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_readCallLog">read call log</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_readCallLog" product="tablet">Allows the app to read - your tablet\'s call log, including data about incoming and outgoing calls. - This permission allows apps to save your call log data, and malicious apps - may share call log data without your knowledge.</string> - <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_readCallLog" product="tv">Allows the app to read - your TV\'s call log, including data about incoming and outgoing calls. - This permission allows apps to save your call log data, and malicious apps - may share call log data without your knowledge.</string> - <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_readCallLog" product="default">Allows the app to read - your phone\'s call log, including data about incoming and outgoing calls. - This permission allows apps to save your call log data, and malicious apps - may share call log data without your knowledge.</string> + <string name="permdesc_readCallLog">This app can read your call history.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_writeCallLog">write call log</string> @@ -939,46 +932,24 @@ that monitor your physical condition, such as your heart rate.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permlab_readCalendar">read calendar events plus confidential information</string> + <string name="permlab_readCalendar">Read calendar events and details</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_readCalendar" product="tablet">Allows the app to read - all calendar events stored on your tablet, including those of friends or - co-workers. This may allow the app to share or save your calendar data, - regardless of confidentiality or sensitivity.</string> + <string name="permdesc_readCalendar" product="tablet">This app can read all calendar events stored on your tablet and share or save your calendar data.</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_readCalendar" product="tv">Allows the app to read - all calendar events stored on your TV, including those of friends or - co-workers. This may allow the app to share or save your calendar data, - regardless of confidentiality or sensitivity.</string> + <string name="permdesc_readCalendar" product="tv">This app can read all calendar events stored on your TV and share or save your calendar data.</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_readCalendar" product="default">Allows the app to - read all calendar events stored on your phone, including those of friends - or co-workers. This may allow the app to share or save your calendar data, - regardless of confidentiality or sensitivity.</string> + <string name="permdesc_readCalendar" product="default">This app can read all calendar events stored on your phone and share or save your calendar data.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_writeCalendar">add or modify calendar events and send email to guests without owners\' knowledge</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_writeCalendar" product="tablet">Allows the app to - add, remove, change events that you can modify on your tablet, including - those of friends or co-workers. This may allow the app to send messages - that appear to come from calendar owners, or modify events without the - owners\' knowledge.</string> + <string name="permdesc_writeCalendar" product="tablet">This app can add, remove, or change calendar events on your tablet. This app can send messages that may appear to come from calendar owners, or change events without notifying their owners.</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_writeCalendar" product="tv">Allows the app to - add, remove, change events that you can modify on your TV, including - those of friends or co-workers. This may allow the app to send messages - that appear to come from calendar owners, or modify events without the - owners\' knowledge.</string> + <string name="permdesc_writeCalendar" product="tv">This app can add, remove, or change calendar events on your TV. This app can send messages that may appear to come from calendar owners, or change events without notifying their owners.</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_writeCalendar" product="default">Allows the app to - add, remove, change events that you can modify on your phone, including - those of friends or co-workers. This may allow the app to send messages - that appear to come from calendar owners, or modify events without the - owners\' knowledge.</string> - + <string name="permdesc_writeCalendar" product="default">This app can add, remove, or change calendar events on your phone. This app can send messages that may appear to come from calendar owners, or change events without notifying their owners.</string> - <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the applicatfion to do this. --> <string name="permlab_accessLocationExtraCommands">access extra location provider commands</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_accessLocationExtraCommands">Allows the app to access @@ -989,23 +960,17 @@ <string name="permlab_accessFineLocation">access precise location (GPS and network-based)</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_accessFineLocation">Allows the app to get your - precise location using the Global Positioning System (GPS) or network - location sources such as cell towers and Wi-Fi. These location services - must be turned on and available to your device for the app to use them. - Apps may use this to determine where you are, and may consume additional - battery power.</string> + <string name="permdesc_accessFineLocation">This app can get your location based on GPS or network location sources such as cell towers and Wi-Fi networks. These location services must be turned on and available on your phone for the app to be able to use them. This may increase battery consumption.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_accessCoarseLocation">access approximate location (network-based)</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_accessCoarseLocation">Allows the app to get your - approximate location. This location is derived by location services using - network location sources such as cell towers and Wi-Fi. These location - services must be turned on and available to your device for the app to - use them. Apps may use this to determine approximately where you - are.</string> + <string name="permdesc_accessCoarseLocation" product="tablet">This app can get your location based on network sources such as cell towers and Wi-Fi networks. These location services must be turned on and available on your tablet for the app to be able to use them.</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_accessCoarseLocation" product="tv">This app can get your location based on network sources such as cell towers and Wi-Fi networks. These location services must be turned on and available on your TV for the app to be able to use them.</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_accessCoarseLocation" product="default">This app can get your location based on network sources such as cell towers and Wi-Fi networks. These location services must be turned on and available on your phone for the app to be able to use them.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_modifyAudioSettings">change your audio settings</string> @@ -1015,9 +980,7 @@ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_recordAudio">record audio</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_recordAudio">Allows the app to record audio with the - microphone. This permission allows the app to record audio at any time - without your confirmation.</string> + <string name="permdesc_recordAudio">This app can record audio using the microphone at any time.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_sim_communication">send commands to the SIM</string> @@ -1027,9 +990,7 @@ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_camera">take pictures and videos</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_camera">Allows the app to take pictures and videos - with the camera. This permission allows the app to use the camera at any - time without your confirmation.</string> + <string name="permdesc_camera">This app can take pictures and record videos using the camera at any time.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_vibrate">control vibration</string> @@ -2431,22 +2392,6 @@ <!-- Appened to express the value is this unit of time. --> <string name="years">years</string> - <!-- Phrase describing a time duration using seconds [CHAR LIMIT=16] --> - <plurals name="duration_seconds"> - <item quantity="one">1 second</item> - <item quantity="other"><xliff:g id="count">%d</xliff:g> seconds</item> - </plurals> - <!-- Phrase describing a time duration using minutes [CHAR LIMIT=16] --> - <plurals name="duration_minutes"> - <item quantity="one">1 minute</item> - <item quantity="other"><xliff:g id="count">%d</xliff:g> minutes</item> - </plurals> - <!-- Phrase describing a time duration using hours [CHAR LIMIT=16] --> - <plurals name="duration_hours"> - <item quantity="one">1 hour</item> - <item quantity="other"><xliff:g id="count">%d</xliff:g> hours</item> - </plurals> - <!-- A string denoting the current point in time that should be as short as possible. Abbreviations are preferred to full strings as this might be shown repetitively. It is used in the header of notifications. [CHAR LIMIT=8]--> <string name="now_string_shortest">now</string> @@ -4277,7 +4222,7 @@ <string name="zen_mode_alarm">Until <xliff:g id="formattedTime" example="10:00 PM">%1$s</xliff:g> (next alarm)</string> <!-- Zen mode condition: no exit criteria. [CHAR LIMIT=NONE] --> - <string name="zen_mode_forever">Until you turn this off</string> + <string name="zen_mode_forever">Until you turn off Do Not Disturb</string> <!-- Zen mode condition: no exit criteria, includes the name of the feature for emphasis. [CHAR LIMIT=NONE] --> <string name="zen_mode_forever_dnd">Until you turn off Do Not Disturb</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a85ddf1e54d6..8efb13112c75 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -307,6 +307,7 @@ <java-symbol type="bool" name="config_supportAudioSourceUnprocessed" /> <java-symbol type="bool" name="config_freeformWindowManagement" /> <java-symbol type="bool" name="config_supportsMultiWindow" /> + <java-symbol type="bool" name="config_supportsSplitScreenMultiWindow" /> <java-symbol type="bool" name="config_guestUserEphemeral" /> <java-symbol type="bool" name="config_localDisplaysMirrorContent" /> <java-symbol type="integer" name="config_defaultDisplayDefaultColorMode" /> @@ -493,6 +494,8 @@ <java-symbol type="string" name="RestrictedOnData" /> <java-symbol type="string" name="RestrictedOnEmergency" /> <java-symbol type="string" name="RestrictedOnNormal" /> + <java-symbol type="string" name="NetworkPreferenceSwitchSummary" /> + <java-symbol type="string" name="NetworkPreferenceSwitchTitle" /> <java-symbol type="string" name="SetupCallDefault" /> <java-symbol type="string" name="accept" /> <java-symbol type="string" name="accessibility_enabled" /> @@ -732,6 +735,7 @@ <java-symbol type="string" name="mmiComplete" /> <java-symbol type="string" name="mmiError" /> <java-symbol type="string" name="mmiFdnError" /> + <java-symbol type="string" name="mmiErrorWhileRoaming" /> <java-symbol type="string" name="month_day_year" /> <java-symbol type="string" name="more_item_label" /> <java-symbol type="string" name="needPuk" /> @@ -1117,6 +1121,8 @@ <java-symbol type="string" name="work_profile_deleted_description" /> <java-symbol type="string" name="work_profile_deleted_details" /> <java-symbol type="string" name="work_profile_deleted_description_dpm_wipe" /> + <java-symbol type="string" name="network_logging_notification_title" /> + <java-symbol type="string" name="network_logging_notification_text" /> <java-symbol type="string" name="factory_reset_warning" /> <java-symbol type="string" name="factory_reset_message" /> <java-symbol type="string" name="lockscreen_transport_play_description" /> @@ -1124,6 +1130,7 @@ <java-symbol type="string" name="config_ethernet_tcp_buffers" /> <java-symbol type="string" name="config_wifi_tcp_buffers" /> <java-symbol type="string" name="config_demoModeLauncherComponent" /> + <java-symbol type="string" name="config_demoModePassword" /> <java-symbol type="string" name="demo_starting_message" /> <java-symbol type="string" name="demo_restarting_message" /> <java-symbol type="string" name="conference_call" /> @@ -1131,9 +1138,6 @@ <java-symbol type="plurals" name="bugreport_countdown" /> - <java-symbol type="plurals" name="duration_hours" /> - <java-symbol type="plurals" name="duration_minutes" /> - <java-symbol type="plurals" name="duration_seconds" /> <java-symbol type="plurals" name="last_num_days" /> <java-symbol type="plurals" name="matches_found" /> <java-symbol type="plurals" name="restr_pin_countdown" /> @@ -1229,6 +1233,7 @@ <java-symbol type="drawable" name="ic_print" /> <java-symbol type="drawable" name="ic_print_error" /> <java-symbol type="drawable" name="ic_grayedout_printer" /> + <java-symbol type="drawable" name="ic_qs_network_logging" /> <java-symbol type="drawable" name="jog_dial_arrow_long_left_green" /> <java-symbol type="drawable" name="jog_dial_arrow_long_right_red" /> <java-symbol type="drawable" name="jog_dial_arrow_short_left_and_right" /> @@ -2753,6 +2758,9 @@ <java-symbol type="dimen" name="config_appTransitionAnimationDurationScaleDefault" /> -<!-- Network Recommendation --> + <!-- Network Recommendation --> <java-symbol type="array" name="config_networkRecommendationPackageNames" /> + + <!-- Whether allow 3rd party apps on internal storage. --> + <java-symbol type="bool" name="config_allow3rdPartyAppOnInternal" /> </resources> diff --git a/core/tests/coretests/README b/core/tests/coretests/README new file mode 100644 index 000000000000..4a6984320e61 --- /dev/null +++ b/core/tests/coretests/README @@ -0,0 +1,50 @@ +* 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. + + +INTRODUCTION + +The Android platform core tests (APCT) consist of unit tests for core platform +functionality. These differ from CTS in that they are not necessarily testing +public APIs and are not guaranteed to work outside of AOSP builds. + + +INSTRUCTIONS + +To run a test or set of tests, first build the FrameworksCoreTests package: + + make FrameworksCoreTests + +Next, install the resulting APK and run tests as you would normal JUnit tests: + + adb install out/target/product/.../data/app/FrameworksCoreTests/FrameworksCoreTests.apk + adb shell am instrument -w \ + com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner + +To run a tests within a specific package, add the following argument AFTER -w: + + -e package android.content.pm + +To run a specific test or method within a test: + + -e class android.content.pm.PackageParserTest + -e class android.content.pm.PackageParserTest#testComputeMinSdkVersion + +To run tests in debug mode: + + -e debug true + +For more arguments, see the guide to command=line testing: + + https://developer.android.com/studio/test/command-line.html diff --git a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java b/core/tests/coretests/src/android/content/pm/PackageHelperTests.java index 5af2667d29ff..c4d00c677cc8 100644 --- a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java +++ b/core/tests/coretests/src/android/content/pm/PackageHelperTests.java @@ -16,17 +16,28 @@ package android.content.pm; -import static android.net.TrafficStats.MB_IN_BYTES; - +import android.content.Context; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.storage.IStorageManager; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; import android.test.AndroidTestCase; import android.util.Log; import com.android.internal.content.PackageHelper; +import org.mockito.Mockito; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static android.net.TrafficStats.MB_IN_BYTES; +import static android.os.storage.VolumeInfo.STATE_MOUNTED; + public class PackageHelperTests extends AndroidTestCase { private static final boolean localLOGV = true; public static final String TAG = "PackageHelperTests"; @@ -35,6 +46,94 @@ public class PackageHelperTests extends AndroidTestCase { private String fullId; private String fullId2; + private static final String sInternalVolPath = "/data"; + private static final String sAdoptedVolPath = "/mnt/expand/123"; + private static final String sPublicVolPath = "/emulated"; + + private static final String sInternalVolUuid = StorageManager.UUID_PRIVATE_INTERNAL; + private static final String sAdoptedVolUuid = "adopted"; + private static final String sPublicVolUuid = "emulated"; + + private static final long sInternalSize = 20000; + private static final long sAdoptedSize = 10000; + private static final long sPublicSize = 1000000; + + private static final StorageManager sStorageManager = createStorageManagerMock(); + + private static StorageManager createStorageManagerMock() { + VolumeInfo internalVol = new VolumeInfo("private", + VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/); + internalVol.path = sInternalVolPath; + internalVol.state = STATE_MOUNTED; + internalVol.fsUuid = sInternalVolUuid; + + VolumeInfo adoptedVol = new VolumeInfo("adopted", + VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/); + adoptedVol.path = sAdoptedVolPath; + adoptedVol.state = STATE_MOUNTED; + adoptedVol.fsUuid = sAdoptedVolUuid; + + VolumeInfo publicVol = new VolumeInfo("public", + VolumeInfo.TYPE_PUBLIC, null /*DiskInfo*/, null /*partGuid*/); + publicVol.state = STATE_MOUNTED; + publicVol.path = sPublicVolPath; + publicVol.fsUuid = sPublicVolUuid; + + List<VolumeInfo> volumes = new ArrayList<>(); + volumes.add(internalVol); + volumes.add(adoptedVol); + volumes.add(publicVol); + + StorageManager storageManager = Mockito.mock(StorageManager.class); + Mockito.when(storageManager.getVolumes()).thenReturn(volumes); + + File internalFile = new File(sInternalVolPath); + File adoptedFile = new File(sAdoptedVolPath); + File publicFile = new File(sPublicVolPath); + Mockito.when(storageManager.getStorageBytesUntilLow(internalFile)).thenReturn(sInternalSize); + Mockito.when(storageManager.getStorageBytesUntilLow(adoptedFile)).thenReturn(sAdoptedSize); + Mockito.when(storageManager.getStorageBytesUntilLow(publicFile)).thenReturn(sPublicSize); + return storageManager; + } + + private static final class MockedInterface extends PackageHelper.TestableInterface { + private boolean mForceAllowOnExternal = false; + private boolean mAllow3rdPartyOnInternal = true; + private ApplicationInfo mApplicationInfo = null; + + public void setMockValues(ApplicationInfo applicationInfo, + boolean forceAllowOnExternal, boolean allow3rdPartyOnInternal) { + mForceAllowOnExternal = forceAllowOnExternal; + mAllow3rdPartyOnInternal = allow3rdPartyOnInternal; + mApplicationInfo = applicationInfo; + } + + @Override + public StorageManager getStorageManager(Context context) { + return sStorageManager; + } + + @Override + public boolean getForceAllowOnExternalSetting(Context context) { + return mForceAllowOnExternal; + } + + @Override + public boolean getAllow3rdPartyOnInternalConfig(Context context) { + return mAllow3rdPartyOnInternal; + } + + @Override + public ApplicationInfo getExistingAppInfo(Context context, String packagename) { + return mApplicationInfo; + } + + @Override + public File getDataDirectory() { + return new File(sInternalVolPath); + } + } + private IStorageManager getSm() { IBinder service = ServiceManager.getService("mount"); if (service != null) { @@ -131,4 +230,328 @@ public class PackageHelperTests extends AndroidTestCase { }; return r; } + + public void testResolveInstallVolumeInternal_SystemApp() throws IOException { + ApplicationInfo systemAppInfo = new ApplicationInfo(); + systemAppInfo.flags = ApplicationInfo.FLAG_SYSTEM; + + // All test cases for when the system app fits on internal. + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); + + mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); + + mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); + + mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); + + + // All test cases for when the system app does not fit on internal. + // Exception should be thrown. + mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch(IOException e) { + // expected + } + + mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch(IOException e) { + // expected + } + + mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch(IOException e) { + // expected + } + + mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch(IOException e) { + // expected + } + } + + public void testResolveInstallVolumeInternal_3rdParty_existing_not_too_big() + throws IOException { + // Existing apps always stay on the same volume. + // Test cases for existing app on internal. + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.volumeUuid = sInternalVolUuid; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sInternalVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sInternalVolUuid, volume); + } + + public void testResolveInstallVolumeInternal_3rdParty_existing_not_too_big_adopted() + throws IOException { + // Test cases for existing app on the adopted media. + ApplicationInfo appInfo = new ApplicationInfo(); + MockedInterface mockedInterface = new MockedInterface(); + String volume; + appInfo.volumeUuid = sAdoptedVolUuid; + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + } + + public void testResolveInstallVolumeAdopted_3rdParty_existing_too_big() { + // Test: update size too big, will throw exception. + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.volumeUuid = sAdoptedVolUuid; + + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); + fail("Expected exception was not thrown " + appInfo.volumeUuid); + } catch (IOException e) { + //expected + } + + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); + fail("Expected exception was not thrown " + appInfo.volumeUuid); + } catch (IOException e) { + //expected + } + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); + fail("Expected exception was not thrown " + appInfo.volumeUuid); + } catch (IOException e) { + //expected + } + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); + fail("Expected exception was not thrown " + appInfo.volumeUuid); + } catch (IOException e) { + //expected + } + } + + public void testResolveInstallVolumeInternal_3rdParty_auto() throws IOException { + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the volume with bigger available space. + assertEquals(sInternalVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the volume with bigger available space. + assertEquals(sInternalVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the volume with bigger available space. + assertEquals(sAdoptedVolUuid, volume); + + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the volume with bigger available space. + assertEquals(sAdoptedVolUuid, volume); + + + } + + public void testResolveInstallVolumeInternal_3rdParty_internal_only() throws IOException { + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sInternalVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sInternalVolUuid, volume); + + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + try { + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch (IOException e) { + //expected + } + + appInfo = null; + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + } + + public void testResolveInstallVolumeInternal_3rdParty_not_allowed_on_internal() + throws IOException { + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the non-internal volume. + assertEquals(sAdoptedVolUuid, volume); + + appInfo = null; + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the non-internal volume. + assertEquals(sAdoptedVolUuid, volume); + } + + public void testResolveInstallVolumeInternal_3rdParty_internal_only_too_big() { + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + String volume = null; + try { + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal ONLY*/, + 1000000 /*size too big*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch (IOException e) { + //expected + } + } + + public void testResolveInstallVolumeInternal_3rdParty_internal_only_not_allowed() + throws IOException { + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + String volume = null; + try { + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch (IOException e) { + //expected + } + + appInfo = null; + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + + } + + public void testResolveInstallVolumeInternal_3rdParty_internal_only_forced_to_external() + throws IOException { + // New/existing installation: New + // app request location: Internal Only + // 3rd party allowed on internal: False + // Force allow external in setting: True + // Size fit? Yes + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + } } diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java new file mode 100644 index 000000000000..2a3c22c64ec2 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java @@ -0,0 +1,218 @@ +/* + * 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.content.pm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import android.os.Build; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class PackageParserTest { + private static final String RELEASED = null; + private static final String OLDER_PRE_RELEASE = "A"; + private static final String PRE_RELEASE = "B"; + private static final String NEWER_PRE_RELEASE = "C"; + + private static final String[] CODENAMES_RELEASED = { /* empty */ }; + private static final String[] CODENAMES_PRE_RELEASE = { PRE_RELEASE }; + + private static final int OLDER_VERSION = 10; + private static final int PLATFORM_VERSION = 20; + private static final int NEWER_VERSION = 30; + + private void verifyComputeMinSdkVersion(int minSdkVersion, String minSdkCodename, + boolean isPlatformReleased, int expectedMinSdk) { + final String[] outError = new String[1]; + final int result = PackageParser.computeMinSdkVersion( + minSdkVersion, + minSdkCodename, + PLATFORM_VERSION, + isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE, + outError); + + assertEquals(result, expectedMinSdk); + + if (expectedMinSdk == -1) { + assertNotNull(outError[0]); + } else { + assertNull(outError[0]); + } + } + + @Test + public void testComputeMinSdkVersion_preReleasePlatform() { + // Do allow older release minSdkVersion on pre-release platform. + // APP: Released API 10 + // DEV: Pre-release API 20 + verifyComputeMinSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION); + + // Do allow same release minSdkVersion on pre-release platform. + // APP: Released API 20 + // DEV: Pre-release API 20 + verifyComputeMinSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION); + + // Don't allow newer release minSdkVersion on pre-release platform. + // APP: Released API 30 + // DEV: Pre-release API 20 + verifyComputeMinSdkVersion(NEWER_VERSION, RELEASED, false, -1); + + // Don't allow older pre-release minSdkVersion on pre-release platform. + // APP: Pre-release API 10 + // DEV: Pre-release API 20 + verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1); + + // Do allow same pre-release minSdkVersion on pre-release platform, + // but overwrite the specified version with CUR_DEVELOPMENT. + // APP: Pre-release API 20 + // DEV: Pre-release API 20 + verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false, + Build.VERSION_CODES.CUR_DEVELOPMENT); + + // Don't allow newer pre-release minSdkVersion on pre-release platform. + // APP: Pre-release API 30 + // DEV: Pre-release API 20 + verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1); + } + + @Test + public void testComputeMinSdkVersion_releasedPlatform() { + // Do allow older release minSdkVersion on released platform. + // APP: Released API 10 + // DEV: Released API 20 + verifyComputeMinSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION); + + // Do allow same release minSdkVersion on released platform. + // APP: Released API 20 + // DEV: Released API 20 + verifyComputeMinSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION); + + // Don't allow newer release minSdkVersion on released platform. + // APP: Released API 30 + // DEV: Released API 20 + verifyComputeMinSdkVersion(NEWER_VERSION, RELEASED, true, -1); + + // Don't allow older pre-release minSdkVersion on released platform. + // APP: Pre-release API 10 + // DEV: Released API 20 + verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1); + + // Don't allow same pre-release minSdkVersion on released platform. + // APP: Pre-release API 20 + // DEV: Released API 20 + verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1); + + // Don't allow newer pre-release minSdkVersion on released platform. + // APP: Pre-release API 30 + // DEV: Released API 20 + verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1); + } + + private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename, + boolean isPlatformReleased, int expectedTargetSdk) { + final String[] outError = new String[1]; + final int result = PackageParser.computeTargetSdkVersion( + targetSdkVersion, + targetSdkCodename, + PLATFORM_VERSION, + isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE, + outError); + + assertEquals(result, expectedTargetSdk); + + if (expectedTargetSdk == -1) { + assertNotNull(outError[0]); + } else { + assertNull(outError[0]); + } + } + + @Test + public void testComputeTargetSdkVersion_preReleasePlatform() { + // Do allow older release targetSdkVersion on pre-release platform. + // APP: Released API 10 + // DEV: Pre-release API 20 + verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION); + + // Do allow same release targetSdkVersion on pre-release platform. + // APP: Released API 20 + // DEV: Pre-release API 20 + verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION); + + // Do allow newer release targetSdkVersion on pre-release platform. + // APP: Released API 30 + // DEV: Pre-release API 20 + verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, NEWER_VERSION); + + // Don't allow older pre-release targetSdkVersion on pre-release platform. + // APP: Pre-release API 10 + // DEV: Pre-release API 20 + verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1); + + // Do allow same pre-release targetSdkVersion on pre-release platform, + // but overwrite the specified version with CUR_DEVELOPMENT. + // APP: Pre-release API 20 + // DEV: Pre-release API 20 + verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false, + Build.VERSION_CODES.CUR_DEVELOPMENT); + + // Don't allow newer pre-release targetSdkVersion on pre-release platform. + // APP: Pre-release API 30 + // DEV: Pre-release API 20 + verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1); + } + + @Test + public void testComputeTargetSdkVersion_releasedPlatform() { + // Do allow older release targetSdkVersion on released platform. + // APP: Released API 10 + // DEV: Released API 20 + verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION); + + // Do allow same release targetSdkVersion on released platform. + // APP: Released API 20 + // DEV: Released API 20 + verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION); + + // Do allow newer release targetSdkVersion on released platform. + // APP: Released API 30 + // DEV: Released API 20 + verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, NEWER_VERSION); + + // Don't allow older pre-release targetSdkVersion on released platform. + // APP: Pre-release API 10 + // DEV: Released API 20 + verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1); + + // Don't allow same pre-release targetSdkVersion on released platform. + // APP: Pre-release API 20 + // DEV: Released API 20 + verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1); + + // Don't allow newer pre-release targetSdkVersion on released platform. + // APP: Pre-release API 30 + // DEV: Released API 20 + verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1); + } +} diff --git a/core/tests/coretests/src/android/content/res/ConfigurationTest.java b/core/tests/coretests/src/android/content/res/ConfigurationTest.java new file mode 100644 index 000000000000..72b9197089d7 --- /dev/null +++ b/core/tests/coretests/src/android/content/res/ConfigurationTest.java @@ -0,0 +1,58 @@ +/* +1;3409;0c * 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.content.res; + +import org.junit.runner.RunWith; +import org.junit.Test; +import org.junit.runners.JUnit4; + +import android.content.res.Configuration; +import android.support.test.filters.SmallTest; +import android.platform.test.annotations.Presubmit; + +import junit.framework.TestCase; + +import static org.junit.Assert.assertEquals; + +/** + * Build/install/run: bit FrameworksCoreTests:android.content.res.ConfigurationTest + */ +@RunWith(JUnit4.class) +@SmallTest +@Presubmit +public class ConfigurationTest extends TestCase { + @Test + public void testUpdateFromPreservesRoundBit() { + Configuration config = new Configuration(); + config.screenLayout = Configuration.SCREENLAYOUT_ROUND_YES; + Configuration config2 = new Configuration(); + + config.updateFrom(config2); + assertEquals(config.screenLayout, Configuration.SCREENLAYOUT_ROUND_YES); + } + + @Test + public void testUpdateFromPreservesCompatNeededBit() { + Configuration config = new Configuration(); + config.screenLayout = Configuration.SCREENLAYOUT_COMPAT_NEEDED; + Configuration config2 = new Configuration(); + config.updateFrom(config2); + assertEquals(config.screenLayout, Configuration.SCREENLAYOUT_COMPAT_NEEDED); + + config2.updateFrom(config); + assertEquals(config2.screenLayout, Configuration.SCREENLAYOUT_COMPAT_NEEDED); + } +} diff --git a/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java new file mode 100644 index 000000000000..9a81401e535e --- /dev/null +++ b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java @@ -0,0 +1,161 @@ +package android.net; + +import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT; +import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE; + +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IRemoteCallback; +import android.test.InstrumentationTestCase; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Unit test for the {@link NetworkRecommendationProvider}. + */ +public class NetworkRecommendationProviderTest extends InstrumentationTestCase { + @Mock private IRemoteCallback mMockRemoteCallback; + private NetworkRecProvider mRecProvider; + private Handler mHandler; + private INetworkRecommendationProvider mStub; + private CountDownLatch mRecRequestLatch; + private CountDownLatch mScoreRequestLatch; + private NetworkKey[] mTestNetworkKeys; + + @Override + public void setUp() throws Exception { + super.setUp(); + + // Configuration needed to make mockito/dexcache work. + final Context context = getInstrumentation().getTargetContext(); + System.setProperty("dexmaker.dexcache", + context.getCacheDir().getPath()); + ClassLoader newClassLoader = getInstrumentation().getClass().getClassLoader(); + Thread.currentThread().setContextClassLoader(newClassLoader); + + MockitoAnnotations.initMocks(this); + + HandlerThread thread = new HandlerThread("NetworkRecommendationProviderTest"); + thread.start(); + mRecRequestLatch = new CountDownLatch(1); + mScoreRequestLatch = new CountDownLatch(1); + mHandler = new Handler(thread.getLooper()); + mRecProvider = new NetworkRecProvider(mHandler, mRecRequestLatch, mScoreRequestLatch); + mStub = INetworkRecommendationProvider.Stub.asInterface(mRecProvider.getBinder()); + mTestNetworkKeys = new NetworkKey[2]; + mTestNetworkKeys[0] = new NetworkKey(new WifiKey("\"ssid_01\"", "00:00:00:00:00:11")); + mTestNetworkKeys[1] = new NetworkKey(new WifiKey("\"ssid_02\"", "00:00:00:00:00:22")); + } + + @MediumTest + public void testRecommendationRequestReceived() throws Exception { + final RecommendationRequest request = new RecommendationRequest.Builder().build(); + final int sequence = 100; + mStub.requestRecommendation(request, mMockRemoteCallback, sequence); + + // wait for onRequestRecommendation() to be called in our impl below. + mRecRequestLatch.await(200, TimeUnit.MILLISECONDS); + NetworkRecommendationProvider.ResultCallback expectedResultCallback = + new NetworkRecommendationProvider.ResultCallback(mMockRemoteCallback, sequence); + assertEquals(request, mRecProvider.mCapturedRequest); + assertEquals(expectedResultCallback, mRecProvider.mCapturedCallback); + } + + @SmallTest + public void testResultCallbackOnResult() throws Exception { + final int sequence = 100; + final NetworkRecommendationProvider.ResultCallback callback = + new NetworkRecommendationProvider.ResultCallback(mMockRemoteCallback, sequence); + + final RecommendationResult result = new RecommendationResult(null); + callback.onResult(result); + + final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + Mockito.verify(mMockRemoteCallback).sendResult(bundleCaptor.capture()); + Bundle capturedBundle = bundleCaptor.getValue(); + assertEquals(sequence, capturedBundle.getInt(EXTRA_SEQUENCE)); + assertSame(result, capturedBundle.getParcelable(EXTRA_RECOMMENDATION_RESULT)); + } + + @SmallTest + public void testResultCallbackOnResult_runTwice_throwsException() throws Exception { + final int sequence = 100; + final NetworkRecommendationProvider.ResultCallback callback = + new NetworkRecommendationProvider.ResultCallback(mMockRemoteCallback, sequence); + + final RecommendationResult result = new RecommendationResult(null); + callback.onResult(result); + + try { + callback.onResult(result); + fail("Callback ran more than once."); + } catch (IllegalStateException e) { + // expected + } + } + + @MediumTest + public void testScoreRequestReceived() throws Exception { + mStub.requestScores(mTestNetworkKeys); + + // wait for onRequestScores() to be called in our impl below. + mScoreRequestLatch.await(200, TimeUnit.MILLISECONDS); + + assertSame(mTestNetworkKeys, mRecProvider.mCapturedNetworks); + } + + @MediumTest + public void testScoreRequest_nullInput() throws Exception { + mStub.requestScores(null); + + // onRequestScores() should never be called + assertFalse(mScoreRequestLatch.await(200, TimeUnit.MILLISECONDS)); + } + + @MediumTest + public void testScoreRequest_emptyInput() throws Exception { + mStub.requestScores(new NetworkKey[0]); + + // onRequestScores() should never be called + assertFalse(mScoreRequestLatch.await(200, TimeUnit.MILLISECONDS)); + } + + private static class NetworkRecProvider extends NetworkRecommendationProvider { + private final CountDownLatch mRecRequestLatch; + private final CountDownLatch mScoreRequestLatch; + RecommendationRequest mCapturedRequest; + ResultCallback mCapturedCallback; + NetworkKey[] mCapturedNetworks; + + NetworkRecProvider(Handler handler, CountDownLatch recRequestLatch, + CountDownLatch networkRequestLatch) { + super(handler); + mRecRequestLatch = recRequestLatch; + mScoreRequestLatch = networkRequestLatch; + } + + @Override + public void onRequestRecommendation(RecommendationRequest request, + ResultCallback callback) { + mCapturedRequest = request; + mCapturedCallback = callback; + mRecRequestLatch.countDown(); + } + + @Override + public void onRequestScores(NetworkKey[] networks) { + mCapturedNetworks = networks; + mScoreRequestLatch.countDown(); + } + } +} diff --git a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java index 02c25170bb74..5bfff26b0813 100644 --- a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java +++ b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java @@ -16,32 +16,33 @@ package android.net; +import static org.mockito.Mockito.when; + import android.Manifest.permission; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.res.Resources; import android.net.NetworkScorerAppManager.NetworkScorerAppData; -import android.os.UserHandle; +import android.provider.Settings; import android.test.InstrumentationTestCase; - +import com.android.internal.R; +import java.util.List; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - public class NetworkScorerAppManagerTest extends InstrumentationTestCase { @Mock private Context mMockContext; @Mock private PackageManager mMockPm; - + @Mock private Resources mResources; + @Mock private ContentResolver mContentResolver; + private Context mTargetContext; private NetworkScorerAppManager mNetworkScorerAppManager; @Override @@ -49,154 +50,161 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase { super.setUp(); // Configuration needed to make mockito/dexcache work. - System.setProperty("dexmaker.dexcache", - getInstrumentation().getTargetContext().getCacheDir().getPath()); + mTargetContext = getInstrumentation().getTargetContext(); + System.setProperty("dexmaker.dexcache", mTargetContext.getCacheDir().getPath()); ClassLoader newClassLoader = getInstrumentation().getClass().getClassLoader(); Thread.currentThread().setContextClassLoader(newClassLoader); MockitoAnnotations.initMocks(this); - Mockito.when(mMockContext.getPackageManager()).thenReturn(mMockPm); + when(mMockContext.getPackageManager()).thenReturn(mMockPm); + when(mMockContext.getResources()).thenReturn(mResources); + when(mMockContext.getContentResolver()).thenReturn(mTargetContext.getContentResolver()); mNetworkScorerAppManager = new NetworkScorerAppManager(mMockContext); } - public void testGetAllValidScorers() throws Exception { - // Package 1 - Valid scorer. - ResolveInfoHolder package1 = buildResolveInfo("package1", 1, true, true, false, false); - - // Package 2 - Receiver does not have BROADCAST_NETWORK_PRIVILEGED permission. - ResolveInfoHolder package2 = buildResolveInfo("package2", 2, false, true, false, false); - - // Package 3 - App does not have SCORE_NETWORKS permission. - ResolveInfoHolder package3 = buildResolveInfo("package3", 3, true, false, false, false); - - // Package 4 - Valid scorer w/ optional config activity. - ResolveInfoHolder package4 = buildResolveInfo("package4", 4, true, true, true, false); - - // Package 5 - Valid scorer w/ optional service to bind to. - ResolveInfoHolder package5 = buildResolveInfo("package5", 5, true, true, false, true); - - List<ResolveInfoHolder> scorers = new ArrayList<>(); - scorers.add(package1); - scorers.add(package2); - scorers.add(package3); - scorers.add(package4); - scorers.add(package5); - setScorers(scorers); - - Iterator<NetworkScorerAppData> result = - mNetworkScorerAppManager.getAllValidScorers().iterator(); - - assertTrue(result.hasNext()); - NetworkScorerAppData next = result.next(); - assertEquals("package1", next.mPackageName); - assertEquals(1, next.mPackageUid); - assertNull(next.mConfigurationActivityClassName); - - assertTrue(result.hasNext()); - next = result.next(); - assertEquals("package4", next.mPackageName); - assertEquals(4, next.mPackageUid); - assertEquals(".ConfigActivity", next.mConfigurationActivityClassName); - - assertTrue(result.hasNext()); - next = result.next(); - assertEquals("package5", next.mPackageName); - assertEquals(5, next.mPackageUid); - assertEquals(".ScoringService", next.mScoringServiceClassName); - - assertFalse(result.hasNext()); - } - - private void setScorers(List<ResolveInfoHolder> scorers) { - List<ResolveInfo> receivers = new ArrayList<>(); - for (final ResolveInfoHolder scorer : scorers) { - receivers.add(scorer.scorerResolveInfo); - if (scorer.configActivityResolveInfo != null) { - // This scorer has a config activity. - Mockito.when(mMockPm.queryIntentActivities( - Mockito.argThat(new ArgumentMatcher<Intent>() { - @Override - public boolean matches(Object object) { - Intent intent = (Intent) object; - return NetworkScoreManager.ACTION_CUSTOM_ENABLE.equals( - intent.getAction()) - && scorer.scorerResolveInfo.activityInfo.packageName.equals( - intent.getPackage()); - } - }), Mockito.eq(0))).thenReturn( - Collections.singletonList(scorer.configActivityResolveInfo)); - } - - if (scorer.serviceResolveInfo != null) { - // This scorer has a service to bind to - Mockito.when(mMockPm.resolveService( - Mockito.argThat(new ArgumentMatcher<Intent>() { - @Override - public boolean matches(Object object) { - Intent intent = (Intent) object; - return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals( - intent.getAction()) - && scorer.scorerResolveInfo.activityInfo.packageName.equals( - intent.getPackage()); - } - }), Mockito.eq(0))).thenReturn(scorer.serviceResolveInfo); - } - } + public void testGetPotentialRecommendationProviderPackages_emptyConfig() throws Exception { + setNetworkRecommendationPackageNames(/*no configured packages*/); + assertTrue(mNetworkScorerAppManager.getPotentialRecommendationProviderPackages().isEmpty()); + } - Mockito.when(mMockPm.queryBroadcastReceiversAsUser( - Mockito.argThat(new ArgumentMatcher<Intent>() { - @Override - public boolean matches(Object object) { - Intent intent = (Intent) object; - return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals(intent.getAction()); - } - }), Mockito.eq(0), Mockito.eq(UserHandle.USER_SYSTEM))) - .thenReturn(receivers); - } - - private ResolveInfoHolder buildResolveInfo(String packageName, int packageUid, - boolean hasReceiverPermission, boolean hasScorePermission, boolean hasConfigActivity, - boolean hasServiceInfo) throws Exception { - Mockito.when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName)) - .thenReturn(hasScorePermission ? - PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED); - - ResolveInfo resolveInfo = new ResolveInfo(); - resolveInfo.activityInfo = new ActivityInfo(); - resolveInfo.activityInfo.packageName = packageName; - resolveInfo.activityInfo.applicationInfo = new ApplicationInfo(); - resolveInfo.activityInfo.applicationInfo.uid = packageUid; - if (hasReceiverPermission) { - resolveInfo.activityInfo.permission = permission.BROADCAST_NETWORK_PRIVILEGED; - } + public void testGetPotentialRecommendationProviderPackages_permissionNotGranted() + throws Exception { + setNetworkRecommendationPackageNames("package1"); + mockScoreNetworksDenied("package1"); - ResolveInfo configActivityInfo = null; - if (hasConfigActivity) { - configActivityInfo = new ResolveInfo(); - configActivityInfo.activityInfo = new ActivityInfo(); - configActivityInfo.activityInfo.name = ".ConfigActivity"; - } + assertTrue(mNetworkScorerAppManager.getPotentialRecommendationProviderPackages().isEmpty()); + } - ResolveInfo serviceInfo = null; - if (hasServiceInfo) { - serviceInfo = new ResolveInfo(); - serviceInfo.serviceInfo = new ServiceInfo(); - serviceInfo.serviceInfo.name = ".ScoringService"; - } + public void testGetPotentialRecommendationProviderPackages_permissionGranted() + throws Exception { + setNetworkRecommendationPackageNames("package1"); + mockScoreNetworksGranted("package1"); + + List<String> potentialProviderPackages = + mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); + + assertFalse(potentialProviderPackages.isEmpty()); + assertEquals("package1", potentialProviderPackages.get(0)); + } + + public void testGetPotentialRecommendationProviderPackages_multipleConfigured() + throws Exception { + setNetworkRecommendationPackageNames("package1", "package2"); + mockScoreNetworksDenied("package1"); + mockScoreNetworksGranted("package2"); + + List<String> potentialProviderPackages = + mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); + + assertEquals(1, potentialProviderPackages.size()); + assertEquals("package2", potentialProviderPackages.get(0)); + } + + public void testGetNetworkRecommendationProviderData_noPotentialPackages() throws Exception { + setNetworkRecommendationPackageNames(/*no configured packages*/); + assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData()); + } + + public void testGetNetworkRecommendationProviderData_serviceMissing() throws Exception { + setNetworkRecommendationPackageNames("package1"); + mockScoreNetworksGranted("package1"); - return new ResolveInfoHolder(resolveInfo, configActivityInfo, serviceInfo); + assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData()); } - private static class ResolveInfoHolder { - final ResolveInfo scorerResolveInfo; - final ResolveInfo configActivityResolveInfo; - final ResolveInfo serviceResolveInfo; + public void testGetNetworkRecommendationProviderData_scoreNetworksNotGranted() + throws Exception { + setNetworkRecommendationPackageNames("package1"); + mockScoreNetworksDenied("package1"); + mockRecommendationServiceAvailable("package1", 924 /* packageUid */); - public ResolveInfoHolder(ResolveInfo scorerResolveInfo, - ResolveInfo configActivityResolveInfo, ResolveInfo serviceResolveInfo) { - this.scorerResolveInfo = scorerResolveInfo; - this.configActivityResolveInfo = configActivityResolveInfo; - this.serviceResolveInfo = serviceResolveInfo; + assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData()); + } + + public void testGetNetworkRecommendationProviderData_available() throws Exception { + setNetworkRecommendationPackageNames("package1"); + mockScoreNetworksGranted("package1"); + mockRecommendationServiceAvailable("package1", 924 /* packageUid */); + + NetworkScorerAppData appData = + mNetworkScorerAppManager.getNetworkRecommendationProviderData(); + assertNotNull(appData); + assertEquals("package1", appData.packageName); + assertEquals(924, appData.packageUid); + assertEquals(".RecommendationService", appData.recommendationServiceClassName); + } + + public void testGetActiveScorer_providerAvailable() throws Exception { + setNetworkRecommendationPackageNames("package1"); + mockScoreNetworksGranted("package1"); + mockRecommendationServiceAvailable("package1", 924 /* packageUid */); + + ContentResolver cr = mTargetContext.getContentResolver(); + Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1); + + final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); + assertNotNull(activeScorer); + assertEquals("package1", activeScorer.packageName); + assertEquals(924, activeScorer.packageUid); + assertEquals(".RecommendationService", activeScorer.recommendationServiceClassName); + } + + public void testGetActiveScorer_providerNotAvailable() + throws Exception { + ContentResolver cr = mTargetContext.getContentResolver(); + Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1); + + final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); + assertNull(activeScorer); + } + + public void testGetActiveScorer_recommendationsDisabled() throws Exception { + setNetworkRecommendationPackageNames("package1"); + mockScoreNetworksGranted("package1"); + mockRecommendationServiceAvailable("package1", 924 /* packageUid */); + ContentResolver cr = mTargetContext.getContentResolver(); + Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0); + + final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); + assertNull(activeScorer); + } + + private void setNetworkRecommendationPackageNames(String... names) { + if (names == null) { + names = new String[0]; } + when(mResources.getStringArray(R.array.config_networkRecommendationPackageNames)) + .thenReturn(names); + } + + private void mockScoreNetworksGranted(String packageName) { + when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + } + + private void mockScoreNetworksDenied(String packageName) { + when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName)) + .thenReturn(PackageManager.PERMISSION_DENIED); + } + + private void mockRecommendationServiceAvailable(final String packageName, int packageUid) { + final ResolveInfo serviceInfo = new ResolveInfo(); + serviceInfo.serviceInfo = new ServiceInfo(); + serviceInfo.serviceInfo.name = ".RecommendationService"; + serviceInfo.serviceInfo.packageName = packageName; + serviceInfo.serviceInfo.applicationInfo = new ApplicationInfo(); + serviceInfo.serviceInfo.applicationInfo.uid = packageUid; + + final int flags = 0; + when(mMockPm.resolveService( + Mockito.argThat(new ArgumentMatcher<Intent>() { + @Override + public boolean matches(Object object) { + Intent intent = (Intent) object; + return NetworkScoreManager.ACTION_RECOMMEND_NETWORKS + .equals(intent.getAction()) + && packageName.equals(intent.getPackage()); + } + }), Mockito.eq(flags))).thenReturn(serviceInfo); } } diff --git a/core/tests/coretests/src/android/net/RecommendationRequestTest.java b/core/tests/coretests/src/android/net/RecommendationRequestTest.java new file mode 100644 index 000000000000..31560b0ac8c3 --- /dev/null +++ b/core/tests/coretests/src/android/net/RecommendationRequestTest.java @@ -0,0 +1,84 @@ +package android.net; + +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.os.Parcel; +import android.test.AndroidTestCase; + +public class RecommendationRequestTest extends AndroidTestCase { + private ScanResult[] mScanResults; + private WifiConfiguration mConfiguration; + private NetworkCapabilities mCapabilities; + + @Override + public void setUp() throws Exception { + mScanResults = new ScanResult[2]; + mScanResults[0] = new ScanResult(); + mScanResults[1] = new ScanResult( + "ssid", + "bssid", + 0L /*hessid*/, + 1 /*anqpDominId*/, + "caps", + 2 /*level*/, + 3 /*frequency*/, + 4L /*tsf*/, + 5 /*distCm*/, + 6 /*distSdCm*/, + 7 /*channelWidth*/, + 8 /*centerFreq0*/, + 9 /*centerFreq1*/, + false /*is80211McRTTResponder*/); + mConfiguration = new WifiConfiguration(); + mConfiguration.SSID = "RecommendationRequestTest"; + mCapabilities = new NetworkCapabilities() + .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED); + } + + public void testParceling() throws Exception { + RecommendationRequest request = new RecommendationRequest.Builder() + .setCurrentRecommendedWifiConfig(mConfiguration) + .setScanResults(mScanResults) + .setNetworkCapabilities(mCapabilities) + .build(); + + RecommendationRequest parceled = passThroughParcel(request); + assertEquals(request.getCurrentSelectedConfig().SSID, + parceled.getCurrentSelectedConfig().SSID); + assertEquals(request.getRequiredCapabilities(), parceled.getRequiredCapabilities()); + ScanResult[] parceledScanResults = parceled.getScanResults(); + assertNotNull(parceledScanResults); + assertEquals(mScanResults.length, parceledScanResults.length); + for (int i = 0; i < mScanResults.length; i++) { + assertEquals(mScanResults[i].SSID, parceledScanResults[i].SSID); + } + } + + public void testParceling_nullScanResults() throws Exception { + RecommendationRequest request = new RecommendationRequest.Builder() + .setCurrentRecommendedWifiConfig(mConfiguration) + .setNetworkCapabilities(mCapabilities) + .build(); + + RecommendationRequest parceled = passThroughParcel(request); + assertEquals(request.getCurrentSelectedConfig().SSID, + parceled.getCurrentSelectedConfig().SSID); + assertEquals(request.getRequiredCapabilities(), parceled.getRequiredCapabilities()); + ScanResult[] parceledScanResults = parceled.getScanResults(); + assertNull(parceledScanResults); + } + + private RecommendationRequest passThroughParcel(RecommendationRequest request) { + Parcel p = Parcel.obtain(); + RecommendationRequest output = null; + try { + request.writeToParcel(p, 0); + p.setDataPosition(0); + output = RecommendationRequest.CREATOR.createFromParcel(p); + } finally { + p.recycle(); + } + assertNotNull(output); + return output; + } +} diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java index 909f971db1b1..de43fc67dcf5 100644 --- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java +++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java @@ -61,6 +61,18 @@ public class DateUtilsTest extends TestCase { assertEquals("1 second", DateUtils.formatDuration(500)); assertEquals("1 second", DateUtils.formatDuration(1000)); assertEquals("2 seconds", DateUtils.formatDuration(1500)); + + assertEquals("0 seconds", DateUtils.formatDuration(0, DateUtils.LENGTH_LONG)); + assertEquals("1 second", DateUtils.formatDuration(1000, DateUtils.LENGTH_LONG)); + assertEquals("2 seconds", DateUtils.formatDuration(1500, DateUtils.LENGTH_LONG)); + + assertEquals("0 sec", DateUtils.formatDuration(0, DateUtils.LENGTH_SHORT)); + assertEquals("1 sec", DateUtils.formatDuration(1000, DateUtils.LENGTH_SHORT)); + assertEquals("2 sec", DateUtils.formatDuration(1500, DateUtils.LENGTH_SHORT)); + + assertEquals("0s", DateUtils.formatDuration(0, DateUtils.LENGTH_SHORTEST)); + assertEquals("1s", DateUtils.formatDuration(1000, DateUtils.LENGTH_SHORTEST)); + assertEquals("2s", DateUtils.formatDuration(1500, DateUtils.LENGTH_SHORTEST)); } @SmallTest @@ -69,6 +81,15 @@ public class DateUtilsTest extends TestCase { assertEquals("60 seconds", DateUtils.formatDuration(59500)); assertEquals("1 minute", DateUtils.formatDuration(60000)); assertEquals("2 minutes", DateUtils.formatDuration(120000)); + + assertEquals("1 minute", DateUtils.formatDuration(60000, DateUtils.LENGTH_LONG)); + assertEquals("2 minutes", DateUtils.formatDuration(120000, DateUtils.LENGTH_LONG)); + + assertEquals("1 min", DateUtils.formatDuration(60000, DateUtils.LENGTH_SHORT)); + assertEquals("2 min", DateUtils.formatDuration(120000, DateUtils.LENGTH_SHORT)); + + assertEquals("1m", DateUtils.formatDuration(60000, DateUtils.LENGTH_SHORTEST)); + assertEquals("2m", DateUtils.formatDuration(120000, DateUtils.LENGTH_SHORTEST)); } @SmallTest @@ -76,5 +97,14 @@ public class DateUtilsTest extends TestCase { assertEquals("59 minutes", DateUtils.formatDuration(3540000)); assertEquals("1 hour", DateUtils.formatDuration(3600000)); assertEquals("48 hours", DateUtils.formatDuration(172800000)); + + assertEquals("1 hour", DateUtils.formatDuration(3600000, DateUtils.LENGTH_LONG)); + assertEquals("48 hours", DateUtils.formatDuration(172800000, DateUtils.LENGTH_LONG)); + + assertEquals("1 hr", DateUtils.formatDuration(3600000, DateUtils.LENGTH_SHORT)); + assertEquals("48 hr", DateUtils.formatDuration(172800000, DateUtils.LENGTH_SHORT)); + + assertEquals("1h", DateUtils.formatDuration(3600000, DateUtils.LENGTH_SHORTEST)); + assertEquals("48h", DateUtils.formatDuration(172800000, DateUtils.LENGTH_SHORTEST)); } } diff --git a/core/tests/coretests/src/android/text/method/WordIteratorTest.java b/core/tests/coretests/src/android/text/method/WordIteratorTest.java index 28a480dd2b2e..66cf65f7bc05 100644 --- a/core/tests/coretests/src/android/text/method/WordIteratorTest.java +++ b/core/tests/coretests/src/android/text/method/WordIteratorTest.java @@ -516,4 +516,34 @@ public class WordIteratorTest extends AndroidTestCase { assertFalse(wordIterator.isOnPunctuation(text.length())); assertFalse(wordIterator.isOnPunctuation(text.length() + 1)); } + + @SmallTest + public void testApostropheMiddleOfWord() { + // These tests confirm that the word "isn't" is treated like one word. + final String text = "isn't he"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + wordIterator.setCharSequence(text, 0, text.length()); + + assertEquals(text.indexOf('i'), wordIterator.preceding(text.indexOf('h'))); + assertEquals(text.indexOf('t') + 1, wordIterator.following(text.indexOf('i'))); + + assertTrue(wordIterator.isBoundary(text.indexOf('i'))); + assertFalse(wordIterator.isBoundary(text.indexOf('\''))); + assertFalse(wordIterator.isBoundary(text.indexOf('t'))); + assertTrue(wordIterator.isBoundary(text.indexOf('t') + 1)); + assertTrue(wordIterator.isBoundary(text.indexOf('h'))); + + assertEquals(text.indexOf('i'), wordIterator.getBeginning(text.indexOf('i'))); + assertEquals(text.indexOf('i'), wordIterator.getBeginning(text.indexOf('n'))); + assertEquals(text.indexOf('i'), wordIterator.getBeginning(text.indexOf('\''))); + assertEquals(text.indexOf('i'), wordIterator.getBeginning(text.indexOf('t'))); + assertEquals(text.indexOf('i'), wordIterator.getBeginning(text.indexOf('t') + 1)); + assertEquals(text.indexOf('h'), wordIterator.getBeginning(text.indexOf('h'))); + + assertEquals(text.indexOf('t') + 1, wordIterator.getEnd(text.indexOf('i'))); + assertEquals(text.indexOf('t') + 1, wordIterator.getEnd(text.indexOf('n'))); + assertEquals(text.indexOf('t') + 1, wordIterator.getEnd(text.indexOf('\''))); + assertEquals(text.indexOf('t') + 1, wordIterator.getEnd(text.indexOf('t'))); + assertEquals(text.indexOf('e') + 1, wordIterator.getEnd(text.indexOf('h'))); + } } diff --git a/core/tests/coretests/src/android/view/ViewCaptureTest.java b/core/tests/coretests/src/android/view/ViewCaptureTest.java index 15cfe230eb57..1b4d4a2fcd3a 100644 --- a/core/tests/coretests/src/android/view/ViewCaptureTest.java +++ b/core/tests/coretests/src/android/view/ViewCaptureTest.java @@ -16,6 +16,8 @@ package android.view; +import static org.junit.Assert.assertTrue; + import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -32,8 +34,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.assertTrue; - @RunWith(AndroidJUnit4.class) public class ViewCaptureTest { @@ -70,6 +70,7 @@ public class ViewCaptureTest { private void testCreateSnapshot(boolean skipChildren, int goldenResId) { Bitmap result = mViewToCapture.createSnapshot(Bitmap.Config.ARGB_8888, 0, skipChildren); + result.setHasAlpha(false); // resource will have no alpha, since content is opaque Bitmap golden = BitmapFactory.decodeResource(mActivity.getResources(), goldenResId); assertTrue(golden.sameAs(result)); } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java index a15e3679a181..f1aeecc46265 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsDurationTimerTest.java @@ -18,12 +18,9 @@ package com.android.internal.os; import android.os.BatteryStats; import android.os.Parcel; import android.support.test.filters.SmallTest; -import android.util.Log; import junit.framework.TestCase; -import org.mockito.Mockito; - /** * Test BatteryStatsImpl.DurationTimer. * @@ -39,7 +36,7 @@ public class BatteryStatsDurationTimerTest extends TestCase { final BatteryStatsImpl.TimeBase timeBase = new BatteryStatsImpl.TimeBase(); timeBase.init(clocks.uptimeMillis(), clocks.elapsedRealtime()); - final BatteryStatsImpl.DurationTimer timer = new BatteryStatsImpl.DurationTimer(clocks, + final BatteryStatsImpl.DurationTimer timer = new BatteryStatsImpl.DurationTimer(clocks, null, BatteryStats.WAKE_TYPE_PARTIAL, null, timeBase); // TimeBase running, timer not running: current and max are 0 @@ -82,15 +79,15 @@ public class BatteryStatsDurationTimerTest extends TestCase { // Stop the TimeBase. The values should be frozen. timeBase.setRunning(false, /* uptimeUs */ 10, /* realtimeUs */ 55000*1000); assertTrue(timer.isRunningLocked()); - assertEquals(28100, timer.getCurrentDurationMsLocked(110100)); // Why 28100 and not 28000? - assertEquals(28100, timer.getMaxDurationMsLocked(110101)); + assertEquals(28000, timer.getCurrentDurationMsLocked(110100)); + assertEquals(28000, timer.getMaxDurationMsLocked(110101)); // Start the TimeBase. The values should be the old value plus the delta // between when the timer restarted and the current time timeBase.setRunning(true, /* uptimeUs */ 10, /* realtimeUs */ 220100*1000); assertTrue(timer.isRunningLocked()); - assertEquals(28300, timer.getCurrentDurationMsLocked(220300)); // extra 100 from above?? - assertEquals(28301, timer.getMaxDurationMsLocked(220301)); + assertEquals(28200, timer.getCurrentDurationMsLocked(220300)); + assertEquals(28201, timer.getMaxDurationMsLocked(220301)); } @SmallTest @@ -104,7 +101,7 @@ public class BatteryStatsDurationTimerTest extends TestCase { final BatteryStatsImpl.TimeBase timeBase = new BatteryStatsImpl.TimeBase(); timeBase.init(clocks.uptimeMillis(), clocks.elapsedRealtime()); - final BatteryStatsImpl.DurationTimer timer = new BatteryStatsImpl.DurationTimer(clocks, + final BatteryStatsImpl.DurationTimer timer = new BatteryStatsImpl.DurationTimer(clocks, null, BatteryStats.WAKE_TYPE_PARTIAL, null, timeBase); // Start running on battery. @@ -124,7 +121,7 @@ public class BatteryStatsDurationTimerTest extends TestCase { summaryParcel.setDataPosition(0); // Read summary - final BatteryStatsImpl.DurationTimer summary = new BatteryStatsImpl.DurationTimer(clocks, + final BatteryStatsImpl.DurationTimer summary = new BatteryStatsImpl.DurationTimer(clocks, null, BatteryStats.WAKE_TYPE_PARTIAL, null, timeBase); summary.startRunningLocked(3100); summary.readSummaryFromParcelLocked(summaryParcel); @@ -138,9 +135,9 @@ public class BatteryStatsDurationTimerTest extends TestCase { final Parcel fullParcel = Parcel.obtain(); timer.writeToParcel(fullParcel, 1500*1000); fullParcel.setDataPosition(0); - + // Read full - Should be the same as the summary as far as DurationTimer is concerned. - final BatteryStatsImpl.DurationTimer full = new BatteryStatsImpl.DurationTimer(clocks, + final BatteryStatsImpl.DurationTimer full = new BatteryStatsImpl.DurationTimer(clocks, null, BatteryStats.WAKE_TYPE_PARTIAL, null, timeBase, fullParcel); // The new one shouldn't be running, and therefore 0 for current time assertFalse(full.isRunningLocked()); diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk index f4c5b53dd36b..5baa75b8aac3 100644 --- a/data/fonts/Android.mk +++ b/data/fonts/Android.mk @@ -13,7 +13,7 @@ # limitations under the License. # We have to use BUILD_PREBUILT instead of PRODUCT_COPY_FIES, -# because SMALLER_FONT_FOOTPRINT is only available in Android.mks. +# because MINIMAL_FONT_FOOTPRINT is only available in Android.mks. LOCAL_PATH := $(call my-dir) @@ -59,21 +59,6 @@ include $(BUILD_PREBUILT) extra_font_files := ################################ -# Include the DroidSansFallback subset on SMALLER_FONT_FOOTPRINT build -ifeq ($(SMALLER_FONT_FOOTPRINT),true) - -include $(CLEAR_VARS) -LOCAL_MODULE := DroidSansFallback.ttf -LOCAL_SRC_FILES := $(LOCAL_MODULE) -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_TAGS := optional -LOCAL_MODULE_PATH := $(TARGET_OUT)/fonts -include $(BUILD_PREBUILT) -droidsans_fallback_src := - -endif # SMALLER_FONT_FOOTPRINT - -################################ # Build the rest of font files as prebuilt. # $(1): The source file name in LOCAL_PATH. @@ -101,7 +86,7 @@ font_src_files := checkbuild: fontchain_lint FONTCHAIN_LINTER := frameworks/base/tools/fonts/fontchain_lint.py -ifeq ($(SMALLER_FONT_FOOTPRINT),true) +ifeq ($(MINIMAL_FONT_FOOTPRINT),true) CHECK_EMOJI := false else CHECK_EMOJI := true diff --git a/data/fonts/fonts.mk b/data/fonts/fonts.mk index acd785ede249..23c54aec62fe 100644 --- a/data/fonts/fonts.mk +++ b/data/fonts/fonts.mk @@ -18,6 +18,5 @@ PRODUCT_COPY_FILES := \ frameworks/base/data/fonts/fonts.xml:$(TARGET_COPY_OUT_SYSTEM)/etc/fonts.xml PRODUCT_PACKAGES := \ - DroidSansFallback.ttf \ DroidSansMono.ttf \ - AndroidClock.ttf \ + AndroidClock.ttf diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index 345ec378746a..465e20731654 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -353,9 +353,6 @@ <family lang="und-Zsym"> <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font> </family> - <family> - <font weight="400" style="normal">DroidSansFallback.ttf</font> - </family> <!-- Tai Le and Mongolian are intentionally kept last, to make sure they don't override the East Asian punctuation for Chinese. diff --git a/docs/__DEPRECATED__DO_NOT_EDIT__.txt b/docs/__DEPRECATED__DO_NOT_EDIT__.txt new file mode 100644 index 000000000000..3f8fc80c3b0b --- /dev/null +++ b/docs/__DEPRECATED__DO_NOT_EDIT__.txt @@ -0,0 +1,16 @@ +### DEPRECATED: DO NOT EDIT ### + +The source files for developer.android.com are NO LONGER MAINTAINED HERE, as +of 12/2016. Migration of content was completed on 10/16/2016. + +All authoring of content has been moved to Piper (go/dac-source). + +Exceptions and Caveats: + +- Reference documentation is still maintained via building of .java source files, + so you may continue to update JavaDoc comments to update documentation. + +- Sample code documentation is not maintained in Piper, but is published from + a separate code repository. + +For answers to further questions, please email: android-writers@google.com diff --git a/docs/html/__DEPRECATED__DO_NOT_EDIT__.txt b/docs/html/__DEPRECATED__DO_NOT_EDIT__.txt new file mode 100644 index 000000000000..3f8fc80c3b0b --- /dev/null +++ b/docs/html/__DEPRECATED__DO_NOT_EDIT__.txt @@ -0,0 +1,16 @@ +### DEPRECATED: DO NOT EDIT ### + +The source files for developer.android.com are NO LONGER MAINTAINED HERE, as +of 12/2016. Migration of content was completed on 10/16/2016. + +All authoring of content has been moved to Piper (go/dac-source). + +Exceptions and Caveats: + +- Reference documentation is still maintained via building of .java source files, + so you may continue to update JavaDoc comments to update documentation. + +- Sample code documentation is not maintained in Piper, but is published from + a separate code repository. + +For answers to further questions, please email: android-writers@google.com diff --git a/docs/html/reference/images/graphics/colorspace_ucs.png b/docs/html/reference/images/graphics/colorspace_ucs.png Binary files differnew file mode 100644 index 000000000000..3e0f0c6f0cc4 --- /dev/null +++ b/docs/html/reference/images/graphics/colorspace_ucs.png diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 756087c75db0..4b899f6e20b3 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -140,7 +140,7 @@ public final class Bitmap implements Parcelable { * Native bitmap has been reconfigured, so set premult and cached * width/height values */ - // called from JNI + @SuppressWarnings("unused") // called from JNI void reinit(int width, int height, boolean requestPremultiplied) { mWidth = width; mHeight = height; @@ -465,16 +465,29 @@ public final class Bitmap implements Parcelable { */ ARGB_8888 (5), + /** + * Each pixels is stored on 8 bytes. Each channel (RGB and alpha + * for translucency) is stored as a + * {@link android.util.Half half-precision floating point value}. + * + * This configuration is particularly suited for wide-gamut and + * HDR content. + */ + RGBA_F16 (6), /** - * @hide + * Special configuration, when bitmap is stored only in graphic memory. + * Bitmaps in this configuration are always immutable. + * + * It is optimal for cases, when the only operation with the bitmap is to draw it on a + * screen. */ - HARDWARE (6); + HARDWARE (7); final int nativeInt; private static Config sConfigs[] = { - null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, HARDWARE + null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE }; Config(int ni) { @@ -579,9 +592,13 @@ public final class Bitmap implements Parcelable { * @param isMutable True if the resulting bitmap should be mutable (i.e. * its pixels can be modified) * @return the new bitmap, or null if the copy could not be made. + * @throws IllegalArgumentException if config is {@link Config#HARDWARE} and isMutable is true */ public Bitmap copy(Config config, boolean isMutable) { checkRecycled("Can't copy a recycled bitmap"); + if (config == Config.HARDWARE && isMutable) { + throw new IllegalArgumentException("Hardware bitmaps are always immutable"); + } Bitmap b = nativeCopy(mNativePtr, config.nativeInt, isMutable); if (b != null) { b.setPremultiplied(mRequestPremultiplied); @@ -756,6 +773,9 @@ public final class Bitmap implements Parcelable { case ALPHA_8: newConfig = Config.ALPHA_8; break; + case RGBA_F16: + newConfig = Config.RGBA_F16; + break; //noinspection deprecation case ARGB_4444: case ARGB_8888: @@ -777,8 +797,13 @@ public final class Bitmap implements Parcelable { neww = Math.round(deviceR.width()); newh = Math.round(deviceR.height()); - bitmap = createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig, - transformed || source.hasAlpha()); + Config transformedConfig = newConfig; + if (transformed) { + if (transformedConfig != Config.ARGB_8888 && transformedConfig != Config.RGBA_F16) { + transformedConfig = Config.ARGB_8888; + } + } + bitmap = createBitmap(neww, newh, transformedConfig, transformed || source.hasAlpha()); canvas.translate(-deviceR.left, -deviceR.top); canvas.concat(m); @@ -810,7 +835,8 @@ public final class Bitmap implements Parcelable { * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create. - * @throws IllegalArgumentException if the width or height are <= 0 + * @throws IllegalArgumentException if the width or height are <= 0, or if + * Config is Config.HARDWARE, because hardware bitmaps are always immutable */ public static Bitmap createBitmap(int width, int height, Config config) { return createBitmap(width, height, config, true); @@ -825,7 +851,8 @@ public final class Bitmap implements Parcelable { * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create. - * @throws IllegalArgumentException if the width or height are <= 0 + * @throws IllegalArgumentException if the width or height are <= 0, or if + * Config is Config.HARDWARE, because hardware bitmaps are always immutable */ public static Bitmap createBitmap(DisplayMetrics display, int width, int height, Config config) { @@ -839,13 +866,14 @@ public final class Bitmap implements Parcelable { * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create. - * @param hasAlpha If the bitmap is ARGB_8888 this flag can be used to mark the - * bitmap as opaque. Doing so will clear the bitmap in black + * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to + * mark the bitmap as opaque. Doing so will clear the bitmap in black * instead of transparent. * - * @throws IllegalArgumentException if the width or height are <= 0 + * @throws IllegalArgumentException if the width or height are <= 0, or if + * Config is Config.HARDWARE, because hardware bitmaps are always immutable */ - private static Bitmap createBitmap(int width, int height, Config config, boolean hasAlpha) { + public static Bitmap createBitmap(int width, int height, Config config, boolean hasAlpha) { return createBitmap(null, width, height, config, hasAlpha); } @@ -858,23 +886,27 @@ public final class Bitmap implements Parcelable { * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create. - * @param hasAlpha If the bitmap is ARGB_8888 this flag can be used to mark the - * bitmap as opaque. Doing so will clear the bitmap in black + * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to + * mark the bitmap as opaque. Doing so will clear the bitmap in black * instead of transparent. * - * @throws IllegalArgumentException if the width or height are <= 0 + * @throws IllegalArgumentException if the width or height are <= 0, or if + * Config is Config.HARDWARE, because hardware bitmaps are always immutable */ - private static Bitmap createBitmap(DisplayMetrics display, int width, int height, + public static Bitmap createBitmap(DisplayMetrics display, int width, int height, Config config, boolean hasAlpha) { if (width <= 0 || height <= 0) { throw new IllegalArgumentException("width and height must be > 0"); } + if (config == Config.HARDWARE) { + throw new IllegalArgumentException("can't create mutable bitmap with Config.HARDWARE"); + } Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true); if (display != null) { bm.mDensity = display.densityDpi; } bm.setHasAlpha(hasAlpha); - if (config == Config.ARGB_8888 && !hasAlpha) { + if ((config == Config.ARGB_8888 || config == Config.RGBA_F16) && !hasAlpha) { nativeErase(bm.mNativePtr, 0xff000000); } // No need to initialize the bitmap to zeroes with other configs; @@ -1515,7 +1547,7 @@ public final class Bitmap implements Parcelable { /** * <p>Replace pixels in the bitmap with the colors in the array. Each element - * in the array is a packed int prepresenting a non-premultiplied ARGB + * in the array is a packed int representing a non-premultiplied ARGB * {@link Color}.</p> * * @param pixels The colors to write to the bitmap diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index 447a4c4e8345..64a726babce0 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -16,6 +16,8 @@ package android.graphics; +import static android.graphics.BitmapFactory.Options.validate; + import android.content.res.AssetManager; import android.content.res.Resources; import android.os.Trace; @@ -49,8 +51,8 @@ public class BitmapFactory { /** * If set, decode methods that take the Options object will attempt to * reuse this bitmap when loading content. If the decode operation - * cannot use this bitmap, the decode method will return - * <code>null</code> and will throw an IllegalArgumentException. The + * cannot use this bitmap, the decode method will throw an + * {@link java.lang.IllegalArgumentException}. The * current implementation necessitates that the reused bitmap be * mutable, and the resulting reused bitmap will continue to remain * mutable even when decoding a resource which would normally result in @@ -103,6 +105,9 @@ public class BitmapFactory { * If set, decode methods will always return a mutable Bitmap instead of * an immutable one. This can be used for instance to programmatically apply * effects to a Bitmap loaded through BitmapFactory. + * <p>Can not be set simultaneously with inPreferredConfig = + * {@link android.graphics.Bitmap.Config#HARDWARE}, + * because hardware bitmaps are always immutable. */ @SuppressWarnings({"UnusedDeclaration"}) // used in native code public boolean inMutable; @@ -381,6 +386,12 @@ public class BitmapFactory { public void requestCancelDecode() { mCancel = true; } + + static void validate(Options opts) { + if (opts != null && opts.inMutable && opts.inPreferredConfig == Bitmap.Config.HARDWARE) { + throw new IllegalArgumentException("Bitmaps with Config.HARWARE are always immutable"); + } + } } /** @@ -393,8 +404,12 @@ public class BitmapFactory { * @return The decoded bitmap, or null if the image data could not be * decoded, or, if opts is non-null, if opts requested only the * size be returned (in opts.outWidth and opts.outHeight) + * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig} + * is {@link android.graphics.Bitmap.Config#HARDWARE} + * and {@link BitmapFactory.Options#inMutable} is set. */ public static Bitmap decodeFile(String pathName, Options opts) { + validate(opts); Bitmap bm = null; InputStream stream = null; try { @@ -431,10 +446,13 @@ public class BitmapFactory { /** * Decode a new Bitmap from an InputStream. This InputStream was obtained from * resources, which we pass to be able to scale the bitmap accordingly. + * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig} + * is {@link android.graphics.Bitmap.Config#HARDWARE} + * and {@link BitmapFactory.Options#inMutable} is set. */ public static Bitmap decodeResourceStream(Resources res, TypedValue value, InputStream is, Rect pad, Options opts) { - + validate(opts); if (opts == null) { opts = new Options(); } @@ -466,8 +484,12 @@ public class BitmapFactory { * @return The decoded bitmap, or null if the image data could not be * decoded, or, if opts is non-null, if opts requested only the * size be returned (in opts.outWidth and opts.outHeight) + * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig} + * is {@link android.graphics.Bitmap.Config#HARDWARE} + * and {@link BitmapFactory.Options#inMutable} is set. */ public static Bitmap decodeResource(Resources res, int id, Options opts) { + validate(opts); Bitmap bm = null; InputStream is = null; @@ -520,11 +542,15 @@ public class BitmapFactory { * @return The decoded bitmap, or null if the image data could not be * decoded, or, if opts is non-null, if opts requested only the * size be returned (in opts.outWidth and opts.outHeight) + * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig} + * is {@link android.graphics.Bitmap.Config#HARDWARE} + * and {@link BitmapFactory.Options#inMutable} is set. */ public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) { if ((offset | length) < 0 || data.length < offset + length) { throw new ArrayIndexOutOfBoundsException(); } + validate(opts); Bitmap bm; @@ -598,6 +624,9 @@ public class BitmapFactory { * @return The decoded bitmap, or null if the image data could not be * decoded, or, if opts is non-null, if opts requested only the * size be returned (in opts.outWidth and opts.outHeight) + * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig} + * is {@link android.graphics.Bitmap.Config#HARDWARE} + * and {@link BitmapFactory.Options#inMutable} is set. * * <p class="note">Prior to {@link android.os.Build.VERSION_CODES#KITKAT}, * if {@link InputStream#markSupported is.markSupported()} returns true, @@ -610,6 +639,7 @@ public class BitmapFactory { if (is == null) { return null; } + validate(opts); Bitmap bm = null; @@ -673,8 +703,12 @@ public class BitmapFactory { * @param opts null-ok; Options that control downsampling and whether the * image should be completely decoded, or just its size returned. * @return the decoded bitmap, or null + * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig} + * is {@link android.graphics.Bitmap.Config#HARDWARE} + * and {@link BitmapFactory.Options#inMutable} is set. */ public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) { + validate(opts); Bitmap bm; Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeFileDescriptor"); diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java index e689b083ad97..04abca1f4bd4 100644 --- a/graphics/java/android/graphics/BitmapRegionDecoder.java +++ b/graphics/java/android/graphics/BitmapRegionDecoder.java @@ -178,8 +178,12 @@ public final class BitmapRegionDecoder { * inPurgeable is not supported. * @return The decoded bitmap, or null if the image data could not be * decoded. + * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig} + * is {@link android.graphics.Bitmap.Config#HARDWARE} + * and {@link BitmapFactory.Options#inMutable} is set. */ public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) { + BitmapFactory.Options.validate(options); synchronized (mNativeLock) { checkRecycled("decodeRegion called on recycled region decoder"); if (rect.right <= 0 || rect.bottom <= 0 || rect.left >= getWidth() diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 7dc5de3051e6..d968516f36bc 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -1671,6 +1671,28 @@ public abstract class ColorSpace { } /** + * Converts values from CIE xyY to CIE L*u*v*. Y is assumed to be 1 so the + * input xyY array only contains the x and y components. After this method + * returns, the xyY array contains the converted u and v components. + * + * @param xyY The xyY value to convert to XYZ, cannot be null, + * length must be a multiple of 2 + */ + private static void xyYToUv(@NonNull @Size(multiple = 2) float[] xyY) { + for (int i = 0; i < xyY.length; i += 2) { + float x = xyY[i]; + float y = xyY[i + 1]; + + float d = -2.0f * x + 12.0f * y + 3; + float u = (4.0f * x) / d; + float v = (9.0f * y) / d; + + xyY[i] = u; + xyY[i + 1] = v; + } + } + + /** * <p>Computes the chromatic adaptation transform from the specified * source white point to the specified destination white point.</p> * @@ -3162,10 +3184,10 @@ public abstract class ColorSpace { /** * <p>A color space renderer can be used to visualize and compare the gamut and * white point of one or more color spaces. The output is an sRGB {@link Bitmap} - * showing a CIE 1931 xyY chromaticity diagram.</p> + * showing a CIE 1931 xyY or a CIE 1976 UCS chromaticity diagram.</p> * * <p>The following code snippet shows how to compare the {@link Named#SRGB} - * and {@link Named#DCI_P3} color spaces:</p> + * and {@link Named#DCI_P3} color spaces in a CIE 1931 diagram:</p> * * <pre class="prettyprint"> * Bitmap bitmap = ColorSpace.createRenderer() @@ -3188,12 +3210,18 @@ public abstract class ColorSpace { */ public static class Renderer { private static final int NATIVE_SIZE = 1440; + private static final float UCS_SCALE = 9.0f / 6.0f; + + // Number of subdivision of the inside of the spectral locus + private static final int CHROMATICITY_RESOLUTION = 32; + private static final double ONE_THIRD = 1.0 / 3.0; @IntRange(from = 128, to = Integer.MAX_VALUE) private int mSize = 1024; private boolean mShowWhitePoint = true; private boolean mClip = false; + private boolean mUcs = false; private final List<Pair<ColorSpace, Integer>> mColorSpaces = new ArrayList<>(2); private final List<Point> mPoints = new ArrayList<>(0); @@ -3241,6 +3269,35 @@ public abstract class ColorSpace { } /** + * <p>Defines whether the chromaticity diagram should use the uniform + * chromaticity scale (CIE 1976 UCS). When the uniform chromaticity scale + * is used, the distance between two points on the diagram is approximately + * proportional to the perceived color difference.</p> + * + * <p>The following code snippet shows how to enable the uniform chromaticity + * scale. The image below shows the result:</p> + * <pre class="prettyprint"> + * Bitmap bitmap = ColorSpace.createRenderer() + * .uniformChromaticityScale(true) + * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) + * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) + * .render(); + * </pre> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_ucs.png" /> + * <figcaption style="text-align: center;">CIE 1976 UCS diagram</figcaption> + * </p> + * + * @param ucs True to render the chromaticity diagram as the CIE 1976 UCS diagram + * @return This instance of {@link Renderer} + */ + @NonNull + public Renderer uniformChromaticityScale(boolean ucs) { + mUcs = ucs; + return this; + } + + /** * Sets the dimensions (width and height) in pixels of the output bitmap. * The size must be at least 128px and defaults to 1024px. * @@ -3302,7 +3359,7 @@ public abstract class ColorSpace { * </pre> * <p> * <img src="{@docRoot}reference/android/images/graphics/colorspace_comparison2.png" /> - * <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption> + * <figcaption style="text-align: center;">sRGB, DCI-P3, ACES and scRGB</figcaption> * </p> * * @param colorSpace The color space whose gamut to render on the diagram @@ -3385,6 +3442,7 @@ public abstract class ColorSpace { setTransform(canvas, width, height, primaries); drawBox(canvas, width, height, paint, path); + setUcsTransform(canvas, height); drawLocus(canvas, width, height, paint, path, primaries); drawGamuts(canvas, width, height, paint, path, primaries, whitePoint); drawPoints(canvas, width, height, paint); @@ -3406,7 +3464,11 @@ public abstract class ColorSpace { paint.setStyle(Paint.Style.FILL); + float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f); + float[] v = new float[3]; + float[] xy = new float[2]; + for (final Point point : mPoints) { v[0] = point.mRgb[0]; v[1] = point.mRgb[1]; @@ -3415,10 +3477,13 @@ public abstract class ColorSpace { paint.setColor(point.mColor); - // XYZ to xyY, assuming Y=1.0 + // XYZ to xyY, assuming Y=1.0, then to L*u*v* if needed float sum = v[0] + v[1] + v[2]; - canvas.drawCircle(width * v[0] / sum, height - height * v[1] / sum, - 4.0f, paint); + xy[0] = v[0] / sum; + xy[1] = v[1] / sum; + if (mUcs) xyYToUv(xy); + + canvas.drawCircle(width * xy[0], height - height * xy[1], radius, paint); } } @@ -3440,6 +3505,8 @@ public abstract class ColorSpace { @NonNull Paint paint, @NonNull Path path, @NonNull @Size(6) float[] primaries, @NonNull @Size(2) float[] whitePoint) { + float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f); + for (final Pair<ColorSpace, Integer> item : mColorSpaces) { ColorSpace colorSpace = item.first; int color = item.second; @@ -3447,7 +3514,7 @@ public abstract class ColorSpace { if (colorSpace.getModel() != Model.RGB) continue; Rgb rgb = (Rgb) colorSpace; - getPrimaries(rgb, primaries); + getPrimaries(rgb, primaries, mUcs); path.rewind(); path.moveTo(width * primaries[0], height - height * primaries[1]); @@ -3462,11 +3529,12 @@ public abstract class ColorSpace { // Draw the white point if (mShowWhitePoint) { rgb.getWhitePoint(whitePoint); + if (mUcs) xyYToUv(whitePoint); paint.setStyle(Paint.Style.FILL); paint.setColor(color); - canvas.drawCircle(width * whitePoint[0], height - height * whitePoint[1], - 4.0f, paint); + canvas.drawCircle( + width * whitePoint[0], height - height * whitePoint[1], radius, paint); } } } @@ -3477,10 +3545,12 @@ public abstract class ColorSpace { * * @param rgb The color space whose primaries to extract * @param primaries A pre-allocated array of 6 floats that will hold the result + * @param asUcs True if the primaries should be returned in Luv, false for xyY */ @NonNull @Size(6) - private static float[] getPrimaries(@NonNull Rgb rgb, @NonNull @Size(6) float[] primaries) { + private static float[] getPrimaries(@NonNull Rgb rgb, + @NonNull @Size(6) float[] primaries, boolean asUcs) { // TODO: We should find a better way to handle these cases if (rgb.equals(ColorSpace.get(Named.EXTENDED_SRGB)) || rgb.equals(ColorSpace.get(Named.LINEAR_EXTENDED_SRGB))) { @@ -3490,9 +3560,11 @@ public abstract class ColorSpace { primaries[3] = 1.24f; primaries[4] = -0.23f; primaries[5] = -0.57f; - return primaries; + } else { + rgb.getPrimaries(primaries); } - return rgb.getPrimaries(primaries); + if (asUcs) xyYToUv(primaries); + return primaries; } /** @@ -3513,7 +3585,13 @@ public abstract class ColorSpace { int vertexCount = SPECTRUM_LOCUS_X.length * CHROMATICITY_RESOLUTION * 6; float[] vertices = new float[vertexCount * 2]; int[] colors = new int[vertices.length]; - computeChromaticityMesh(NATIVE_SIZE, NATIVE_SIZE, vertices, colors); + computeChromaticityMesh(vertices, colors); + + if (mUcs) xyYToUv(vertices); + for (int i = 0; i < vertices.length; i += 2) { + vertices[i] *= width; + vertices[i + 1] = height - vertices[i + 1] * height; + } // Draw the spectral locus if (mClip && mColorSpaces.size() > 0) { @@ -3522,7 +3600,8 @@ public abstract class ColorSpace { if (colorSpace.getModel() != Model.RGB) continue; Rgb rgb = (Rgb) colorSpace; - getPrimaries(rgb, primaries); + getPrimaries(rgb, primaries, mUcs); + break; } @@ -3559,6 +3638,7 @@ public abstract class ColorSpace { } path.close(); + paint.setStrokeWidth(4.0f / (mUcs ? UCS_SCALE : 1.0f)); paint.setStyle(Paint.Style.STROKE); paint.setColor(0xff000000); canvas.drawPath(path, paint); @@ -3576,25 +3656,38 @@ public abstract class ColorSpace { */ private void drawBox(@NonNull Canvas canvas, int width, int height, @NonNull Paint paint, @NonNull Path path) { + + int lineCount = 10; + float scale = 1.0f; + if (mUcs) { + lineCount = 7; + scale = UCS_SCALE; + } + // Draw the unit grid paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(2.0f); paint.setColor(0xffc0c0c0); - for (int i = 1; i <= 9; i++) { - canvas.drawLine(0.0f, height - (height * i / 10.0f), - 0.9f * width, height - (height * i / 10.0f), paint); - canvas.drawLine(width * i / 10.0f, height, - width * i / 10.0f, 0.1f * height, paint); + + for (int i = 1; i < lineCount - 1; i++) { + float v = i / 10.0f; + float x = (width * v) * scale; + float y = height - (height * v) * scale; + + canvas.drawLine(0.0f, y, 0.9f * width, y, paint); + canvas.drawLine(x, height, x, 0.1f * height, paint); } // Draw tick marks paint.setStrokeWidth(4.0f); paint.setColor(0xff000000); - for (int i = 1; i <= 9; i++) { - canvas.drawLine(0.0f, height - (height * i / 10.0f), - width / 100.0f, height - (height * i / 10.0f), paint); - canvas.drawLine(width * i / 10.0f, height, - width * i / 10.0f, height - (height / 100.0f), paint); + for (int i = 1; i < lineCount - 1; i++) { + float v = i / 10.0f; + float x = (width * v) * scale; + float y = height - (height * v) * scale; + + canvas.drawLine(0.0f, y, width / 100.0f, y, paint); + canvas.drawLine(x, height, x, height - (height / 100.0f), paint); } // Draw the axis labels @@ -3603,14 +3696,15 @@ public abstract class ColorSpace { paint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); Rect bounds = new Rect(); - for (int i = 1; i < 9; i++) { + for (int i = 1; i < lineCount - 1; i++) { String text = "0." + i; paint.getTextBounds(text, 0, text.length(), bounds); - float y = height - (height * i / 10.0f); - canvas.drawText(text, -0.05f * width + 10, y + bounds.height() / 2.0f, paint); + float v = i / 10.0f; + float x = (width * v) * scale; + float y = height - (height * v) * scale; - float x = width * i / 10.0f; + canvas.drawText(text, -0.05f * width + 10, y + bounds.height() / 2.0f, paint); canvas.drawText(text, x - bounds.width() / 2.0f, height + bounds.height() + 16, paint); } @@ -3643,7 +3737,7 @@ public abstract class ColorSpace { if (colorSpace.getModel() != Model.RGB) continue; Rgb rgb = (Rgb) colorSpace; - getPrimaries(rgb, primaries); + getPrimaries(rgb, primaries, mUcs); primariesBounds.left = Math.min(primariesBounds.left, primaries[4]); primariesBounds.top = Math.min(primariesBounds.top, primaries[5]); @@ -3651,26 +3745,42 @@ public abstract class ColorSpace { primariesBounds.bottom = Math.max(primariesBounds.bottom, primaries[3]); } + float max = mUcs ? 0.6f : 0.9f; + primariesBounds.left = Math.min(0.0f, primariesBounds.left); primariesBounds.top = Math.min(0.0f, primariesBounds.top); - primariesBounds.right = Math.max(0.9f, primariesBounds.right); - primariesBounds.bottom = Math.max(0.9f, primariesBounds.bottom); + primariesBounds.right = Math.max(max, primariesBounds.right); + primariesBounds.bottom = Math.max(max, primariesBounds.bottom); - float scaleX = 0.9f / primariesBounds.width(); - float scaleY = 0.9f / primariesBounds.height(); + float scaleX = max / primariesBounds.width(); + float scaleY = max / primariesBounds.height(); float scale = Math.min(scaleX, scaleY); canvas.scale(mSize / (float) NATIVE_SIZE, mSize / (float) NATIVE_SIZE); canvas.scale(scale, scale); canvas.translate( - (primariesBounds.width() - 0.9f) * width / 2.0f, - (primariesBounds.height() - 0.9f) * height / 2.0f); + (primariesBounds.width() - max) * width / 2.0f, + (primariesBounds.height() - max) * height / 2.0f); // The spectrum extends ~0.85 vertically and ~0.65 horizontally // We shift the canvas a little bit to get nicer margins canvas.translate(0.05f * width, -0.05f * height); } + /** + * Computes and applies the Canvas transforms required to render the CIE + * 197 UCS chromaticity diagram. + * + * @param canvas The canvas to transform + * @param height Height in pixel of the final image + */ + private void setUcsTransform(@NonNull Canvas canvas, int height) { + if (mUcs) { + canvas.translate(0.0f, (height - height * UCS_SCALE)); + canvas.scale(UCS_SCALE, UCS_SCALE); + } + } + // X coordinates of the spectral locus in CIE 1931 private static final float[] SPECTRUM_LOCUS_X = { 0.175596f, 0.172787f, 0.170806f, 0.170085f, 0.160343f, @@ -3716,21 +3826,15 @@ public abstract class ColorSpace { 0.037799f, 0.029673f, 0.021547f, 0.013421f, 0.005295f }; - // Number of subdivision of the inside of the spectral locus - private static final int CHROMATICITY_RESOLUTION = 32; - private static final double ONE_THIRD = 1.0 / 3.0; - /** * Computes a 2D mesh representation of the CIE 1931 chromaticity * diagram. * - * @param width Width in pixels of the mesh - * @param height Height in pixels of the mesh * @param vertices Array of floats that will hold the mesh vertices * @param colors Array of floats that will hold the mesh colors */ - private static void computeChromaticityMesh(int width, int height, - @NonNull float[] vertices, @NonNull int[] colors) { + private static void computeChromaticityMesh(@NonNull float[] vertices, + @NonNull int[] colors) { ColorSpace colorSpace = get(Named.SRGB); @@ -3796,18 +3900,18 @@ public abstract class ColorSpace { colorIndex += 6; // Flip the mesh upside down to match Canvas' coordinates system - vertices[vertexIndex++] = v1x * width; - vertices[vertexIndex++] = height - v1y * height; - vertices[vertexIndex++] = v2x * width; - vertices[vertexIndex++] = height - v2y * height; - vertices[vertexIndex++] = v3x * width; - vertices[vertexIndex++] = height - v3y * height; - vertices[vertexIndex++] = v1x * width; - vertices[vertexIndex++] = height - v1y * height; - vertices[vertexIndex++] = v3x * width; - vertices[vertexIndex++] = height - v3y * height; - vertices[vertexIndex++] = v4x * width; - vertices[vertexIndex++] = height - v4y * height; + vertices[vertexIndex++] = v1x; + vertices[vertexIndex++] = v1y; + vertices[vertexIndex++] = v2x; + vertices[vertexIndex++] = v2y; + vertices[vertexIndex++] = v3x; + vertices[vertexIndex++] = v3y; + vertices[vertexIndex++] = v1x; + vertices[vertexIndex++] = v1y; + vertices[vertexIndex++] = v3x; + vertices[vertexIndex++] = v3y; + vertices[vertexIndex++] = v4x; + vertices[vertexIndex++] = v4y; } } } diff --git a/graphics/java/android/graphics/LayerRasterizer.java b/graphics/java/android/graphics/LayerRasterizer.java index b692ecf9c7f2..25155ab284fb 100644 --- a/graphics/java/android/graphics/LayerRasterizer.java +++ b/graphics/java/android/graphics/LayerRasterizer.java @@ -16,26 +16,20 @@ package android.graphics; +/** + * @removed feature is not supported by hw-accerlerated or PDF backends + */ @Deprecated public class LayerRasterizer extends Rasterizer { - public LayerRasterizer() { - native_instance = nativeConstructor(); - } + public LayerRasterizer() { } /** Add a new layer (above any previous layers) to the rasterizer. The layer will extract those fields that affect the mask from the specified paint, but will not retain a reference to the paint object itself, so it may be reused without danger of side-effects. */ - public void addLayer(Paint paint, float dx, float dy) { - nativeAddLayer(native_instance, paint.getNativeInstance(), dx, dy); - } - - public void addLayer(Paint paint) { - nativeAddLayer(native_instance, paint.getNativeInstance(), 0, 0); - } + public void addLayer(Paint paint, float dx, float dy) { } - private static native long nativeConstructor(); - private static native void nativeAddLayer(long native_layer, long native_paint, float dx, float dy); + public void addLayer(Paint paint) { } } diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java index aa81b9196fe1..3e59f34f6bc7 100644 --- a/graphics/java/android/graphics/Outline.java +++ b/graphics/java/android/graphics/Outline.java @@ -58,8 +58,12 @@ public final class Outline { @Mode public int mMode = MODE_EMPTY; - /** @hide */ - public final Path mPath = new Path(); + /** + * Only guaranteed to be non-null when mode == MODE_CONVEX_PATH + * + * @hide + */ + public Path mPath; /** @hide */ public final Rect mRect = new Rect(); @@ -87,8 +91,11 @@ public final class Outline { * @see #isEmpty() */ public void setEmpty() { + if (mPath != null) { + // rewind here to avoid thrashing the allocations, but could alternately clear ref + mPath.rewind(); + } mMode = MODE_EMPTY; - mPath.rewind(); mRect.setEmpty(); mRadius = RADIUS_UNDEFINED; } @@ -148,7 +155,12 @@ public final class Outline { */ public void set(@NonNull Outline src) { mMode = src.mMode; - mPath.set(src.mPath); + if (src.mMode == MODE_CONVEX_PATH) { + if (mPath == null) { + mPath = new Path(); + } + mPath.set(src.mPath); + } mRect.set(src.mRect); mRadius = src.mRadius; mAlpha = src.mAlpha; @@ -180,10 +192,13 @@ public final class Outline { return; } + if (mMode == MODE_CONVEX_PATH) { + // rewind here to avoid thrashing the allocations, but could alternately clear ref + mPath.rewind(); + } mMode = MODE_ROUND_RECT; mRect.set(left, top, right, bottom); mRadius = radius; - mPath.rewind(); } /** @@ -236,8 +251,13 @@ public final class Outline { return; } + if (mPath == null) { + mPath = new Path(); + } else { + mPath.rewind(); + } + mMode = MODE_CONVEX_PATH; - mPath.rewind(); mPath.addOval(left, top, right, bottom, Path.Direction.CW); mRect.setEmpty(); mRadius = RADIUS_UNDEFINED; @@ -264,6 +284,10 @@ public final class Outline { throw new IllegalArgumentException("path must be convex"); } + if (mPath == null) { + mPath = new Path(); + } + mMode = MODE_CONVEX_PATH; mPath.set(convexPath); mRect.setEmpty(); diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 554e5d2614dd..7815ae16e7e2 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -61,7 +61,6 @@ public class Paint { private ColorFilter mColorFilter; private MaskFilter mMaskFilter; private PathEffect mPathEffect; - private Rasterizer mRasterizer; private Shader mShader; private Typeface mTypeface; private Xfermode mXfermode; @@ -494,7 +493,6 @@ public class Paint { mColorFilter = null; mMaskFilter = null; mPathEffect = null; - mRasterizer = null; mShader = null; mNativeShader = 0; mTypeface = null; @@ -532,7 +530,6 @@ public class Paint { mColorFilter = paint.mColorFilter; mMaskFilter = paint.mMaskFilter; mPathEffect = paint.mPathEffect; - mRasterizer = paint.mRasterizer; mShader = paint.mShader; mNativeShader = paint.mNativeShader; mTypeface = paint.mTypeface; @@ -1164,11 +1161,12 @@ public class Paint { * * @return the paint's rasterizer (or null) * - * @deprecated Rasterizer is not supported by either the HW or PDF backends. + * @deprecated Rasterizer is not supported by either the HW or PDF backends. + * @removed */ @Deprecated public Rasterizer getRasterizer() { - return mRasterizer; + return null; } /** @@ -1181,16 +1179,11 @@ public class Paint { * the paint. * @return rasterizer * - * @deprecated Rasterizer is not supported by either the HW or PDF backends. + * @deprecated Rasterizer is not supported by either the HW or PDF backends. + * @removed */ @Deprecated public Rasterizer setRasterizer(Rasterizer rasterizer) { - long rasterizerNative = 0; - if (rasterizer != null) { - rasterizerNative = rasterizer.native_instance; - } - nSetRasterizer(mNativePaint, rasterizerNative); - mRasterizer = rasterizer; return rasterizer; } @@ -1364,7 +1357,7 @@ public class Paint { /** * Return the paint's text size. * - * @return the paint's text size. + * @return the paint's text size in pixel units. */ public float getTextSize() { return nGetTextSize(mNativePaint); @@ -1373,7 +1366,7 @@ public class Paint { /** * Set the paint's text size. This value must be > 0 * - * @param textSize set the paint's text size. + * @param textSize set the paint's text size in pixel units. */ public void setTextSize(float textSize) { nSetTextSize(mNativePaint, textSize); @@ -2715,8 +2708,6 @@ public class Paint { @CriticalNative private static native long nSetTypeface(long paintPtr, long typeface); @CriticalNative - private static native long nSetRasterizer(long paintPtr, long rasterizer); - @CriticalNative private static native int nGetTextAlign(long paintPtr); @CriticalNative private static native void nSetTextAlign(long paintPtr, int align); diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index 4ed2581f2b32..363137321f5f 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -19,6 +19,9 @@ package android.graphics; import android.annotation.NonNull; import android.annotation.Nullable; +import dalvik.annotation.optimization.CriticalNative; +import dalvik.annotation.optimization.FastNative; + /** * The Path class encapsulates compound (multiple contour) geometric paths * consisting of straight line segments, quadratic curves, and cubic curves. @@ -46,7 +49,7 @@ public class Path { * Create an empty path */ public Path() { - mNativePath = init1(); + mNativePath = nInit(); } /** @@ -63,7 +66,7 @@ public class Path { rects = new Region(src.rects); } } - mNativePath = init2(valNative); + mNativePath = nInit(valNative); } /** @@ -77,7 +80,7 @@ public class Path { // We promised not to change this, so preserve it around the native // call, which does now reset fill type. final FillType fillType = getFillType(); - native_reset(mNativePath); + nReset(mNativePath); setFillType(fillType); } @@ -89,7 +92,7 @@ public class Path { isSimplePath = true; mLastDirection = null; if (rects != null) rects.setEmpty(); - native_rewind(mNativePath); + nRewind(mNativePath); } /** Replace the contents of this with the contents of src. @@ -99,7 +102,7 @@ public class Path { return; } isSimplePath = src.isSimplePath; - native_set(mNativePath, src.mNativePath); + nSet(mNativePath, src.mNativePath); if (!isSimplePath) { return; } @@ -174,7 +177,7 @@ public class Path { * @see #op(Path, android.graphics.Path.Op) */ public boolean op(Path path1, Path path2, Op op) { - if (native_op(path1.mNativePath, path2.mNativePath, op.ordinal(), this.mNativePath)) { + if (nOp(path1.mNativePath, path2.mNativePath, op.ordinal(), this.mNativePath)) { isSimplePath = false; rects = null; return true; @@ -194,7 +197,7 @@ public class Path { * @return True if the path is convex. */ public boolean isConvex() { - return native_isConvex(mNativePath); + return nIsConvex(mNativePath); } /** @@ -243,7 +246,7 @@ public class Path { * @return the path's fill type */ public FillType getFillType() { - return sFillTypeArray[native_getFillType(mNativePath)]; + return sFillTypeArray[nGetFillType(mNativePath)]; } /** @@ -252,7 +255,7 @@ public class Path { * @param ft The new fill type for this path */ public void setFillType(FillType ft) { - native_setFillType(mNativePath, ft.nativeInt); + nSetFillType(mNativePath, ft.nativeInt); } /** @@ -261,7 +264,7 @@ public class Path { * @return true if the filltype is one of the INVERSE variants */ public boolean isInverseFillType() { - final int ft = native_getFillType(mNativePath); + final int ft = nGetFillType(mNativePath); return (ft & FillType.INVERSE_WINDING.nativeInt) != 0; } @@ -269,9 +272,9 @@ public class Path { * Toggles the INVERSE state of the filltype */ public void toggleInverseFillType() { - int ft = native_getFillType(mNativePath); + int ft = nGetFillType(mNativePath); ft ^= FillType.INVERSE_WINDING.nativeInt; - native_setFillType(mNativePath, ft); + nSetFillType(mNativePath, ft); } /** @@ -280,7 +283,7 @@ public class Path { * @return true if the path is empty (contains no lines or curves) */ public boolean isEmpty() { - return native_isEmpty(mNativePath); + return nIsEmpty(mNativePath); } /** @@ -293,7 +296,7 @@ public class Path { * @return true if the path specifies a rectangle */ public boolean isRect(RectF rect) { - return native_isRect(mNativePath, rect); + return nIsRect(mNativePath, rect); } /** @@ -306,7 +309,7 @@ public class Path { */ @SuppressWarnings({"UnusedDeclaration"}) public void computeBounds(RectF bounds, boolean exact) { - native_computeBounds(mNativePath, bounds); + nComputeBounds(mNativePath, bounds); } /** @@ -317,7 +320,7 @@ public class Path { * path */ public void incReserve(int extraPtCount) { - native_incReserve(mNativePath, extraPtCount); + nIncReserve(mNativePath, extraPtCount); } /** @@ -327,7 +330,7 @@ public class Path { * @param y The y-coordinate of the start of a new contour */ public void moveTo(float x, float y) { - native_moveTo(mNativePath, x, y); + nMoveTo(mNativePath, x, y); } /** @@ -341,7 +344,7 @@ public class Path { * previous contour, to specify the start of a new contour */ public void rMoveTo(float dx, float dy) { - native_rMoveTo(mNativePath, dx, dy); + nRMoveTo(mNativePath, dx, dy); } /** @@ -354,7 +357,7 @@ public class Path { */ public void lineTo(float x, float y) { isSimplePath = false; - native_lineTo(mNativePath, x, y); + nLineTo(mNativePath, x, y); } /** @@ -369,7 +372,7 @@ public class Path { */ public void rLineTo(float dx, float dy) { isSimplePath = false; - native_rLineTo(mNativePath, dx, dy); + nRLineTo(mNativePath, dx, dy); } /** @@ -384,7 +387,7 @@ public class Path { */ public void quadTo(float x1, float y1, float x2, float y2) { isSimplePath = false; - native_quadTo(mNativePath, x1, y1, x2, y2); + nQuadTo(mNativePath, x1, y1, x2, y2); } /** @@ -403,7 +406,7 @@ public class Path { */ public void rQuadTo(float dx1, float dy1, float dx2, float dy2) { isSimplePath = false; - native_rQuadTo(mNativePath, dx1, dy1, dx2, dy2); + nRQuadTo(mNativePath, dx1, dy1, dx2, dy2); } /** @@ -421,7 +424,7 @@ public class Path { public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) { isSimplePath = false; - native_cubicTo(mNativePath, x1, y1, x2, y2, x3, y3); + nCubicTo(mNativePath, x1, y1, x2, y2, x3, y3); } /** @@ -432,7 +435,7 @@ public class Path { public void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) { isSimplePath = false; - native_rCubicTo(mNativePath, x1, y1, x2, y2, x3, y3); + nRCubicTo(mNativePath, x1, y1, x2, y2, x3, y3); } /** @@ -483,7 +486,7 @@ public class Path { public void arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo) { isSimplePath = false; - native_arcTo(mNativePath, left, top, right, bottom, startAngle, sweepAngle, forceMoveTo); + nArcTo(mNativePath, left, top, right, bottom, startAngle, sweepAngle, forceMoveTo); } /** @@ -492,7 +495,7 @@ public class Path { */ public void close() { isSimplePath = false; - native_close(mNativePath); + nClose(mNativePath); } /** @@ -544,7 +547,7 @@ public class Path { */ public void addRect(float left, float top, float right, float bottom, Direction dir) { detectSimplePath(left, top, right, bottom, dir); - native_addRect(mNativePath, left, top, right, bottom, dir.nativeInt); + nAddRect(mNativePath, left, top, right, bottom, dir.nativeInt); } /** @@ -564,7 +567,7 @@ public class Path { */ public void addOval(float left, float top, float right, float bottom, Direction dir) { isSimplePath = false; - native_addOval(mNativePath, left, top, right, bottom, dir.nativeInt); + nAddOval(mNativePath, left, top, right, bottom, dir.nativeInt); } /** @@ -577,7 +580,7 @@ public class Path { */ public void addCircle(float x, float y, float radius, Direction dir) { isSimplePath = false; - native_addCircle(mNativePath, x, y, radius, dir.nativeInt); + nAddCircle(mNativePath, x, y, radius, dir.nativeInt); } /** @@ -600,7 +603,7 @@ public class Path { public void addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle) { isSimplePath = false; - native_addArc(mNativePath, left, top, right, bottom, startAngle, sweepAngle); + nAddArc(mNativePath, left, top, right, bottom, startAngle, sweepAngle); } /** @@ -625,7 +628,7 @@ public class Path { public void addRoundRect(float left, float top, float right, float bottom, float rx, float ry, Direction dir) { isSimplePath = false; - native_addRoundRect(mNativePath, left, top, right, bottom, rx, ry, dir.nativeInt); + nAddRoundRect(mNativePath, left, top, right, bottom, rx, ry, dir.nativeInt); } /** @@ -658,7 +661,7 @@ public class Path { throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values"); } isSimplePath = false; - native_addRoundRect(mNativePath, left, top, right, bottom, radii, dir.nativeInt); + nAddRoundRect(mNativePath, left, top, right, bottom, radii, dir.nativeInt); } /** @@ -669,7 +672,7 @@ public class Path { */ public void addPath(Path src, float dx, float dy) { isSimplePath = false; - native_addPath(mNativePath, src.mNativePath, dx, dy); + nAddPath(mNativePath, src.mNativePath, dx, dy); } /** @@ -679,7 +682,7 @@ public class Path { */ public void addPath(Path src) { isSimplePath = false; - native_addPath(mNativePath, src.mNativePath); + nAddPath(mNativePath, src.mNativePath); } /** @@ -689,7 +692,7 @@ public class Path { */ public void addPath(Path src, Matrix matrix) { if (!src.isSimplePath) isSimplePath = false; - native_addPath(mNativePath, src.mNativePath, matrix.native_instance); + nAddPath(mNativePath, src.mNativePath, matrix.native_instance); } /** @@ -725,7 +728,7 @@ public class Path { } else { isSimplePath = false; } - native_offset(mNativePath, dx, dy); + nOffset(mNativePath, dx, dy); } /** @@ -736,7 +739,7 @@ public class Path { */ public void setLastPoint(float dx, float dy) { isSimplePath = false; - native_setLastPoint(mNativePath, dx, dy); + nSetLastPoint(mNativePath, dx, dy); } /** @@ -753,7 +756,7 @@ public class Path { dst.isSimplePath = false; dstNative = dst.mNativePath; } - native_transform(mNativePath, matrix.native_instance, dstNative); + nTransform(mNativePath, matrix.native_instance, dstNative); } /** @@ -763,12 +766,12 @@ public class Path { */ public void transform(Matrix matrix) { isSimplePath = false; - native_transform(mNativePath, matrix.native_instance); + nTransform(mNativePath, matrix.native_instance); } protected void finalize() throws Throwable { try { - finalizer(mNativePath); + nFinalize(mNativePath); mNativePath = 0; // Other finalizers can still call us. } finally { super.finalize(); @@ -803,59 +806,68 @@ public class Path { * @return An array of components for points approximating the Path. */ public float[] approximate(float acceptableError) { - return native_approximate(mNativePath, acceptableError); - } - - private static native long init1(); - private static native long init2(long nPath); - private static native void native_reset(long nPath); - private static native void native_rewind(long nPath); - private static native void native_set(long native_dst, long native_src); - private static native boolean native_isConvex(long nPath); - private static native int native_getFillType(long nPath); - private static native void native_setFillType(long nPath, int ft); - private static native boolean native_isEmpty(long nPath); - private static native boolean native_isRect(long nPath, RectF rect); - private static native void native_computeBounds(long nPath, RectF bounds); - private static native void native_incReserve(long nPath, int extraPtCount); - private static native void native_moveTo(long nPath, float x, float y); - private static native void native_rMoveTo(long nPath, float dx, float dy); - private static native void native_lineTo(long nPath, float x, float y); - private static native void native_rLineTo(long nPath, float dx, float dy); - private static native void native_quadTo(long nPath, float x1, float y1, - float x2, float y2); - private static native void native_rQuadTo(long nPath, float dx1, float dy1, - float dx2, float dy2); - private static native void native_cubicTo(long nPath, float x1, float y1, - float x2, float y2, float x3, float y3); - private static native void native_rCubicTo(long nPath, float x1, float y1, - float x2, float y2, float x3, float y3); - private static native void native_arcTo(long nPath, float left, float top, - float right, float bottom, float startAngle, - float sweepAngle, boolean forceMoveTo); - private static native void native_close(long nPath); - private static native void native_addRect(long nPath, float left, float top, - float right, float bottom, int dir); - private static native void native_addOval(long nPath, float left, float top, + return nApproximate(mNativePath, acceptableError); + } + + // ------------------ Regular JNI ------------------------ + + private static native long nInit(); + private static native long nInit(long nPath); + private static native void nFinalize(long nPath); + private static native void nSet(long native_dst, long nSrc); + private static native void nComputeBounds(long nPath, RectF bounds); + private static native void nIncReserve(long nPath, int extraPtCount); + private static native void nMoveTo(long nPath, float x, float y); + private static native void nRMoveTo(long nPath, float dx, float dy); + private static native void nLineTo(long nPath, float x, float y); + private static native void nRLineTo(long nPath, float dx, float dy); + private static native void nQuadTo(long nPath, float x1, float y1, float x2, float y2); + private static native void nRQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2); + private static native void nCubicTo(long nPath, float x1, float y1, float x2, float y2, + float x3, float y3); + private static native void nRCubicTo(long nPath, float x1, float y1, float x2, float y2, + float x3, float y3); + private static native void nArcTo(long nPath, float left, float top, float right, float bottom, + float startAngle, float sweepAngle, boolean forceMoveTo); + private static native void nClose(long nPath); + private static native void nAddRect(long nPath, float left, float top, + float right, float bottom, int dir); + private static native void nAddOval(long nPath, float left, float top, float right, float bottom, int dir); - private static native void native_addCircle(long nPath, float x, float y, float radius, int dir); - private static native void native_addArc(long nPath, float left, float top, - float right, float bottom, - float startAngle, float sweepAngle); - private static native void native_addRoundRect(long nPath, float left, float top, - float right, float bottom, - float rx, float ry, int dir); - private static native void native_addRoundRect(long nPath, float left, float top, - float right, float bottom, - float[] radii, int dir); - private static native void native_addPath(long nPath, long src, float dx, float dy); - private static native void native_addPath(long nPath, long src); - private static native void native_addPath(long nPath, long src, long matrix); - private static native void native_offset(long nPath, float dx, float dy); - private static native void native_setLastPoint(long nPath, float dx, float dy); - private static native void native_transform(long nPath, long matrix, long dst_path); - private static native void native_transform(long nPath, long matrix); - private static native boolean native_op(long path1, long path2, int op, long result); - private static native void finalizer(long nPath); - private static native float[] native_approximate(long nPath, float error); + private static native void nAddCircle(long nPath, float x, float y, float radius, int dir); + private static native void nAddArc(long nPath, float left, float top, float right, float bottom, + float startAngle, float sweepAngle); + private static native void nAddRoundRect(long nPath, float left, float top, + float right, float bottom, float rx, float ry, int dir); + private static native void nAddRoundRect(long nPath, float left, float top, + float right, float bottom, float[] radii, int dir); + private static native void nAddPath(long nPath, long src, float dx, float dy); + private static native void nAddPath(long nPath, long src); + private static native void nAddPath(long nPath, long src, long matrix); + private static native void nOffset(long nPath, float dx, float dy); + private static native void nSetLastPoint(long nPath, float dx, float dy); + private static native void nTransform(long nPath, long matrix, long dst_path); + private static native void nTransform(long nPath, long matrix); + private static native boolean nOp(long path1, long path2, int op, long result); + private static native float[] nApproximate(long nPath, float error); + + // ------------------ Fast JNI ------------------------ + + @FastNative + private static native boolean nIsRect(long nPath, RectF rect); + + // ------------------ Critical JNI ------------------------ + + @CriticalNative + private static native void nReset(long nPath); + @CriticalNative + private static native void nRewind(long nPath); + @CriticalNative + private static native boolean nIsEmpty(long nPath); + @CriticalNative + private static native boolean nIsConvex(long nPath); + @CriticalNative + private static native int nGetFillType(long nPath); + @CriticalNative + private static native void nSetFillType(long nPath, int ft); } diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java index 98082cae8379..c3a944327fb5 100644 --- a/graphics/java/android/graphics/PixelFormat.java +++ b/graphics/java/android/graphics/PixelFormat.java @@ -22,13 +22,17 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; public class PixelFormat { - /** @hide */ @IntDef({UNKNOWN, TRANSLUCENT, TRANSPARENT, OPAQUE}) @Retention(RetentionPolicy.SOURCE) public @interface Opacity {} - /* these constants need to match those in hardware/hardware.h */ + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({RGBA_8888, RGBX_8888, RGBA_F16, RGB_888, RGB_565}) + public @interface Format { }; + + // NOTE: these constants must match the values from graphics/common/x.x/types.hal public static final int UNKNOWN = 0; @@ -62,7 +66,6 @@ public class PixelFormat { @Deprecated public static final int RGB_332 = 0xB; - /** * @deprecated use {@link android.graphics.ImageFormat#NV16 * ImageFormat.NV16} instead. @@ -84,6 +87,8 @@ public class PixelFormat { @Deprecated public static final int YCbCr_422_I = 0x14; + public static final int RGBA_F16 = 0x16; + /** * @deprecated use {@link android.graphics.ImageFormat#JPEG * ImageFormat.JPEG} instead. @@ -91,7 +96,10 @@ public class PixelFormat { @Deprecated public static final int JPEG = 0x100; - public static void getPixelFormatInfo(int format, PixelFormat info) { + public int bytesPerPixel; + public int bitsPerPixel; + + public static void getPixelFormatInfo(@Format int format, PixelFormat info) { switch (format) { case RGBA_8888: case RGBX_8888: @@ -124,18 +132,23 @@ public class PixelFormat { info.bitsPerPixel = 12; info.bytesPerPixel = 1; break; + case RGBA_F16: + info.bitsPerPixel = 64; + info.bytesPerPixel = 8; + break; default: throw new IllegalArgumentException("unknown pixel format " + format); } } - public static boolean formatHasAlpha(int format) { + public static boolean formatHasAlpha(@Format int format) { switch (format) { case PixelFormat.A_8: case PixelFormat.LA_88: case PixelFormat.RGBA_4444: case PixelFormat.RGBA_5551: case PixelFormat.RGBA_8888: + case PixelFormat.RGBA_F16: case PixelFormat.TRANSLUCENT: case PixelFormat.TRANSPARENT: return true; @@ -143,9 +156,6 @@ public class PixelFormat { return false; } - public int bytesPerPixel; - public int bitsPerPixel; - /** * Determine whether or not this is a public-visible and non-deprecated {@code format}. * @@ -159,12 +169,13 @@ public class PixelFormat { * * @hide */ - public static boolean isPublicFormat(int format) { + public static boolean isPublicFormat(@Format int format) { switch (format) { case RGBA_8888: case RGBX_8888: case RGB_888: case RGB_565: + case RGBA_F16: return true; } diff --git a/graphics/java/android/graphics/Rasterizer.java b/graphics/java/android/graphics/Rasterizer.java index f6a38fe89eab..29d82fa1d98b 100644 --- a/graphics/java/android/graphics/Rasterizer.java +++ b/graphics/java/android/graphics/Rasterizer.java @@ -21,15 +21,10 @@ package android.graphics; -@Deprecated +/** + * @removed feature is not supported by hw-accerlerated or PDF backends + */ public class Rasterizer { - protected void finalize() throws Throwable { - finalizer(native_instance); - native_instance = 0; - } - - private static native void finalizer(long native_instance); - - long native_instance; + protected void finalize() throws Throwable { } } diff --git a/legacy-test/Android.mk b/legacy-test/Android.mk index 7bab1894373d..5e72a0d541cb 100644 --- a/legacy-test/Android.mk +++ b/legacy-test/Android.mk @@ -25,7 +25,6 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_MODULE := legacy-test LOCAL_NO_STANDARD_LIBRARIES := true LOCAL_JAVA_LIBRARIES := core-oj core-libart framework -LOCAL_STATIC_JAVA_LIBRARIES := core-junit-static include $(BUILD_JAVA_LIBRARY) diff --git a/legacy-test/src/junit/MODULE_LICENSE_CPL b/legacy-test/src/junit/MODULE_LICENSE_CPL new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/legacy-test/src/junit/MODULE_LICENSE_CPL diff --git a/legacy-test/src/junit/README.android b/legacy-test/src/junit/README.android new file mode 100644 index 000000000000..1384a1fedda2 --- /dev/null +++ b/legacy-test/src/junit/README.android @@ -0,0 +1,11 @@ +URL: https://github.com/junit-team/junit4 +License: Common Public License Version 1.0 +License File: cpl-v10.html + +This is JUnit 4.10 source that was previously part of the Android Public API. +Where necessary it has been patched to be compatible (according to Android API +requirements) with JUnit 3.8. + +These are copied here to ensure that the android.test.runner target remains +compatible with the last version of the Android API (25) that contained these +classes even when external/junit is upgraded to a later version. diff --git a/legacy-test/src/junit/cpl-v10.html b/legacy-test/src/junit/cpl-v10.html new file mode 100644 index 000000000000..36aa208d4a29 --- /dev/null +++ b/legacy-test/src/junit/cpl-v10.html @@ -0,0 +1,125 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"> +<HTML> +<HEAD> +<TITLE>Common Public License - v 1.0</TITLE> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +</HEAD> + +<BODY BGCOLOR="#FFFFFF" VLINK="#800000"> + + +<P ALIGN="CENTER"><B>Common Public License - v 1.0</B> +<P><B></B><FONT SIZE="3"></FONT> +<P><FONT SIZE="3"></FONT><FONT SIZE="2">THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.</FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"><B>1. DEFINITIONS</B></FONT> +<P><FONT SIZE="2">"Contribution" means:</FONT> + +<UL><FONT SIZE="2">a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and<BR CLEAR="LEFT"> +b) in the case of each subsequent Contributor:</FONT></UL> + + +<UL><FONT SIZE="2">i) changes to the Program, and</FONT></UL> + + +<UL><FONT SIZE="2">ii) additions to the Program;</FONT></UL> + + +<UL><FONT SIZE="2">where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. </FONT><FONT SIZE="2">A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. </FONT><FONT SIZE="2">Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. </FONT></UL> + +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">"Contributor" means any person or entity that distributes the Program.</FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">"Licensed Patents " mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. </FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2">"Program" means the Contributions distributed in accordance with this Agreement.</FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.</FONT> +<P><FONT SIZE="2"><B></B></FONT> +<P><FONT SIZE="2"><B>2. GRANT OF RIGHTS</B></FONT> + +<UL><FONT SIZE="2"></FONT><FONT SIZE="2">a) </FONT><FONT SIZE="2">Subject to the terms of this Agreement, each Contributor hereby grants</FONT><FONT SIZE="2"> Recipient a non-exclusive, worldwide, royalty-free copyright license to</FONT><FONT SIZE="2" COLOR="#FF0000"> </FONT><FONT SIZE="2">reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.</FONT></UL> + + +<UL><FONT SIZE="2"></FONT></UL> + + +<UL><FONT SIZE="2"></FONT><FONT SIZE="2">b) Subject to the terms of this Agreement, each Contributor hereby grants </FONT><FONT SIZE="2">Recipient a non-exclusive, worldwide,</FONT><FONT SIZE="2" COLOR="#008000"> </FONT><FONT SIZE="2">royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. </FONT></UL> + + +<UL><FONT SIZE="2"></FONT></UL> + + +<UL><FONT SIZE="2">c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program.</FONT></UL> + + +<UL><FONT SIZE="2"></FONT></UL> + + +<UL><FONT SIZE="2">d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. </FONT></UL> + + +<UL><FONT SIZE="2"></FONT></UL> + +<P><FONT SIZE="2"><B>3. REQUIREMENTS</B></FONT> +<P><FONT SIZE="2"><B></B>A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:</FONT> + +<UL><FONT SIZE="2">a) it complies with the terms and conditions of this Agreement; and</FONT></UL> + + +<UL><FONT SIZE="2">b) its license agreement:</FONT></UL> + + +<UL><FONT SIZE="2">i) effectively disclaims</FONT><FONT SIZE="2"> on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; </FONT></UL> + + +<UL><FONT SIZE="2">ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; </FONT></UL> + + +<UL><FONT SIZE="2">iii)</FONT><FONT SIZE="2"> states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and</FONT></UL> + + +<UL><FONT SIZE="2">iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.</FONT><FONT SIZE="2" COLOR="#0000FF"> </FONT><FONT SIZE="2" COLOR="#FF0000"></FONT></UL> + + +<UL><FONT SIZE="2" COLOR="#FF0000"></FONT><FONT SIZE="2"></FONT></UL> + +<P><FONT SIZE="2">When the Program is made available in source code form:</FONT> + +<UL><FONT SIZE="2">a) it must be made available under this Agreement; and </FONT></UL> + + +<UL><FONT SIZE="2">b) a copy of this Agreement must be included with each copy of the Program. </FONT></UL> + +<P><FONT SIZE="2"></FONT><FONT SIZE="2" COLOR="#0000FF"><STRIKE></STRIKE></FONT> +<P><FONT SIZE="2" COLOR="#0000FF"><STRIKE></STRIKE></FONT><FONT SIZE="2">Contributors may not remove or alter any copyright notices contained within the Program. </FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. </FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"><B>4. COMMERCIAL DISTRIBUTION</B></FONT> +<P><FONT SIZE="2">Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense.</FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages.</FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2" COLOR="#0000FF"></FONT> +<P><FONT SIZE="2" COLOR="#0000FF"></FONT><FONT SIZE="2"><B>5. NO WARRANTY</B></FONT> +<P><FONT SIZE="2">EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is</FONT><FONT SIZE="2"> solely responsible for determining the appropriateness of using and distributing </FONT><FONT SIZE="2">the Program</FONT><FONT SIZE="2"> and assumes all risks associated with its exercise of rights under this Agreement</FONT><FONT SIZE="2">, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, </FONT><FONT SIZE="2">programs or equipment, and unavailability or interruption of operations</FONT><FONT SIZE="2">. </FONT><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2"><B>6. DISCLAIMER OF LIABILITY</B></FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2">EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES </FONT><FONT SIZE="2">(INCLUDING WITHOUT LIMITATION LOST PROFITS),</FONT><FONT SIZE="2"> HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.</FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"><B>7. GENERAL</B></FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2">If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.</FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">If Recipient institutes patent litigation against a Contributor with respect to a patent applicable to software (including a cross-claim or counterclaim in a lawsuit), then any patent licenses granted by that Contributor to such Recipient under this Agreement shall terminate as of the date such litigation is filed. In addition, if Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. </FONT><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. </FONT><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2">Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to </FONT><FONT SIZE="2">publish new versions (including revisions) of this Agreement from time to </FONT><FONT SIZE="2">time. No one other than the Agreement Steward has the right to modify this Agreement. IBM is the initial Agreement Steward. IBM may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. </FONT><FONT SIZE="2">Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new </FONT><FONT SIZE="2">version. </FONT><FONT SIZE="2">Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, </FONT><FONT SIZE="2">by implication, estoppel or otherwise</FONT><FONT SIZE="2">.</FONT><FONT SIZE="2"> All rights in the Program not expressly granted under this Agreement are reserved.</FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.</FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"></FONT> + +</BODY> + +</HTML>
\ No newline at end of file diff --git a/legacy-test/src/junit/framework/Assert.java b/legacy-test/src/junit/framework/Assert.java new file mode 100644 index 000000000000..3dcc23d71c19 --- /dev/null +++ b/legacy-test/src/junit/framework/Assert.java @@ -0,0 +1,296 @@ +package junit.framework; + +/** + * A set of assert methods. Messages are only displayed when an assert fails. + */ + +public class Assert { + /** + * Protect constructor since it is a static only class + */ + protected Assert() { + } + + /** + * Asserts that a condition is true. If it isn't it throws + * an AssertionFailedError with the given message. + */ + static public void assertTrue(String message, boolean condition) { + if (!condition) + fail(message); + } + /** + * Asserts that a condition is true. If it isn't it throws + * an AssertionFailedError. + */ + static public void assertTrue(boolean condition) { + assertTrue(null, condition); + } + /** + * Asserts that a condition is false. If it isn't it throws + * an AssertionFailedError with the given message. + */ + static public void assertFalse(String message, boolean condition) { + assertTrue(message, !condition); + } + /** + * Asserts that a condition is false. If it isn't it throws + * an AssertionFailedError. + */ + static public void assertFalse(boolean condition) { + assertFalse(null, condition); + } + /** + * Fails a test with the given message. + */ + static public void fail(String message) { + if (message == null) { + throw new AssertionFailedError(); + } + throw new AssertionFailedError(message); + } + /** + * Fails a test with no message. + */ + static public void fail() { + fail(null); + } + /** + * Asserts that two objects are equal. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertEquals(String message, Object expected, Object actual) { + if (expected == null && actual == null) + return; + if (expected != null && expected.equals(actual)) + return; + failNotEquals(message, expected, actual); + } + /** + * Asserts that two objects are equal. If they are not + * an AssertionFailedError is thrown. + */ + static public void assertEquals(Object expected, Object actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that two Strings are equal. + */ + static public void assertEquals(String message, String expected, String actual) { + if (expected == null && actual == null) + return; + if (expected != null && expected.equals(actual)) + return; + String cleanMessage= message == null ? "" : message; + throw new ComparisonFailure(cleanMessage, expected, actual); + } + /** + * Asserts that two Strings are equal. + */ + static public void assertEquals(String expected, String actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that two doubles are equal concerning a delta. If they are not + * an AssertionFailedError is thrown with the given message. If the expected + * value is infinity then the delta value is ignored. + */ + static public void assertEquals(String message, double expected, double actual, double delta) { + if (Double.compare(expected, actual) == 0) + return; + if (!(Math.abs(expected-actual) <= delta)) + failNotEquals(message, new Double(expected), new Double(actual)); + } + /** + * Asserts that two doubles are equal concerning a delta. If the expected + * value is infinity then the delta value is ignored. + */ + static public void assertEquals(double expected, double actual, double delta) { + assertEquals(null, expected, actual, delta); + } + /** + * Asserts that two floats are equal concerning a positive delta. If they + * are not an AssertionFailedError is thrown with the given message. If the + * expected value is infinity then the delta value is ignored. + */ + static public void assertEquals(String message, float expected, float actual, float delta) { + if (Float.compare(expected, actual) == 0) + return; + if (!(Math.abs(expected - actual) <= delta)) + failNotEquals(message, new Float(expected), new Float(actual)); + } + /** + * Asserts that two floats are equal concerning a delta. If the expected + * value is infinity then the delta value is ignored. + */ + static public void assertEquals(float expected, float actual, float delta) { + assertEquals(null, expected, actual, delta); + } + /** + * Asserts that two longs are equal. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertEquals(String message, long expected, long actual) { + assertEquals(message, new Long(expected), new Long(actual)); + } + /** + * Asserts that two longs are equal. + */ + static public void assertEquals(long expected, long actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that two booleans are equal. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertEquals(String message, boolean expected, boolean actual) { + assertEquals(message, Boolean.valueOf(expected), Boolean.valueOf(actual)); + } + /** + * Asserts that two booleans are equal. + */ + static public void assertEquals(boolean expected, boolean actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that two bytes are equal. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertEquals(String message, byte expected, byte actual) { + assertEquals(message, new Byte(expected), new Byte(actual)); + } + /** + * Asserts that two bytes are equal. + */ + static public void assertEquals(byte expected, byte actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that two chars are equal. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertEquals(String message, char expected, char actual) { + assertEquals(message, new Character(expected), new Character(actual)); + } + /** + * Asserts that two chars are equal. + */ + static public void assertEquals(char expected, char actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that two shorts are equal. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertEquals(String message, short expected, short actual) { + assertEquals(message, new Short(expected), new Short(actual)); + } + /** + * Asserts that two shorts are equal. + */ + static public void assertEquals(short expected, short actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that two ints are equal. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertEquals(String message, int expected, int actual) { + assertEquals(message, new Integer(expected), new Integer(actual)); + } + /** + * Asserts that two ints are equal. + */ + static public void assertEquals(int expected, int actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that an object isn't null. + */ + static public void assertNotNull(Object object) { + assertNotNull(null, object); + } + /** + * Asserts that an object isn't null. If it is + * an AssertionFailedError is thrown with the given message. + */ + static public void assertNotNull(String message, Object object) { + assertTrue(message, object != null); + } + /** + * Asserts that an object is null. If it isn't an {@link AssertionError} is + * thrown. + * Message contains: Expected: <null> but was: object + * + * @param object + * Object to check or <code>null</code> + */ + static public void assertNull(Object object) { + String message = "Expected: <null> but was: " + String.valueOf(object); + assertNull(message, object); + } + /** + * Asserts that an object is null. If it is not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertNull(String message, Object object) { + assertTrue(message, object == null); + } + /** + * Asserts that two objects refer to the same object. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertSame(String message, Object expected, Object actual) { + if (expected == actual) + return; + failNotSame(message, expected, actual); + } + /** + * Asserts that two objects refer to the same object. If they are not + * the same an AssertionFailedError is thrown. + */ + static public void assertSame(Object expected, Object actual) { + assertSame(null, expected, actual); + } + /** + * Asserts that two objects do not refer to the same object. If they do + * refer to the same object an AssertionFailedError is thrown with the + * given message. + */ + static public void assertNotSame(String message, Object expected, Object actual) { + if (expected == actual) + failSame(message); + } + /** + * Asserts that two objects do not refer to the same object. If they do + * refer to the same object an AssertionFailedError is thrown. + */ + static public void assertNotSame(Object expected, Object actual) { + assertNotSame(null, expected, actual); + } + + static public void failSame(String message) { + String formatted= ""; + if (message != null) + formatted= message+" "; + fail(formatted+"expected not same"); + } + + static public void failNotSame(String message, Object expected, Object actual) { + String formatted= ""; + if (message != null) + formatted= message+" "; + fail(formatted+"expected same:<"+expected+"> was not:<"+actual+">"); + } + + static public void failNotEquals(String message, Object expected, Object actual) { + fail(format(message, expected, actual)); + } + + public static String format(String message, Object expected, Object actual) { + String formatted= ""; + if (message != null && message.length() > 0) + formatted= message+" "; + return formatted+"expected:<"+expected+"> but was:<"+actual+">"; + } +} diff --git a/legacy-test/src/junit/framework/AssertionFailedError.java b/legacy-test/src/junit/framework/AssertionFailedError.java new file mode 100644 index 000000000000..0d7802c431c6 --- /dev/null +++ b/legacy-test/src/junit/framework/AssertionFailedError.java @@ -0,0 +1,20 @@ +package junit.framework; + +/** + * Thrown when an assertion failed. + */ +public class AssertionFailedError extends AssertionError { + + private static final long serialVersionUID= 1L; + + public AssertionFailedError() { + } + + public AssertionFailedError(String message) { + super(defaultString(message)); + } + + private static String defaultString(String message) { + return message == null ? "" : message; + } +}
\ No newline at end of file diff --git a/legacy-test/src/junit/framework/ComparisonCompactor.java b/legacy-test/src/junit/framework/ComparisonCompactor.java new file mode 100644 index 000000000000..e540f03b87d3 --- /dev/null +++ b/legacy-test/src/junit/framework/ComparisonCompactor.java @@ -0,0 +1,87 @@ +package junit.framework; + +// android-changed add @hide +/** + * @hide not needed for public API + */ +public class ComparisonCompactor { + + private static final String ELLIPSIS= "..."; + private static final String DELTA_END= "]"; + private static final String DELTA_START= "["; + + private int fContextLength; + private String fExpected; + private String fActual; + private int fPrefix; + private int fSuffix; + + public ComparisonCompactor(int contextLength, String expected, String actual) { + fContextLength= contextLength; + fExpected= expected; + fActual= actual; + } + + public String compact(String message) { + if (fExpected == null || fActual == null || areStringsEqual()) { + // android-changed use local method instead of Assert.format, since + // the later is not part of Android API till API 16 + return format(message, fExpected, fActual); + } + findCommonPrefix(); + findCommonSuffix(); + String expected= compactString(fExpected); + String actual= compactString(fActual); + // android-changed use local format method + return format(message, expected, actual); + } + + private String compactString(String source) { + String result= DELTA_START + source.substring(fPrefix, source.length() - fSuffix + 1) + DELTA_END; + if (fPrefix > 0) + result= computeCommonPrefix() + result; + if (fSuffix > 0) + result= result + computeCommonSuffix(); + return result; + } + + private void findCommonPrefix() { + fPrefix= 0; + int end= Math.min(fExpected.length(), fActual.length()); + for (; fPrefix < end; fPrefix++) { + if (fExpected.charAt(fPrefix) != fActual.charAt(fPrefix)) + break; + } + } + + private void findCommonSuffix() { + int expectedSuffix= fExpected.length() - 1; + int actualSuffix= fActual.length() - 1; + for (; actualSuffix >= fPrefix && expectedSuffix >= fPrefix; actualSuffix--, expectedSuffix--) { + if (fExpected.charAt(expectedSuffix) != fActual.charAt(actualSuffix)) + break; + } + fSuffix= fExpected.length() - expectedSuffix; + } + + private String computeCommonPrefix() { + return (fPrefix > fContextLength ? ELLIPSIS : "") + fExpected.substring(Math.max(0, fPrefix - fContextLength), fPrefix); + } + + private String computeCommonSuffix() { + int end= Math.min(fExpected.length() - fSuffix + 1 + fContextLength, fExpected.length()); + return fExpected.substring(fExpected.length() - fSuffix + 1, end) + (fExpected.length() - fSuffix + 1 < fExpected.length() - fContextLength ? ELLIPSIS : ""); + } + + private boolean areStringsEqual() { + return fExpected.equals(fActual); + } + + // android-changed copy of Assert.format for reasons described above + private static String format(String message, Object expected, Object actual) { + String formatted= ""; + if (message != null && message.length() > 0) + formatted= message+" "; + return formatted+"expected:<"+expected+"> but was:<"+actual+">"; + } +} diff --git a/legacy-test/src/junit/framework/ComparisonFailure.java b/legacy-test/src/junit/framework/ComparisonFailure.java new file mode 100644 index 000000000000..507799328a44 --- /dev/null +++ b/legacy-test/src/junit/framework/ComparisonFailure.java @@ -0,0 +1,52 @@ +package junit.framework; + +/** + * Thrown when an assert equals for Strings failed. + * + * Inspired by a patch from Alex Chaffee mailto:alex@purpletech.com + */ +public class ComparisonFailure extends AssertionFailedError { + private static final int MAX_CONTEXT_LENGTH= 20; + private static final long serialVersionUID= 1L; + + private String fExpected; + private String fActual; + + /** + * Constructs a comparison failure. + * @param message the identifying message or null + * @param expected the expected string value + * @param actual the actual string value + */ + public ComparisonFailure (String message, String expected, String actual) { + super (message); + fExpected= expected; + fActual= actual; + } + + /** + * Returns "..." in place of common prefix and "..." in + * place of common suffix between expected and actual. + * + * @see Throwable#getMessage() + */ + @Override + public String getMessage() { + return new ComparisonCompactor(MAX_CONTEXT_LENGTH, fExpected, fActual).compact(super.getMessage()); + } + + /** + * Gets the actual string value + * @return the actual string value + */ + public String getActual() { + return fActual; + } + /** + * Gets the expected string value + * @return the expected string value + */ + public String getExpected() { + return fExpected; + } +}
\ No newline at end of file diff --git a/legacy-test/src/junit/framework/Protectable.java b/legacy-test/src/junit/framework/Protectable.java new file mode 100644 index 000000000000..e1432370cfaf --- /dev/null +++ b/legacy-test/src/junit/framework/Protectable.java @@ -0,0 +1,14 @@ +package junit.framework; + +/** + * A <em>Protectable</em> can be run and can throw a Throwable. + * + * @see TestResult + */ +public interface Protectable { + + /** + * Run the the following method protected. + */ + public abstract void protect() throws Throwable; +}
\ No newline at end of file diff --git a/legacy-test/src/junit/framework/Test.java b/legacy-test/src/junit/framework/Test.java new file mode 100644 index 000000000000..a016ee8308f1 --- /dev/null +++ b/legacy-test/src/junit/framework/Test.java @@ -0,0 +1,17 @@ +package junit.framework; + +/** + * A <em>Test</em> can be run and collect its results. + * + * @see TestResult + */ +public interface Test { + /** + * Counts the number of test cases that will be run by this test. + */ + public abstract int countTestCases(); + /** + * Runs a test and collects its result in a TestResult instance. + */ + public abstract void run(TestResult result); +}
\ No newline at end of file diff --git a/legacy-test/src/junit/framework/TestCase.java b/legacy-test/src/junit/framework/TestCase.java new file mode 100644 index 000000000000..b047ec9e1afc --- /dev/null +++ b/legacy-test/src/junit/framework/TestCase.java @@ -0,0 +1,212 @@ +package junit.framework; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * A test case defines the fixture to run multiple tests. To define a test case<br/> + * <ol> + * <li>implement a subclass of <code>TestCase</code></li> + * <li>define instance variables that store the state of the fixture</li> + * <li>initialize the fixture state by overriding {@link #setUp()}</li> + * <li>clean-up after a test by overriding {@link #tearDown()}.</li> + * </ol> + * Each test runs in its own fixture so there + * can be no side effects among test runs. + * Here is an example: + * <pre> + * public class MathTest extends TestCase { + * protected double fValue1; + * protected double fValue2; + * + * protected void setUp() { + * fValue1= 2.0; + * fValue2= 3.0; + * } + * } + * </pre> + * + * For each test implement a method which interacts + * with the fixture. Verify the expected results with assertions specified + * by calling {@link junit.framework.Assert#assertTrue(String, boolean)} with a boolean. + * <pre> + * public void testAdd() { + * double result= fValue1 + fValue2; + * assertTrue(result == 5.0); + * } + * </pre> + * + * Once the methods are defined you can run them. The framework supports + * both a static type safe and more dynamic way to run a test. + * In the static way you override the runTest method and define the method to + * be invoked. A convenient way to do so is with an anonymous inner class. + * <pre> + * TestCase test= new MathTest("add") { + * public void runTest() { + * testAdd(); + * } + * }; + * test.run(); + * </pre> + * + * The dynamic way uses reflection to implement {@link #runTest()}. It dynamically finds + * and invokes a method. + * In this case the name of the test case has to correspond to the test method + * to be run. + * <pre> + * TestCase test= new MathTest("testAdd"); + * test.run(); + * </pre> + * + * The tests to be run can be collected into a TestSuite. JUnit provides + * different <i>test runners</i> which can run a test suite and collect the results. + * A test runner either expects a static method <code>suite</code> as the entry + * point to get a test to run or it will extract the suite automatically. + * <pre> + * public static Test suite() { + * suite.addTest(new MathTest("testAdd")); + * suite.addTest(new MathTest("testDivideByZero")); + * return suite; + * } + * </pre> + * @see TestResult + * @see TestSuite + */ +public abstract class TestCase extends Assert implements Test { + /** + * the name of the test case + */ + private String fName; + + /** + * No-arg constructor to enable serialization. This method + * is not intended to be used by mere mortals without calling setName(). + */ + public TestCase() { + fName= null; + } + /** + * Constructs a test case with the given name. + */ + public TestCase(String name) { + fName= name; + } + /** + * Counts the number of test cases executed by run(TestResult result). + */ + public int countTestCases() { + return 1; + } + /** + * Creates a default TestResult object + * + * @see TestResult + */ + protected TestResult createResult() { + return new TestResult(); + } + /** + * A convenience method to run this test, collecting the results with a + * default TestResult object. + * + * @see TestResult + */ + public TestResult run() { + TestResult result= createResult(); + run(result); + return result; + } + /** + * Runs the test case and collects the results in TestResult. + */ + public void run(TestResult result) { + result.run(this); + } + /** + * Runs the bare test sequence. + * @throws Throwable if any exception is thrown + */ + public void runBare() throws Throwable { + Throwable exception= null; + setUp(); + try { + runTest(); + } catch (Throwable running) { + exception= running; + } + finally { + try { + tearDown(); + } catch (Throwable tearingDown) { + if (exception == null) exception= tearingDown; + } + } + if (exception != null) throw exception; + } + /** + * Override to run the test and assert its state. + * @throws Throwable if any exception is thrown + */ + protected void runTest() throws Throwable { + assertNotNull("TestCase.fName cannot be null", fName); // Some VMs crash when calling getMethod(null,null); + Method runMethod= null; + try { + // use getMethod to get all public inherited + // methods. getDeclaredMethods returns all + // methods of this class but excludes the + // inherited ones. + runMethod= getClass().getMethod(fName, (Class[])null); + } catch (NoSuchMethodException e) { + fail("Method \""+fName+"\" not found"); + } + if (!Modifier.isPublic(runMethod.getModifiers())) { + fail("Method \""+fName+"\" should be public"); + } + + try { + runMethod.invoke(this); + } + catch (InvocationTargetException e) { + e.fillInStackTrace(); + throw e.getTargetException(); + } + catch (IllegalAccessException e) { + e.fillInStackTrace(); + throw e; + } + } + /** + * Sets up the fixture, for example, open a network connection. + * This method is called before a test is executed. + */ + protected void setUp() throws Exception { + } + /** + * Tears down the fixture, for example, close a network connection. + * This method is called after a test is executed. + */ + protected void tearDown() throws Exception { + } + /** + * Returns a string representation of the test case + */ + @Override + public String toString() { + return getName() + "(" + getClass().getName() + ")"; + } + /** + * Gets the name of a TestCase + * @return the name of the TestCase + */ + public String getName() { + return fName; + } + /** + * Sets the name of a TestCase + * @param name the name to set + */ + public void setName(String name) { + fName= name; + } +} diff --git a/legacy-test/src/junit/framework/TestFailure.java b/legacy-test/src/junit/framework/TestFailure.java new file mode 100644 index 000000000000..6662b1fab1b2 --- /dev/null +++ b/legacy-test/src/junit/framework/TestFailure.java @@ -0,0 +1,58 @@ +package junit.framework; + +import java.io.PrintWriter; +import java.io.StringWriter; + + +/** + * A <code>TestFailure</code> collects a failed test together with + * the caught exception. + * @see TestResult + */ +public class TestFailure extends Object { + protected Test fFailedTest; + protected Throwable fThrownException; + + + /** + * Constructs a TestFailure with the given test and exception. + */ + public TestFailure(Test failedTest, Throwable thrownException) { + fFailedTest= failedTest; + fThrownException= thrownException; + } + /** + * Gets the failed test. + */ + public Test failedTest() { + return fFailedTest; + } + /** + * Gets the thrown exception. + */ + public Throwable thrownException() { + return fThrownException; + } + /** + * Returns a short description of the failure. + */ + @Override + public String toString() { + StringBuffer buffer= new StringBuffer(); + buffer.append(fFailedTest+": "+fThrownException.getMessage()); + return buffer.toString(); + } + public String trace() { + StringWriter stringWriter= new StringWriter(); + PrintWriter writer= new PrintWriter(stringWriter); + thrownException().printStackTrace(writer); + StringBuffer buffer= stringWriter.getBuffer(); + return buffer.toString(); + } + public String exceptionMessage() { + return thrownException().getMessage(); + } + public boolean isFailure() { + return thrownException() instanceof AssertionFailedError; + } +}
\ No newline at end of file diff --git a/legacy-test/src/junit/framework/TestListener.java b/legacy-test/src/junit/framework/TestListener.java new file mode 100644 index 000000000000..9b6944361b9d --- /dev/null +++ b/legacy-test/src/junit/framework/TestListener.java @@ -0,0 +1,23 @@ +package junit.framework; + +/** + * A Listener for test progress + */ +public interface TestListener { + /** + * An error occurred. + */ + public void addError(Test test, Throwable t); + /** + * A failure occurred. + */ + public void addFailure(Test test, AssertionFailedError t); + /** + * A test ended. + */ + public void endTest(Test test); + /** + * A test started. + */ + public void startTest(Test test); +}
\ No newline at end of file diff --git a/legacy-test/src/junit/framework/TestResult.java b/legacy-test/src/junit/framework/TestResult.java new file mode 100644 index 000000000000..3052e94074fd --- /dev/null +++ b/legacy-test/src/junit/framework/TestResult.java @@ -0,0 +1,174 @@ +package junit.framework; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.Vector; + +/** + * A <code>TestResult</code> collects the results of executing + * a test case. It is an instance of the Collecting Parameter pattern. + * The test framework distinguishes between <i>failures</i> and <i>errors</i>. + * A failure is anticipated and checked for with assertions. Errors are + * unanticipated problems like an {@link ArrayIndexOutOfBoundsException}. + * + * @see Test + */ +public class TestResult extends Object { + // BEGIN android-changed changed types from List<> to Vector<> for API compatibility + protected Vector<TestFailure> fFailures; + protected Vector<TestFailure> fErrors; + protected Vector<TestListener> fListeners; + // END android-changed + protected int fRunTests; + private boolean fStop; + + public TestResult() { + // BEGIN android-changed to Vector + fFailures= new Vector<TestFailure>(); + fErrors= new Vector<TestFailure>(); + fListeners= new Vector<TestListener>(); + // END android-changed + fRunTests= 0; + fStop= false; + } + /** + * Adds an error to the list of errors. The passed in exception + * caused the error. + */ + public synchronized void addError(Test test, Throwable t) { + fErrors.add(new TestFailure(test, t)); + for (TestListener each : cloneListeners()) + each.addError(test, t); + } + /** + * Adds a failure to the list of failures. The passed in exception + * caused the failure. + */ + public synchronized void addFailure(Test test, AssertionFailedError t) { + fFailures.add(new TestFailure(test, t)); + for (TestListener each : cloneListeners()) + each.addFailure(test, t); + } + /** + * Registers a TestListener + */ + public synchronized void addListener(TestListener listener) { + fListeners.add(listener); + } + /** + * Unregisters a TestListener + */ + public synchronized void removeListener(TestListener listener) { + fListeners.remove(listener); + } + /** + * Returns a copy of the listeners. + */ + private synchronized List<TestListener> cloneListeners() { + List<TestListener> result= new ArrayList<TestListener>(); + result.addAll(fListeners); + return result; + } + /** + * Informs the result that a test was completed. + */ + public void endTest(Test test) { + for (TestListener each : cloneListeners()) + each.endTest(test); + } + /** + * Gets the number of detected errors. + */ + public synchronized int errorCount() { + return fErrors.size(); + } + /** + * Returns an Enumeration for the errors + */ + public synchronized Enumeration<TestFailure> errors() { + return Collections.enumeration(fErrors); + } + + + /** + * Gets the number of detected failures. + */ + public synchronized int failureCount() { + return fFailures.size(); + } + /** + * Returns an Enumeration for the failures + */ + public synchronized Enumeration<TestFailure> failures() { + return Collections.enumeration(fFailures); + } + + /** + * Runs a TestCase. + */ + protected void run(final TestCase test) { + startTest(test); + Protectable p= new Protectable() { + public void protect() throws Throwable { + test.runBare(); + } + }; + runProtected(test, p); + + endTest(test); + } + /** + * Gets the number of run tests. + */ + public synchronized int runCount() { + return fRunTests; + } + /** + * Runs a TestCase. + */ + public void runProtected(final Test test, Protectable p) { + try { + p.protect(); + } + catch (AssertionFailedError e) { + addFailure(test, e); + } + catch (ThreadDeath e) { // don't catch ThreadDeath by accident + throw e; + } + catch (Throwable e) { + addError(test, e); + } + } + /** + * Checks whether the test run should stop + */ + public synchronized boolean shouldStop() { + return fStop; + } + /** + * Informs the result that a test will be started. + */ + public void startTest(Test test) { + final int count= test.countTestCases(); + synchronized(this) { + fRunTests+= count; + } + for (TestListener each : cloneListeners()) + each.startTest(test); + } + /** + * Marks that the test run should stop. + */ + public synchronized void stop() { + fStop= true; + } + /** + * Returns whether the entire test was successful or not. + */ + public synchronized boolean wasSuccessful() { + return failureCount() == 0 && errorCount() == 0; + } +} diff --git a/legacy-test/src/junit/framework/TestSuite.java b/legacy-test/src/junit/framework/TestSuite.java new file mode 100644 index 000000000000..336efd1800d7 --- /dev/null +++ b/legacy-test/src/junit/framework/TestSuite.java @@ -0,0 +1,307 @@ +package junit.framework; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Vector; + +/** + * <p>A <code>TestSuite</code> is a <code>Composite</code> of Tests. + * It runs a collection of test cases. Here is an example using + * the dynamic test definition. + * <pre> + * TestSuite suite= new TestSuite(); + * suite.addTest(new MathTest("testAdd")); + * suite.addTest(new MathTest("testDivideByZero")); + * </pre> + * </p> + * + * <p>Alternatively, a TestSuite can extract the tests to be run automatically. + * To do so you pass the class of your TestCase class to the + * TestSuite constructor. + * <pre> + * TestSuite suite= new TestSuite(MathTest.class); + * </pre> + * </p> + * + * <p>This constructor creates a suite with all the methods + * starting with "test" that take no arguments.</p> + * + * <p>A final option is to do the same for a large array of test classes. + * <pre> + * Class[] testClasses = { MathTest.class, AnotherTest.class } + * TestSuite suite= new TestSuite(testClasses); + * </pre> + * </p> + * + * @see Test + */ +public class TestSuite implements Test { + + /** + * ...as the moon sets over the early morning Merlin, Oregon + * mountains, our intrepid adventurers type... + */ + static public Test createTest(Class<?> theClass, String name) { + Constructor<?> constructor; + try { + constructor= getTestConstructor(theClass); + } catch (NoSuchMethodException e) { + return warning("Class "+theClass.getName()+" has no public constructor TestCase(String name) or TestCase()"); + } + Object test; + try { + if (constructor.getParameterTypes().length == 0) { + test= constructor.newInstance(new Object[0]); + if (test instanceof TestCase) + ((TestCase) test).setName(name); + } else { + test= constructor.newInstance(new Object[]{name}); + } + } catch (InstantiationException e) { + return(warning("Cannot instantiate test case: "+name+" ("+exceptionToString(e)+")")); + } catch (InvocationTargetException e) { + return(warning("Exception in constructor: "+name+" ("+exceptionToString(e.getTargetException())+")")); + } catch (IllegalAccessException e) { + return(warning("Cannot access test case: "+name+" ("+exceptionToString(e)+")")); + } + return (Test) test; + } + + /** + * Gets a constructor which takes a single String as + * its argument or a no arg constructor. + */ + public static Constructor<?> getTestConstructor(Class<?> theClass) throws NoSuchMethodException { + try { + return theClass.getConstructor(String.class); + } catch (NoSuchMethodException e) { + // fall through + } + return theClass.getConstructor(new Class[0]); + } + + /** + * Returns a test which will fail and log a warning message. + */ + public static Test warning(final String message) { + return new TestCase("warning") { + @Override + protected void runTest() { + fail(message); + } + }; + } + + /** + * Converts the stack trace into a string + */ + private static String exceptionToString(Throwable t) { + StringWriter stringWriter= new StringWriter(); + PrintWriter writer= new PrintWriter(stringWriter); + t.printStackTrace(writer); + return stringWriter.toString(); + } + + private String fName; + + private Vector<Test> fTests= new Vector<Test>(10); // Cannot convert this to List because it is used directly by some test runners + + /** + * Constructs an empty TestSuite. + */ + public TestSuite() { + } + + /** + * Constructs a TestSuite from the given class. Adds all the methods + * starting with "test" as test cases to the suite. + * Parts of this method were written at 2337 meters in the Hueffihuette, + * Kanton Uri + */ + public TestSuite(final Class<?> theClass) { + addTestsFromTestCase(theClass); + } + + private void addTestsFromTestCase(final Class<?> theClass) { + fName= theClass.getName(); + try { + getTestConstructor(theClass); // Avoid generating multiple error messages + } catch (NoSuchMethodException e) { + addTest(warning("Class "+theClass.getName()+" has no public constructor TestCase(String name) or TestCase()")); + return; + } + + if (!Modifier.isPublic(theClass.getModifiers())) { + addTest(warning("Class "+theClass.getName()+" is not public")); + return; + } + + Class<?> superClass= theClass; + List<String> names= new ArrayList<String>(); + while (Test.class.isAssignableFrom(superClass)) { + for (Method each : superClass.getDeclaredMethods()) + addTestMethod(each, names, theClass); + superClass= superClass.getSuperclass(); + } + if (fTests.size() == 0) + addTest(warning("No tests found in "+theClass.getName())); + } + + /** + * Constructs a TestSuite from the given class with the given name. + * @see TestSuite#TestSuite(Class) + */ + public TestSuite(Class<? extends TestCase> theClass, String name) { + this(theClass); + setName(name); + } + + /** + * Constructs an empty TestSuite. + */ + public TestSuite(String name) { + setName(name); + } + + /** + * Constructs a TestSuite from the given array of classes. + * @param classes {@link TestCase}s + */ + public TestSuite (Class<?>... classes) { + for (Class<?> each : classes) + addTest(testCaseForClass(each)); + } + + private Test testCaseForClass(Class<?> each) { + if (TestCase.class.isAssignableFrom(each)) + return new TestSuite(each.asSubclass(TestCase.class)); + else + return warning(each.getCanonicalName() + " does not extend TestCase"); + } + + /** + * Constructs a TestSuite from the given array of classes with the given name. + * @see TestSuite#TestSuite(Class[]) + */ + public TestSuite(Class<? extends TestCase>[] classes, String name) { + this(classes); + setName(name); + } + + /** + * Adds a test to the suite. + */ + public void addTest(Test test) { + fTests.add(test); + } + + /** + * Adds the tests from the given class to the suite + */ + public void addTestSuite(Class<? extends TestCase> testClass) { + addTest(new TestSuite(testClass)); + } + + /** + * Counts the number of test cases that will be run by this test. + */ + public int countTestCases() { + int count= 0; + for (Test each : fTests) + count+= each.countTestCases(); + return count; + } + + /** + * Returns the name of the suite. Not all + * test suites have a name and this method + * can return null. + */ + public String getName() { + return fName; + } + + /** + * Runs the tests and collects their result in a TestResult. + */ + public void run(TestResult result) { + for (Test each : fTests) { + if (result.shouldStop() ) + break; + runTest(each, result); + } + } + + public void runTest(Test test, TestResult result) { + test.run(result); + } + + /** + * Sets the name of the suite. + * @param name the name to set + */ + public void setName(String name) { + fName= name; + } + + /** + * Returns the test at the given index + */ + public Test testAt(int index) { + return fTests.get(index); + } + + /** + * Returns the number of tests in this suite + */ + public int testCount() { + return fTests.size(); + } + + /** + * Returns the tests as an enumeration + */ + public Enumeration<Test> tests() { + return fTests.elements(); + } + + /** + */ + @Override + public String toString() { + if (getName() != null) + return getName(); + return super.toString(); + } + + private void addTestMethod(Method m, List<String> names, Class<?> theClass) { + String name= m.getName(); + if (names.contains(name)) + return; + if (! isPublicTestMethod(m)) { + if (isTestMethod(m)) + addTest(warning("Test method isn't public: "+ m.getName() + "(" + theClass.getCanonicalName() + ")")); + return; + } + names.add(name); + addTest(createTest(theClass, name)); + } + + private boolean isPublicTestMethod(Method m) { + return isTestMethod(m) && Modifier.isPublic(m.getModifiers()); + } + + private boolean isTestMethod(Method m) { + return + m.getParameterTypes().length == 0 && + m.getName().startsWith("test") && + m.getReturnType().equals(Void.TYPE); + } +}
\ No newline at end of file diff --git a/libs/androidfw/tests/Split_test.cpp b/libs/androidfw/tests/Split_test.cpp index 1f207e2e6373..2c242dbd3e28 100644 --- a/libs/androidfw/tests/Split_test.cpp +++ b/libs/androidfw/tests/Split_test.cpp @@ -165,7 +165,7 @@ TEST_F(SplitTest, TypeEntrySpecFlagsAreUpdated) { &specFlags, NULL); EXPECT_GE(block, 0); - EXPECT_EQ(static_cast<uint32_t>(0), specFlags); + EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), specFlags); ASSERT_EQ(NO_ERROR, table.add(basic_de_fr_contents_.data(), basic_de_fr_contents_.size())); @@ -173,9 +173,10 @@ TEST_F(SplitTest, TypeEntrySpecFlagsAreUpdated) { uint32_t frSpecFlags = 0; block = table.getResource(R::string::test1, &val, MAY_NOT_BE_BAG, 0, &frSpecFlags, NULL); - EXPECT_GE(block, 0); + ASSERT_GE(block, 0); - EXPECT_EQ(ResTable_config::CONFIG_LOCALE, frSpecFlags); + EXPECT_EQ(static_cast<uint32_t>(ResTable_config::CONFIG_LOCALE | ResTable_typeSpec::SPEC_PUBLIC), + frSpecFlags); } TEST_F(SplitTest, SelectBestDensity) { @@ -222,7 +223,7 @@ TEST_F(SplitTest, TestNewResourceIsAccessible) { table.add(feature_contents_.data(), feature_contents_.size())); block = table.getResource(R::string::test3, &val, MAY_NOT_BE_BAG); - EXPECT_GE(block, 0); + ASSERT_GE(block, 0); EXPECT_EQ(Res_value::TYPE_STRING, val.dataType); } diff --git a/libs/androidfw/tests/data/basic/basic.apk b/libs/androidfw/tests/data/basic/basic.apk Binary files differindex 2d71f5b5cb4c..2c9771b18934 100644 --- a/libs/androidfw/tests/data/basic/basic.apk +++ b/libs/androidfw/tests/data/basic/basic.apk diff --git a/libs/androidfw/tests/data/basic/basic_de_fr.apk b/libs/androidfw/tests/data/basic/basic_de_fr.apk Binary files differindex 69a2f304db83..04814440e0f8 100644 --- a/libs/androidfw/tests/data/basic/basic_de_fr.apk +++ b/libs/androidfw/tests/data/basic/basic_de_fr.apk diff --git a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk Binary files differindex 011808bb108e..a8d06e7f3c19 100644 --- a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk +++ b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk diff --git a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk Binary files differindex 6d4353c8d830..d1dfb143f91b 100644 --- a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk +++ b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk diff --git a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk Binary files differindex e3bda88659fb..dca6f2fbc0ca 100644 --- a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk +++ b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk diff --git a/libs/androidfw/tests/data/basic/build b/libs/androidfw/tests/data/basic/build index 68b911a3bc15..af0fd8787f48 100755 --- a/libs/androidfw/tests/data/basic/build +++ b/libs/androidfw/tests/data/basic/build @@ -17,6 +17,6 @@ set -e -PATH_TO_FRAMEWORK_RES=$(gettop)/prebuilts/sdk/current/android.jar +PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/android.jar aapt package -M AndroidManifest.xml -S res -I $PATH_TO_FRAMEWORK_RES --split hdpi --split xhdpi --split xxhdpi --split fr,de -F basic.apk -f diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml index 75e0435e06fa..11f6b8adbdba 100644 --- a/libs/androidfw/tests/data/basic/res/values/values.xml +++ b/libs/androidfw/tests/data/basic/res/values/values.xml @@ -15,24 +15,40 @@ --> <resources> + <public type="attr" name="attr1" id="0x7f010000" /> <attr name="attr1" format="reference|integer" /> + + <public type="attr" name="attr2" id="0x7f010001" /> <attr name="attr2" format="reference|integer" /> + <public type="layout" name="main" id="0x7f020000" /> + + <public type="string" name="test1" id="0x7f030000" /> <string name="test1">test1</string> + + <public type="string" name="test2" id="0x7f030001" /> <string name="test2">test2</string> + <public type="string" name="density" id="0x7f030002" /> + + <public type="integer" name="number1" id="0x7f040000" /> <integer name="number1">200</integer> + + <public type="integer" name="number2" id="0x7f040001" /> <integer name="number2">@array/integerArray1</integer> + <public type="style" name="Theme1" id="0x7f050000" /> <style name="Theme1"> <item name="com.android.basic:attr1">100</item> <item name="com.android.basic:attr2">@integer/number1</item> </style> + <public type="style" name="Theme2" id="0x7f050001" /> <style name="Theme2" parent="@com.android.basic:style/Theme1"> <item name="com.android.basic:attr1">300</item> </style> + <public type="array" name="integerArray1" id="0x7f060000" /> <integer-array name="integerArray1"> <item>1</item> <item>2</item> diff --git a/libs/androidfw/tests/data/feature/build b/libs/androidfw/tests/data/feature/build index 3316e41cd292..6ed3e416fb10 100755 --- a/libs/androidfw/tests/data/feature/build +++ b/libs/androidfw/tests/data/feature/build @@ -17,4 +17,6 @@ set -e -aapt package -M AndroidManifest.xml -S res --feature-of ../basic/basic.apk -F feature.apk -f +PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/android.jar + +aapt package -M AndroidManifest.xml -S res -I $PATH_TO_FRAMEWORK_RES --feature-of ../basic/basic.apk -F feature.apk -f diff --git a/libs/androidfw/tests/data/feature/feature.apk b/libs/androidfw/tests/data/feature/feature.apk Binary files differindex 1e65c2732ebf..04940fb9bce2 100644 --- a/libs/androidfw/tests/data/feature/feature.apk +++ b/libs/androidfw/tests/data/feature/feature.apk diff --git a/libs/androidfw/tests/data/feature/res/values/values.xml b/libs/androidfw/tests/data/feature/res/values/values.xml index 343fd6c8389f..59f7d93ee389 100644 --- a/libs/androidfw/tests/data/feature/res/values/values.xml +++ b/libs/androidfw/tests/data/feature/res/values/values.xml @@ -15,8 +15,13 @@ --> <resources> + <!-- Features are offset, so 7f020000 will become 7f080000 at runtime. --> + <public type="string" name="test3" id="0x7f020000" /> <string name="test3">test3</string> + + <public type="string" name="test4" id="0x7f020001" /> <string name="test4">test4</string> + <public type="integer" name="number3" id="0x7f030000" /> <integer name="number3">200</integer> </resources> diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp index 489039c2547a..dbbf00d8bdec 100644 --- a/libs/hwui/CanvasState.cpp +++ b/libs/hwui/CanvasState.cpp @@ -225,7 +225,7 @@ void CanvasState::setClippingOutline(LinearAllocator& allocator, const Outline* bool outlineIsRounded = MathUtils::isPositive(radius); if (!outlineIsRounded || currentTransform()->isSimple()) { // TODO: consider storing this rect separately, so that this can't be replaced with clip ops - clipRect(bounds.left, bounds.top, bounds.right, bounds.bottom, kIntersect_SkClipOp); + clipRect(bounds.left, bounds.top, bounds.right, bounds.bottom, SkClipOp::kIntersect); } if (outlineIsRounded) { setClippingRoundRect(allocator, bounds, radius, false); diff --git a/libs/hwui/FloatColor.h b/libs/hwui/FloatColor.h index 9df73387c36a..d8afa35d32bf 100644 --- a/libs/hwui/FloatColor.h +++ b/libs/hwui/FloatColor.h @@ -38,13 +38,13 @@ struct FloatColor { } // "color" is a gamma-encoded sRGB color - // After calling this method, the color is stored as a pre-multiplied linear color - // if linear blending is enabled. - void setSRGB(uint32_t color) { + // After calling this method, the color is stored as a linear color. The color + // is not pre-multiplied. + void setUnPreMultipliedSRGB(uint32_t color) { a = ((color >> 24) & 0xff) / 255.0f; - r = a * EOCF_sRGB(((color >> 16) & 0xff) / 255.0f); - g = a * EOCF_sRGB(((color >> 8) & 0xff) / 255.0f); - b = a * EOCF_sRGB(((color ) & 0xff) / 255.0f); + r = EOCF_sRGB(((color >> 16) & 0xff) / 255.0f); + g = EOCF_sRGB(((color >> 8) & 0xff) / 255.0f); + b = EOCF_sRGB(((color ) & 0xff) / 255.0f); } bool isNotBlack() { diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp index 5b683e06b6ed..a53a55a06964 100644 --- a/libs/hwui/FrameBuilder.cpp +++ b/libs/hwui/FrameBuilder.cpp @@ -120,7 +120,7 @@ void FrameBuilder::deferRenderNode(float tx, float ty, Rect clipRect, RenderNode mCanvasState.save(SaveFlags::MatrixClip); mCanvasState.translate(tx, ty); mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, - kIntersect_SkClipOp); + SkClipOp::kIntersect); deferNodePropsAndOps(renderNode); mCanvasState.restore(); } @@ -262,7 +262,7 @@ void FrameBuilder::deferNodePropsAndOps(RenderNode& node) { Rect clipRect; properties.getClippingRectForFlags(clipFlags, &clipRect); mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, - kIntersect_SkClipOp); + SkClipOp::kIntersect); } if (properties.getRevealClip().willClip()) { diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp index 0972ac1cafc2..1dad58fd64b9 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -188,26 +188,28 @@ size_t GradientCache::sourceBytesPerPixel() const { void GradientCache::mixBytes(const FloatColor& start, const FloatColor& end, float amount, uint8_t*& dst) const { float oppAmount = 1.0f - amount; - *dst++ = uint8_t(OECF_sRGB(start.r * oppAmount + end.r * amount) * 255.0f); - *dst++ = uint8_t(OECF_sRGB(start.g * oppAmount + end.g * amount) * 255.0f); - *dst++ = uint8_t(OECF_sRGB(start.b * oppAmount + end.b * amount) * 255.0f); - *dst++ = uint8_t( (start.a * oppAmount + end.a * amount) * 255.0f); + float a = start.a * oppAmount + end.a * amount; + *dst++ = uint8_t(a * OECF_sRGB((start.r * oppAmount + end.r * amount)) * 255.0f); + *dst++ = uint8_t(a * OECF_sRGB((start.g * oppAmount + end.g * amount)) * 255.0f); + *dst++ = uint8_t(a * OECF_sRGB((start.b * oppAmount + end.b * amount)) * 255.0f); + *dst++ = uint8_t(a * 255.0f); } void GradientCache::mixFloats(const FloatColor& start, const FloatColor& end, float amount, uint8_t*& dst) const { float oppAmount = 1.0f - amount; + float a = start.a * oppAmount + end.a * amount; float* d = (float*) dst; #ifdef ANDROID_ENABLE_LINEAR_BLENDING - *d++ = start.r * oppAmount + end.r * amount; - *d++ = start.g * oppAmount + end.g * amount; - *d++ = start.b * oppAmount + end.b * amount; + *d++ = a * (start.r * oppAmount + end.r * amount); + *d++ = a * (start.g * oppAmount + end.g * amount); + *d++ = a * (start.b * oppAmount + end.b * amount); #else - *d++ = OECF_sRGB(start.r * oppAmount + end.r * amount); - *d++ = OECF_sRGB(start.g * oppAmount + end.g * amount); - *d++ = OECF_sRGB(start.b * oppAmount + end.b * amount); + *d++ = a * OECF_sRGB(start.r * oppAmount + end.r * amount); + *d++ = a * OECF_sRGB(start.g * oppAmount + end.g * amount); + *d++ = a * OECF_sRGB(start.b * oppAmount + end.b * amount); #endif - *d++ = start.a * oppAmount + end.a * amount; + *d++ = a; dst += 4 * sizeof(float); } @@ -217,16 +219,19 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions, uint8_t pixels[rowBytes * height]; static ChannelMixer gMixers[] = { - &android::uirenderer::GradientCache::mixBytes, // colors are stored gamma-encoded - &android::uirenderer::GradientCache::mixFloats, // colors are stored in linear + // colors are stored gamma-encoded + &android::uirenderer::GradientCache::mixBytes, + // colors are stored in linear (linear blending on) + // or gamma-encoded (linear blending off) + &android::uirenderer::GradientCache::mixFloats, }; ChannelMixer mix = gMixers[mUseFloatTexture]; FloatColor start; - start.setSRGB(colors[0]); + start.setUnPreMultipliedSRGB(colors[0]); FloatColor end; - end.setSRGB(colors[1]); + end.setUnPreMultipliedSRGB(colors[1]); int currentPos = 1; float startPos = positions[0]; @@ -241,7 +246,7 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions, currentPos++; - end.setSRGB(colors[currentPos]); + end.setUnPreMultipliedSRGB(colors[currentPos]); distance = positions[currentPos] - startPos; } diff --git a/libs/hwui/OpenGLReadback.cpp b/libs/hwui/OpenGLReadback.cpp index 74a8395f9fff..938b6efa7901 100644 --- a/libs/hwui/OpenGLReadback.cpp +++ b/libs/hwui/OpenGLReadback.cpp @@ -108,6 +108,15 @@ CopyResult OpenGLReadback::copyGraphicBufferInto(GraphicBuffer* graphicBuffer, S return copyGraphicBufferInto(graphicBuffer, transform, srcRect, bitmap); } +static float sFlipVInit[16] = { + 1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, 1, 0, + 0, 1, 0, 1, +}; + +static const Matrix4 sFlipV(sFlipVInit); + //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// @@ -122,6 +131,13 @@ inline CopyResult copyTextureInto(Caches& caches, RenderState& renderState, destWidth, destHeight, caches.maxTextureSize); return CopyResult::DestinationInvalid; } + + // TODO: Add support for RGBA_F16 destinations + if (bitmap->colorType() == kRGBA_F16_SkColorType) { + ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported"); + return CopyResult::DestinationInvalid; + } + GLuint fbo = renderState.createFramebuffer(); if (!fbo) { ALOGW("Could not obtain an FBO"); @@ -183,11 +199,15 @@ inline CopyResult copyTextureInto(Caches& caches, RenderState& renderState, Matrix4 croppedTexTransform(texTransform); if (!srcRect.isEmpty()) { - croppedTexTransform.loadTranslate(srcRect.left / sourceTexture.width(), + // We flipV to convert to 0,0 top-left for the srcRect + // coordinates then flip back to 0,0 bottom-left for + // GLES coordinates. + croppedTexTransform.multiply(sFlipV); + croppedTexTransform.translate(srcRect.left / sourceTexture.width(), srcRect.top / sourceTexture.height(), 0); croppedTexTransform.scale(srcRect.getWidth() / sourceTexture.width(), srcRect.getHeight() / sourceTexture.height(), 1); - croppedTexTransform.multiply(texTransform); + croppedTexTransform.multiply(sFlipV); } Glop glop; GlopBuilder(renderState, caches, &glop) diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp index 52c62cc34670..983c17e92266 100644 --- a/libs/hwui/PatchCache.cpp +++ b/libs/hwui/PatchCache.cpp @@ -36,8 +36,7 @@ PatchCache::PatchCache(RenderState& renderState) , mSize(0) , mCache(LruCache<PatchDescription, Patch*>::kUnlimitedCapacity) , mMeshBuffer(0) - , mFreeBlocks(nullptr) - , mGenerationId(0) {} + , mFreeBlocks(nullptr) {} PatchCache::~PatchCache() { clear(); @@ -158,7 +157,6 @@ void PatchCache::createVertexBuffer() { mMaxSize, nullptr, GL_DYNAMIC_DRAW); mSize = 0; mFreeBlocks = new BufferBlock(0, mMaxSize); - mGenerationId++; } /** diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h index 0624c355332c..aa746c7dfa15 100644 --- a/libs/hwui/PatchCache.h +++ b/libs/hwui/PatchCache.h @@ -69,10 +69,6 @@ public: return mMeshBuffer; } - uint32_t getGenerationId() const { - return mGenerationId; - } - /** * Removes the entries associated with the specified 9-patch. This is meant * to be called from threads that are not the EGL context thread (GC thread @@ -175,8 +171,6 @@ private: // First available free block inside the mesh buffer BufferBlock* mFreeBlocks; - uint32_t mGenerationId; - // Garbage tracking, required to handle GC events on the VM side Vector<Res_png_9patch*> mGarbage; mutable Mutex mLock; diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h index 9c4cb098b4c3..e70982f5444a 100644 --- a/libs/hwui/Program.h +++ b/libs/hwui/Program.h @@ -54,6 +54,7 @@ namespace uirenderer { #define PROGRAM_KEY_COLOR_MATRIX 0x20 #define PROGRAM_KEY_COLOR_BLEND 0x40 #define PROGRAM_KEY_BITMAP_NPOT 0x80 +#define PROGRAM_KEY_BITMAP_EXTERNAL 0x100 #define PROGRAM_KEY_SWAP_SRC_DST 0x2000 @@ -133,6 +134,7 @@ struct ProgramDescription { // Shaders bool hasBitmap; + bool isShaderBitmapExternal; bool useShaderBasedWrap; bool hasVertexAlpha; @@ -180,6 +182,7 @@ struct ProgramDescription { modulate = false; hasBitmap = false; + isShaderBitmapExternal = false; useShaderBasedWrap = false; hasGradient = false; @@ -239,6 +242,9 @@ struct ProgramDescription { key |= getEnumForWrap(bitmapWrapS) << PROGRAM_BITMAP_WRAPS_SHIFT; key |= getEnumForWrap(bitmapWrapT) << PROGRAM_BITMAP_WRAPT_SHIFT; } + if (isShaderBitmapExternal) { + key |= PROGRAM_KEY_BITMAP_EXTERNAL; + } } if (hasGradient) key |= PROGRAM_KEY_GRADIENT; key |= programid(gradientType) << PROGRAM_GRADIENT_TYPE_SHIFT; diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp index 0c2309faf4ea..71076791cf7f 100644 --- a/libs/hwui/ProgramCache.cpp +++ b/libs/hwui/ProgramCache.cpp @@ -145,6 +145,8 @@ const char* gFS_Uniforms_GradientSampler[2] = { }; const char* gFS_Uniforms_BitmapSampler = "uniform sampler2D bitmapSampler;\n"; +const char* gFS_Uniforms_BitmapExternalSampler = + "uniform samplerExternalOES bitmapSampler;\n"; const char* gFS_Uniforms_ColorOp[3] = { // None "", @@ -175,10 +177,11 @@ const char* gFS_Gradient_Functions = const char* gFS_Gradient_Preamble[2] = { // Linear framebuffer "\nvec4 dither(const vec4 color) {\n" - " return vec4(color.rgb + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0), color.a);" + " return vec4(color.rgb + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0), color.a);\n" "}\n" "\nvec4 gammaMix(const vec4 a, const vec4 b, float v) {\n" - " return pow(mix(a, b, v), vec4(vec3(1.0 / 2.2), 1.0));" + " vec4 c = pow(mix(a, b, v), vec4(vec3(1.0 / 2.2), 1.0));\n" + " return vec4(c.rgb * c.a, c.a);\n" "}\n", // sRGB framebuffer "\nvec4 dither(const vec4 color) {\n" @@ -186,7 +189,8 @@ const char* gFS_Gradient_Preamble[2] = { " return vec4(dithered * dithered, color.a);\n" "}\n" "\nvec4 gammaMix(const vec4 a, const vec4 b, float v) {\n" - " return mix(a, b, v);" + " vec4 c = mix(a, b, v);\n" + " return vec4(c.rgb * c.a, c.a);\n" "}\n" }; @@ -576,7 +580,8 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti if (blendFramebuffer) { shader.append(gFS_Header_Extension_FramebufferFetch); } - if (description.hasExternalTexture) { + if (description.hasExternalTexture + || (description.hasBitmap && description.isShaderBitmapExternal)) { shader.append(gFS_Header_Extension_ExternalTexture); } @@ -693,7 +698,11 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti } if (description.hasBitmap) { - shader.append(gFS_Uniforms_BitmapSampler); + if (description.isShaderBitmapExternal) { + shader.append(gFS_Uniforms_BitmapExternalSampler); + } else { + shader.append(gFS_Uniforms_BitmapSampler); + } } shader.append(gFS_Uniforms_ColorOp[static_cast<int>(description.colorOp)]); diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 8ab57c9de554..8cae771f39b2 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -199,11 +199,11 @@ static inline SkCanvas::SaveLayerFlags layerFlags(SaveFlags::Flags flags) { int SkiaCanvas::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint, SaveFlags::Flags flags) { const SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom); - const SkCanvas::SaveLayerRec rec(&bounds, paint, layerFlags(flags)); + //always save matrix and clip to match the behaviour of Skia and HWUI pipelines and to ensure + //android state tracking behavior matches that of the Skia API (partial save is not supported) + const SkCanvas::SaveLayerRec rec(&bounds, paint, layerFlags(flags | SaveFlags::MatrixClip)); - int count = mCanvas->saveLayer(rec); - recordPartialSave(flags); - return count; + return mCanvas->saveLayer(rec); } int SkiaCanvas::saveLayerAlpha(float left, float top, float right, float bottom, diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp index 34e6a06c3dc2..fadb96065105 100644 --- a/libs/hwui/SkiaShader.cpp +++ b/libs/hwui/SkiaShader.cpp @@ -81,7 +81,7 @@ static void computeScreenSpaceMatrix(mat4& screenSpace, const SkMatrix& unitMatr } /////////////////////////////////////////////////////////////////////////////// -// gradient shader matrix helpers +// Gradient shader matrix helpers /////////////////////////////////////////////////////////////////////////////// static void toLinearUnitMatrix(const SkPoint pts[2], SkMatrix* matrix) { @@ -161,7 +161,7 @@ bool tryStoreGradient(Caches& caches, const SkShader& shader, const Matrix4 mode gradInfo.fColorOffsets = &colorOffsets[0]; shader.asAGradient(&gradInfo); - if (CC_UNLIKELY(!isSimpleGradient(gradInfo))) { + if (CC_UNLIKELY(!description->isSimpleGradient)) { outData->gradientSampler = (*textureUnit)++; #ifndef SK_SCALAR_IS_FLOAT @@ -174,8 +174,8 @@ bool tryStoreGradient(Caches& caches, const SkShader& shader, const Matrix4 mode outData->gradientSampler = 0; outData->gradientTexture = nullptr; - outData->startColor.setSRGB(gradInfo.fColors[0]); - outData->endColor.setSRGB(gradInfo.fColors[1]); + outData->startColor.setUnPreMultipliedSRGB(gradInfo.fColors[0]); + outData->endColor.setUnPreMultipliedSRGB(gradInfo.fColors[1]); } return true; @@ -218,6 +218,7 @@ bool tryStoreBitmap(Caches& caches, const SkShader& shader, const Matrix4& model const float height = outData->bitmapTexture->height(); description->hasBitmap = true; + description->isShaderBitmapExternal = hwuiBitmap->isHardware(); // gralloc doesn't support non-clamp modes if (hwuiBitmap->isHardware() || (!caches.extensions().hasNPot() && (!isPowerOfTwo(width) || !isPowerOfTwo(height)) diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index 287e58af0f69..4f92657d01ba 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -115,7 +115,7 @@ public: * the specified operation. The specified rectangle is considered * already transformed. */ - void clipTransformed(const Rect& r, SkClipOp op = kIntersect_SkClipOp); + void clipTransformed(const Rect& r, SkClipOp op = SkClipOp::kIntersect); /** * Modifies the current clip with the specified region and operation. diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp index 5b5b74e1c3f3..705395e1e2b7 100644 --- a/libs/hwui/Texture.cpp +++ b/libs/hwui/Texture.cpp @@ -225,6 +225,12 @@ void Texture::colorTypeToGlFormatAndType(const Caches& caches, SkColorType color *outInternalFormat = GL_LUMINANCE; *outType = GL_UNSIGNED_BYTE; break; + case kRGBA_F16_SkColorType: + // This format is always linear + *outFormat = GL_RGBA; + *outInternalFormat = GL_RGBA16F; + *outType = GL_HALF_FLOAT; + break; default: LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", colorType); break; diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h index 288f73361bbc..504dabb5bcc0 100644 --- a/libs/hwui/font/Font.h +++ b/libs/hwui/font/Font.h @@ -22,14 +22,16 @@ #include <utils/KeyedVector.h> #include <SkScalar.h> -#include <SkGlyphCache.h> #include <SkPaint.h> #include <SkPathMeasure.h> +#include <SkTypeface.h> #include "FontUtil.h" #include "../Rect.h" #include "../Matrix.h" +class SkGlyphCache; + namespace android { namespace uirenderer { diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 64ef866631f3..f6585d6d04c1 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -98,14 +98,16 @@ static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, si // TODO: handle SRGB sanely static PixelFormat internalFormatToPixelFormat(GLint internalFormat) { switch (internalFormat) { - case GL_ALPHA: - return PIXEL_FORMAT_TRANSPARENT; case GL_LUMINANCE: return PIXEL_FORMAT_RGBA_8888; case GL_SRGB8_ALPHA8: return PIXEL_FORMAT_RGBA_8888; case GL_RGBA: return PIXEL_FORMAT_RGBA_8888; + case GL_RGB: + return PIXEL_FORMAT_RGB_565; + case GL_RGBA16F: + return PIXEL_FORMAT_RGBA_FP16; default: LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", internalFormat); return PIXEL_FORMAT_UNKNOWN; @@ -213,8 +215,8 @@ sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(uirenderer::renderthread::RenderThr } const SkImageInfo& info = skBitmap.info(); - if (info.colorType() == kUnknown_SkColorType) { - ALOGW("unable to create hardware bitmap of configuration"); + if (info.colorType() == kUnknown_SkColorType || info.colorType() == kAlpha_8_SkColorType) { + ALOGW("unable to create hardware bitmap of colortype: %d", info.colorType()); return nullptr; } @@ -247,7 +249,7 @@ sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(uirenderer::renderthread::RenderThr if (!uploadBitmapToGraphicBuffer(caches, bitmap, *buffer, format, type)) { return nullptr; } - return sk_sp<Bitmap>(new Bitmap(buffer.get(), info)); + return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info())); } sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(SkBitmap& bitmap) { @@ -304,11 +306,13 @@ sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, SkPixelRef& pixelRef) sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer) { PixelFormat format = graphicBuffer->getPixelFormat(); - if (!graphicBuffer.get() || format != PIXEL_FORMAT_RGBA_8888) { + if (!graphicBuffer.get() || + (format != PIXEL_FORMAT_RGBA_8888 && format != PIXEL_FORMAT_RGBA_FP16)) { return nullptr; } SkImageInfo info = SkImageInfo::Make(graphicBuffer->getWidth(), graphicBuffer->getHeight(), - kRGBA_8888_SkColorType, kPremul_SkAlphaType); + kRGBA_8888_SkColorType, kPremul_SkAlphaType, + SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named)); return sk_sp<Bitmap>(new Bitmap(graphicBuffer.get(), info)); } diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index 14cc44973c8f..117395bfc75c 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -68,7 +68,7 @@ static void clipOutline(const Outline& outline, SkCanvas* canvas, const SkRect* if (pendingClip && !pendingClip->contains(rect)) { canvas->clipRect(*pendingClip); } - canvas->clipRRect(SkRRect::MakeRectXY(rect, radius, radius), kIntersect_SkClipOp, true); + canvas->clipRRect(SkRRect::MakeRectXY(rect, radius, radius), SkClipOp::kIntersect, true); } else { if (pendingClip) { (void)rect.intersect(*pendingClip); @@ -263,7 +263,7 @@ void RenderNodeDrawable::setViewProperties(const RenderProperties& properties, S } if (properties.getRevealClip().willClip()) { - canvas->clipPath(*properties.getRevealClip().getPath(), kIntersect_SkClipOp, true); + canvas->clipPath(*properties.getRevealClip().getPath(), SkClipOp::kIntersect, true); } else if (properties.getOutline().willClip()) { clipOutline(properties.getOutline(), canvas, pendingClip); pendingClip = nullptr; diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 6606b02e09a6..430d6bea70c1 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -18,6 +18,7 @@ #include "utils/TraceUtils.h" #include <SkImageEncoder.h> +#include <SkImagePriv.h> #include <SkOSFile.h> #include <SkOverdrawCanvas.h> #include <SkOverdrawColorFilter.h> @@ -99,7 +100,7 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) int saveCount = layerCanvas->save(); SkASSERT(saveCount == 1); - layerCanvas->clipRect(layerDamage.toSkRect(), kReplace_SkClipOp); + layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect()); auto savedLightCenter = mLightCenter; // map current light center into RenderNode's coordinate space @@ -232,8 +233,8 @@ static Rect nodeBounds(RenderNode& node) { void SkiaPipeline::renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect &contentDrawBounds, SkCanvas* canvas) { - - canvas->clipRect(clip, kReplace_SkClipOp); + SkAutoCanvasRestore saver(canvas, true); + canvas->androidFramework_setDeviceClipRestriction(clip.roundOut()); if (!opaque) { canvas->clear(SK_ColorTRANSPARENT); @@ -241,7 +242,6 @@ void SkiaPipeline::renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& if (1 == nodes.size()) { if (!nodes[0]->nothingToDraw()) { - SkAutoCanvasRestore acr(canvas, true); RenderNodeDrawable root(nodes[0].get(), canvas); root.draw(canvas); } diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index 95db2586f1bc..dbe0296eae24 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -21,6 +21,7 @@ #include "LayerDrawable.h" #include "NinePatchUtils.h" #include "pipeline/skia/AnimatedDrawables.h" +#include <SkImagePriv.h> namespace android { namespace uirenderer { diff --git a/include/private/README b/libs/hwui/private/README index ee4149270ae2..ee4149270ae2 100644 --- a/include/private/README +++ b/libs/hwui/private/README diff --git a/include/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h index f57851322716..f57851322716 100644 --- a/include/private/hwui/DrawGlInfo.h +++ b/libs/hwui/private/hwui/DrawGlInfo.h diff --git a/libs/hwui/tests/common/BitmapAllocationTestUtils.h b/libs/hwui/tests/common/BitmapAllocationTestUtils.h index 6dadd3e364cf..4892179f0d48 100644 --- a/libs/hwui/tests/common/BitmapAllocationTestUtils.h +++ b/libs/hwui/tests/common/BitmapAllocationTestUtils.h @@ -39,7 +39,9 @@ public: static sk_sp<Bitmap> allocateHardwareBitmap(int width, int height, SkColorType colorType, std::function<void(SkBitmap& bitmap)> setup) { SkBitmap skBitmap; - sk_sp<Bitmap> heapBitmap(TestUtils::createBitmap(width, height, &skBitmap)); + SkImageInfo info = SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType); + skBitmap.setInfo(info); + sk_sp<Bitmap> heapBitmap(Bitmap::allocateHeapBitmap(&skBitmap, nullptr)); setup(skBitmap); return Bitmap::allocateHardwareBitmap(skBitmap); } diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index 243e401a8356..5f6bcb3b8667 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -24,6 +24,8 @@ #include <utils/Unicode.h> #include <SkClipStack.h> +#include <SkGlyphCache.h> + namespace android { namespace uirenderer { diff --git a/libs/hwui/tests/common/scenes/BitmapShaders.cpp b/libs/hwui/tests/common/scenes/BitmapShaders.cpp index 9b0b9507b503..e03c9e84b739 100644 --- a/libs/hwui/tests/common/scenes/BitmapShaders.cpp +++ b/libs/hwui/tests/common/scenes/BitmapShaders.cpp @@ -17,6 +17,7 @@ #include "TestSceneBase.h" #include "utils/Color.h" #include "tests/common/BitmapAllocationTestUtils.h" +#include <SkImagePriv.h> class BitmapShaders; @@ -70,4 +71,4 @@ public: void doFrame(int frameNr) override { } BitmapAllocationTestUtils::BitmapAllocator mAllocator; -};
\ No newline at end of file +}; diff --git a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp index 45443b0332d3..f47e05a1f3e6 100644 --- a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp @@ -34,11 +34,11 @@ public: [](RenderProperties& props, Canvas& canvas) { canvas.save(SaveFlags::MatrixClip); { - canvas.clipRect(0, 0, 200, 200, kIntersect_SkClipOp); + canvas.clipRect(0, 0, 200, 200, SkClipOp::kIntersect); canvas.translate(100, 100); canvas.rotate(45); canvas.translate(-100, -100); - canvas.clipRect(0, 0, 200, 200, kIntersect_SkClipOp); + canvas.clipRect(0, 0, 200, 200, SkClipOp::kIntersect); canvas.drawColor(Color::Blue_500, SkBlendMode::kSrcOver); } canvas.restore(); @@ -47,7 +47,7 @@ public: { SkPath clipCircle; clipCircle.addCircle(100, 300, 100); - canvas.clipPath(&clipCircle, kIntersect_SkClipOp); + canvas.clipPath(&clipCircle, SkClipOp::kIntersect); canvas.drawColor(Color::Red_500, SkBlendMode::kSrcOver); } canvas.restore(); diff --git a/libs/hwui/tests/common/scenes/HwBitmap565.cpp b/libs/hwui/tests/common/scenes/HwBitmap565.cpp new file mode 100644 index 000000000000..18fea3d1cb81 --- /dev/null +++ b/libs/hwui/tests/common/scenes/HwBitmap565.cpp @@ -0,0 +1,49 @@ +/* + * 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 "TestSceneBase.h" +#include "utils/Color.h" +#include "tests/common/BitmapAllocationTestUtils.h" + +class HwBitmap565; + +static TestScene::Registrar _HwBitmap565(TestScene::Info{ + "hwBitmap565", + "Draws composite shader with hardware bitmap", + TestScene::simpleCreateScene<HwBitmap565> +}); + +class HwBitmap565 : public TestScene { +public: + sp<RenderNode> card; + void createContent(int width, int height, Canvas& canvas) override { + canvas.drawColor(Color::Grey_200, SkBlendMode::kSrcOver); + + sk_sp<Bitmap> hardwareBitmap = BitmapAllocationTestUtils::allocateHardwareBitmap(200, 200, + kRGB_565_SkColorType, [](SkBitmap& skBitmap) { + skBitmap.eraseColor(Color::White); + SkCanvas skCanvas(skBitmap); + SkPaint skPaint; + skPaint.setColor(Color::Red_500); + skCanvas.drawRect(SkRect::MakeWH(100, 100), skPaint); + skPaint.setColor(Color::Blue_500); + skCanvas.drawRect(SkRect::MakeXYWH(100, 100, 100, 100), skPaint); + }); + canvas.drawBitmap(*hardwareBitmap, 10.0f, 10.0f, nullptr); + } + + void doFrame(int frameNr) override { } +};
\ No newline at end of file diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp new file mode 100644 index 000000000000..83b01e906427 --- /dev/null +++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp @@ -0,0 +1,91 @@ +/* + * 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 "TestSceneBase.h" +#include "utils/Color.h" + +#include <gui/IGraphicBufferAlloc.h> +#include <gui/ISurfaceComposer.h> +#include <private/gui/ComposerService.h> +#include <binder/IServiceManager.h> +#include <ui/PixelFormat.h> +#include <SkGradientShader.h> +#include <SkImagePriv.h> + +class HwBitmapInCompositeShader; + +static TestScene::Registrar _HwBitmapInCompositeShader(TestScene::Info{ + "hwbitmapcompositeshader", + "Draws composite shader with hardware bitmap", + TestScene::simpleCreateScene<HwBitmapInCompositeShader> +}); + +class HwBitmapInCompositeShader : public TestScene { +public: + sp<RenderNode> card; + void createContent(int width, int height, Canvas& canvas) override { + canvas.drawColor(Color::Red_500, SkBlendMode::kSrcOver); + + status_t error; + sp<ISurfaceComposer> composer(ComposerService::getComposerService()); + sp<IGraphicBufferAlloc> alloc(composer->createGraphicBufferAlloc()); + uint32_t usage = GraphicBuffer::USAGE_HW_TEXTURE + | GraphicBuffer::USAGE_SW_READ_NEVER + | GRALLOC_USAGE_SW_WRITE_RARELY; + sp<GraphicBuffer> buffer = alloc->createGraphicBuffer(400, 200, PIXEL_FORMAT_RGBA_8888, 1, + usage, &error); + + unsigned char* pixels = nullptr; + buffer->lock(GraphicBuffer::USAGE_SW_WRITE_RARELY, ((void**)&pixels)); + size_t size = bytesPerPixel(buffer->getPixelFormat()) * buffer->getStride() + * buffer->getHeight(); + memset(pixels, 0, size); + for (int i = 0; i < 6000; i++) { + pixels[4000 + 4 * i + 0] = 255; + pixels[4000 + 4 * i + 1] = 255; + pixels[4000 + 4 * i + 2] = 0; + pixels[4000 + 4 * i + 3] = 255; + } + buffer->unlock(); + sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer)); + sk_sp<SkShader> hardwareShader(createBitmapShader(*hardwareBitmap)); + + SkPoint center; + center.set(50, 50); + SkColor colors[2]; + colors[0] = Color::Black; + colors[1] = Color::White; + sk_sp<SkShader> gradientShader = SkGradientShader::MakeRadial(center, 50, colors, nullptr, + 2, SkShader::TileMode::kRepeat_TileMode); + + sk_sp<SkShader> compositeShader( + SkShader::MakeComposeShader(hardwareShader, gradientShader, SkBlendMode::kDstATop)); + + SkPaint paint; + paint.setShader(std::move(compositeShader)); + canvas.drawRoundRect(0, 0, 400, 200, 10.0f, 10.0f, paint); + } + + void doFrame(int frameNr) override { } + + sk_sp<SkShader> createBitmapShader(Bitmap& bitmap) { + SkBitmap skBitmap; + bitmap.getSkBitmapForShaders(&skBitmap); + sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode); + return image->makeShader(SkShader::TileMode::kClamp_TileMode, + SkShader::TileMode::kClamp_TileMode); + } +};
\ No newline at end of file diff --git a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp index 8b2852b17b00..7e8a7d9d14f2 100644 --- a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp +++ b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp @@ -36,7 +36,7 @@ public: // nested clipped saveLayers canvas.saveLayerAlpha(0, 0, 400, 400, 200, SaveFlags::ClipToLayer); canvas.drawColor(Color::Green_700, SkBlendMode::kSrcOver); - canvas.clipRect(50, 50, 350, 350, kIntersect_SkClipOp); + canvas.clipRect(50, 50, 350, 350, SkClipOp::kIntersect); canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer); canvas.drawColor(Color::Blue_500, SkBlendMode::kSrcOver); canvas.restore(); diff --git a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp index d44c3d5baa90..09e70ebf7b5f 100644 --- a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp @@ -82,7 +82,7 @@ public: int middleCount = canvas.save(SaveFlags::MatrixClip); for (auto op : ops) { int innerCount = canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(0, 0, cellSize, cellSize, kIntersect_SkClipOp); + canvas.clipRect(0, 0, cellSize, cellSize, SkClipOp::kIntersect); canvas.drawColor(Color::White, SkBlendMode::kSrcOver); op(canvas, cellSize, paint); canvas.restoreToCount(innerCount); diff --git a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp index 3ef0d1cf80df..f166c5ceb974 100644 --- a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp +++ b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp @@ -177,7 +177,7 @@ void BM_DisplayListCanvas_basicViewGroupDraw(benchmark::State& benchState) { // Clip to padding // Can expect ~25% of views to have clip to padding with a non-null padding int clipRestoreCount = canvas->save(SaveFlags::MatrixClip); - canvas->clipRect(1, 1, 199, 199, kIntersect_SkClipOp); + canvas->clipRect(1, 1, 199, 199, SkClipOp::kIntersect); canvas->insertReorderBarrier(true); diff --git a/libs/hwui/tests/scripts/prep_marlfish.sh b/libs/hwui/tests/scripts/prep_marlfish.sh new file mode 100755 index 000000000000..f3c14d52fad5 --- /dev/null +++ b/libs/hwui/tests/scripts/prep_marlfish.sh @@ -0,0 +1,45 @@ +#marlfish is marlin & sailfish (☞゚ヮ゚)☞ + +cpubase=/sys/devices/system/cpu + +adb root +adb wait-for-device +adb shell stop thermal-engine +adb shell stop perfd + +# silver cores +#307200 384000 460800 537600 614400 691200 768000 844800 902400 979200 +#1056000 1132800 1209600 1286400 1363200 1440000 1516800 1593600 +# gold cores +#307200 384000 460800 537600 614400 691200 748800 825600 902400 979200 +#1056000 1132800 1209600 1286400 1363200 1440000 1516800 1593600 1670400 +#1747200 1824000 1900800 1977600 2054400 2150400 + +S=979200 +cpu=0 +# Changing governor and frequency in one core will be automatically applied +# to other cores in the cluster +while [ $((cpu < 3)) -eq 1 ]; do + adb shell "echo userspace > $cpubase/cpu2/cpufreq/scaling_governor" + echo "Setting cpu ${cpu} & $(($cpu + 1)) cluster to $S hz" + adb shell "echo $S > $cpubase/cpu${cpu}/cpufreq/scaling_max_freq" + adb shell "echo $S > $cpubase/cpu${cpu}/cpufreq/scaling_min_freq" + cpu=$(($cpu + 2)) +done + +echo "setting GPU bus and idle timer" +adb shell "echo 0 > /sys/class/kgsl/kgsl-3d0/bus_split" +adb shell "echo 1 > /sys/class/kgsl/kgsl-3d0/force_clk_on" +adb shell "echo 10000 > /sys/class/kgsl/kgsl-3d0/idle_timer" + +#0 762 1144 1525 2288 3143 4173 5195 5859 7759 9887 11863 13763 +adb shell "echo 13763 > /sys/class/devfreq/soc:qcom,gpubw/min_freq" &> /dev/null + +#133000000 214000000 315000000 401800000 510000000 560000000 624000000 +echo "performance mode, 315 MHz" +adb shell "echo performance > /sys/class/kgsl/kgsl-3d0/devfreq/governor" +adb shell "echo 315000000 > /sys/class/kgsl/kgsl-3d0/devfreq/min_freq" +adb shell "echo 315000000 > /sys/class/kgsl/kgsl-3d0/devfreq/max_freq" + +adb shell "echo 4 > /sys/class/kgsl/kgsl-3d0/min_pwrlevel" +adb shell "echo 4 > /sys/class/kgsl/kgsl-3d0/max_pwrlevel" diff --git a/libs/hwui/tests/scripts/stopruntime.sh b/libs/hwui/tests/scripts/stopruntime.sh index 85a91db58137..0a796183eaf9 100755 --- a/libs/hwui/tests/scripts/stopruntime.sh +++ b/libs/hwui/tests/scripts/stopruntime.sh @@ -23,4 +23,5 @@ for pid in $( adb shell ps | awk '{ if ( $9 == "surfaceflinger" ) { print $2 } } done adb shell setprop debug.egl.traceGpuCompletion 1 adb shell setprop debug.sf.nobootanimation 1 +# Daemonize command is available only in eng builds. adb shell daemonize surfaceflinger diff --git a/libs/hwui/tests/unit/CanvasStateTests.cpp b/libs/hwui/tests/unit/CanvasStateTests.cpp index 7555fd4edd7a..43974f650084 100644 --- a/libs/hwui/tests/unit/CanvasStateTests.cpp +++ b/libs/hwui/tests/unit/CanvasStateTests.cpp @@ -68,13 +68,13 @@ TEST(CanvasState, simpleClipping) { state.initializeSaveStack(200, 200, 0, 0, 200, 200, Vector3()); - state.clipRect(0, 0, 100, 100, kIntersect_SkClipOp); + state.clipRect(0, 0, 100, 100, SkClipOp::kIntersect); ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(100, 100)); - state.clipRect(10, 10, 200, 200, kIntersect_SkClipOp); + state.clipRect(10, 10, 200, 200, SkClipOp::kIntersect); ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10, 100, 100)); - state.clipRect(50, 50, 150, 150, kReplace_SkClipOp); + state.clipRect(50, 50, 150, 150, SkClipOp::kReplace); ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(50, 50, 150, 150)); } @@ -88,7 +88,7 @@ TEST(CanvasState, complexClipping) { // rotated clip causes complex clip state.rotate(10); EXPECT_TRUE(state.clipIsSimple()); - state.clipRect(0, 0, 200, 200, kIntersect_SkClipOp); + state.clipRect(0, 0, 200, 200, SkClipOp::kIntersect); EXPECT_FALSE(state.clipIsSimple()); } state.restore(); @@ -97,7 +97,7 @@ TEST(CanvasState, complexClipping) { { // subtracted clip causes complex clip EXPECT_TRUE(state.clipIsSimple()); - state.clipRect(50, 50, 150, 150, kDifference_SkClipOp); + state.clipRect(50, 50, 150, 150, SkClipOp::kDifference); EXPECT_FALSE(state.clipIsSimple()); } state.restore(); @@ -108,7 +108,7 @@ TEST(CanvasState, complexClipping) { SkPath path; path.addOval(SkRect::MakeWH(200, 200)); EXPECT_TRUE(state.clipIsSimple()); - state.clipPath(&path, kDifference_SkClipOp); + state.clipPath(&path, SkClipOp::kDifference); EXPECT_FALSE(state.clipIsSimple()); } state.restore(); @@ -121,7 +121,7 @@ TEST(CanvasState, saveAndRestore) { state.save(SaveFlags::Clip); { - state.clipRect(0, 0, 10, 10, kIntersect_SkClipOp); + state.clipRect(0, 0, 10, 10, SkClipOp::kIntersect); ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10)); } state.restore(); @@ -145,7 +145,7 @@ TEST(CanvasState, saveAndRestoreButNotTooMuch) { state.save(SaveFlags::Matrix); // NOTE: clip not saved { - state.clipRect(0, 0, 10, 10, kIntersect_SkClipOp); + state.clipRect(0, 0, 10, 10, SkClipOp::kIntersect); ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10)); } state.restore(); diff --git a/libs/hwui/tests/unit/FatalTestCanvas.h b/libs/hwui/tests/unit/FatalTestCanvas.h index b4132b7b44ed..4831722b93e6 100644 --- a/libs/hwui/tests/unit/FatalTestCanvas.h +++ b/libs/hwui/tests/unit/FatalTestCanvas.h @@ -121,13 +121,13 @@ public: const SkPaint*) { ADD_FAILURE() << "onDrawBitmapLattice not expected in this test"; } - void onClipRRect(const SkRRect& rrect, ClipOp, ClipEdgeStyle) { + void onClipRRect(const SkRRect& rrect, SkClipOp, ClipEdgeStyle) { ADD_FAILURE() << "onClipRRect not expected in this test"; } - void onClipPath(const SkPath& path, ClipOp, ClipEdgeStyle) { + void onClipPath(const SkPath& path, SkClipOp, ClipEdgeStyle) { ADD_FAILURE() << "onClipPath not expected in this test"; } - void onClipRegion(const SkRegion& deviceRgn, ClipOp) { + void onClipRegion(const SkRegion& deviceRgn, SkClipOp) { ADD_FAILURE() << "onClipRegion not expected in this test"; } void onDiscard() { diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp index 12622ffd8845..21394ae6144a 100644 --- a/libs/hwui/tests/unit/FrameBuilderTests.cpp +++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp @@ -174,7 +174,7 @@ RENDERTHREAD_TEST(FrameBuilder, simpleRejection) { auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(200, 200, 400, 400, kIntersect_SkClipOp); // intersection should be empty + canvas.clipRect(200, 200, 400, 400, SkClipOp::kIntersect); // intersection should be empty canvas.drawRect(0, 0, 400, 400, SkPaint()); canvas.restore(); }); @@ -453,19 +453,19 @@ RENDERTHREAD_TEST(FrameBuilder, clippedMerging) { sk_sp<Bitmap> bitmap(TestUtils::createBitmap(20, 20)); // left side clipped (to inset left half) - canvas.clipRect(10, 0, 50, 100, kReplace_SkClipOp); + canvas.clipRect(10, 0, 50, 100, SkClipOp::kReplace); canvas.drawBitmap(*bitmap, 0, 40, nullptr); // top side clipped (to inset top half) - canvas.clipRect(0, 10, 100, 50, kReplace_SkClipOp); + canvas.clipRect(0, 10, 100, 50, SkClipOp::kReplace); canvas.drawBitmap(*bitmap, 40, 0, nullptr); // right side clipped (to inset right half) - canvas.clipRect(50, 0, 90, 100, kReplace_SkClipOp); + canvas.clipRect(50, 0, 90, 100, SkClipOp::kReplace); canvas.drawBitmap(*bitmap, 80, 40, nullptr); // bottom not clipped, just abutting (inset bottom half) - canvas.clipRect(0, 50, 100, 90, kReplace_SkClipOp); + canvas.clipRect(0, 50, 100, 90, SkClipOp::kReplace); canvas.drawBitmap(*bitmap, 40, 70, nullptr); }); @@ -488,7 +488,7 @@ RENDERTHREAD_TEST(FrameBuilder, regionClipStopsMerge) { SkPath path; path.addCircle(200, 200, 200, SkPath::kCW_Direction); canvas.save(SaveFlags::MatrixClip); - canvas.clipPath(&path, kIntersect_SkClipOp); + canvas.clipPath(&path, SkClipOp::kIntersect); SkPaint paint; paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setAntiAlias(true); @@ -649,7 +649,7 @@ RENDERTHREAD_TEST(FrameBuilder, textureLayer_clipLocalMatrix) { auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) { canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(50, 50, 150, 150, kIntersect_SkClipOp); + canvas.clipRect(50, 50, 150, 150, SkClipOp::kIntersect); canvas.drawLayer(layerUpdater.get()); canvas.restore(); }); @@ -973,7 +973,7 @@ RENDERTHREAD_TEST(FrameBuilder, saveLayer_contentRejection) { auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(200, 200, 400, 400, kIntersect_SkClipOp); + canvas.clipRect(200, 200, 400, 400, SkClipOp::kIntersect); canvas.saveLayerAlpha(200, 200, 400, 400, 128, SaveFlags::ClipToLayer); // draw within save layer may still be recorded, but shouldn't be drawn @@ -1781,7 +1781,7 @@ RENDERTHREAD_TEST(FrameBuilder, projectionChildScroll) { auto child = TestUtils::createNode<RecordingCanvas>(0, 0, 400, 400, [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) { // Record time clip will be ignored by projectee - canvas.clipRect(100, 100, 300, 300, kIntersect_SkClipOp); + canvas.clipRect(100, 100, 300, 300, SkClipOp::kIntersect); canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally) canvas.drawRenderNode(projectingRipple.get()); @@ -1993,7 +1993,7 @@ RENDERTHREAD_TEST(FrameBuilder, shadowClipping) { [](RenderProperties& props, RecordingCanvas& canvas) { // Apply a clip before the reorder barrier/shadow casting child is drawn. // This clip must be applied to the shadow cast by the child. - canvas.clipRect(25, 25, 75, 75, kIntersect_SkClipOp); + canvas.clipRect(25, 25, 75, 75, SkClipOp::kIntersect); canvas.insertReorderBarrier(true); canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); }); @@ -2252,7 +2252,7 @@ RENDERTHREAD_TEST(FrameBuilder, clip_replace) { }; auto node = TestUtils::createNode<RecordingCanvas>(20, 20, 30, 30, [](RenderProperties& props, RecordingCanvas& canvas) { - canvas.clipRect(0, -20, 10, 30, kReplace_SkClipOp); + canvas.clipRect(0, -20, 10, 30, SkClipOp::kReplace); canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); }); diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp index 14fa5d643ae0..4a7338314c55 100644 --- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp +++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp @@ -25,6 +25,7 @@ #include <utils/Color.h> #include <SkGradientShader.h> +#include <SkImagePriv.h> #include <SkShader.h> namespace android { @@ -57,7 +58,7 @@ TEST(RecordingCanvas, emptyPlayback) { TEST(RecordingCanvas, clipRect) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) { canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(0, 0, 100, 100, kIntersect_SkClipOp); + canvas.clipRect(0, 0, 100, 100, SkClipOp::kIntersect); canvas.drawRect(0, 0, 50, 50, SkPaint()); canvas.drawRect(50, 50, 100, 100, SkPaint()); canvas.restore(); @@ -73,8 +74,8 @@ TEST(RecordingCanvas, clipRect) { TEST(RecordingCanvas, emptyClipRect) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(0, 0, 100, 100, kIntersect_SkClipOp); - canvas.clipRect(100, 100, 200, 200, kIntersect_SkClipOp); + canvas.clipRect(0, 0, 100, 100, SkClipOp::kIntersect); + canvas.clipRect(100, 100, 200, 200, SkClipOp::kIntersect); canvas.drawRect(0, 0, 50, 50, SkPaint()); // rejected at record time canvas.restore(); }); @@ -440,7 +441,7 @@ TEST(RecordingCanvas, saveLayer_simpleUnclipped) { TEST(RecordingCanvas, saveLayer_addClipFlag) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(10, 20, 190, 180, kIntersect_SkClipOp); + canvas.clipRect(10, 20, 190, 180, SkClipOp::kIntersect); canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped canvas.drawRect(10, 20, 190, 180, SkPaint()); canvas.restore(); @@ -459,7 +460,7 @@ TEST(RecordingCanvas, saveLayer_addClipFlag) { TEST(RecordingCanvas, saveLayer_viewportCrop) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { // shouldn't matter, since saveLayer will clip to its bounds - canvas.clipRect(-1000, -1000, 1000, 1000, kReplace_SkClipOp); + canvas.clipRect(-1000, -1000, 1000, 1000, SkClipOp::kReplace); canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer); canvas.drawRect(0, 0, 400, 400, SkPaint()); @@ -549,7 +550,7 @@ TEST(RecordingCanvas, saveLayer_rejectBegin) { canvas.save(SaveFlags::MatrixClip); canvas.translate(0, -20); // avoid identity case // empty clip rect should force layer + contents to be rejected - canvas.clipRect(0, -20, 200, -20, kIntersect_SkClipOp); + canvas.clipRect(0, -20, 200, -20, SkClipOp::kIntersect); canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer); canvas.drawRect(0, 0, 200, 200, SkPaint()); canvas.restore(); @@ -568,7 +569,7 @@ TEST(RecordingCanvas, drawRenderNode_rejection) { }); auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&child](RecordingCanvas& canvas) { - canvas.clipRect(0, 0, 0, 0, kIntersect_SkClipOp); // empty clip, reject node + canvas.clipRect(0, 0, 0, 0, SkClipOp::kIntersect); // empty clip, reject node canvas.drawRenderNode(child.get()); // shouldn't crash when rejecting node... }); ASSERT_TRUE(dl->isEmpty()); @@ -621,7 +622,7 @@ TEST(RecordingCanvas, firstClipWillReplace) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { canvas.save(SaveFlags::MatrixClip); // since no explicit clip set on canvas, this should be the one observed on op: - canvas.clipRect(-100, -100, 300, 300, kIntersect_SkClipOp); + canvas.clipRect(-100, -100, 300, 300, SkClipOp::kIntersect); SkPaint paint; paint.setColor(SK_ColorWHITE); @@ -637,7 +638,7 @@ TEST(RecordingCanvas, firstClipWillReplace) { TEST(RecordingCanvas, replaceClipIntersectWithRoot) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) { canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(-10, -10, 110, 110, kReplace_SkClipOp); + canvas.clipRect(-10, -10, 110, 110, SkClipOp::kReplace); canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); canvas.restore(); }); @@ -675,7 +676,7 @@ TEST(RecordingCanvas, insertReorderBarrier_clip) { canvas.drawRect(0, 0, 400, 400, SkPaint()); // second chunk: no recorded clip, since inorder region - canvas.clipRect(0, 0, 200, 200, kIntersect_SkClipOp); + canvas.clipRect(0, 0, 200, 200, SkClipOp::kIntersect); canvas.insertReorderBarrier(false); canvas.drawRect(0, 0, 400, 400, SkPaint()); diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp index 4d4705c0685a..0906cc84ab7a 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -168,6 +168,59 @@ TEST(RenderNodeDrawable, composeOnLayer) } namespace { +static SkRect getRecorderClipBounds(const SkiaRecordingCanvas& recorder) { + SkRect clipBounds; + recorder.getClipBounds(&clipBounds); + return clipBounds; +} + +static SkMatrix getRecorderMatrix(const SkiaRecordingCanvas& recorder) { + SkMatrix matrix; + recorder.getMatrix(&matrix); + return matrix; +} +} + +TEST(RenderNodeDrawable, saveLayerClipAndMatrixRestore) +{ + auto surface = SkSurface::MakeRasterN32Premul(400, 800); + SkCanvas& canvas = *surface->getCanvas(); + canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE); + + auto rootNode = TestUtils::createSkiaNode(0, 0, 400, 800, + [](RenderProperties& props, SkiaRecordingCanvas& recorder) { + SkPaint layerPaint; + ASSERT_EQ(SkRect::MakeLTRB(0, 0, 400, 800), getRecorderClipBounds(recorder)); + EXPECT_TRUE(getRecorderMatrix(recorder).isIdentity()); + + //note we don't pass SaveFlags::MatrixClip, but matrix and clip will be saved + recorder.saveLayer(0, 0, 400, 400, &layerPaint, SaveFlags::ClipToLayer); + ASSERT_EQ(SkRect::MakeLTRB(0, 0, 400, 400), getRecorderClipBounds(recorder)); + EXPECT_TRUE(getRecorderMatrix(recorder).isIdentity()); + + recorder.clipRect(50, 50, 350, 350, SkClipOp::kIntersect); + ASSERT_EQ(SkRect::MakeLTRB(50, 50, 350, 350), getRecorderClipBounds(recorder)); + + recorder.translate(300.0f, 400.0f); + EXPECT_EQ(SkMatrix::MakeTrans(300.0f, 400.0f), getRecorderMatrix(recorder)); + + recorder.restore(); + ASSERT_EQ(SkRect::MakeLTRB(0, 0, 400, 800), getRecorderClipBounds(recorder)); + EXPECT_TRUE(getRecorderMatrix(recorder).isIdentity()); + + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(SK_ColorGREEN); + recorder.drawRect(0.0f, 400.0f, 400.0f, 800.0f, paint); + }); + + RenderNodeDrawable drawable(rootNode.get(), &canvas, true); + canvas.drawDrawable(&drawable); + ASSERT_EQ(SK_ColorGREEN, TestUtils::getColor(surface, 200, 600)); +} + +namespace { class ContextFactory : public IContextFactory { public: virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override { @@ -455,7 +508,7 @@ RENDERTHREAD_TEST(RenderNodeDrawable, projectionChildScroll) { auto child = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) { // Record time clip will be ignored by projectee - canvas.clipRect(100, 100, 300, 300, kIntersect_SkClipOp); + canvas.clipRect(100, 100, 300, 300, SkClipOp::kIntersect); canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally) canvas.drawRenderNode(projectingRipple.get()); diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 9b5fa307ebf7..f3a663e802da 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -201,13 +201,11 @@ namespace { template <typename T> class DeferLayer : public SkSurface_Base { public: - DeferLayer(T *canvas) - : SkSurface_Base(canvas->imageInfo(), nullptr) - , mCanvas(canvas) { + DeferLayer() + : SkSurface_Base(T().imageInfo(), nullptr) { } SkCanvas* onNewCanvas() override { - mCanvas->ref(); - return mCanvas; + return new T(); } sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return sk_sp<SkSurface>(); @@ -215,8 +213,8 @@ public: sk_sp<SkImage> onNewImageSnapshot(SkBudgeted, SkCopyPixelsMode) override { return sk_sp<SkImage>(); } + T* canvas() { return static_cast<T*>(getCanvas()); } void onCopyOnWrite(ContentChangeMode) override {} - T* mCanvas; }; } @@ -281,10 +279,9 @@ RENDERTHREAD_TEST(SkiaPipeline, deferRenderNodeScene) { LayerUpdateQueue layerUpdateQueue; SkRect dirty = SkRect::MakeWH(800, 600); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); - sk_sp<DeferTestCanvas> canvas(new DeferTestCanvas()); - sk_sp<SkSurface> surface(new DeferLayer<DeferTestCanvas>(canvas.get())); + sk_sp<DeferLayer<DeferTestCanvas>> surface(new DeferLayer<DeferTestCanvas>()); pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, contentDrawBounds, surface); - EXPECT_EQ(4, canvas->mDrawCounter); + EXPECT_EQ(4, surface->canvas()->mDrawCounter); } RENDERTHREAD_TEST(SkiaPipeline, clipped) { @@ -312,11 +309,10 @@ RENDERTHREAD_TEST(SkiaPipeline, clipped) { LayerUpdateQueue layerUpdateQueue; SkRect dirty = SkRect::MakeLTRB(10, 20, 30, 40); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); - sk_sp<ClippedTestCanvas> canvas(new ClippedTestCanvas()); - sk_sp<SkSurface> surface(new DeferLayer<ClippedTestCanvas>(canvas.get())); + sk_sp<DeferLayer<ClippedTestCanvas>> surface(new DeferLayer<ClippedTestCanvas>()); pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface); - EXPECT_EQ(1, canvas->mDrawCounter); + EXPECT_EQ(1, surface->canvas()->mDrawCounter); } RENDERTHREAD_TEST(SkiaPipeline, clip_replace) { @@ -328,11 +324,8 @@ RENDERTHREAD_TEST(SkiaPipeline, clip_replace) { } void onDrawPaint(const SkPaint&) { EXPECT_EQ(0, mDrawCounter++); - //TODO: this unit test is failing on the commented check below, because of a missing - //feature. In Snapshot::applyClip HWUI is intersecting the clip with the clip root, - //even for kReplace_Op clips. We need to implement the same for Skia pipelines. - //EXPECT_EQ(SkRect::MakeLTRB(20, 10, 30, 40), TestUtils::getClipBounds(this)) //got instead 20 0 30 50 - // << "Expect resolved clip to be intersection of viewport clip and clip op"; + EXPECT_EQ(SkRect::MakeLTRB(20, 10, 30, 40), TestUtils::getClipBounds(this)) + << "Expect resolved clip to be intersection of viewport clip and clip op"; } int mDrawCounter = 0; }; @@ -340,16 +333,15 @@ RENDERTHREAD_TEST(SkiaPipeline, clip_replace) { std::vector<sp<RenderNode>> nodes; nodes.push_back(TestUtils::createSkiaNode(20, 20, 30, 30, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { - canvas.clipRect(0, -20, 10, 30, kReplace_SkClipOp); + canvas.clipRect(0, -20, 10, 30, SkClipOp::kReplace); canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); })); LayerUpdateQueue layerUpdateQueue; SkRect dirty = SkRect::MakeLTRB(10, 10, 40, 40); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); - sk_sp<ClipReplaceTestCanvas> canvas(new ClipReplaceTestCanvas()); - sk_sp<SkSurface> surface(new DeferLayer<ClipReplaceTestCanvas>(canvas.get())); + sk_sp<DeferLayer<ClipReplaceTestCanvas>> surface(new DeferLayer<ClipReplaceTestCanvas>()); pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface); - EXPECT_EQ(1, canvas->mDrawCounter); + EXPECT_EQ(1, surface->canvas()->mDrawCounter); } diff --git a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp index ec2efc8d88b6..e7171c8d44ab 100644 --- a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp +++ b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp @@ -51,7 +51,7 @@ static void testProperty(std::function<void(RenderProperties&)> propSetupCallbac EXPECT_EQ(mDrawCounter++, 0); mCallback(*this); } - void onClipRRect(const SkRRect& rrect, ClipOp op, ClipEdgeStyle style) { + void onClipRRect(const SkRRect& rrect, SkClipOp op, ClipEdgeStyle style) { SkCanvas::onClipRRect(rrect, op, style); } std::function<void(const SkCanvas&)> mCallback; @@ -65,10 +65,10 @@ static void testProperty(std::function<void(RenderProperties&)> propSetupCallbac canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, paint); }); - sk_sp<PropertyTestCanvas> canvas(new PropertyTestCanvas(opValidateCallback)); - RenderNodeDrawable drawable(node.get(), canvas.get(), true); - canvas->drawDrawable(&drawable); - EXPECT_EQ(1, canvas->mDrawCounter); + PropertyTestCanvas canvas(opValidateCallback); + RenderNodeDrawable drawable(node.get(), &canvas, true); + canvas.drawDrawable(&drawable); + EXPECT_EQ(1, canvas.mDrawCounter); } } diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp index 598cd1ef6ae7..8b80d690b39f 100644 --- a/libs/hwui/utils/TestWindowContext.cpp +++ b/libs/hwui/utils/TestWindowContext.cpp @@ -93,7 +93,7 @@ public: SkCanvas* prepareToDraw() { //mCanvas->reset(mSize.width(), mSize.height()); - mCanvas->clipRect(0, 0, mSize.width(), mSize.height(), kReplace_SkClipOp); + mCanvas->clipRect(0, 0, mSize.width(), mSize.height(), SkClipOp::kReplace); return mCanvas->asSkCanvas(); } diff --git a/libs/incident/Android.mk b/libs/incident/Android.mk new file mode 100644 index 000000000000..439e86deba5d --- /dev/null +++ b/libs/incident/Android.mk @@ -0,0 +1,41 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := libincident + +LOCAL_CFLAGS := \ + -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter + +LOCAL_SHARED_LIBRARIES := \ + libbinder \ + liblog \ + libutils + +LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/../../core/java +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include + +LOCAL_SRC_FILES := \ + ../../core/java/android/os/IIncidentManager.aidl \ + ../../core/java/android/os/IIncidentReportCompletedListener.aidl \ + ../../core/java/android/os/IIncidentReportStatusListener.aidl \ + src/IncidentReportArgs.cpp + +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include + +include $(BUILD_SHARED_LIBRARY) + diff --git a/libs/incident/include/android/os/IncidentReportArgs.h b/libs/incident/include/android/os/IncidentReportArgs.h new file mode 100644 index 000000000000..956ef6c39b99 --- /dev/null +++ b/libs/incident/include/android/os/IncidentReportArgs.h @@ -0,0 +1,62 @@ +/** + * 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. + */ + +#ifndef ANDROID_OS_DUMPSTATE_ARGS_H_ +#define ANDROID_OS_DUMPSTATE_ARGS_H_ + +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <utils/String16.h> + +#include <set> +#include <vector> + +namespace android { +namespace os { + +using namespace std; + +class IncidentReportArgs : public Parcelable { +public: + IncidentReportArgs(); + explicit IncidentReportArgs(const IncidentReportArgs& that); + virtual ~IncidentReportArgs(); + + virtual status_t writeToParcel(Parcel* out) const; + virtual status_t readFromParcel(const Parcel* in); + + void setAll(bool all); + void addSection(int section); + void addHeader(const vector<int8_t>& header); + + inline bool all() const { return mAll; }; + bool containsSection(int section) const; + + inline const set<int>& sections() const { return mSections; } + inline const vector<vector<int8_t>>& headers() const { return mHeaders; } + + void merge(const IncidentReportArgs& that); + +private: + set<int> mSections; + vector<vector<int8_t>> mHeaders; + bool mAll; +}; + +} +} + +#endif // ANDROID_OS_DUMPSTATE_ARGS_H_ diff --git a/libs/incident/proto/android/privacy.proto b/libs/incident/proto/android/privacy.proto new file mode 100644 index 000000000000..ae5af0e4afa7 --- /dev/null +++ b/libs/incident/proto/android/privacy.proto @@ -0,0 +1,57 @@ +/* + * 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. + */ + +syntax = "proto2"; + +option java_package = "android"; +option java_multiple_files = true; + +import "google/protobuf/descriptor.proto"; + +package android; + +// TODO: It's better to track this by semantic types and set policy for those. +// Do this for now to bootstrap the tools. +enum Destination { + // Fields or messages annotated with DEST_LOCAL must never be + // extracted from the device automatically. They can be accessed + // by tools on the developer's workstation, and if they are sent + // to another device that must be by the user, with a PII warning. (TBD) + DEST_LOCAL = 0; + + // Fields or messages annotated with DEST_EXPLICIT can be sent + // off the device with an explicit user action. + DEST_EXPLICIT = 1; + + // Fields or messages annotated with DEST_LOCAL can be sent by + // automatic means, without per-sending user consent. The user + // still must have previously accepted a consent to share this + // information. + DEST_AUTOMATIC = 2; + + // There is no more permissive option than DEST_AUTOMATIC. +} + +message PrivacyFlags { + optional Destination dest = 1 [ + default = DEST_LOCAL + ]; +} + +extend google.protobuf.FieldOptions { + // Flags for automatically filtering statistics + optional PrivacyFlags privacy = 102672883; +} diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp new file mode 100644 index 000000000000..f60490911aed --- /dev/null +++ b/libs/incident/src/IncidentReportArgs.cpp @@ -0,0 +1,172 @@ +/** + * 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. + */ + +#define LOG_TAG "dumpstate" + +#include <android/os/IncidentReportArgs.h> + +#include <cutils/log.h> + +namespace android { +namespace os { + +IncidentReportArgs::IncidentReportArgs() + :mSections(), + mAll(false) +{ +} + +IncidentReportArgs::IncidentReportArgs(const IncidentReportArgs& that) + :mSections(that.mSections), + mHeaders(that.mHeaders), + mAll(that.mAll) +{ +} + +IncidentReportArgs::~IncidentReportArgs() +{ +} + +status_t +IncidentReportArgs::writeToParcel(Parcel* out) const +{ + status_t err; + + err = out->writeInt32(mAll); + if (err != NO_ERROR) { + return err; + } + + err = out->writeInt32(mSections.size()); + if (err != NO_ERROR) { + return err; + } + + for (set<int>::const_iterator it=mSections.begin(); it!=mSections.end(); it++) { + err = out->writeInt32(*it); + if (err != NO_ERROR) { + return err; + } + } + + err = out->writeInt32(mHeaders.size()); + if (err != NO_ERROR) { + return err; + } + + for (vector<vector<int8_t>>::const_iterator it = mHeaders.begin(); it != mHeaders.end(); it++) { + err = out->writeByteVector(*it); + if (err != NO_ERROR) { + return err; + } + } + + return NO_ERROR; +} + +status_t +IncidentReportArgs::readFromParcel(const Parcel* in) +{ + status_t err; + + int32_t all; + err = in->readInt32(&all); + if (err != NO_ERROR) { + return err; + } + if (all != 0) { + mAll = all; + } + + mSections.clear(); + int32_t sectionCount; + err = in->readInt32(§ionCount); + if (err != NO_ERROR) { + return err; + } + for (int i=0; i<sectionCount; i++) { + int32_t section; + err = in->readInt32(§ion); + if (err != NO_ERROR) { + return err; + } + + mSections.insert(section); + } + + int32_t headerCount; + err = in->readInt32(&headerCount); + if (err != NO_ERROR) { + return err; + } + mHeaders.resize(headerCount); + for (int i=0; i<headerCount; i++) { + err = in->readByteVector(&mHeaders[i]); + if (err != NO_ERROR) { + return err; + } + } + + return OK; +} + +void +IncidentReportArgs::setAll(bool all) +{ + mAll = all; + if (all) { + mSections.clear(); + } +} + +void +IncidentReportArgs::addSection(int section) +{ + if (!mAll) { + mSections.insert(section); + } +} + +void +IncidentReportArgs::addHeader(const vector<int8_t>& header) +{ + mHeaders.push_back(header); +} + +bool +IncidentReportArgs::containsSection(int section) const +{ + return mAll || mSections.find(section) != mSections.end(); +} + +void +IncidentReportArgs::merge(const IncidentReportArgs& that) +{ + if (mAll) { + return; + } else if (that.mAll) { + mAll = true; + mSections.clear(); + } else { + for (set<int>::const_iterator it=that.mSections.begin(); + it!=that.mSections.end(); it++) { + mSections.insert(*it); + } + } +} + +} +} diff --git a/libs/services/Android.mk b/libs/services/Android.mk new file mode 100644 index 000000000000..cbfd4b3f9f10 --- /dev/null +++ b/libs/services/Android.mk @@ -0,0 +1,43 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) + +# Provides C++ wrappers for system services. + +include $(CLEAR_VARS) + +LOCAL_MODULE := libservices +LOCAL_SRC_FILES := \ + ../../core/java/com/android/internal/os/IDropBoxManagerService.aidl \ + src/os/DropBoxManager.cpp + +LOCAL_AIDL_INCLUDES := \ + $(LOCAL_PATH)/../../core/java +LOCAL_C_INCLUDES := \ + system/core/include +LOCAL_SHARED_LIBRARIES := \ + libbinder \ + liblog \ + libcutils \ + libutils + +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +LOCAL_C_INCLUDES += $(LOCAL_PATH)/include + +LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code + +include $(BUILD_SHARED_LIBRARY) + + diff --git a/libs/services/include/android/os/DropBoxManager.h b/libs/services/include/android/os/DropBoxManager.h new file mode 100644 index 000000000000..8717178bb7d6 --- /dev/null +++ b/libs/services/include/android/os/DropBoxManager.h @@ -0,0 +1,94 @@ +/* + * 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. + */ + +#ifndef _ANDROID_OS_DROPBOXMANAGER_H +#define _ANDROID_OS_DROPBOXMANAGER_H + +#include <android-base/unique_fd.h> +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <binder/Status.h> +#include <utils/RefBase.h> + +#include <vector> + +namespace android { +namespace os { + +using namespace android; +using namespace android::base; +using namespace android::binder; +using namespace std; + +class DropBoxManager : public virtual RefBase +{ +public: + enum { + IS_EMPTY = 1, + IS_TEXT = 2, + IS_GZIPPED = 4 + }; + + DropBoxManager(); + virtual ~DropBoxManager(); + + static sp<DropBoxManager> create(); + + // Create a new entry with plain text contents. + Status addText(const String16& tag, const string& text); + + // Create a new Entry with byte array contents. Makes a copy of the data. + Status addData(const String16& tag, uint8_t const* data, size_t size, int flags); + + // Create a new Entry from a file. The file will be opened in this process + // and a handle will be passed to the system process, so no additional permissions + // are required from the system process. Returns NULL if the file can't be opened. + Status addFile(const String16& tag, const string& filename, int flags); + + class Entry : public virtual RefBase, public Parcelable { + public: + Entry(); + virtual ~Entry(); + + virtual status_t writeToParcel(Parcel* out) const; + virtual status_t readFromParcel(const Parcel* in); + + private: + Entry(const String16& tag, int32_t flags); + Entry(const String16& tag, int32_t flags, int fd); + + String16 mTag; + int64_t mTimeMillis; + int32_t mFlags; + + vector<uint8_t> mData; + unique_fd mFd; + + friend class DropBoxManager; + }; + +private: + enum { + HAS_BYTE_ARRAY = 8 + }; + + Status add(const Entry& entry); +}; + +}} // namespace android::os + +#endif // _ANDROID_OS_DROPBOXMANAGER_H + diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp new file mode 100644 index 000000000000..bbb45f022a87 --- /dev/null +++ b/libs/services/src/os/DropBoxManager.cpp @@ -0,0 +1,199 @@ +/* + * 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. + */ + +#define LOG_TAG "DropBoxManager" + +#include <android/os/DropBoxManager.h> + +#include <binder/IServiceManager.h> +#include <com/android/internal/os/IDropBoxManagerService.h> +#include <cutils/log.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +namespace android { +namespace os { + +using namespace ::com::android::internal::os; + +DropBoxManager::Entry::Entry() + :mTag(), + mTimeMillis(0), + mFlags(IS_EMPTY), + mData(), + mFd() +{ + mFlags = IS_EMPTY; +} + +DropBoxManager::Entry::Entry(const String16& tag, int32_t flags) + :mTag(tag), + mTimeMillis(0), + mFlags(flags), + mData(), + mFd() +{ +} + +DropBoxManager::Entry::Entry(const String16& tag, int32_t flags, int fd) + :mTag(tag), + mTimeMillis(0), + mFlags(flags), + mData(), + mFd(fd) +{ +} + +DropBoxManager::Entry::~Entry() +{ +} + +status_t +DropBoxManager::Entry::writeToParcel(Parcel* out) const +{ + status_t err; + + err = out->writeString16(mTag); + if (err != NO_ERROR) { + return err; + } + + err = out->writeInt64(mTimeMillis); + if (err != NO_ERROR) { + return err; + } + + if (mFd.get() != -1) { + err = out->writeInt32(mFlags & ~HAS_BYTE_ARRAY); // Clear bit just to be safe + if (err != NO_ERROR) { + return err; + } + ALOGD("writing fd %d\n", mFd.get()); + err = out->writeParcelFileDescriptor(mFd); + if (err != NO_ERROR) { + return err; + } + } else { + err = out->writeInt32(mFlags | HAS_BYTE_ARRAY); + if (err != NO_ERROR) { + return err; + } + err = out->writeByteVector(mData); + if (err != NO_ERROR) { + return err; + } + } + return NO_ERROR; +} + +status_t +DropBoxManager::Entry::readFromParcel(const Parcel* in) +{ + status_t err; + + err = in->readString16(&mTag); + if (err != NO_ERROR) { + return err; + } + + err = in->readInt64(&mTimeMillis); + if (err != NO_ERROR) { + return err; + } + + err = in->readInt32(&mFlags); + if (err != NO_ERROR) { + return err; + } + + if ((mFlags & HAS_BYTE_ARRAY) != 0) { + err = in->readByteVector(&mData); + if (err != NO_ERROR) { + return err; + } + mFlags &= ~HAS_BYTE_ARRAY; + } else { + int fd; + fd = in->readParcelFileDescriptor(); + if (fd == -1) { + return EBADF; + } + fd = dup(fd); + if (fd == -1) { + return errno; + } + mFd.reset(fd); + } + + return NO_ERROR; +} + + +DropBoxManager::DropBoxManager() +{ +} + +DropBoxManager::~DropBoxManager() +{ +} + +Status +DropBoxManager::addText(const String16& tag, const string& text) +{ + Entry entry(tag, IS_TEXT); + entry.mData.assign(text.c_str(), text.c_str() + text.size()); + return add(entry); +} + +Status +DropBoxManager::addData(const String16& tag, uint8_t const* data, + size_t size, int flags) +{ + Entry entry(tag, flags); + entry.mData.assign(data, data+size); + return add(entry); +} + +Status +DropBoxManager::addFile(const String16& tag, const string& filename, int flags) +{ + int fd = open(filename.c_str(), O_RDONLY); + if (fd == -1) { + string message("addFile can't open file: "); + message += filename; + ALOGW("DropboxManager: %s", message.c_str()); + return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, message.c_str()); + } + + Entry entry(tag, flags, fd); + return add(entry); +} + +Status +DropBoxManager::add(const Entry& entry) +{ + sp<IDropBoxManagerService> service = interface_cast<IDropBoxManagerService>( + defaultServiceManager()->getService(android::String16("dropbox"))); + if (service == NULL) { + return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service"); + } + return service->add(entry); +} + +}} // namespace android::os + diff --git a/libs/storage/Android.bp b/libs/storage/Android.bp new file mode 100644 index 000000000000..911bd1d25393 --- /dev/null +++ b/libs/storage/Android.bp @@ -0,0 +1,19 @@ +cc_library_static { + name: "libstorage", + + srcs: [ + "IMountServiceListener.cpp", + "IMountShutdownObserver.cpp", + "IObbActionListener.cpp", + "IMountService.cpp", + ], + + export_include_dirs: ["include"], + + cflags: [ + "-Wall", + "-Werror", + ], + + shared_libs: ["libbinder"], +} diff --git a/libs/storage/Android.mk b/libs/storage/Android.mk deleted file mode 100644 index d0eb6d4eb425..000000000000 --- a/libs/storage/Android.mk +++ /dev/null @@ -1,16 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES:= \ - IMountServiceListener.cpp \ - IMountShutdownObserver.cpp \ - IObbActionListener.cpp \ - IMountService.cpp - -LOCAL_MODULE:= libstorage - -LOCAL_CFLAGS += -Wall -Werror - -LOCAL_SHARED_LIBRARIES := libbinder - -include $(BUILD_STATIC_LIBRARY) diff --git a/include/storage/IMountService.h b/libs/storage/include/storage/IMountService.h index c3d34d84958b..c3d34d84958b 100644 --- a/include/storage/IMountService.h +++ b/libs/storage/include/storage/IMountService.h diff --git a/include/storage/IMountServiceListener.h b/libs/storage/include/storage/IMountServiceListener.h index 5b1f21cf98b5..5b1f21cf98b5 100644 --- a/include/storage/IMountServiceListener.h +++ b/libs/storage/include/storage/IMountServiceListener.h diff --git a/include/storage/IMountShutdownObserver.h b/libs/storage/include/storage/IMountShutdownObserver.h index d019e019bc0d..d019e019bc0d 100644 --- a/include/storage/IMountShutdownObserver.h +++ b/libs/storage/include/storage/IMountShutdownObserver.h diff --git a/include/storage/IObbActionListener.h b/libs/storage/include/storage/IObbActionListener.h index d78273cf2dd4..d78273cf2dd4 100644 --- a/include/storage/IObbActionListener.h +++ b/libs/storage/include/storage/IObbActionListener.h diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java index 957c2d6eb09f..81db37ea3ad8 100644 --- a/media/java/android/media/browse/MediaBrowser.java +++ b/media/java/android/media/browse/MediaBrowser.java @@ -109,13 +109,13 @@ public final class MediaBrowser { private IMediaBrowserServiceCallbacks mServiceCallbacks; /** - * Creates a media browser for the specified media browse service. + * Creates a media browser for the specified media browser service. * * @param context The context. - * @param serviceComponent The component name of the media browse service. + * @param serviceComponent The component name of the media browser service. * @param callback The connection callback. * @param rootHints An optional bundle of service-specific arguments to send - * to the media browse service when connecting and retrieving the root id + * to the media browser service when connecting and retrieving the root id * for browsing, or null if none. The contents of this bundle may affect * the information returned when browsing. * @see android.service.media.MediaBrowserService.BrowserRoot#EXTRA_RECENT @@ -140,7 +140,7 @@ public final class MediaBrowser { } /** - * Connects to the media browse service. + * Connects to the media browser service. * <p> * The connection callback specified in the constructor will be invoked * when the connection completes or fails. @@ -206,7 +206,7 @@ public final class MediaBrowser { } /** - * Disconnects from the media browse service. + * Disconnects from the media browser service. * After this, no more callbacks will be received. */ public void disconnect() { @@ -362,7 +362,7 @@ public final class MediaBrowser { * * @param parentId The id of the parent media item whose list of children * will be subscribed. - * @param options A bundle of service-specific arguments to send to the media + * @param options The bundle of service-specific arguments to send to the media * browse service. The contents of this bundle may affect the * information returned when browsing. * @param callback The callback to receive the list of children. @@ -370,7 +370,7 @@ public final class MediaBrowser { public void subscribe(@NonNull String parentId, @NonNull Bundle options, @NonNull SubscriptionCallback callback) { if (options == null) { - throw new IllegalArgumentException("options are null"); + throw new IllegalArgumentException("options cannot be null"); } subscribeInternal(parentId, new Bundle(options), callback); } @@ -398,11 +398,11 @@ public final class MediaBrowser { * * @param parentId The id of the parent media item whose list of children * will be unsubscribed. - * @param callback A callback sent to the media browse service to subscribe. + * @param callback A callback sent to the media browser service to subscribe. */ public void unsubscribe(@NonNull String parentId, @NonNull SubscriptionCallback callback) { if (callback == null) { - throw new IllegalArgumentException("callback is null"); + throw new IllegalArgumentException("callback cannot be null"); } unsubscribeInternal(parentId, callback); } @@ -417,10 +417,10 @@ public final class MediaBrowser { */ public void getItem(final @NonNull String mediaId, @NonNull final ItemCallback cb) { if (TextUtils.isEmpty(mediaId)) { - throw new IllegalArgumentException("mediaId is empty."); + throw new IllegalArgumentException("mediaId cannot be empty."); } if (cb == null) { - throw new IllegalArgumentException("cb is null."); + throw new IllegalArgumentException("cb cannot be null."); } if (mState != CONNECT_STATE_CONNECTED) { Log.i(TAG, "Not connected, unable to retrieve the MediaItem."); @@ -451,7 +451,7 @@ public final class MediaBrowser { try { mServiceBinder.getMediaItem(mediaId, receiver, mServiceCallbacks); } catch (RemoteException e) { - Log.i(TAG, "Remote error getting media item."); + Log.i(TAG, "Remote error getting media item.", e); mHandler.post(new Runnable() { @Override public void run() { @@ -461,13 +461,74 @@ public final class MediaBrowser { } } + /** + * Searches {@link MediaItem media items} from the connected service. Not + * all services may support this, and {@link SearchCallback#onError} will be + * called if not implemented. + * + * @param query The search query that contains keywords separated by space. Should not be + * an empty string. + * @param extras The bundle of service-specific arguments to send to the media browser + * service. The contents of this bundle may affect the search result. + * @param callback The callback to receive the search result. + */ + public void search(@NonNull final String query, final Bundle extras, SearchCallback callback) { + if (TextUtils.isEmpty(query)) { + throw new IllegalArgumentException("query cannot be empty."); + } + if (callback == null) { + throw new IllegalArgumentException("callback cannot be null."); + } + if (mState != CONNECT_STATE_CONNECTED) { + Log.i(TAG, "Not connected, unable to search."); + mHandler.post(new Runnable() { + @Override + public void run() { + callback.onError(query, extras); + } + }); + return; + } + ResultReceiver receiver = new ResultReceiver(mHandler) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (resultCode != 0 || resultData == null + || !resultData.containsKey(MediaBrowserService.KEY_SEARCH_RESULTS)) { + callback.onError(query, extras); + return; + } + Parcelable[] items = resultData.getParcelableArray( + MediaBrowserService.KEY_SEARCH_RESULTS); + List<MediaItem> results = null; + if (items != null) { + results = new ArrayList<>(); + for (Parcelable item : items) { + results.add((MediaItem) item); + } + } + callback.onSearchResult(query, extras, results); + } + }; + try { + mServiceBinder.search(query, extras, receiver, mServiceCallbacks); + } catch (RemoteException e) { + Log.i(TAG, "Remote error getting media item.", e); + mHandler.post(new Runnable() { + @Override + public void run() { + callback.onError(query, extras); + } + }); + } + } + private void subscribeInternal(String parentId, Bundle options, SubscriptionCallback callback) { // Check arguments. if (TextUtils.isEmpty(parentId)) { - throw new IllegalArgumentException("parentId is empty."); + throw new IllegalArgumentException("parentId cannot be empty."); } if (callback == null) { - throw new IllegalArgumentException("callback is null"); + throw new IllegalArgumentException("callback cannot be null"); } // Update or create the subscription. Subscription sub = mSubscriptions.get(parentId); @@ -497,7 +558,7 @@ public final class MediaBrowser { private void unsubscribeInternal(String parentId, SubscriptionCallback callback) { // Check arguments. if (TextUtils.isEmpty(parentId)) { - throw new IllegalArgumentException("parentId is empty."); + throw new IllegalArgumentException("parentId cannot be empty."); } Subscription sub = mSubscriptions.get(parentId); @@ -712,7 +773,9 @@ public final class MediaBrowser { } /** - * A class with information on a single media item for use in browsing media. + * A class with information on a single media item for use in browsing/searching media. + * MediaItems are application dependent so we cannot guarantee that they contain the + * right values. */ public static class MediaItem implements Parcelable { private final int mFlags; @@ -830,7 +893,7 @@ public final class MediaBrowser { * Returns the media id in the {@link MediaDescription} for this item. * @see android.media.MediaMetadata#METADATA_KEY_MEDIA_ID */ - public @NonNull String getMediaId() { + public @Nullable String getMediaId() { return mDescription.getMediaId(); } } @@ -882,7 +945,7 @@ public final class MediaBrowser { * * @param parentId The media id of the parent media item. * @param children The children which were loaded. - * @param options A bundle of service-specific arguments sent to the media + * @param options The bundle of service-specific arguments sent to the media * browse service. The contents of this bundle may affect the * information returned when browsing. */ @@ -912,8 +975,8 @@ public final class MediaBrowser { * * @param parentId The media id of the parent media item whose children could * not be loaded. - * @param options A bundle of service-specific arguments sent to the media - * browse service. + * @param options The bundle of service-specific arguments sent to the media + * browser service. */ public void onError(@NonNull String parentId, @NonNull Bundle options) { } @@ -924,7 +987,7 @@ public final class MediaBrowser { */ public static abstract class ItemCallback { /** - * Called when the item has been returned by the browser service. + * Called when the item has been returned by the connected service. * * @param item The item that was returned or null if it doesn't exist. */ @@ -932,11 +995,38 @@ public final class MediaBrowser { } /** - * Called when the item doesn't exist or there was an error retrieving it. + * Called there was an error retrieving it or the connected service doesn't support + * {@link #getItem}. + * + * @param mediaId The media id of the media item which could not be loaded. + */ + public void onError(@NonNull String mediaId) { + } + } + + /** + * Callback for receiving the result of {@link #search}. + */ + public static abstract class SearchCallback { + /** + * Called when the {@link #search} finished successfully. + * + * @param query The search query sent for the search request to the connected service. + * @param extras The bundle of service-specific arguments sent to the connected service. + * @param items The list of media items which contains the search result. + */ + public void onSearchResult(@NonNull String query, Bundle extras, + @NonNull List<MediaItem> items) { + } + + /** + * Called when an error happens while {@link #search} or the connected service doesn't + * support {@link #search}. * - * @param itemId The media id of the media item which could not be loaded. + * @param query The search query sent for the search request to the connected service. + * @param extras The bundle of service-specific arguments sent to the connected service. */ - public void onError(@NonNull String itemId) { + public void onError(@NonNull String query, Bundle extras) { } } diff --git a/media/java/android/mtp/MtpServer.java b/media/java/android/mtp/MtpServer.java index 99f93e412b11..9801949916bb 100644 --- a/media/java/android/mtp/MtpServer.java +++ b/media/java/android/mtp/MtpServer.java @@ -72,6 +72,11 @@ public class MtpServer implements Runnable { native_remove_storage(storage.getStorageId()); } + public static void configure(boolean usePtp) { + native_configure(usePtp); + } + + public static native final void native_configure(boolean usePtp); private native final void native_setup(MtpDatabase database, boolean usePtp); private native final void native_run(); private native final void native_cleanup(); diff --git a/media/java/android/service/media/IMediaBrowserService.aidl b/media/java/android/service/media/IMediaBrowserService.aidl index 84f41f6c3afe..e95154ff4540 100644 --- a/media/java/android/service/media/IMediaBrowserService.aidl +++ b/media/java/android/service/media/IMediaBrowserService.aidl @@ -19,8 +19,10 @@ oneway interface IMediaBrowserService { void addSubscriptionDeprecated(String uri, IMediaBrowserServiceCallbacks callbacks); void removeSubscriptionDeprecated(String uri, IMediaBrowserServiceCallbacks callbacks); - void getMediaItem(String uri, in ResultReceiver cb, IMediaBrowserServiceCallbacks callbacks); + void search(String query, in Bundle extras, in ResultReceiver cb, + IMediaBrowserServiceCallbacks callbacks); + void addSubscription(String uri, in IBinder token, in Bundle options, IMediaBrowserServiceCallbacks callbacks); void removeSubscription(String uri, in IBinder token, IMediaBrowserServiceCallbacks callbacks); diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java index a19e347b4507..16847c1b76d1 100644 --- a/media/java/android/service/media/MediaBrowserService.java +++ b/media/java/android/service/media/MediaBrowserService.java @@ -52,9 +52,9 @@ import java.util.Iterator; import java.util.List; /** - * Base class for media browse services. + * Base class for media browser services. * <p> - * Media browse services enable applications to browse media content provided by an application + * Media browser services enable applications to browse media content provided by an application * and ask the application to start playing it. They may also be used to control content that * is already playing by way of a {@link MediaSession}. * </p> @@ -85,18 +85,27 @@ public abstract class MediaBrowserService extends Service { /** * A key for passing the MediaItem to the ResultReceiver in getItem. - * * @hide */ public static final String KEY_MEDIA_ITEM = "media_item"; - private static final int RESULT_FLAG_OPTION_NOT_HANDLED = 0x00000001; - private static final int RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED = 0x00000002; + /** + * A key for passing the list of MediaItems to the ResultReceiver in search. + * @hide + */ + public static final String KEY_SEARCH_RESULTS = "search_results"; + + private static final int RESULT_FLAG_OPTION_NOT_HANDLED = 1 << 0; + private static final int RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED = 1 << 1; + private static final int RESULT_FLAG_ON_SEARCH_NOT_IMPLEMENTED = 1 << 2; + + private static final int RESULT_ERROR = -1; + private static final int RESULT_OK = 0; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag=true, value = { RESULT_FLAG_OPTION_NOT_HANDLED, - RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED }) + RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED, RESULT_FLAG_ON_SEARCH_NOT_IMPLEMENTED }) private @interface ResultFlags { } private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap<>(); @@ -307,10 +316,6 @@ public abstract class MediaBrowserService extends Service { @Override public void getMediaItem(final String mediaId, final ResultReceiver receiver, final IMediaBrowserServiceCallbacks callbacks) { - if (TextUtils.isEmpty(mediaId) || receiver == null) { - return; - } - mHandler.post(new Runnable() { @Override public void run() { @@ -324,6 +329,23 @@ public abstract class MediaBrowserService extends Service { } }); } + + @Override + public void search(final String query, Bundle extras, ResultReceiver receiver, + final IMediaBrowserServiceCallbacks callbacks) { + mHandler.post(new Runnable() { + @Override + public void run() { + final IBinder b = callbacks.asBinder(); + ConnectionRecord connection = mConnections.get(b); + if (connection == null) { + Log.w(TAG, "search for callback that isn't registered query=" + query); + return; + } + performSearch(query, extras, connection, receiver); + } + }); + } } @Override @@ -358,7 +380,7 @@ public abstract class MediaBrowserService extends Service { * @param clientUid The uid of the application which is requesting access to * browse media. * @param rootHints An optional bundle of service-specific arguments to send - * to the media browse service when connecting and retrieving the + * to the media browser service when connecting and retrieving the * root id for browsing, or null if none. The contents of this * bundle may affect the information returned when browsing. * @return The {@link BrowserRoot} for accessing this app's content or null. @@ -412,8 +434,8 @@ public abstract class MediaBrowserService extends Service { * @param parentId The id of the parent media item whose children are to be * queried. * @param result The Result to send the list of children to. - * @param options A bundle of service-specific arguments sent from the media - * browse. The information returned through the result should be + * @param options The bundle of service-specific arguments sent from the media + * browser. The information returned through the result should be * affected by the contents of this bundle. */ public void onLoadChildren(@NonNull String parentId, @@ -450,6 +472,32 @@ public abstract class MediaBrowserService extends Service { } /** + * Called to get the search result. + * <p> + * Implementations must call {@link Result#sendResult result.sendResult}. If + * the search will be an expensive operation {@link Result#detach result.detach} + * may be called before returning from this function, and then {@link Result#sendResult + * result.sendResult} called when the search has been completed. + * </p><p> + * In case there are no search results, call {@link Result#sendResult} with an empty list. + * In case there are some errors happened, call {@link Result#sendResult result.sendResult} + * with {@code null}, which will invoke {@link MediaBrowser.SearchCallback#onError}. + * </p><p> + * The default implementation will invoke {@link MediaBrowser.SearchCallback#onError}. + * </p> + * + * @param query The search query sent from the media browser. It contains keywords separated + * by space. + * @param extras The bundle of service-specific arguments sent from the media browser. + * @param result The {@link Result} to send the search result. + */ + public void onSearch(@NonNull String query, Bundle extras, + Result<List<MediaBrowser.MediaItem>> result) { + result.setFlags(RESULT_FLAG_ON_SEARCH_NOT_IMPLEMENTED); + result.sendResult(null); + } + + /** * Call to set the media session. * <p> * This should be called as soon as possible during the service's startup. @@ -531,8 +579,8 @@ public abstract class MediaBrowserService extends Service { * * @param parentId The id of the parent media item whose * children changed. - * @param options A bundle of service-specific arguments to send - * to the media browse. The contents of this bundle may + * @param options The bundle of service-specific arguments to send + * to the media browser. The contents of this bundle may * contain the information about the change. */ public void notifyChildrenChanged(@NonNull String parentId, @NonNull Bundle options) { @@ -705,12 +753,12 @@ public abstract class MediaBrowserService extends Service { @Override void onResultSent(MediaBrowser.MediaItem item, @ResultFlags int flag) { if ((flag & RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED) != 0) { - receiver.send(-1, null); + receiver.send(RESULT_ERROR, null); return; } Bundle bundle = new Bundle(); bundle.putParcelable(KEY_MEDIA_ITEM, item); - receiver.send(0, bundle); + receiver.send(RESULT_OK, bundle); } }; @@ -724,6 +772,34 @@ public abstract class MediaBrowserService extends Service { } } + private void performSearch(String query, Bundle extras, final ConnectionRecord connection, + final ResultReceiver receiver) { + final Result<List<MediaBrowser.MediaItem>> result = + new Result<List<MediaBrowser.MediaItem>>(query) { + @Override + void onResultSent(List<MediaBrowser.MediaItem> items, @ResultFlags int flag) { + if ((flag & RESULT_FLAG_ON_SEARCH_NOT_IMPLEMENTED) != 0 + || items == null) { + receiver.send(RESULT_ERROR, null); + return; + } + Bundle bundle = new Bundle(); + bundle.putParcelableArray(KEY_SEARCH_RESULTS, + items.toArray(new MediaBrowser.MediaItem[0])); + receiver.send(RESULT_OK, bundle); + } + }; + + mCurConnection = connection; + onSearch(query, extras, result); + mCurConnection = null; + + if (!result.isDone()) { + throw new IllegalStateException("onSearch must call detach() or sendResult()" + + " before returning for query=" + query); + } + } + /** * Contains information that the browser service needs to send to the client * when first connected. diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp index d13187c39846..afd3082ec51b 100644 --- a/media/jni/android_mtp_MtpServer.cpp +++ b/media/jni/android_mtp_MtpServer.cpp @@ -56,17 +56,16 @@ static inline MtpServer* getMtpServer(JNIEnv *env, jobject thiz) { return (MtpServer*)env->GetLongField(thiz, field_MtpServer_nativeContext); } +static void android_mtp_configure(JNIEnv *, jobject, jboolean usePtp) { + MtpServer::configure(usePtp); +} + static void android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, jboolean usePtp) { - int fd = open("/dev/mtp_usb", O_RDWR); - if (fd >= 0) { - MtpServer* server = new MtpServer(fd, getMtpDatabase(env, javaDatabase), - usePtp, AID_MEDIA_RW, 0664, 0775); - env->SetLongField(thiz, field_MtpServer_nativeContext, (jlong)server); - } else { - ALOGE("could not open MTP driver, errno: %d", errno); - } + MtpServer* server = new MtpServer(getMtpDatabase(env, javaDatabase), + usePtp, AID_MEDIA_RW, 0664, 0775); + env->SetLongField(thiz, field_MtpServer_nativeContext, (jlong)server); } static void @@ -180,6 +179,7 @@ android_mtp_MtpServer_remove_storage(JNIEnv *env, jobject thiz, jint storageId) // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { + {"native_configure", "(Z)V", (void *)android_mtp_configure}, {"native_setup", "(Landroid/mtp/MtpDatabase;Z)V", (void *)android_mtp_MtpServer_setup}, {"native_run", "()V", (void *)android_mtp_MtpServer_run}, diff --git a/media/mca/filterfw/native/core/shader_program.cpp b/media/mca/filterfw/native/core/shader_program.cpp index 1e573fbf84a2..d46051292c38 100644 --- a/media/mca/filterfw/native/core/shader_program.cpp +++ b/media/mca/filterfw/native/core/shader_program.cpp @@ -1028,7 +1028,11 @@ bool ShaderProgram::SetAttributeValues(ProgramVar var, attrib.values = data_cpy; attrib.owned_data = data_cpy; // Marks this for deletion later on - return StoreAttribute(attrib); + if (StoreAttribute(attrib)) + return true; + // If storing this failed, then it won't be deleted on its own. + delete[] data_cpy; + return false; } bool ShaderProgram::StoreAttribute(VertexAttrib attrib) { diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java index 7751fccd265f..6511cc89555e 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkIntegrationTestRunner.java @@ -38,7 +38,7 @@ public class MediaFrameworkIntegrationTestRunner extends InstrumentationTestRunn private static final String TAG = "MediaFrameworkIntegrationTestRunner"; - public static int mCameraId = 0; + public static String mCameraId = "0"; @Override public TestSuite getAllTests() { @@ -62,7 +62,7 @@ public class MediaFrameworkIntegrationTestRunner extends InstrumentationTestRunn try { Log.v(TAG, String.format("Reading camera_id from icicle: '%s'", cameraId)); - mCameraId = Integer.parseInt(cameraId); + mCameraId = cameraId; } catch (NumberFormatException e) { Log.e(TAG, String.format("Failed to convert camera_id to integer")); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java index 4f2fdff558bc..fcb861ce07da 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java @@ -116,8 +116,8 @@ public class CameraBinderTest extends AndroidTestCase { @SmallTest public void testSupportsCamera2Api() throws Exception { for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) { - - boolean supports = mUtils.getCameraService().supportsCameraApi(cameraId, API_VERSION_2); + boolean supports = mUtils.getCameraService().supportsCameraApi( + String.valueOf(cameraId), API_VERSION_2); Log.v(TAG, "Camera " + cameraId + " supports api2: " + supports); } @@ -128,7 +128,8 @@ public class CameraBinderTest extends AndroidTestCase { public void testSupportsCamera1Api() throws Exception { for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) { - boolean supports = mUtils.getCameraService().supportsCameraApi(cameraId, API_VERSION_1); + boolean supports = mUtils.getCameraService().supportsCameraApi( + String.valueOf(cameraId), API_VERSION_1); assertTrue( "Camera service returned false when queried if it supports camera1 api " + " for camera ID " + cameraId, supports); @@ -285,7 +286,7 @@ public class CameraBinderTest extends AndroidTestCase { ICameraDeviceUser cameraUser = mUtils.getCameraService().connectDevice( - dummyCallbacks, cameraId, + dummyCallbacks, String.valueOf(cameraId), clientPackageName, ICameraService.USE_CALLING_UID); assertNotNull(String.format("Camera %s was null", cameraId), cameraUser); @@ -298,9 +299,9 @@ public class CameraBinderTest extends AndroidTestCase { static class DummyCameraServiceListener extends ICameraServiceListener.Stub { @Override - public void onStatusChanged(int status, int cameraId) + public void onStatusChanged(int status, String cameraId) throws RemoteException { - Log.v(TAG, String.format("Camera %d has status changed to 0x%x", cameraId, status)); + Log.v(TAG, String.format("Camera %s has status changed to 0x%x", cameraId, status)); } public void onTorchStatusChanged(int status, String cameraId) throws RemoteException { diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java index 832363c645d6..9a78544b8415 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java @@ -66,7 +66,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase { private static final int DEFAULT_IMAGE_HEIGHT = 480; private static final int MAX_NUM_IMAGES = 5; - private int mCameraId; + private String mCameraId; private ICameraDeviceUser mCameraUser; private CameraBinderTestUtils mUtils; private ICameraDeviceCallbacks.Stub mMockCb; diff --git a/packages/BackupRestoreConfirmation/res/values-si-rLK/strings.xml b/packages/BackupRestoreConfirmation/res/values-si-rLK/strings.xml index aaeaf04498f1..cbc130df9c9a 100644 --- a/packages/BackupRestoreConfirmation/res/values-si-rLK/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-si-rLK/strings.xml @@ -29,7 +29,7 @@ <string name="device_encryption_backup_text" msgid="5866590762672844664">"කරුණාකර ඔබගේ උපාංගයේ සංකේතන මුරපදය පහත ඇතුලත් කරන්න. සංරක්ෂිත උපස්ථ සංකේතනය කිරීමට මෙය භාවිත කළ හැක."</string> <string name="backup_enc_password_text" msgid="4981585714795233099">"කරුණාකර සියලු උපස්ථ දත්ත සංකේතනය කිරීම සඳහා භාවිතයට මුරපදයක් ඇතුළත් කරන්න. මෙය හිස්ව තැබුවොත්, ඔබගේ වර්තමාන උපස්ථ මුරපදය භාවිත වෙයි:"</string> <string name="backup_enc_password_optional" msgid="1350137345907579306">"සියලු උපස්ථ දත්ත සංකේතනය කිරීමට ඔබ අදහස් කරන්නේ නම්, මුරපදය පහලින් ඇතුලත් කරන්න:"</string> - <string name="backup_enc_password_required" msgid="7889652203371654149">"ඔබගේ උපාංගය සංකේතනය කර තිබෙන නිසා, ඔබගේ උපස්ථය සංකේතනය ඔබට අවශ්ය වී තිබේ. කරුණාකර මුරපදය පහළින් එකතු කරන්න:"</string> + <string name="backup_enc_password_required" msgid="7889652203371654149">"ඔබගේ උපාංගය සංකේතනය කර තිබෙන නිසා, ඔබගේ උපස්ථය සංකේතනය ඔබට අවශ්ය වී තිබේ. මුරපදය පහළින් එක් කරන්න:"</string> <string name="restore_enc_password_text" msgid="6140898525580710823">"යළි පිහිටුවන දත්ත සංකේතනය කරන ලද ඒවානම්, කරුණාකර මුරපදය පහලින් ඇතුල් කරන්න:"</string> <string name="toast_backup_started" msgid="550354281452756121">"උපස්ථ කිරීම ආරම්භ කරමින්..."</string> <string name="toast_backup_ended" msgid="3818080769548726424">"උපස්ථය අවසන්"</string> diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java index 03f0b4d5d5da..23a8655a3bb6 100644 --- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -63,6 +63,7 @@ public class CaptivePortalLoginActivity extends Activity { private enum Result { DISMISSED, UNWANTED, WANTED_AS_IS }; private URL mUrl; + private String mUserAgent; private Network mNetwork; private CaptivePortal mCaptivePortal; private NetworkCallback mNetworkCallback; @@ -76,6 +77,8 @@ public class CaptivePortalLoginActivity extends Activity { mCm = ConnectivityManager.from(this); mNetwork = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_NETWORK); mCaptivePortal = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL); + mUserAgent = getIntent().getParcelableExtra( + ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT); mUrl = getUrl(); if (mUrl == null) { // getUrl() failed to parse the url provided in the intent: bail out in a way that @@ -252,6 +255,7 @@ public class CaptivePortalLoginActivity extends Activity { } private void testForCaptivePortal() { + // TODO: reuse NetworkMonitor facilities for consistent captive portal detection. new Thread(new Runnable() { public void run() { // Give time for captive portal to open. @@ -262,11 +266,14 @@ public class CaptivePortalLoginActivity extends Activity { HttpURLConnection urlConnection = null; int httpResponseCode = 500; try { - urlConnection = (HttpURLConnection) mUrl.openConnection(); + urlConnection = (HttpURLConnection) mNetwork.openConnection(mUrl); urlConnection.setInstanceFollowRedirects(false); urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); urlConnection.setUseCaches(false); + if (mUserAgent != null) { + urlConnection.setRequestProperty("User-Agent", mUserAgent); + } urlConnection.getInputStream(); httpResponseCode = urlConnection.getResponseCode(); } catch (IOException e) { diff --git a/packages/PrintRecommendationService/AndroidManifest.xml b/packages/PrintRecommendationService/AndroidManifest.xml index c6736d7bac23..2e9342c9354a 100644 --- a/packages/PrintRecommendationService/AndroidManifest.xml +++ b/packages/PrintRecommendationService/AndroidManifest.xml @@ -18,11 +18,11 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.printservice.recommendation" - android:versionCode="1" - android:versionName="1.0.0"> + android:versionCode="2" + android:versionName="1.1.0"> <uses-sdk android:minSdkVersion="24" - android:targetSdkVersion="24" /> + android:targetSdkVersion="25" /> <uses-permission android:name="android.permission.INTERNET" /> diff --git a/packages/PrintSpooler/res/values-si-rLK/strings.xml b/packages/PrintSpooler/res/values-si-rLK/strings.xml index ffff8e0b33b3..e661f9343e12 100644 --- a/packages/PrintSpooler/res/values-si-rLK/strings.xml +++ b/packages/PrintSpooler/res/values-si-rLK/strings.xml @@ -52,7 +52,7 @@ <string name="add_print_service_label" msgid="5356702546188981940">"සේවාව එක් කිරීම"</string> <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"සෙවීම් කොටුව පෙන්වන ලදී"</string> <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"සෙවීම් කොටුව සඟවන ලදී"</string> - <string name="print_add_printer" msgid="1088656468360653455">"මුද්රණ යන්ත්ර එකතු කරන්න"</string> + <string name="print_add_printer" msgid="1088656468360653455">"මුද්රණ යන්ත්ර එක් කරන්න"</string> <string name="print_select_printer" msgid="7388760939873368698">"මුද්රකය තේරීම"</string> <string name="print_forget_printer" msgid="5035287497291910766">"මුද්රකය අමතක කිරීම"</string> <plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868"> diff --git a/packages/SettingsLib/res/drawable/ic_menu.xml b/packages/SettingsLib/res/drawable/ic_menu.xml index 910a3d08b3ba..b77db08b336d 100644 --- a/packages/SettingsLib/res/drawable/ic_menu.xml +++ b/packages/SettingsLib/res/drawable/ic_menu.xml @@ -17,7 +17,8 @@ android:width="24.0dp" android:height="24.0dp" android:viewportWidth="24.0" - android:viewportHeight="24.0"> + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal"> <path android:fillColor="#FFFFFFFF" android:pathData="M3.0,18.0l18.0,0.0l0.0,-2.0L3.0,16.0l0.0,2.0zm0.0,-5.0l18.0,0.0l0.0,-2.0L3.0,11.0l0.0,2.0zm0.0,-7.0l0.0,2.0l18.0,0.0L21.0,6.0L3.0,6.0z"/> diff --git a/packages/SettingsLib/res/layout/restricted_switch_preference.xml b/packages/SettingsLib/res/layout/restricted_switch_preference.xml index bb1d90614d5a..0e4ef6b7a92d 100644 --- a/packages/SettingsLib/res/layout/restricted_switch_preference.xml +++ b/packages/SettingsLib/res/layout/restricted_switch_preference.xml @@ -24,7 +24,7 @@ android:clipToPadding="false"> <LinearLayout - android:id="@+id/icon_frame" + android:id="@+id/icon_container" android:layout_width="wrap_content" android:layout_height="wrap_content" android:minWidth="60dp" diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index 4353282d6b72..2d89b0091623 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -31,7 +31,7 @@ <string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"No se establecerá conexión automáticamente"</string> <string name="wifi_no_internet" msgid="3880396223819116454">"No se ha detectado acceso a Internet"</string> <string name="saved_network" msgid="4352716707126620811">"Guardadas por <xliff:g id="NAME">%1$s</xliff:g>"</string> - <string name="connected_via_wfa" msgid="3805736726317410714">"Conectado a través de asistente Wi‑Fi"</string> + <string name="connected_via_wfa" msgid="3805736726317410714">"Conectado a través del asistente de Wi‑Fi"</string> <string name="connected_via_passpoint" msgid="2826205693803088747">"Conectado a través de %1$s"</string> <string name="available_via_passpoint" msgid="1617440946846329613">"Disponible a través de %1$s"</string> <string name="wifi_connected_no_internet" msgid="3149853966840874992">"Conexión sin Internet"</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java index 6bd8a879dabb..99d7f1e20f95 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java @@ -16,6 +16,7 @@ package com.android.settingslib; +import android.annotation.UserIdInt; import android.app.AppGlobals; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; @@ -118,21 +119,25 @@ public class RestrictedLockUtils { */ public static EnforcedAdmin checkIfKeyguardFeaturesDisabled(Context context, int keyguardFeatures, int userId) { + final LockSettingCheck check = + (DevicePolicyManager dpm, ComponentName admin, @UserIdInt int checkUser) -> + (dpm.getKeyguardDisabledFeatures(admin, checkUser) & keyguardFeatures) != 0; + final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); if (dpm == null) { return null; } + final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); - LockPatternUtils lockPatternUtils = new LockPatternUtils(context); - EnforcedAdmin enforcedAdmin = null; if (um.getUserInfo(userId).isManagedProfile()) { final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userId); if (admins == null) { return null; } + EnforcedAdmin enforcedAdmin = null; for (ComponentName admin : admins) { - if ((dpm.getKeyguardDisabledFeatures(admin, userId) & keyguardFeatures) != 0) { + if (check.isEnforcing(dpm, admin, userId)) { if (enforcedAdmin == null) { enforcedAdmin = new EnforcedAdmin(admin, userId); } else { @@ -140,49 +145,10 @@ public class RestrictedLockUtils { } } } + return enforcedAdmin; } else { - // Consider all admins for this user and the profiles that are visible from this - // user that do not use a separate work challenge. - for (UserInfo userInfo : um.getProfiles(userId)) { - final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id); - if (admins == null) { - continue; - } - final boolean isSeparateProfileChallengeEnabled = - lockPatternUtils.isSeparateProfileChallengeEnabled(userInfo.id); - for (ComponentName admin : admins) { - if (!isSeparateProfileChallengeEnabled) { - if ((dpm.getKeyguardDisabledFeatures(admin, userInfo.id) - & keyguardFeatures) != 0) { - if (enforcedAdmin == null) { - enforcedAdmin = new EnforcedAdmin(admin, userInfo.id); - } else { - return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; - } - // This same admins could have set policies both on the managed profile - // and on the parent. So, if the admin has set the policy on the - // managed profile here, we don't need to further check if that admin - // has set policy on the parent admin. - continue; - } - } - if (userInfo.isManagedProfile()) { - // If userInfo.id is a managed profile, we also need to look at - // the policies set on the parent. - DevicePolicyManager parentDpm = dpm.getParentProfileInstance(userInfo); - if ((parentDpm.getKeyguardDisabledFeatures(admin, userInfo.id) - & keyguardFeatures) != 0) { - if (enforcedAdmin == null) { - enforcedAdmin = new EnforcedAdmin(admin, userInfo.id); - } else { - return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; - } - } - } - } - } + return checkForLockSetting(context, userId, check); } - return enforcedAdmin; } public static EnforcedAdmin checkIfUninstallBlocked(Context context, @@ -383,6 +349,11 @@ public class RestrictedLockUtils { * */ public static EnforcedAdmin checkIfPasswordQualityIsSet(Context context, int userId) { + final LockSettingCheck check = + (DevicePolicyManager dpm, ComponentName admin, @UserIdInt int checkUser) -> + dpm.getPasswordQuality(admin, checkUser) + > DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; + final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); if (dpm == null) { @@ -390,7 +361,6 @@ public class RestrictedLockUtils { } LockPatternUtils lockPatternUtils = new LockPatternUtils(context); - EnforcedAdmin enforcedAdmin = null; if (lockPatternUtils.isSeparateProfileChallengeEnabled(userId)) { // userId is managed profile and has a separate challenge, only consider // the admins in that user. @@ -398,9 +368,9 @@ public class RestrictedLockUtils { if (admins == null) { return null; } + EnforcedAdmin enforcedAdmin = null; for (ComponentName admin : admins) { - if (dpm.getPasswordQuality(admin, userId) - > DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { + if (check.isEnforcing(dpm, admin, userId)) { if (enforcedAdmin == null) { enforcedAdmin = new EnforcedAdmin(admin, userId); } else { @@ -408,50 +378,10 @@ public class RestrictedLockUtils { } } } + return enforcedAdmin; } else { - // Return all admins for this user and the profiles that are visible from this - // user that do not use a separate work challenge. - final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); - for (UserInfo userInfo : um.getProfiles(userId)) { - final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id); - if (admins == null) { - continue; - } - final boolean isSeparateProfileChallengeEnabled = - lockPatternUtils.isSeparateProfileChallengeEnabled(userInfo.id); - for (ComponentName admin : admins) { - if (!isSeparateProfileChallengeEnabled) { - if (dpm.getPasswordQuality(admin, userInfo.id) - > DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { - if (enforcedAdmin == null) { - enforcedAdmin = new EnforcedAdmin(admin, userInfo.id); - } else { - return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; - } - // This same admins could have set policies both on the managed profile - // and on the parent. So, if the admin has set the policy on the - // managed profile here, we don't need to further check if that admin - // has set policy on the parent admin. - continue; - } - } - if (userInfo.isManagedProfile()) { - // If userInfo.id is a managed profile, we also need to look at - // the policies set on the parent. - DevicePolicyManager parentDpm = dpm.getParentProfileInstance(userInfo); - if (parentDpm.getPasswordQuality(admin, userInfo.id) - > DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { - if (enforcedAdmin == null) { - enforcedAdmin = new EnforcedAdmin(admin, userInfo.id); - } else { - return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; - } - } - } - } - } + return checkForLockSetting(context, userId, check); } - return enforcedAdmin; } /** @@ -512,6 +442,65 @@ public class RestrictedLockUtils { return enforcedAdmin; } + private interface LockSettingCheck { + boolean isEnforcing(DevicePolicyManager dpm, ComponentName admin, @UserIdInt int userId); + } + + /** + * Checks whether any of the user's profiles enforce the lock setting. A managed profile is only + * included if it does not have a separate challenege but the settings for it's parent (i.e. the + * user being checked) are always included. + */ + private static EnforcedAdmin checkForLockSetting( + Context context, @UserIdInt int userId, LockSettingCheck check) { + final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( + Context.DEVICE_POLICY_SERVICE); + if (dpm == null) { + return null; + } + final LockPatternUtils lockPatternUtils = new LockPatternUtils(context); + EnforcedAdmin enforcedAdmin = null; + // Return all admins for this user and the profiles that are visible from this + // user that do not use a separate work challenge. + for (UserInfo userInfo : UserManager.get(context).getProfiles(userId)) { + final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id); + if (admins == null) { + continue; + } + final boolean isSeparateProfileChallengeEnabled = + lockPatternUtils.isSeparateProfileChallengeEnabled(userInfo.id); + for (ComponentName admin : admins) { + if (!isSeparateProfileChallengeEnabled) { + if (check.isEnforcing(dpm, admin, userInfo.id)) { + if (enforcedAdmin == null) { + enforcedAdmin = new EnforcedAdmin(admin, userInfo.id); + } else { + return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; + } + // This same admins could have set policies both on the managed profile + // and on the parent. So, if the admin has set the policy on the + // managed profile here, we don't need to further check if that admin + // has set policy on the parent admin. + continue; + } + } + if (userInfo.isManagedProfile()) { + // If userInfo.id is a managed profile, we also need to look at + // the policies set on the parent. + final DevicePolicyManager parentDpm = dpm.getParentProfileInstance(userInfo); + if (check.isEnforcing(parentDpm, admin, userInfo.id)) { + if (enforcedAdmin == null) { + enforcedAdmin = new EnforcedAdmin(admin, userInfo.id); + } else { + return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; + } + } + } + } + } + return enforcedAdmin; + } + public static EnforcedAdmin getProfileOrDeviceOwner(Context context, int userId) { if (userId == UserHandle.USER_NULL) { return null; diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java index f3658c38ca35..6ccba92e2e5f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java @@ -50,19 +50,25 @@ public class CategoryManager { private final Map<String, DashboardCategory> mCategoryByKeyMap; private List<DashboardCategory> mCategories; + private String mExtraAction; public static CategoryManager get(Context context) { + return get(context, null); + } + + public static CategoryManager get(Context context, String action) { if (sInstance == null) { - sInstance = new CategoryManager(context); + sInstance = new CategoryManager(context, action); } return sInstance; } - CategoryManager(Context context) { + CategoryManager(Context context, String action) { mTileByComponentCache = new ArrayMap<>(); mCategoryByKeyMap = new ArrayMap<>(); mInterestingConfigChanges = new InterestingConfigChanges(); mInterestingConfigChanges.applyNewConfig(context.getResources()); + mExtraAction = action; } public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) { @@ -111,7 +117,7 @@ public class CategoryManager { } mCategoryByKeyMap.clear(); mCategories = TileUtils.getCategories(context, mTileByComponentCache, - false /* categoryDefinedInManifest */); + false /* categoryDefinedInManifest */, mExtraAction); for (DashboardCategory category : mCategories) { mCategoryByKeyMap.put(category.key, category); } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java index d12e8c09b8c0..6c5a09d23c61 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java @@ -144,6 +144,19 @@ public class TileUtils { */ public static List<DashboardCategory> getCategories(Context context, Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest) { + return getCategories(context, cache, categoryDefinedInManifest, null); + } + + /** + * Build a list of DashboardCategory. + * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to + * represent this category (eg: .Settings$DeviceSettings) + * @param extraAction additional intent filter action to be used to build the dashboard + * categories + */ + public static List<DashboardCategory> getCategories(Context context, + Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest, + String extraAction) { final long startTime = System.currentTimeMillis(); boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) != 0; @@ -162,6 +175,9 @@ public class TileUtils { if (setup) { getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false); getTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false); + if (extraAction != null) { + getTilesForAction(context, user, extraAction, cache, null, tiles, false); + } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/AnimatedImageView.java b/packages/SettingsLib/src/com/android/settingslib/widget/AnimatedImageView.java index f5e39bef3d63..a8cd655bee7b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/widget/AnimatedImageView.java +++ b/packages/SettingsLib/src/com/android/settingslib/widget/AnimatedImageView.java @@ -55,7 +55,7 @@ public class AnimatedImageView extends ImageView { private void updateAnimating() { if (mDrawable != null) { - if (isShown() && mAnimating) { + if (getVisibility() == View.VISIBLE && mAnimating) { mDrawable.start(); } else { mDrawable.stop(); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index c2161ae323bf..e7450fafda25 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -288,7 +288,7 @@ public class AccessPoint implements Comparable<AccessPoint> { public boolean matches(WifiConfiguration config) { if (config.isPasspoint() && mConfig != null && mConfig.isPasspoint()) { - return config.FQDN.equals(mConfig.FQDN); + return ssid.equals(removeDoubleQuotes(config.SSID)) && config.FQDN.equals(mConfig.FQDN); } else { return ssid.equals(removeDoubleQuotes(config.SSID)) && security == getSecurity(config) @@ -839,16 +839,19 @@ public class AccessPoint implements Comparable<AccessPoint> { if (state == DetailedState.CONNECTED) { IWifiManager wifiManager = IWifiManager.Stub.asInterface( ServiceManager.getService(Context.WIFI_SERVICE)); - Network nw; + NetworkCapabilities nc = null; try { - nw = wifiManager.getCurrentNetwork(); - } catch (RemoteException e) { - nw = null; - } - NetworkCapabilities nc = cm.getNetworkCapabilities(nw); - if (nc != null && !nc.hasCapability(nc.NET_CAPABILITY_VALIDATED)) { - return context.getString(R.string.wifi_connected_no_internet); + nc = cm.getNetworkCapabilities(wifiManager.getCurrentNetwork()); + } catch (RemoteException e) {} + + if (nc != null) { + if (nc.hasCapability(nc.NET_CAPABILITY_CAPTIVE_PORTAL)) { + return context.getString( + com.android.internal.R.string.network_available_sign_in); + } else if (!nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) { + return context.getString(R.string.wifi_connected_no_internet); + } } } if (state == null) { diff --git a/packages/SettingsLib/tests/robotests/Android.mk b/packages/SettingsLib/tests/robotests/Android.mk index c1108f6fcc91..208fa6df05ee 100644 --- a/packages/SettingsLib/tests/robotests/Android.mk +++ b/packages/SettingsLib/tests/robotests/Android.mk @@ -26,7 +26,7 @@ LOCAL_MODULE_TAGS := optional LOCAL_PRIVILEGED_MODULE := true LOCAL_JAVA_LIBRARIES := \ - junit4-target \ + junit \ platform-robolectric-prebuilt LOCAL_STATIC_JAVA_LIBRARIES := \ @@ -55,7 +55,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ truth-prebuilt LOCAL_JAVA_LIBRARIES := \ - junit4-target \ + junit \ platform-robolectric-prebuilt LOCAL_INSTRUMENTATION_FOR := SettingsLibShell diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java new file mode 100644 index 000000000000..025bbc2ffd92 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java @@ -0,0 +1,120 @@ +/* + * 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.settingslib; + +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.UserManager; + +import com.android.internal.util.ArrayUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.Arrays; + +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT; +import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class RestrictedLockUtilsTest { + + @Mock + private Context mContext; + @Mock + private DevicePolicyManager mDevicePolicyManager; + @Mock + private UserManager mUserManager; + + private static final int mUserId = 194; + private static final ComponentName mAdmin1 = new ComponentName("admin1", "admin1class"); + private static final ComponentName mAdmin2 = new ComponentName("admin2", "admin2class"); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)) + .thenReturn(mDevicePolicyManager); + when(mContext.getSystemService(Context.USER_SERVICE)) + .thenReturn(mUserManager); + } + + @Test + public void checkIfKeyguardFeaturesDisabled_noEnforcedAdminForManagedProfile() { + setUpManagedProfile(mUserId); + setUpActiveAdmins(mUserId, new ComponentName[] {mAdmin1, mAdmin2}); + + final EnforcedAdmin enforcedAdmin = RestrictedLockUtils.checkIfKeyguardFeaturesDisabled( + mContext, KEYGUARD_DISABLE_FINGERPRINT, mUserId); + + assertThat(enforcedAdmin).isEqualTo(null); + } + + @Test + public void checkIfKeyguardFeaturesDisabled_oneEnforcedAdminForManagedProfile() { + setUpManagedProfile(mUserId); + setUpActiveAdmins(mUserId, new ComponentName[] {mAdmin1, mAdmin2}); + + when(mDevicePolicyManager.getKeyguardDisabledFeatures(mAdmin1, mUserId)) + .thenReturn(KEYGUARD_DISABLE_FINGERPRINT); + + final EnforcedAdmin enforcedAdmin = RestrictedLockUtils.checkIfKeyguardFeaturesDisabled( + mContext, KEYGUARD_DISABLE_FINGERPRINT, mUserId); + + assertThat(enforcedAdmin).isEqualTo(new EnforcedAdmin(mAdmin1, mUserId)); + } + + @Test + public void checkIfKeyguardFeaturesDisabled_multipleEnforcedAdminForManagedProfile() { + setUpManagedProfile(mUserId); + setUpActiveAdmins(mUserId, new ComponentName[] {mAdmin1, mAdmin2}); + + when(mDevicePolicyManager.getKeyguardDisabledFeatures(mAdmin1, mUserId)) + .thenReturn(KEYGUARD_DISABLE_REMOTE_INPUT); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(mAdmin2, mUserId)) + .thenReturn(KEYGUARD_DISABLE_REMOTE_INPUT); + + final EnforcedAdmin enforcedAdmin = RestrictedLockUtils.checkIfKeyguardFeaturesDisabled( + mContext, KEYGUARD_DISABLE_REMOTE_INPUT, mUserId); + + assertThat(enforcedAdmin).isEqualTo(EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN); + } + + private UserInfo setUpManagedProfile(int userId) { + final UserInfo userInfo = new UserInfo(userId, "myuser", UserInfo.FLAG_MANAGED_PROFILE); + when(mUserManager.getUserInfo(userId)).thenReturn(userInfo); + return userInfo; + } + + private void setUpActiveAdmins(int userId, ComponentName[] activeAdmins) { + when(mDevicePolicyManager.getActiveAdminsAsUser(userId)) + .thenReturn(Arrays.asList(activeAdmins)); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java index 86b210ac4aef..c6c6aad22cf0 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java @@ -26,6 +26,8 @@ import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.os.Bundle; import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings.Global; import android.util.ArrayMap; import android.util.Pair; @@ -34,6 +36,7 @@ import com.android.settingslib.TestConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; @@ -46,6 +49,7 @@ import java.util.Map; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; @@ -59,6 +63,8 @@ public class TileUtilsTest { private PackageManager mPackageManager; @Mock private Resources mResources; + @Mock + private UserManager mUserManager; @Before public void setUp() throws NameNotFoundException { @@ -127,6 +133,30 @@ public class TileUtilsTest { assertThat(outTiles.isEmpty()).isTrue(); } + @Test + public void getCategories_shouldHandleExtraIntentAction() { + final String testCategory = "category1"; + final String testAction = "action1"; + Map<Pair<String, String>, Tile> cache = new ArrayMap<>(); + List<ResolveInfo> info = new ArrayList<>(); + info.add(newInfo(true, testCategory)); + Global.putInt(mContext.getContentResolver(), Global.DEVICE_PROVISIONED, 1); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + List<UserHandle> userHandleList = new ArrayList<>(); + userHandleList.add(UserHandle.CURRENT); + when(mUserManager.getUserProfiles()).thenReturn(userHandleList); + + when(mPackageManager.queryIntentActivitiesAsUser(argThat(new ArgumentMatcher<Intent>() { + public boolean matches(Object event) { + return testAction.equals(((Intent) event).getAction()); + } + }), anyInt(), anyInt())).thenReturn(info); + + List<DashboardCategory> categoryList = TileUtils.getCategories( + mContext, cache, false /* categoryDefinedInManifest */, testAction); + + assertThat(categoryList.get(0).tiles.get(0).category).isEqualTo(testCategory); + } private ResolveInfo newInfo(boolean systemApp, String category) { return newInfo(systemApp, category, null); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java new file mode 100644 index 000000000000..2c9c8685af50 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java @@ -0,0 +1,52 @@ +/* + * 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.settingslib.widget; + +import android.app.Activity; +import android.graphics.drawable.AnimatedRotateDrawable; +import android.view.View; +import com.android.settingslib.TestConfig; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class AnimatedImageViewTest { + private AnimatedImageView mAnimatedImageView; + + @Before + public void setUp() { + Activity activity = Robolectric.setupActivity(Activity.class); + mAnimatedImageView = new AnimatedImageView(activity); + mAnimatedImageView.setImageDrawable(new AnimatedRotateDrawable()); + } + + @Test + public void testAnimation_ViewVisible_AnimationRunning() { + mAnimatedImageView.setVisibility(View.VISIBLE); + mAnimatedImageView.setAnimating(true); + AnimatedRotateDrawable drawable = (AnimatedRotateDrawable) mAnimatedImageView.getDrawable(); + assertThat(drawable.isRunning()).isTrue(); + } + +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 88ee33c32ad3..c149876cedaa 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -57,6 +57,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.UserManagerInternal; import android.provider.Settings; +import android.provider.Settings.Global; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -2175,7 +2176,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 135; + private static final int SETTINGS_VERSION = 136; private final int mUserId; @@ -2553,6 +2554,30 @@ public class SettingsProvider extends ContentProvider { currentVersion = 135; } + if (currentVersion == 135) { + // Version 135: Migrating the NETWORK_SCORER_APP setting to the + // NETWORK_RECOMMENDATIONS_ENABLED setting. + if (userId == UserHandle.USER_SYSTEM) { + final SettingsState globalSettings = getGlobalSettingsLocked(); + Setting currentSetting = globalSettings.getSettingLocked( + Global.NETWORK_SCORER_APP); + if (!currentSetting.isNull()) { + // A scorer was set so enable recommendations. + globalSettings.insertSettingLocked( + Global.NETWORK_RECOMMENDATIONS_ENABLED, + "1", + SettingsState.SYSTEM_PACKAGE_NAME); + + // and clear the scorer setting since it's no longer needed. + globalSettings.insertSettingLocked( + Global.NETWORK_SCORER_APP, + null, + SettingsState.SYSTEM_PACKAGE_NAME); + } + } + currentVersion = 136; + } + if (currentVersion != newVersion) { Slog.wtf("SettingsProvider", "warning: upgrading settings database to version " + newVersion + " left it at " diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index d232e00d02e2..5c2787b0f04a 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -287,22 +287,6 @@ </intent-filter> </activity> - <activity android:name=".recents.grid.RecentsGridActivity" - android:label="@string/accessibility_desc_recent_apps" - android:exported="false" - android:launchMode="singleInstance" - android:excludeFromRecents="true" - android:stateNotNeeded="true" - android:resumeWhilePausing="true" - android:screenOrientation="behind" - android:resizeableActivity="true" - android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" - android:theme="@style/RecentsTheme.Grid"> - <intent-filter> - <action android:name="com.android.systemui.recents.TOGGLE_RECENTS" /> - </intent-filter> - </activity> - <activity android:name=".recents.tv.RecentsTvActivity" android:label="@string/accessibility_desc_recent_apps" android:exported="false" diff --git a/packages/SystemUI/res/color/qs_detail_empty.xml b/packages/SystemUI/res/color/qs_detail_empty.xml deleted file mode 100644 index 4be39c7ca6e0..000000000000 --- a/packages/SystemUI/res/color/qs_detail_empty.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:alpha="0.14" android:color="@*android:color/quaternary_device_default_settings" /> -</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable-ldrtl-sw900dp-xhdpi/ic_sysbar_back.png b/packages/SystemUI/res/drawable-ldrtl-sw900dp-xhdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 000000000000..94cb03237ac9 --- /dev/null +++ b/packages/SystemUI/res/drawable-ldrtl-sw900dp-xhdpi/ic_sysbar_back.png diff --git a/packages/SystemUI/res/drawable-sw900dp-xhdpi/ic_sysbar_back.png b/packages/SystemUI/res/drawable-sw900dp-xhdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 000000000000..fbdc93c17ee3 --- /dev/null +++ b/packages/SystemUI/res/drawable-sw900dp-xhdpi/ic_sysbar_back.png diff --git a/packages/SystemUI/res/drawable-sw900dp-xhdpi/ic_sysbar_back_ime.png b/packages/SystemUI/res/drawable-sw900dp-xhdpi/ic_sysbar_back_ime.png Binary files differnew file mode 100644 index 000000000000..419518c099f3 --- /dev/null +++ b/packages/SystemUI/res/drawable-sw900dp-xhdpi/ic_sysbar_back_ime.png diff --git a/packages/SystemUI/res/drawable-sw900dp-xhdpi/ic_sysbar_home.png b/packages/SystemUI/res/drawable-sw900dp-xhdpi/ic_sysbar_home.png Binary files differnew file mode 100644 index 000000000000..a2406b1408c1 --- /dev/null +++ b/packages/SystemUI/res/drawable-sw900dp-xhdpi/ic_sysbar_home.png diff --git a/packages/SystemUI/res/drawable-sw900dp-xhdpi/ic_sysbar_menu.png b/packages/SystemUI/res/drawable-sw900dp-xhdpi/ic_sysbar_menu.png Binary files differnew file mode 100644 index 000000000000..50043ebe3b3d --- /dev/null +++ b/packages/SystemUI/res/drawable-sw900dp-xhdpi/ic_sysbar_menu.png diff --git a/packages/SystemUI/res/drawable-sw900dp-xhdpi/ic_sysbar_recent.png b/packages/SystemUI/res/drawable-sw900dp-xhdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 000000000000..c449449541f4 --- /dev/null +++ b/packages/SystemUI/res/drawable-sw900dp-xhdpi/ic_sysbar_recent.png diff --git a/packages/SystemUI/res/drawable-sw900dp/ic_ime_switcher_default.xml b/packages/SystemUI/res/drawable-sw900dp/ic_ime_switcher_default.xml new file mode 100644 index 000000000000..bcf9cacc45ae --- /dev/null +++ b/packages/SystemUI/res/drawable-sw900dp/ic_ime_switcher_default.xml @@ -0,0 +1,25 @@ +<!-- + ~ 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 + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="28.0dp" + android:height="28.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M20.000000,5.000000L4.000000,5.000000C2.900000,5.000000 2.000000,5.900000 2.000000,7.000000l0.000000,10.000000c0.000000,1.100000 0.900000,2.000000 2.000000,2.000000l16.000000,0.000000c1.100000,0.000000 2.000000,-0.900000 2.000000,-2.000000L22.000000,7.000000C22.000000,5.900000 21.100000,5.000000 20.000000,5.000000zM11.000000,8.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000L11.000000,8.000000zM11.000000,11.000000l2.000000,0.000000l0.000000,2.000000l-2.000000,0.000000L11.000000,11.000000zM8.000000,8.000000l2.000000,0.000000l0.000000,2.000000L8.000000,10.000000L8.000000,8.000000zM8.000000,11.000000l2.000000,0.000000l0.000000,2.000000L8.000000,13.000000L8.000000,11.000000zM7.000000,13.000000L5.000000,13.000000l0.000000,-2.000000l2.000000,0.000000L7.000000,13.000000zM7.000000,10.000000L5.000000,10.000000L5.000000,8.000000l2.000000,0.000000L7.000000,10.000000zM16.000000,17.000000L8.000000,17.000000l0.000000,-2.000000l8.000000,0.000000L16.000000,17.000000zM16.000000,13.000000l-2.000000,0.000000l0.000000,-2.000000l2.000000,0.000000L16.000000,13.000000zM16.000000,10.000000l-2.000000,0.000000L14.000000,8.000000l2.000000,0.000000L16.000000,10.000000zM19.000000,13.000000l-2.000000,0.000000l0.000000,-2.000000l2.000000,0.000000L19.000000,13.000000zM19.000000,10.000000l-2.000000,0.000000L17.000000,8.000000l2.000000,0.000000L19.000000,10.000000z" + android:fillColor="@color/navigation_bar_icon_color"/> +</vector> diff --git a/packages/SystemUI/res/drawable/header_dot.xml b/packages/SystemUI/res/drawable/header_dot.xml index 568a9c29175a..dbc6b378bd63 100644 --- a/packages/SystemUI/res/drawable/header_dot.xml +++ b/packages/SystemUI/res/drawable/header_dot.xml @@ -2,26 +2,27 @@ <!-- ** Copyright 2015, 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 +** 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 +** 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 +** 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. --> <shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="oval"> + android:shape="oval" + android:tint="?android:attr/colorControlNormal"> - <solid + <solid android:color="#FFFFFF"/> - <size + <size android:width="3dp" android:height="3dp"/> </shape> diff --git a/packages/SystemUI/res/drawable/ic_access_alarms_small.xml b/packages/SystemUI/res/drawable/ic_access_alarms_small.xml index cf64689827f3..994274a580f5 100644 --- a/packages/SystemUI/res/drawable/ic_access_alarms_small.xml +++ b/packages/SystemUI/res/drawable/ic_access_alarms_small.xml @@ -17,7 +17,8 @@ Copyright (C) 2014 The Android Open Source Project android:width="16dp" android:height="16dp" android:viewportWidth="24.0" - android:viewportHeight="24.0"> + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal"> <path android:fillColor="#64ffffff" diff --git a/packages/SystemUI/res/drawable/ic_mode_edit.xml b/packages/SystemUI/res/drawable/ic_mode_edit.xml index 8a7368632b06..f32da595aef8 100644 --- a/packages/SystemUI/res/drawable/ic_mode_edit.xml +++ b/packages/SystemUI/res/drawable/ic_mode_edit.xml @@ -17,7 +17,8 @@ android:width="20.0dp" android:height="20.0dp" android:viewportWidth="24.0" - android:viewportHeight="24.0"> + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal"> <path android:fillColor="#FFFFFF" android:pathData="M3.0,17.25L3.0,21.0l3.75,0.0L17.81,9.94l-3.75,-3.75L3.0,17.25zM20.71,7.04c0.39,-0.3 0.39,-1.02 0.0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0.0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/> diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml index 435bb9b81116..33a4047c6f9c 100644 --- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml +++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml @@ -18,9 +18,10 @@ Copyright (C) 2014 The Android Open Source Project android:height="56dp" android:viewportWidth="48.0" android:viewportHeight="48.0" - android:tint="?android:attr/colorControlNormal"> + android:alpha="0.14" + android:tint="?android:attr/colorForeground"> <path - android:fillColor="@color/qs_detail_empty" + android:fillColor="#FFFFFF" android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_detail_empty.xml b/packages/SystemUI/res/drawable/ic_qs_cast_detail_empty.xml index 59dcea2337cb..fc831f446605 100644 --- a/packages/SystemUI/res/drawable/ic_qs_cast_detail_empty.xml +++ b/packages/SystemUI/res/drawable/ic_qs_cast_detail_empty.xml @@ -17,9 +17,11 @@ Copyright (C) 2014 The Android Open Source Project android:width="56dp" android:height="56dp" android:viewportWidth="48.0" - android:viewportHeight="48.0"> + android:viewportHeight="48.0" + android:alpha="0.14" + android:tint="?android:attr/colorForeground"> <path - android:fillColor="@color/qs_detail_empty" + android:fillColor="#FFFFFF" android:pathData="M42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,6.0l4.0,0.0l0.0,-6.0l36.0,0.0l0.0,28.0L28.0,38.0l0.0,4.0l14.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0zM2.0,36.0l0.0,6.0l6.0,0.0C8.0,38.7 5.3,36.0 2.0,36.0zM2.0,28.0l0.0,4.0c5.5,0.0 10.0,4.5 10.0,10.0l4.0,0.0C16.0,34.3 9.7,28.0 2.0,28.0zM2.0,20.0l0.0,4.0c9.9,0.0 18.0,8.1 18.0,18.0l4.0,0.0C24.0,29.8 14.1,20.0 2.0,20.0z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_minus.xml b/packages/SystemUI/res/drawable/ic_qs_minus.xml index 6a3410af2950..147a94b8a794 100644 --- a/packages/SystemUI/res/drawable/ic_qs_minus.xml +++ b/packages/SystemUI/res/drawable/ic_qs_minus.xml @@ -17,7 +17,8 @@ android:height="24.0dp" android:viewportHeight="48.0" android:viewportWidth="48.0" - android:width="24.0dp" > + android:width="24.0dp" + android:tint="?android:attr/colorControlNormal" > <path android:fillColor="#FFFFFFFF" diff --git a/packages/SystemUI/res/drawable/ic_qs_network_logging.xml b/packages/SystemUI/res/drawable/ic_qs_network_logging.xml index 1340ae161729..87b5a14153f7 100644 --- a/packages/SystemUI/res/drawable/ic_qs_network_logging.xml +++ b/packages/SystemUI/res/drawable/ic_qs_network_logging.xml @@ -24,6 +24,6 @@ Copyright (C) 2016 The Android Open Source Project android:tint="#4DFFFFFF" > <path android:fillColor="#FFFFFFFF" - android:pathData="M7,18v-2h6v2H7z M7,14v-2h10v2H7z M8.5,9 12,5.5 15.5,9 13,9 13,13 11,13 11,9z"/> + android:pathData="M2,24v-4h12v4H2z M2,16v-4h20v4H2z M5,7 12,0 19,7 14,7 14,15 10,15 10,7z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_plus.xml b/packages/SystemUI/res/drawable/ic_qs_plus.xml index 393f51c00781..1f254d1ee86b 100644 --- a/packages/SystemUI/res/drawable/ic_qs_plus.xml +++ b/packages/SystemUI/res/drawable/ic_qs_plus.xml @@ -17,7 +17,8 @@ android:height="24.0dp" android:viewportHeight="48.0" android:viewportWidth="48.0" - android:width="24.0dp" > + android:width="24.0dp" + android:tint="?android:attr/colorControlNormal" > <path android:fillColor="#FFFFFFFF" diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml index a7889ab7786a..7993c801e9a2 100644 --- a/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml +++ b/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml @@ -18,9 +18,10 @@ Copyright (C) 2014 The Android Open Source Project android:height="56dp" android:viewportWidth="48.0" android:viewportHeight="48.0" - android:tint="?android:attr/colorControlNormal"> + android:alpha="0.14" + android:tint="?android:attr/colorForeground"> <path android:pathData="M24.0,4.0C15.0,4.0 6.7,7.0 0.0,12.0l24.0,32.0l24.0,-32.0C41.3,7.0 33.0,4.0 24.0,4.0z" - android:fillColor="@color/qs_detail_empty" /> + android:fillColor="#FFFFFF" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_settings_20dp.xml b/packages/SystemUI/res/drawable/ic_settings_20dp.xml index 3170f86811ed..45ee94f55e8a 100644 --- a/packages/SystemUI/res/drawable/ic_settings_20dp.xml +++ b/packages/SystemUI/res/drawable/ic_settings_20dp.xml @@ -17,7 +17,8 @@ android:width="20dp" android:height="20dp" android:viewportWidth="24.0" - android:viewportHeight="24.0"> + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal"> <path android:pathData="M19.4,13.0c0.0,-0.3 0.1,-0.6 0.1,-1.0s0.0,-0.7 -0.1,-1.0l2.1,-1.7c0.2,-0.2 0.2,-0.4 0.1,-0.6l-2.0,-3.5C19.5,5.1 19.3,5.0 19.0,5.1l-2.5,1.0c-0.5,-0.4 -1.1,-0.7 -1.7,-1.0l-0.4,-2.6C14.5,2.2 14.2,2.0 14.0,2.0l-4.0,0.0C9.8,2.0 9.5,2.2 9.5,2.4L9.1,5.1C8.5,5.3 8.0,5.7 7.4,6.1L5.0,5.1C4.7,5.0 4.5,5.1 4.3,5.3l-2.0,3.5C2.2,8.9 2.3,9.2 2.5,9.4L4.6,11.0c0.0,0.3 -0.1,0.6 -0.1,1.0s0.0,0.7 0.1,1.0l-2.1,1.7c-0.2,0.2 -0.2,0.4 -0.1,0.6l2.0,3.5C4.5,18.9 4.7,19.0 5.0,18.9l2.5,-1.0c0.5,0.4 1.1,0.7 1.7,1.0l0.4,2.6c0.0,0.2 0.2,0.4 0.5,0.4l4.0,0.0c0.2,0.0 0.5,-0.2 0.5,-0.4l0.4,-2.6c0.6,-0.3 1.2,-0.6 1.7,-1.0l2.5,1.0c0.2,0.1 0.5,0.0 0.6,-0.2l2.0,-3.5c0.1,-0.2 0.1,-0.5 -0.1,-0.6L19.4,13.0zM12.0,15.5c-1.9,0.0 -3.5,-1.6 -3.5,-3.5s1.6,-3.5 3.5,-3.5s3.5,1.6 3.5,3.5S13.9,15.5 12.0,15.5z" android:fillColor="#ffffffff" /> diff --git a/packages/SystemUI/res/drawable/major_a_b.xml b/packages/SystemUI/res/drawable/major_a_b.xml index 99000483b852..45386f95670c 100644 --- a/packages/SystemUI/res/drawable/major_a_b.xml +++ b/packages/SystemUI/res/drawable/major_a_b.xml @@ -20,7 +20,8 @@ android:width="16dp" android:viewportWidth="16" android:height="8dp" - android:viewportHeight="8" > + android:viewportHeight="8" + android:tint="?android:attr/colorControlNormal"> <group android:name="dot_01" android:translateX="3.25" diff --git a/packages/SystemUI/res/drawable/major_b_a.xml b/packages/SystemUI/res/drawable/major_b_a.xml index 3115887167fb..4fcff0cd351a 100644 --- a/packages/SystemUI/res/drawable/major_b_a.xml +++ b/packages/SystemUI/res/drawable/major_b_a.xml @@ -20,7 +20,8 @@ android:width="16dp" android:viewportWidth="16" android:height="8dp" - android:viewportHeight="8" > + android:viewportHeight="8" + android:tint="?android:attr/colorControlNormal" > <group android:name="dot_01" android:translateX="8" diff --git a/packages/SystemUI/res/drawable/major_b_c.xml b/packages/SystemUI/res/drawable/major_b_c.xml index 899109e60b54..c03a8d571283 100644 --- a/packages/SystemUI/res/drawable/major_b_c.xml +++ b/packages/SystemUI/res/drawable/major_b_c.xml @@ -20,7 +20,8 @@ android:width="16dp" android:viewportWidth="16" android:height="8dp" - android:viewportHeight="8" > + android:viewportHeight="8" + android:tint="?android:attr/colorControlNormal" > <group android:name="dot_01" android:translateX="8" diff --git a/packages/SystemUI/res/drawable/major_c_b.xml b/packages/SystemUI/res/drawable/major_c_b.xml index cc6c6152fdbf..9e4e24575ca1 100644 --- a/packages/SystemUI/res/drawable/major_c_b.xml +++ b/packages/SystemUI/res/drawable/major_c_b.xml @@ -20,7 +20,8 @@ android:width="16dp" android:viewportWidth="16" android:height="8dp" - android:viewportHeight="8" > + android:viewportHeight="8" + android:tint="?android:attr/colorControlNormal" > <group android:name="dot_01" android:translateX="12.75" diff --git a/packages/SystemUI/res/drawable/minor_a_b.xml b/packages/SystemUI/res/drawable/minor_a_b.xml index c5f5c98bfcdf..0622aacd134e 100644 --- a/packages/SystemUI/res/drawable/minor_a_b.xml +++ b/packages/SystemUI/res/drawable/minor_a_b.xml @@ -21,7 +21,8 @@ android:width="16dp" android:viewportWidth="16" android:height="8dp" - android:viewportHeight="8" > + android:viewportHeight="8" + android:tint="?android:attr/colorControlNormal" > <group android:name="dot_02" android:translateX="3.25" diff --git a/packages/SystemUI/res/drawable/minor_b_a.xml b/packages/SystemUI/res/drawable/minor_b_a.xml index 3bb08c47505b..ecb4341d915c 100644 --- a/packages/SystemUI/res/drawable/minor_b_a.xml +++ b/packages/SystemUI/res/drawable/minor_b_a.xml @@ -21,7 +21,8 @@ android:width="16dp" android:viewportWidth="16" android:height="8dp" - android:viewportHeight="8" > + android:viewportHeight="8" + android:tint="?android:attr/colorControlNormal" > <group android:name="dot_02" android:translateX="8" diff --git a/packages/SystemUI/res/drawable/minor_b_c.xml b/packages/SystemUI/res/drawable/minor_b_c.xml index 95c6463753d5..7f59e340f0cf 100644 --- a/packages/SystemUI/res/drawable/minor_b_c.xml +++ b/packages/SystemUI/res/drawable/minor_b_c.xml @@ -21,7 +21,8 @@ android:width="16dp" android:viewportWidth="16" android:height="8dp" - android:viewportHeight="8" > + android:viewportHeight="8" + android:tint="?android:attr/colorControlNormal" > <group android:name="dot_02" android:translateX="8" diff --git a/packages/SystemUI/res/drawable/minor_c_b.xml b/packages/SystemUI/res/drawable/minor_c_b.xml index 523afaa2e5b9..97309bcde701 100644 --- a/packages/SystemUI/res/drawable/minor_c_b.xml +++ b/packages/SystemUI/res/drawable/minor_c_b.xml @@ -21,7 +21,8 @@ android:width="16dp" android:viewportWidth="16" android:height="8dp" - android:viewportHeight="8" > + android:viewportHeight="8" + android:tint="?android:attr/colorControlNormal" > <group android:name="dot_02" android:translateX="12.75" diff --git a/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml b/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml index a726161c2c66..efdfae75c1ff 100644 --- a/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml +++ b/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml @@ -67,7 +67,7 @@ android:paddingTop="3dp" android:drawablePadding="8dp" android:drawableStart="@drawable/ic_access_alarms_small" - android:textColor="#64ffffff" + android:textColor="?android:attr/textColorSecondary" android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date" android:gravity="top" android:background="?android:attr/selectableItemBackground" diff --git a/packages/SystemUI/res/layout/battery_detail.xml b/packages/SystemUI/res/layout/battery_detail.xml index 8abfcf6057aa..6162d650f768 100644 --- a/packages/SystemUI/res/layout/battery_detail.xml +++ b/packages/SystemUI/res/layout/battery_detail.xml @@ -41,7 +41,7 @@ android:layout_marginEnd="24dp" systemui:sideLabels="@array/battery_labels" android:colorAccent="?android:attr/colorAccent" - systemui:textColor="#66FFFFFF" /> + systemui:textColor="?android:attr/textColorSecondary" /> <com.android.systemui.ResizingSpace android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/pip_menu_action.xml b/packages/SystemUI/res/layout/pip_menu_action.xml new file mode 100644 index 000000000000..db6ae19a9c34 --- /dev/null +++ b/packages/SystemUI/res/layout/pip_menu_action.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:background="?android:selectableItemBackground"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:orientation="vertical"> + <ImageView + android:id="@+id/icon" + android:layout_width="@dimen/pip_menu_action_icon_size" + android:layout_height="@dimen/pip_menu_action_icon_size" + android:layout_gravity="center_horizontal" + android:layout_marginBottom="4dp" /> + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:includeFontPadding="false" + android:gravity="center" + android:textSize="12sp" + android:textColor="#ffffffff" + android:textAllCaps="true" + android:fontFamily="sans-serif" /> + </LinearLayout> +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml index 88e6e725c976..054bfabb2f84 100644 --- a/packages/SystemUI/res/layout/pip_menu_activity.xml +++ b/packages/SystemUI/res/layout/pip_menu_activity.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- 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. @@ -16,21 +16,44 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/menu" 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:id="@+id/actions" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginBottom="48dp"> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="48dp" + android:layout_gravity="bottom" + android:orientation="horizontal"> + <TextView + android:id="@+id/dismiss" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:gravity="center" + android:textSize="12sp" + android:textColor="#ffffffff" + android:fontFamily="sans-serif" + android:text="@string/pip_phone_dismiss" + android:background="?android:selectableItemBackground" /> + <TextView + android:id="@+id/minimize" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:gravity="center" + android:textSize="12sp" android:textColor="#ffffffff" - android:text="@string/pip_phone_expand" - android:fontFamily="sans-serif" /> + android:fontFamily="sans-serif" + android:text="@string/pip_phone_minimize" + android:background="?android:selectableItemBackground" /> </LinearLayout> </FrameLayout> diff --git a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml new file mode 100644 index 000000000000..2ba04fd31c0f --- /dev/null +++ b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/scrollView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipToPadding="false"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="?android:attr/dialogPreferredPadding" + android:paddingRight="?android:attr/dialogPreferredPadding" + android:paddingLeft="?android:attr/dialogPreferredPadding" + android:paddingBottom="?android:attr/dialogPreferredPadding" + android:orientation="vertical"> + <TextView + android:id="@+id/device_owner_warning" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@android:style/TextAppearance.Material.Subhead" + android:textColor="?android:attr/textColorPrimaryInverse" + /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <ImageView + android:id="@+id/vpn_icon" + android:layout_width="@dimen/qs_footer_dialog_icon_size" + android:layout_height="wrap_content" + android:paddingTop="?android:attr/dialogPreferredPadding" + android:layout_marginStart="@dimen/qs_footer_dialog_icon_margin" + android:layout_marginEnd="@dimen/qs_footer_dialog_icon_margin" + android:scaleType="fitCenter" + android:src="@drawable/ic_qs_vpn" + android:tint="?android:attr/textColorPrimaryInverse" + android:adjustViewBounds="true"/> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + <TextView + android:id="@+id/vpn_subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="?android:attr/dialogPreferredPadding" + android:text="@string/monitoring_subtitle_vpn" + style="@android:style/TextAppearance.Material.Title" + android:textColor="?android:attr/textColorPrimaryInverse" + /> + <TextView + android:id="@+id/vpn_warning" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@null" + style="@android:style/TextAppearance.Material.Subhead" + android:textColor="?android:attr/textColorPrimaryInverse" + /> + </LinearLayout> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <ImageView + android:id="@+id/network_logging_icon" + android:layout_width="@dimen/qs_footer_dialog_icon_size" + android:layout_height="wrap_content" + android:paddingTop="?android:attr/dialogPreferredPadding" + android:layout_marginStart="@dimen/qs_footer_dialog_icon_margin" + android:layout_marginEnd="@dimen/qs_footer_dialog_icon_margin" + android:scaleType="fitCenter" + android:src="@drawable/ic_qs_network_logging" + android:tint="?android:attr/textColorPrimaryInverse" + android:alpha="@dimen/qs_footer_dialog_network_logging_icon_alpha" + android:adjustViewBounds="true"/> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + <TextView + android:id="@+id/network_logging_subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="?android:attr/dialogPreferredPadding" + android:text="@string/monitoring_subtitle_network_logging" + style="@android:style/TextAppearance.Material.Title" + android:textColor="?android:attr/textColorPrimaryInverse" + /> + <TextView + android:id="@+id/network_logging_warning" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/monitoring_description_network_logging" + style="@android:style/TextAppearance.Material.Subhead" + android:textColor="?android:attr/textColorPrimaryInverse" + /> + </LinearLayout> + </LinearLayout> + </LinearLayout> +</ScrollView> diff --git a/packages/SystemUI/res/layout/recents_grid_task_view.xml b/packages/SystemUI/res/layout/recents_grid_task_view.xml new file mode 100644 index 000000000000..53bec70bd096 --- /dev/null +++ b/packages/SystemUI/res/layout/recents_grid_task_view.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<com.android.systemui.recents.views.grid.GridTaskView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:focusable="true"> + <com.android.systemui.recents.views.TaskViewThumbnail + android:id="@+id/task_view_thumbnail" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <include layout="@layout/recents_task_view_header" /> + + <!-- TODO: Move this into a view stub --> + <include layout="@layout/recents_task_view_lock_to_app"/> + + <!-- The incompatible app toast --> + <include layout="@layout/recents_task_view_incompatible_app_toast"/> +</com.android.systemui.recents.views.grid.GridTaskView> + + diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml index c8e5b6124196..015e4a2006bb 100644 --- a/packages/SystemUI/res/layout/recents_task_view.xml +++ b/packages/SystemUI/res/layout/recents_task_view.xml @@ -4,9 +4,9 @@ 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. @@ -26,35 +26,10 @@ <include layout="@layout/recents_task_view_header" /> <!-- TODO: Move this into a view stub --> - <com.android.systemui.statusbar.AlphaOptimizedFrameLayout - android:id="@+id/lock_to_app_fab" - android:layout_width="@dimen/recents_lock_to_app_size" - android:layout_height="@dimen/recents_lock_to_app_size" - android:layout_gravity="bottom|end" - android:layout_marginEnd="15dp" - android:layout_marginBottom="15dp" - android:translationZ="4dp" - android:contentDescription="@string/recents_lock_to_app_button_label" - android:background="@drawable/recents_lock_to_task_button_bg" - android:visibility="invisible" - android:alpha="0"> - <ImageView - android:layout_width="@dimen/recents_lock_to_app_icon_size" - android:layout_height="@dimen/recents_lock_to_app_icon_size" - android:layout_gravity="center" - android:src="@drawable/recents_lock_to_app_pin" /> - </com.android.systemui.statusbar.AlphaOptimizedFrameLayout> + <include layout="@layout/recents_task_view_lock_to_app"/> <!-- The incompatible app toast --> - <ViewStub android:id="@+id/incompatible_app_toast_stub" - android:inflatedId="@+id/incompatible_app_toast" - android:layout="@*android:layout/transient_notification" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="top|center_horizontal" - android:layout_marginTop="48dp" - android:layout_marginLeft="16dp" - android:layout_marginRight="16dp" /> + <include layout="@layout/recents_task_view_incompatible_app_toast"/> </com.android.systemui.recents.views.TaskView> diff --git a/packages/SystemUI/res/layout-sw600dp/recents_grid.xml b/packages/SystemUI/res/layout/recents_task_view_incompatible_app_toast.xml index 1a054a534725..d573d6b881d2 100644 --- a/packages/SystemUI/res/layout-sw600dp/recents_grid.xml +++ b/packages/SystemUI/res/layout/recents_task_view_incompatible_app_toast.xml @@ -13,11 +13,14 @@ 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:id="@+id/recents_view" - android:gravity="center" - android:background="#99000000"> - <include layout="@layout/recents_stack_action_button" /> -</FrameLayout>
\ No newline at end of file +<ViewStub + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/incompatible_app_toast_stub" + android:inflatedId="@+id/incompatible_app_toast" + android:layout="@*android:layout/transient_notification" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|center_horizontal" + android:layout_marginTop="48dp" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" />
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/recents_task_view_lock_to_app.xml b/packages/SystemUI/res/layout/recents_task_view_lock_to_app.xml new file mode 100644 index 000000000000..8cece1149ce7 --- /dev/null +++ b/packages/SystemUI/res/layout/recents_task_view_lock_to_app.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<com.android.systemui.statusbar.AlphaOptimizedFrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/lock_to_app_fab" + android:layout_width="@dimen/recents_lock_to_app_size" + android:layout_height="@dimen/recents_lock_to_app_size" + android:layout_gravity="bottom|end" + android:layout_marginEnd="15dp" + android:layout_marginBottom="15dp" + android:translationZ="4dp" + android:contentDescription="@string/recents_lock_to_app_button_label" + android:background="@drawable/recents_lock_to_task_button_bg" + android:visibility="invisible" + android:alpha="0"> + <ImageView + android:layout_width="@dimen/recents_lock_to_app_icon_size" + android:layout_height="@dimen/recents_lock_to_app_icon_size" + android:layout_gravity="center" + android:src="@drawable/recents_lock_to_app_pin" /> +</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar_alarm_group.xml b/packages/SystemUI/res/layout/status_bar_alarm_group.xml index 701b621c469b..03585b854898 100644 --- a/packages/SystemUI/res/layout/status_bar_alarm_group.xml +++ b/packages/SystemUI/res/layout/status_bar_alarm_group.xml @@ -65,7 +65,7 @@ android:paddingTop="3dp" android:drawablePadding="8dp" android:drawableStart="@drawable/ic_access_alarms_small" - android:textColor="#64ffffff" + android:textColor="?android:attr/textColorSecondary" android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date" android:gravity="top" android:background="?android:attr/selectableItemBackground" diff --git a/packages/SystemUI/res/layout/status_bar_notification_shelf.xml b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml index 7bfbd3c542e1..6db16fec5428 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_shelf.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml @@ -19,6 +19,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="@dimen/notification_shelf_height" + android:contentDescription="@string/notification_shelf_content_description" android:focusable="true" android:clickable="true" > diff --git a/packages/SystemUI/res/values-sw900dp/config.xml b/packages/SystemUI/res/values-sw900dp/config.xml new file mode 100644 index 000000000000..182fa3661a30 --- /dev/null +++ b/packages/SystemUI/res/values-sw900dp/config.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 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. +*/ +--> +<resources> + + <!-- Nav bar button default ordering/layout --> + <string name="config_navBarLayout" translatable="false">space[.2],back,home;space;menu_ime,recent,space[.2]</string> + +</resources> diff --git a/packages/SystemUI/res/values-sw900dp/dimens.xml b/packages/SystemUI/res/values-sw900dp/dimens.xml new file mode 100644 index 000000000000..72e10c26ddd4 --- /dev/null +++ b/packages/SystemUI/res/values-sw900dp/dimens.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. +*/ +--> +<resources> + + <!-- All ryu nav buttons are th same size --> + <dimen name="button_size">80dp</dimen> + <dimen name="navigation_side_padding">@dimen/button_size</dimen> + <dimen name="navigation_key_width">@dimen/button_size</dimen> + <dimen name="navigation_extra_key_width">@dimen/button_size</dimen> + + <!-- The maximum width of the navigation bar ripples. --> + <dimen name="key_button_ripple_max_width">76dp</dimen> + + <!-- The padding around the navigation buttons --> + <dimen name="navigation_key_padding">5dp</dimen> + + <!-- The inner radius of the halo. --> + <dimen name="halo_inner_radius">12dp</dimen> + + <!-- The thickness of the halo. --> + <dimen name="halo_thickness">1dp</dimen> + + <!-- The diameter of the halo. This is 2*(halo_inner_radius + halo_thickness). --> + <dimen name="halo_diameter">26dp</dimen> +</resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a7d4aa094cdd..ff4ec5bdaf3f 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -258,6 +258,13 @@ <!-- How far the expanded QS panel peeks from the header in collapsed state. --> <dimen name="qs_peek_height">0dp</dimen> + <!-- How large the icons in the quick settings footer dialog are --> + <dimen name="qs_footer_dialog_icon_size">24sp</dimen> + <!-- Left and right margin of the icons --> + <dimen name="qs_footer_dialog_icon_margin">8sp</dimen> + <!-- Alpha value of network logging icon --> + <dimen name="qs_footer_dialog_network_logging_icon_alpha">0.3</dimen> + <!-- Zen mode panel: condition item button padding --> <dimen name="zen_mode_condition_detail_button_padding">8dp</dimen> @@ -717,18 +724,7 @@ <!-- The size of the PIP dismiss target. --> <dimen name="pip_dismiss_target_size">48dp</dimen> - <!-- Values specific to grid-based recents. --> - <!-- Margins around recent tasks. --> - <dimen name="recents_grid_margin_left">15dp</dimen> - <dimen name="recents_grid_margin_top">70dp</dimen> - <dimen name="recents_grid_margin_right">15dp</dimen> - <dimen name="recents_grid_margin_bottom">90dp</dimen> - <!-- Margins around the "Clear all" button. --> - <dimen name="recents_grid_clear_all_margin_left">0dp</dimen> - <dimen name="recents_grid_clear_all_margin_top">30dp</dimen> - <dimen name="recents_grid_clear_all_margin_right">15dp</dimen> - <dimen name="recents_grid_clear_all_margin_bottom">0dp</dimen> - <!-- Padding in between task views. --> - <dimen name="recents_grid_inter_task_padding">15dp</dimen> + <!-- The size of a PIP menu action icon. --> + <dimen name="pip_menu_action_icon_size">32dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/dimens_grid.xml b/packages/SystemUI/res/values/dimens_grid.xml new file mode 100644 index 000000000000..94ffdb18a80d --- /dev/null +++ b/packages/SystemUI/res/values/dimens_grid.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. +*/ +--> +<resources> + <dimen name="recents_grid_padding_left_right">32dp</dimen> + <dimen name="recents_grid_padding_top_bottom">150dp</dimen> + <dimen name="recents_grid_padding_task_view">20dp</dimen> + <dimen name="recents_grid_task_view_header_height">44dp</dimen> + <dimen name="recents_grid_task_view_header_button_padding">8dp</dimen> +</resources> + diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c8b3b69dce37..645ad6f48d91 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -408,6 +408,9 @@ <!-- Content description of the button for showing a notifications panel in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_notifications_button">Notifications.</string> + <!-- Content description of overflow icon container of the notifications for accessibility (not shown on the screen)[CHAR LIMIT=NONE] --> + <string name="notification_shelf_content_description">Notification overflow container</string> + <!-- Content description of the button for removing a notification in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_remove_notification">Clear notification.</string> @@ -1010,6 +1013,13 @@ <!-- Monitoring dialog title for normal devices [CHAR LIMIT=35]--> <string name="monitoring_title">Network monitoring</string> + <!-- STOPSHIP Monitoring strings still need to be finalized and approved --> + <!-- Monitoring dialog subtitle for the section describing VPN [CHAR LIMIT=TODO]--> + <string name="monitoring_subtitle_vpn">VPN</string> + + <!-- Monitoring dialog subtitle for the section describing network logging [CHAR LIMIT=TODO]--> + <string name="monitoring_subtitle_network_logging">Network Logging</string> + <!-- Monitoring dialog disable vpn button [CHAR LIMIT=30] --> <string name="disable_vpn">Disable VPN</string> @@ -1025,15 +1035,25 @@ <!-- Monitoring dialog: Part of text body explaining what a Device Owner app can do [CHAR LIMIT=130] --> <string name="monitoring_description_do_body">Your administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.</string> - <!-- Monitoring dialog: Part of text body explaining that a VPN is connected and what it can do, for devices managed by a Device Owner app [CHAR LIMIT=130] --> - <string name="monitoring_description_do_body_vpn">You\'re connected to <xliff:g id="vpn_app">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string> - <!-- Monitoring dialog: Space that separates the body text and the "learn more" link that follows it. [CHAR LIMIT=5] --> <string name="monitoring_description_do_learn_more_separator">" "</string> <!-- Monitoring dialog: Link to learn more about what a Device Owner app can do [CHAR LIMIT=55] --> <string name="monitoring_description_do_learn_more">Learn more</string> + <!-- Monitoring dialog: Part of text body explaining that a VPN is connected and what it can do, for devices managed by a Device Owner app [CHAR LIMIT=130] --> + <string name="monitoring_description_do_body_vpn">You\'re connected to <xliff:g id="vpn_app">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string> + + <!-- Monitoring dialog: Space that separates the VPN body text and the "Open VPN Settings" link that follows it. [CHAR LIMIT=5] --> + <string name="monitoring_description_vpn_settings_separator">" "</string> + + <!-- Monitoring dialog: Link to open the VPN settings page [CHAR LIMIT=TODO] --> + <string name="monitoring_description_vpn_settings">Open VPN Settings</string> + + <!-- Monitoring dialog: Network logging text [CHAR LIMIT=TODO] --> + <string name="monitoring_description_network_logging">Your admin has turned on network logging, which monitors traffic on your device.\n\nFor more information contact your admin.</string> + + <!-- Monitoring dialog VPN text [CHAR LIMIT=400] --> <string name="monitoring_description_vpn">You gave an app permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps, and websites.</string> @@ -1121,19 +1141,18 @@ <!-- Toast shown when user unlocks screen and managed profile activity is in the foreground --> <string name="managed_profile_foreground_toast">You\'re using your work profile</string> - <string-array name="volume_stream_titles"> - <item>Call</item> <!-- STREAM_VOICE_CALL --> - <item>System</item> <!-- STREAM_SYSTEM --> - <item>Ring</item> <!-- STREAM_RING --> - <item>Media</item> <!-- STREAM_MUSIC --> - <item>Alarm</item> <!-- STREAM_ALARM --> - <item></item> <!-- STREAM_NOTIFICATION --> - <item>Bluetooth</item> <!-- STREAM_BLUETOOTH_SCO --> - <item></item> <!-- STREAM_SYSTEM_ENFORCED --> - <item></item> <!-- STREAM_DTMF --> - <item></item> <!-- STREAM_TTS --> - <item>Accessibility</item> <!-- STREAM_ACCESSIBILITY --> - </string-array> + <!-- volume stream names. All nouns. --> + <string name="stream_voice_call">Call</string> <!-- STREAM_VOICE_CALL --> + <string name="stream_system">System</string> <!-- STREAM_SYSTEM --> + <string name="stream_ring">Ring</string> <!-- STREAM_RING --> + <string name="stream_music">Media</string> <!-- STREAM_MUSIC --> + <string name="stream_alarm">Alarm</string> <!-- STREAM_ALARM --> + <string name="stream_notification">Notification</string> <!-- STREAM_NOTIFICATION --> + <string name="stream_bluetooth_sco">Bluetooth</string> <!-- STREAM_BLUETOOTH_SCO --> + <string name="stream_system_enforced">System enforced</string> <!-- STREAM_SYSTEM_ENFORCED --> + <string name="stream_dtmf">Dual multi tone frequency</string> <!-- STREAM_DTMF --> + <string name="stream_tts">Text to speech</string> <!-- STREAM_TTS --> + <string name="stream_accessibility">Accessibility</string> <!-- STREAM_ACCESSIBILITY --> <string name="volume_stream_muted" translatable="false">%s silent</string> <string name="volume_stream_vibrate" translatable="false">%s vibrate</string> @@ -1688,9 +1707,15 @@ not appear on production builds ever. --> <string name="tuner_doze_always_on" translatable="false">Always on</string> - <!-- Making the PIP fullscreen --> + <!-- Making the PIP fullscreen [CHAR LIMIT=25] --> <string name="pip_phone_expand">Expand</string> + <!-- Label for PIP action to Minimize the PIP [CHAR LIMIT=25] --> + <string name="pip_phone_minimize">Minimize</string> + + <!-- Label for PIP action to Dismiss the PIP --> + <string name="pip_phone_dismiss">Dismiss</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> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 08b8988c79a4..93b696591c1f 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -44,22 +44,6 @@ <item name="android:layout_marginBottom">0dp</item> </style> - <!-- Grid-based Recents theme. --> - <style name="RecentsTheme.Grid"> - <item name="android:windowBackground">@color/transparent</item> - <item name="android:colorBackgroundCacheHint">@null</item> - <item name="android:windowShowWallpaper">true</item> - <item name="android:windowDisablePreview">true</item> - <item name="clearAllStyle">@style/ClearAllButtonLargeMargins</item> - </style> - - <style name="ClearAllButtonLargeMargins"> - <item name="android:layout_marginStart">@dimen/recents_grid_clear_all_margin_left</item> - <item name="android:layout_marginTop">@dimen/recents_grid_clear_all_margin_top</item> - <item name="android:layout_marginEnd">@dimen/recents_grid_clear_all_margin_right</item> - <item name="android:layout_marginBottom">@dimen/recents_grid_clear_all_margin_bottom</item> - </style> - <!-- Performance optimized Recents theme (no wallpaper) --> <style name="RecentsTheme.NoWallpaper"> <item name="android:windowBackground">@android:color/black</item> diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index 9eceeacc3968..2576bb717690 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -13,6 +13,7 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.PixelFormat; import android.os.AsyncTask; +import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -31,6 +32,7 @@ import android.widget.ImageView; import com.android.internal.app.AssistUtils; import com.android.internal.app.IVoiceInteractionSessionListener; import com.android.internal.app.IVoiceInteractionSessionShowCallback; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.CommandQueue; @@ -155,6 +157,7 @@ public class AssistManager { | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); + lp.token = new Binder(); if (ActivityManager.isHighEndGfx()) { lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } @@ -282,7 +285,7 @@ public class AssistManager { @Nullable private ComponentName getAssistInfo() { - return mAssistUtils.getAssistComponentForUser(UserHandle.USER_CURRENT); + return mAssistUtils.getAssistComponentForUser(KeyguardUpdateMonitor.getCurrentUser()); } public void showDisclosure() { diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java index 1338d9d54793..71ddba5ffdcc 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingLog.java @@ -84,6 +84,11 @@ public class FalsingLog { log("I", tag, s); } + public static void wLogcat(String tag, String s) { + Log.w(TAG, tag + "\t" + s); + log("W", tag, s); + } + public static void w(String tag, String s) { if (LOGCAT) { Log.w(TAG, tag + "\t" + s); @@ -133,7 +138,7 @@ public class FalsingLog { pw.println(); } - public static synchronized void wtf(String tag, String s) { + public static synchronized void wtf(String tag, String s, Throwable here) { if (!ENABLED) { return; } @@ -161,6 +166,6 @@ public class FalsingLog { Log.e(TAG, "Unable to write log, build must be debuggable."); } - Log.wtf(TAG, tag + " " + s + "; " + fileMessage); + Log.wtf(TAG, tag + " " + s + "; " + fileMessage, here); } } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java index 8fc555f14c8a..1abea37519e6 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java @@ -71,6 +71,7 @@ public class FalsingManager implements SensorEventListener { private boolean mSessionActive = false; private int mState = StatusBarState.SHADE; private boolean mScreenOn; + private Runnable mPendingWtf; protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { @Override @@ -136,6 +137,7 @@ public class FalsingManager implements SensorEventListener { private void onSessionStart() { if (FalsingLog.ENABLED) { FalsingLog.i("onSessionStart", "classifierEnabled=" + isClassiferEnabled()); + clearPendingWtf(); } mBouncerOn = false; mSessionActive = true; @@ -172,13 +174,35 @@ public class FalsingManager implements SensorEventListener { if (FalsingLog.ENABLED) { // We're getting some false wtfs from touches that happen after the device went // to sleep. Only report missing sessions that happen when the device is interactive. - if (!mSessionActive && mContext.getSystemService(PowerManager.class).isInteractive()) { - FalsingLog.wtf("isFalseTouch", new StringBuilder() + if (!mSessionActive && mContext.getSystemService(PowerManager.class).isInteractive() + && mPendingWtf == null) { + int enabled = isEnabled() ? 1 : 0; + int screenOn = mScreenOn ? 1 : 0; + String state = StatusBarState.toShortString(mState); + Throwable here = new Throwable("here"); + FalsingLog.wLogcat("isFalseTouch", new StringBuilder() .append("Session is not active, yet there's a query for a false touch.") - .append(" enabled=").append(isEnabled() ? 1 : 0) - .append(" mScreenOn=").append(mScreenOn ? 1 : 0) - .append(" mState=").append(StatusBarState.toShortString(mState)) + .append(" enabled=").append(enabled) + .append(" mScreenOn=").append(screenOn) + .append(" mState=").append(state) + .append(". Escalating to WTF if screen does not turn on soon.") .toString()); + + // Unfortunately we're also getting false positives for touches that happen right + // after the screen turns on, but before that notification has made it to us. + // Unfortunately there's no good way to catch that, except to wait and see if we get + // the screen on notification soon. + mPendingWtf = () -> FalsingLog.wtf("isFalseTouch", new StringBuilder() + .append("Session did not become active after query for a false touch.") + .append(" enabled=").append(enabled) + .append('/').append(isEnabled() ? 1 : 0) + .append(" mScreenOn=").append(screenOn) + .append('/').append(mScreenOn ? 1 : 0) + .append(" mState=").append(state) + .append('/').append(StatusBarState.toShortString(mState)) + .append(". Look for warnings ~1000ms earlier to see root cause.") + .toString(), here); + mHandler.postDelayed(mPendingWtf, 1000); } } if (mAccessibilityManager.isTouchExplorationEnabled()) { @@ -189,6 +213,13 @@ public class FalsingManager implements SensorEventListener { return mHumanInteractionClassifier.isFalseTouch(); } + private void clearPendingWtf() { + if (mPendingWtf != null) { + mHandler.removeCallbacks(mPendingWtf); + mPendingWtf = null; + } + } + @Override public synchronized void onSensorChanged(SensorEvent event) { mDataCollector.onSensorChanged(event); @@ -224,6 +255,7 @@ public class FalsingManager implements SensorEventListener { FalsingLog.i("onScreenTurningOn", new StringBuilder() .append("from=").append(mScreenOn ? 1 : 0) .toString()); + clearPendingWtf(); } mScreenOn = true; if (sessionEntrypoint()) { diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index 13e047cceb30..6a868d53d409 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -218,7 +218,8 @@ public class DozeMachine { Preconditions.checkState(mState == State.DOZE_REQUEST_PULSE); break; case DOZE_PULSE_DONE: - Preconditions.checkState(mState == State.DOZE_PULSING); + Preconditions.checkState( + mState == State.DOZE_REQUEST_PULSE || mState == State.DOZE_PULSING); break; default: break; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index c19e806bed51..d4da9a415237 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -17,6 +17,8 @@ package com.android.systemui.keyguard; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; + +import static com.android.internal.telephony.IccCardConstants.State.ABSENT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; @@ -429,7 +431,11 @@ public class KeyguardViewMediator extends SystemUI { resetStateLocked(); } } - onSimNotReadyLocked(); + if (simState == ABSENT) { + // MVNO SIMs can become transiently NOT_READY when switching networks, + // so we should only lock when they are ABSENT. + onSimAbsentLocked(); + } } break; case PIN_REQUIRED: @@ -456,7 +462,7 @@ public class KeyguardViewMediator extends SystemUI { + "show permanently disabled message in lockscreen."); resetStateLocked(); } - onSimNotReadyLocked(); + onSimAbsentLocked(); } break; case READY: @@ -470,22 +476,20 @@ public class KeyguardViewMediator extends SystemUI { default: if (DEBUG_SIM_STATES) Log.v(TAG, "Unspecific state: " + simState); synchronized (KeyguardViewMediator.this) { - onSimNotReadyLocked(); + onSimAbsentLocked(); } break; } } - private void onSimNotReadyLocked() { + private void onSimAbsentLocked() { if (isSecure() && mLockWhenSimRemoved) { mLockWhenSimRemoved = false; MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_LOCK_BECAUSE_SIM_REMOVED, mShowing); if (!mShowing) { - if (DEBUG_SIM_STATES) Log.d(TAG, "SIM removed, showing keyguard"); + Log.i(TAG, "SIM removed, showing keyguard"); doKeyguardLocked(null); - } else { - resetStateLocked(); } } } @@ -1228,7 +1232,7 @@ public class KeyguardViewMediator extends SystemUI { // if the setup wizard hasn't run yet, don't show final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", false); final boolean absent = SubscriptionManager.isValidSubscriptionId( - mUpdateMonitor.getNextSubIdForState(IccCardConstants.State.ABSENT)); + mUpdateMonitor.getNextSubIdForState(ABSENT)); final boolean disabled = SubscriptionManager.isValidSubscriptionId( mUpdateMonitor.getNextSubIdForState(IccCardConstants.State.PERM_DISABLED)); final boolean lockedOrMissing = mUpdateMonitor.isSimPinSecure() 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 43cfa3249a90..7e275d81031d 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -16,9 +16,17 @@ package com.android.systemui.pip.phone; +import static android.view.Display.DEFAULT_DISPLAY; + import android.app.ActivityManager; import android.app.IActivityManager; import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; +import android.view.IPinnedStackController; +import android.view.IPinnedStackListener; import android.view.IWindowManager; import android.view.WindowManagerGlobal; @@ -33,10 +41,52 @@ public class PipManager { private Context mContext; private IActivityManager mActivityManager; private IWindowManager mWindowManager; + private Handler mHandler = new Handler(); + + private final PinnedStackListener mPinnedStackListener = new PinnedStackListener(); private PipMenuActivityController mMenuController; private PipTouchHandler mTouchHandler; + /** + * Handler for messages from the PIP controller. + */ + private class PinnedStackListener extends IPinnedStackListener.Stub { + + @Override + public void onListenerRegistered(IPinnedStackController controller) { + mHandler.post(() -> { + mTouchHandler.setPinnedStackController(controller); + }); + } + + @Override + public void onBoundsChanged(boolean adjustedForIme) { + // Do nothing + } + + @Override + public void onActionsChanged(ParceledListSlice actions) { + mHandler.post(() -> { + mMenuController.setActions(actions); + }); + } + + @Override + public void onMinimizedStateChanged(boolean isMinimized) { + mHandler.post(() -> { + mTouchHandler.onMinimizedStateChanged(isMinimized); + }); + } + + @Override + public void onSnapToEdgeStateChanged(boolean isSnapToEdge) { + mHandler.post(() -> { + mTouchHandler.onSnapToEdgeStateChanged(isSnapToEdge); + }); + } + } + private PipManager() {} /** @@ -47,6 +97,12 @@ public class PipManager { mActivityManager = ActivityManager.getService(); mWindowManager = WindowManagerGlobal.getWindowManagerService(); + try { + mWindowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register pinned stack listener", e); + } + 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 index bfe5cff90a24..fe8ee6f6b27f 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -19,16 +19,27 @@ package com.android.systemui.pip.phone; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; +import android.app.PendingIntent.CanceledException; +import android.app.RemoteAction; import android.content.Intent; +import android.content.pm.ParceledListSlice; 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.LayoutInflater; import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + import com.android.systemui.R; +import java.util.ArrayList; +import java.util.List; + /** * Translucent activity that gets started on top of a task in PIP to allow the user to control it. */ @@ -36,16 +47,25 @@ public class PipMenuActivity extends Activity { private static final String TAG = "PipMenuActivity"; - public static final int MESSAGE_FINISH_SELF = 2; + public static final int MESSAGE_FINISH_SELF = 1; + public static final int MESSAGE_UPDATE_ACTIONS = 2; private static final long INITIAL_DISMISS_DELAY = 2000; private static final long POST_INTERACTION_DISMISS_DELAY = 1500; + private List<RemoteAction> mActions = new ArrayList<>(); + private View mDismissButton; + private View mMinimizeButton; + + private Handler mHandler = new Handler(); private Messenger mToControllerMessenger; private Messenger mMessenger = new Messenger(new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { + case MESSAGE_UPDATE_ACTIONS: + setActions(((ParceledListSlice) msg.obj).getList()); + break; case MESSAGE_FINISH_SELF: finish(); break; @@ -63,15 +83,27 @@ public class PipMenuActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setContentView(R.layout.pip_menu_activity); - Intent startingIntet = getIntent(); - mToControllerMessenger = startingIntet.getParcelableExtra( + Intent startingIntent = getIntent(); + mToControllerMessenger = startingIntent.getParcelableExtra( PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER); + ParceledListSlice actions = startingIntent.getParcelableExtra( + PipMenuActivityController.EXTRA_ACTIONS); + if (actions != null) { + setActions(actions.getList()); + } - setContentView(R.layout.pip_menu_activity); - findViewById(R.id.expand_pip).setOnClickListener((view) -> { - finish(); - notifyExpandPip(); + findViewById(R.id.menu).setOnClickListener((v) -> { + expandPip(); + }); + mDismissButton = findViewById(R.id.dismiss); + mDismissButton.setOnClickListener((v) -> { + dismissPip(); + }); + mMinimizeButton = findViewById(R.id.minimize); + mMinimizeButton.setOnClickListener((v) -> { + minimizePip(); }); } @@ -107,25 +139,74 @@ public class PipMenuActivity extends Activity { // Do nothing } + private void setActions(List<RemoteAction> actions) { + mActions.clear(); + mActions.addAll(actions); + updateActionViews(); + } + + private void updateActionViews() { + ViewGroup actionsContainer = (ViewGroup) findViewById(R.id.actions); + if (actionsContainer != null) { + actionsContainer.removeAllViews(); + + // Recreate the layout + final LayoutInflater inflater = LayoutInflater.from(this); + for (int i = 0; i < mActions.size(); i++) { + final RemoteAction action = mActions.get(i); + final ViewGroup actionContainer = (ViewGroup) inflater.inflate( + R.layout.pip_menu_action, actionsContainer, false); + actionContainer.setOnClickListener((v) -> { + action.sendActionInvoked(); + }); + + final TextView title = (TextView) actionContainer.findViewById(R.id.title); + title.setText(action.getTitle()); + title.setContentDescription(action.getContentDescription()); + + final ImageView icon = (ImageView) actionContainer.findViewById(R.id.icon); + action.getIcon().loadDrawableAsync(this, (d) -> { + icon.setImageDrawable(d); + }, mHandler); + actionsContainer.addView(actionContainer); + } + } + } + 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); - } + sendMessage(m, "Could not notify controller of PIP menu visibility"); + } + + private void expandPip() { + sendEmptyMessage(PipMenuActivityController.MESSAGE_EXPAND_PIP, + "Could not notify controller to expand PIP"); + } + + private void minimizePip() { + sendEmptyMessage(PipMenuActivityController.MESSAGE_MINIMIZE_PIP, + "Could not notify controller to minimize PIP"); } - private void notifyExpandPip() { + private void dismissPip() { + sendEmptyMessage(PipMenuActivityController.MESSAGE_DISMISS_PIP, + "Could not notify controller to dismiss PIP"); + } + + private void sendEmptyMessage(int what, String errorMsg) { Message m = Message.obtain(); - m.what = PipMenuActivityController.MESSAGE_EXPAND_PIP; + m.what = what; + sendMessage(m, errorMsg); + } + + private void sendMessage(Message m, String errorMsg) { try { mToControllerMessenger.send(m); } catch (RemoteException e) { - Log.e(TAG, "Could not notify controller to expand PIP", e); + Log.e(TAG, errorMsg, e); } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index d1bce0c74ccd..64e2d1a7ff07 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -1,14 +1,13 @@ 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.content.pm.ParceledListSlice; import android.os.Handler; import android.os.Message; import android.os.Messenger; @@ -24,8 +23,12 @@ 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; + public static final String EXTRA_ACTIONS = "actions"; + + public static final int MESSAGE_ACTIVITY_VISIBILITY_CHANGED = 100; + public static final int MESSAGE_EXPAND_PIP = 101; + public static final int MESSAGE_MINIMIZE_PIP = 102; + public static final int MESSAGE_DISMISS_PIP = 103; /** * A listener interface to receive notification on changes in PIP. @@ -35,12 +38,29 @@ public class PipMenuActivityController { * Called when the PIP menu visibility changes. */ void onPipMenuVisibilityChanged(boolean visible); + + /** + * Called when the PIP requested to be expanded. + */ + void onPipExpand(); + + /** + * Called when the PIP requested to be minimized. + */ + void onPipMinimize(); + + /** + * Called when the PIP requested to be expanded. + */ + void onPipDismiss(); } private Context mContext; private IActivityManager mActivityManager; private IWindowManager mWindowManager; + private ArrayList<Listener> mListeners = new ArrayList<>(); + private ParceledListSlice mActions; private Messenger mToActivityMessenger; private Messenger mMessenger = new Messenger(new Handler() { @@ -57,10 +77,23 @@ public class PipMenuActivityController { 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); + int listenerCount = mListeners.size(); + for (int i = 0; i < listenerCount; i++) { + mListeners.get(i).onPipExpand(); + } + break; + } + case MESSAGE_MINIMIZE_PIP: { + int listenerCount = mListeners.size(); + for (int i = 0; i < listenerCount; i++) { + mListeners.get(i).onPipMinimize(); + } + break; + } + case MESSAGE_DISMISS_PIP: { + int listenerCount = mListeners.size(); + for (int i = 0; i < listenerCount; i++) { + mListeners.get(i).onPipDismiss(); } break; } @@ -95,6 +128,7 @@ public class PipMenuActivityController { pinnedStackInfo.taskIds.length > 0) { Intent intent = new Intent(mContext, PipMenuActivity.class); intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger); + intent.putExtra(EXTRA_ACTIONS, mActions); ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchTaskId( pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]); @@ -123,4 +157,22 @@ public class PipMenuActivityController { mToActivityMessenger = null; } } + + /** + * Sets the {@param actions} associated with the PiP. + */ + public void setActions(ParceledListSlice actions) { + mActions = actions; + + if (mToActivityMessenger != null) { + Message m = Message.obtain(); + m.what = PipMenuActivity.MESSAGE_UPDATE_ACTIONS; + m.obj = actions; + try { + mToActivityMessenger.send(m); + } catch (RemoteException e) { + Log.e(TAG, "Could not notify menu activity to update actions", e); + } + } + } } 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 09671e7ac0e6..ff3cc7981bd9 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -17,7 +17,6 @@ package com.android.systemui.pip.phone; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.INPUT_CONSUMER_PIP; import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN; @@ -38,7 +37,6 @@ import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.view.IPinnedStackController; -import android.view.IPinnedStackListener; import android.view.IWindowManager; import android.view.InputChannel; import android.view.InputEvent; @@ -80,7 +78,6 @@ public class PipTouchHandler implements TunerService.Tunable { private final IActivityManager mActivityManager; private final IWindowManager mWindowManager; private final ViewConfiguration mViewConfig; - private final PinnedStackListener mPinnedStackListener = new PinnedStackListener(); private final PipMenuListener mMenuListener = new PipMenuListener(); private IPinnedStackController mPinnedStackController; @@ -149,26 +146,6 @@ public class PipTouchHandler implements TunerService.Tunable { } /** - * Handler for messages from the PIP controller. - */ - private class PinnedStackListener extends IPinnedStackListener.Stub { - - @Override - public void onListenerRegistered(IPinnedStackController controller) { - mPinnedStackController = controller; - - // Update the controller with the current tuner state - setMinimizedState(mIsMinimized); - setSnapToEdge(mEnableSnapToEdge); - } - - @Override - public void onBoundsChanged(boolean adjustedForIme) { - // Do nothing - } - } - - /** * A listener for the PIP menu activity. */ private class PipMenuListener implements PipMenuActivityController.Listener { @@ -181,17 +158,30 @@ public class PipTouchHandler implements TunerService.Tunable { unregisterInputConsumer(); } } + + @Override + public void onPipExpand() { + if (!mIsMinimized) { + expandPinnedStackToFullscreen(); + } + } + + @Override + public void onPipMinimize() { + setMinimizedState(true); + animateToClosestMinimizedTarget(); + } + + @Override + public void onPipDismiss() { + animateDismissPinnedStack(mPinnedStackBounds); + } } public PipTouchHandler(Context context, PipMenuActivityController menuController, IActivityManager activityManager, IWindowManager windowManager) { // Initialize the Pip input consumer - try { - windowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener); - } catch (RemoteException e) { - Log.e(TAG, "Failed to create PIP input consumer", e); - } mContext = context; mActivityManager = activityManager; mWindowManager = windowManager; @@ -255,6 +245,14 @@ public class PipTouchHandler implements TunerService.Tunable { updateBoundedPinnedStackBounds(false /* updatePinnedStackBounds */); } + public void onMinimizedStateChanged(boolean isMinimized) { + mIsMinimized = isMinimized; + } + + public void onSnapToEdgeStateChanged(boolean isSnapToEdge) { + mSnapAlgorithm.setSnapToEdge(isSnapToEdge); + } + private boolean handleTouchEvent(MotionEvent ev) { // Skip touch handling until we are bound to the controller if (mPinnedStackController == null) { @@ -353,10 +351,17 @@ public class PipTouchHandler implements TunerService.Tunable { } /** - * Sets the snap-to-edge state. + * Sets the controller to update the system of changes from user interaction. + */ + void setPinnedStackController(IPinnedStackController controller) { + mPinnedStackController = controller; + } + + /** + * Sets the snap-to-edge state and notifies the controller. */ private void setSnapToEdge(boolean snapToEdge) { - mSnapAlgorithm.setSnapToEdge(snapToEdge); + onSnapToEdgeStateChanged(snapToEdge); if (mPinnedStackController != null) { try { @@ -371,7 +376,7 @@ public class PipTouchHandler implements TunerService.Tunable { * Sets the minimized state and notifies the controller. */ private void setMinimizedState(boolean isMinimized) { - mIsMinimized = isMinimized; + onMinimizedStateChanged(isMinimized); if (mPinnedStackController != null) { try { @@ -432,6 +437,12 @@ public class PipTouchHandler implements TunerService.Tunable { mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds, toBounds, MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN, mUpdatePinnedStackBoundsListener); + mPinnedStackBoundsAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mMenuController.hideMenu(); + } + }); mPinnedStackBoundsAnimator.start(); } @@ -759,7 +770,8 @@ public class PipTouchHandler implements TunerService.Tunable { @Override public boolean onUp(PipTouchState touchState) { - if (mEnableTapThrough && !touchState.isDragging() && !mIsTappingThrough) { + if (mEnableTapThrough && !touchState.isDragging() && !mIsMinimized && + !mIsTappingThrough) { mMenuController.showMenu(); mIsTappingThrough = true; return true; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java index 17d9864e4d25..2e84ceda8ae0 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java @@ -16,11 +16,7 @@ package com.android.systemui.pip.phone; -import android.app.IActivityManager; import android.graphics.PointF; -import android.view.IPinnedStackController; -import android.view.IPinnedStackListener; -import android.view.IWindowManager; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java index f3da47b1ba82..03cd9595d354 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java @@ -31,6 +31,7 @@ import android.text.style.ClickableSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.view.View.OnClickListener; import android.view.Window; import android.widget.ImageView; @@ -115,6 +116,10 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene } private void handleClick() { + showDeviceMonitoringDialog(); + } + + public void showDeviceMonitoringDialog() { mHost.collapsePanels(); // TODO: Delay dialog creation until after panels are collapsed. createDialog(); @@ -175,6 +180,7 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene private void createDialog() { final String deviceOwnerPackage = mSecurityController.getDeviceOwnerName(); final String profileOwnerPackage = mSecurityController.getProfileOwnerName(); + final boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled(); final String primaryVpn = mSecurityController.getPrimaryVpnName(); final String profileVpn = mSecurityController.getProfileVpnName(); final CharSequence deviceOwnerOrganization = @@ -186,24 +192,47 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene if (!isBranded) { mDialog.setTitle(getTitle(deviceOwnerPackage)); } - mDialog.setMessage(getMessage(deviceOwnerPackage, profileOwnerPackage, primaryVpn, - profileVpn, deviceOwnerOrganization, hasProfileOwner, isBranded)); - - mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(isBranded), this); - if (mSecurityController.isVpnEnabled() && !mSecurityController.isVpnRestricted()) { - mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getSettingsButton(), this); + CharSequence msg = getMessage(deviceOwnerPackage, profileOwnerPackage, primaryVpn, + profileVpn, deviceOwnerOrganization, hasProfileOwner, isBranded); + if (deviceOwnerPackage == null) { + mDialog.setMessage(msg); + } else { + View dialogView = LayoutInflater.from(mContext) + .inflate(R.layout.quick_settings_footer_dialog, null, false); + mDialog.setView(dialogView); + TextView deviceOwnerWarning = + (TextView) dialogView.findViewById(R.id.device_owner_warning); + deviceOwnerWarning.setText(msg); + // Make the link "learn more" clickable. + deviceOwnerWarning.setMovementMethod(new LinkMovementMethod()); + if (primaryVpn == null) { + dialogView.findViewById(R.id.vpn_icon).setVisibility(View.GONE); + dialogView.findViewById(R.id.vpn_subtitle).setVisibility(View.GONE); + dialogView.findViewById(R.id.vpn_warning).setVisibility(View.GONE); + } else { + final SpannableStringBuilder message = new SpannableStringBuilder(); + message.append(mContext.getString(R.string.monitoring_description_do_body_vpn, + primaryVpn)); + message.append(mContext.getString( + R.string.monitoring_description_vpn_settings_separator)); + message.append(mContext.getString(R.string.monitoring_description_vpn_settings), + new VpnSpan(), 0); + + TextView vpnWarning = (TextView) dialogView.findViewById(R.id.vpn_warning); + vpnWarning.setText(message); + // Make the link "Open VPN Settings" clickable. + vpnWarning.setMovementMethod(new LinkMovementMethod()); + } + if (!isNetworkLoggingEnabled) { + dialogView.findViewById(R.id.network_logging_icon).setVisibility(View.GONE); + dialogView.findViewById(R.id.network_logging_subtitle).setVisibility(View.GONE); + dialogView.findViewById(R.id.network_logging_warning).setVisibility(View.GONE); + } } - // Make the link "learn more" clickable. - // TODO: Reaching into SystemUIDialog's internal View hierarchy is ugly and error-prone. - // This is a temporary solution. b/33126622 will introduce a custom View hierarchy for this - // dialog, allowing us to set the movement method on the appropriate View without any - // knowledge of SystemUIDialog's internals. - mDialog.create(); - ((TextView) mDialog.getWindow().findViewById(com.android.internal.R.id.message)) - .setMovementMethod(new LinkMovementMethod()); - + mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(isBranded), this); mDialog.show(); + mDialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } private String getSettingsButton() { @@ -229,11 +258,6 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene } message.append("\n\n"); message.append(mContext.getString(R.string.monitoring_description_do_body)); - if (primaryVpn != null) { - message.append("\n\n"); - message.append(mContext.getString(R.string.monitoring_description_do_body_vpn, - primaryVpn)); - } message.append(mContext.getString( R.string.monitoring_description_do_learn_more_separator)); message.append(mContext.getString(R.string.monitoring_description_do_learn_more), @@ -340,4 +364,14 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene return object instanceof EnterprisePrivacySpan; } } + + protected class VpnSpan extends ClickableSpan { + @Override + public void onClick(View widget) { + final Intent intent = new Intent(Settings.ACTION_VPN_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mDialog.dismiss(); + mContext.startActivity(intent); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index d8855c87210e..6b24a1eeef1e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -519,6 +519,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback { return mFooter; } + public void showDeviceMonitoringDialog() { + mFooter.showDeviceMonitoringDialog(); + } + private class H extends Handler { private static final int SHOW_DETAIL = 1; private static final int SET_TILE_VISIBILITY = 2; diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index 9bbead46c33b..0be53b4b7c26 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -60,8 +60,6 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene private final QSDetailClipper mClipper; - private PhoneStatusBar mPhoneStatusBar; - private boolean isShown; private QSTileHost mHost; private RecyclerView mRecyclerView; @@ -119,7 +117,6 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene public void setHost(QSTileHost host) { mHost = host; - mPhoneStatusBar = host.getPhoneStatusBar(); mTileAdapter.setHost(host); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 790f3f66f6e4..0265c9e40112 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -57,7 +57,6 @@ import com.android.systemui.recents.events.component.ShowUserToastEvent; import com.android.systemui.recents.events.ui.RecentsDrawnEvent; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.model.RecentsTaskLoader; -import com.android.systemui.recents.grid.RecentsGridImpl; import com.android.systemui.recents.tv.RecentsTvImpl; import com.android.systemui.stackdivider.Divider; @@ -84,7 +83,6 @@ public class Recents extends SystemUI static { RECENTS_ACTIVITIES.add(RecentsImpl.RECENTS_ACTIVITY); RECENTS_ACTIVITIES.add(RecentsTvImpl.RECENTS_TV_ACTIVITY); - RECENTS_ACTIVITIES.add(RecentsGridImpl.RECENTS_MOSAIC_ACTIVITY); } // Purely for experimentation @@ -207,8 +205,6 @@ public class Recents extends SystemUI getSystemService(Context.UI_MODE_SERVICE); if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { mImpl = new RecentsTvImpl(mContext); - } else if (SystemProperties.getBoolean("ro.recents.grid", false) == true) { - mImpl = new RecentsGridImpl(mContext); } else { mImpl = new RecentsImpl(mContext); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java index 73c6e6e89c58..711f0c679b6d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; +import android.os.SystemProperties; import com.android.systemui.R; import com.android.systemui.recents.misc.SystemServicesProxy; @@ -58,6 +59,10 @@ public class RecentsConfiguration { public boolean fakeShadows; public int svelteLevel; + // Whether this product supports Grid-based Recents. If this is field is set to true, then + // Recents will layout task views in a grid mode when there's enough space in the screen. + public boolean isGridEnabled; + public RecentsConfiguration(Context context) { // Load only resources that can not change after the first load either through developer // settings or via multi window @@ -66,6 +71,7 @@ public class RecentsConfiguration { Resources res = appContext.getResources(); fakeShadows = res.getBoolean(R.bool.config_recents_fake_shadows); svelteLevel = res.getInteger(R.integer.recents_svelte_level); + isGridEnabled = SystemProperties.getBoolean("ro.recents.grid", false); float screenDensity = context.getResources().getDisplayMetrics().density; smallestWidth = ssp.getDeviceSmallestWidth(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index c9c4f2bef31f..137138127775 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -576,7 +576,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener R.dimen.recents_task_view_header_height, R.dimen.recents_task_view_header_height_tablet_land, R.dimen.recents_task_view_header_height, - R.dimen.recents_task_view_header_height_tablet_land); + R.dimen.recents_task_view_header_height_tablet_land, + R.dimen.recents_grid_task_view_header_height); LayoutInflater inflater = LayoutInflater.from(mContext); mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header, @@ -720,7 +721,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener if (task.isFreeformTask()) { mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task, stackScroller.getStackScroll(), mTmpTransform, null, - windowOverrideRect); + windowOverrideRect, false /* useGridLayout */); Bitmap thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform, mThumbTransitionBitmapCache); Rect toTaskRect = new Rect(); @@ -770,7 +771,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener stackView.updateLayoutAlgorithm(true /* boundScroll */); stackView.updateToInitialState(); stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask, - stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect); + stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect, + Recents.getConfiguration().isGridEnabled); return mTmpTransform; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index cf75c4f50bb6..002515d10a01 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -27,6 +27,7 @@ import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; +import android.os.Binder; import android.os.RemoteException; import android.util.DisplayMetrics; import android.view.Gravity; @@ -106,6 +107,7 @@ public class ScreenPinningRequest implements View.OnClickListener { | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED , PixelFormat.TRANSLUCENT); + lp.token = new Binder(); lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; lp.setTitle("ScreenPinningConfirmation"); lp.gravity = Gravity.FILL; diff --git a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java b/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java deleted file mode 100644 index 71c21489554b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java +++ /dev/null @@ -1,504 +0,0 @@ -/* - * 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.recents.grid; - -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; - -import android.app.Activity; -import android.content.Intent; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.Bundle; -import android.util.DisplayMetrics; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.ScaleAnimation; -import android.view.animation.TranslateAnimation; -import android.widget.FrameLayout; -import android.widget.TextView; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.R; -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.RecentsActivity; -import com.android.systemui.recents.RecentsActivityLaunchState; -import com.android.systemui.recents.RecentsConfiguration; -import com.android.systemui.recents.RecentsImpl; -import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.activity.ConfigurationChangedEvent; -import com.android.systemui.recents.events.activity.HideRecentsEvent; -import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent; -import com.android.systemui.recents.events.activity.LaunchTaskEvent; -import com.android.systemui.recents.events.activity.ToggleRecentsEvent; -import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; -import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent; -import com.android.systemui.recents.events.ui.DeleteTaskDataEvent; -import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; -import com.android.systemui.recents.events.ui.DismissTaskViewEvent; -import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.Utilities; -import com.android.systemui.recents.model.RecentsTaskLoadPlan; -import com.android.systemui.recents.model.RecentsTaskLoader; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.TaskStack; -import com.android.systemui.recents.views.TaskView; - -import java.util.ArrayList; -import java.util.Arrays; - -/** - * The main grid recents activity started by the RecentsImpl. - */ -public class RecentsGridActivity extends Activity { - public final static int MAX_VISIBLE_TASKS = 9; - - private final static String TAG = "RecentsGridActivity"; - private final static int TITLE_BAR_HEIGHT_DP = 64; - - private ArrayList<Integer> mMargins = new ArrayList<>(); - - private TaskStack mTaskStack; - private ArrayList<Task> mTasks = new ArrayList<>(); - private ArrayList<TaskView> mTaskViews = new ArrayList<>(); - private ArrayList<Rect> mTaskViewRects; - private FrameLayout mRecentsView; - private TextView mEmptyView; - private View mClearAllButton; - private int mLastDisplayOrientation = Configuration.ORIENTATION_UNDEFINED; - private int mLastDisplayDensity; - private Rect mDisplayRect = new Rect(); - private LayoutInflater mInflater; - private boolean mTouchExplorationEnabled; - private Point mScreenSize; - private int mTitleBarHeightPx; - private int mStatusBarHeightPx; - private int mNavigationBarHeightPx; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.recents_grid); - SystemServicesProxy ssp = Recents.getSystemServices(); - - Resources res = getResources(); - Integer[] margins = { - res.getDimensionPixelSize(R.dimen.recents_grid_margin_left), - res.getDimensionPixelSize(R.dimen.recents_grid_margin_top), - res.getDimensionPixelSize(R.dimen.recents_grid_margin_right), - res.getDimensionPixelSize(R.dimen.recents_grid_margin_bottom), - }; - mMargins.addAll(Arrays.asList(margins)); - - mInflater = LayoutInflater.from(this); - Configuration appConfiguration = Utilities.getAppConfiguration(this); - mDisplayRect = ssp.getDisplayRect(); - mLastDisplayOrientation = appConfiguration.orientation; - mLastDisplayDensity = appConfiguration.densityDpi; - mTouchExplorationEnabled = ssp.isTouchExplorationEnabled(); - mScreenSize = new Point(); - getWindowManager().getDefaultDisplay().getRealSize(mScreenSize); - DisplayMetrics metrics = res.getDisplayMetrics(); - mTitleBarHeightPx = (int) (TITLE_BAR_HEIGHT_DP * - ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)); - mStatusBarHeightPx = res.getDimensionPixelSize(R.dimen.status_bar_height); - mNavigationBarHeightPx = res.getDimensionPixelSize(R.dimen.navigation_bar_height); - - mRecentsView = (FrameLayout) findViewById(R.id.recents_view); - mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); - getWindow().getAttributes().privateFlags |= - WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; - mEmptyView = (TextView) mInflater.inflate(R.layout.recents_empty, mRecentsView, false); - mClearAllButton = findViewById(R.id.button); - - FrameLayout.LayoutParams emptyViewLayoutParams = new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); - emptyViewLayoutParams.gravity = Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL; - mEmptyView.setLayoutParams(emptyViewLayoutParams); - mRecentsView.addView(mEmptyView); - - mClearAllButton.setVisibility(View.VISIBLE); - FrameLayout.LayoutParams lp = - (FrameLayout.LayoutParams) mClearAllButton.getLayoutParams(); - lp.gravity = Gravity.END; - - mClearAllButton.setOnClickListener(v -> { - EventBus.getDefault().send(new DismissAllTaskViewsEvent()); - }); - - mRecentsView.setOnClickListener(v -> { - EventBus.getDefault().send(new HideRecentsEvent( - false /* triggeredFromAltTab */, false /* triggeredFromHomeKey */)); - }); - - EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY); - } - - private TaskView createView() { - return (TaskView) mInflater.inflate(R.layout.recents_task_view, mRecentsView, false); - } - - private void removeTaskViews() { - for (View taskView : mTaskViews) { - ViewGroup parent = (ViewGroup) taskView.getParent(); - if (parent != null) { - parent.removeView(taskView); - } - } - } - - private void clearTaskViews() { - removeTaskViews(); - mTaskViews.clear(); - } - - private TaskView getChildViewForTask(Task task) { - for (TaskView tv : mTaskViews) { - if (tv.getTask() == task) { - return tv; - } - } - return null; - } - - /** - * Starts animations for each task view to either enlarge it to the size of the screen (when - * launching a task), or (if {@code reverse} is true, to reduce it from the size of the screen - * back to its place in the recents layout (when opening recents). - * @param animationListener An animation listener for executing code before or after the - * animations run. - * @param reverse Whether the blow-up animations should be run in reverse. - */ - private void startBlowUpAnimations(Animation.AnimationListener animationListener, - boolean reverse) { - if (mTaskViews.size() == 0) { - return; - } - int screenWidth = mLastDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE - ? mScreenSize.x : mScreenSize.y; - int screenHeight = mLastDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE - ? mScreenSize.y : mScreenSize.x; - screenHeight -= mStatusBarHeightPx + mNavigationBarHeightPx; - for (int i = 0; i < mTaskViews.size(); i++) { - View tv = mTaskViews.get(i); - AnimationSet animations = new AnimationSet(true /* shareInterpolator */); - animations.setInterpolator(new DecelerateInterpolator()); - if (i == 0 && animationListener != null) { - animations.setAnimationListener(animationListener); - } - animations.setFillBefore(reverse); - animations.setFillAfter(!reverse); - Rect initialRect = mTaskViewRects.get(mTaskViewRects.size() - 1 - i); - int xDelta = - initialRect.left; - int yDelta = - initialRect.top - mTitleBarHeightPx + mStatusBarHeightPx; - TranslateAnimation translate = new TranslateAnimation( - reverse ? xDelta : 0, reverse ? 0 : xDelta, - reverse ? yDelta : 0, reverse ? 0 : yDelta); - translate.setDuration(250); - animations.addAnimation(translate); - - - float xScale = (float) screenWidth / (float) initialRect.width(); - float yScale = (float) screenHeight / - ((float) initialRect.height() - mTitleBarHeightPx); - ScaleAnimation scale = new ScaleAnimation( - reverse ? xScale : 1, reverse ? 1 : xScale, - reverse ? yScale : 1, reverse ? 1 : yScale, - Animation.ABSOLUTE, 0, Animation.ABSOLUTE, mStatusBarHeightPx); - scale.setDuration(300); - animations.addAnimation(scale); - - tv.startAnimation(animations); - } - } - - private void updateControlVisibility() { - boolean empty = (mTasks.size() == 0); - mClearAllButton.setVisibility(empty ? View.INVISIBLE : View.VISIBLE); - mEmptyView.setVisibility(empty ? View.VISIBLE : View.INVISIBLE); - if (empty) { - mEmptyView.bringToFront(); - } - } - - private void updateModel() { - RecentsTaskLoader loader = Recents.getTaskLoader(); - RecentsTaskLoadPlan plan = RecentsImpl.consumeInstanceLoadPlan(); - if (plan == null) { - plan = loader.createLoadPlan(this); - } - RecentsConfiguration config = Recents.getConfiguration(); - RecentsActivityLaunchState launchState = config.getLaunchState(); - if (!plan.hasTasks()) { - loader.preloadTasks(plan, -1, !launchState.launchedFromHome); - } - mTaskStack = plan.getTaskStack(); - RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options(); - loadOpts.runningTaskId = launchState.launchedToTaskId; - loadOpts.numVisibleTasks = MAX_VISIBLE_TASKS; - loadOpts.numVisibleTaskThumbnails = MAX_VISIBLE_TASKS; - loader.loadTasks(this, plan, loadOpts); - - mTasks = mTaskStack.getStackTasks(); - } - - private void updateViews() { - int screenWidth = mLastDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE - ? mScreenSize.x : mScreenSize.y; - int screenHeight = mLastDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE - ? mScreenSize.y : mScreenSize.x; - int paddingPixels = getResources().getDimensionPixelSize( - R.dimen.recents_grid_inter_task_padding); - mTaskViewRects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - mTasks.size(), screenWidth, screenHeight, getAppRectRatio(), paddingPixels, - mMargins, mTitleBarHeightPx); - boolean recycleViews = (mTaskViews.size() == mTasks.size()); - if (!recycleViews) { - clearTaskViews(); - } - for (int i = 0; i < mTasks.size(); i++) { - Task task = mTasks.get(i); - // We keep the same ordering in the model as other Recents flavors (older tasks are - // first in the stack) so that the logic can be similar, but we reverse the order - // when placing views on the screen so that most recent tasks are displayed first. - Rect rect = mTaskViewRects.get(mTaskViewRects.size() - 1 - i); - TaskView taskView; - if (recycleViews) { - taskView = mTaskViews.get(i); - } else { - taskView = createView(); - } - taskView.onTaskBound(task, mTouchExplorationEnabled, mLastDisplayOrientation, - mDisplayRect); - Recents.getTaskLoader().loadTaskData(task); - taskView.setTouchEnabled(true); - // Show dismiss button right away. - taskView.startNoUserInteractionAnimation(); - taskView.setLayoutParams(new FrameLayout.LayoutParams(rect.width(), rect.height())); - taskView.setTranslationX(rect.left); - taskView.setTranslationY(rect.top); - if (!recycleViews) { - mRecentsView.addView(taskView); - mTaskViews.add(taskView); - } - } - updateControlVisibility(); - } - - private float getAppRectRatio() { - if (mLastDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE) { - return (float) mScreenSize.x / - (float) (mScreenSize.y - mStatusBarHeightPx - mNavigationBarHeightPx); - } else { - return (float) mScreenSize.y / - (float) (mScreenSize.x - mStatusBarHeightPx - mNavigationBarHeightPx); - } - } - - @Override - protected void onStart() { - super.onStart(); - EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true)); - updateModel(); - updateViews(); - if (mTaskViews.size() > 0) { - mTaskViews.get(mTaskViews.size() - 1).bringToFront(); - } - startBlowUpAnimations(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { } - - @Override - public void onAnimationEnd(Animation animation) { - updateViews(); - } - - @Override - public void onAnimationRepeat(Animation animation) { } - }, true /* reverse */); - } - - @Override - protected void onStop() { - super.onStop(); - EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false)); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - EventBus.getDefault().unregister(this); - } - - @Override - public void onBackPressed() { - // Back behaves like the recents button so just trigger a toggle event. - EventBus.getDefault().send(new ToggleRecentsEvent()); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - // Notify of the config change. - Configuration newDeviceConfiguration = Utilities.getAppConfiguration(this); - mDisplayRect = Recents.getSystemServices().getDisplayRect(); - int numStackTasks = mTaskStack.getStackTaskCount(); - EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */, - mLastDisplayOrientation != newDeviceConfiguration.orientation, - mLastDisplayDensity != newDeviceConfiguration.densityDpi, numStackTasks > 0)); - mLastDisplayOrientation = newDeviceConfiguration.orientation; - mLastDisplayDensity = newDeviceConfiguration.densityDpi; - updateViews(); - } - - void dismissRecentsToHome() { - Intent startMain = new Intent(Intent.ACTION_MAIN); - startMain.addCategory(Intent.CATEGORY_HOME); - startActivity(startMain); - } - - /** Launches the task that recents was launched from if possible. */ - boolean launchPreviousTask() { - if (mRecentsView != null) { - Task task = mTaskStack.getLaunchTarget(); - if (task != null) { - TaskView taskView = getChildViewForTask(task); - EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, - INVALID_STACK_ID, false)); - return true; - } - } - return false; - } - - /** Dismisses recents back to the launch target task. */ - boolean dismissRecentsToLaunchTargetTaskOrHome() { - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.isRecentsActivityVisible()) { - // If we can launch the task that Recents was launched from, do that, otherwise go home. - if (launchPreviousTask()) return true; - dismissRecentsToHome(); - } - return false; - } - - /**** EventBus events ****/ - - public final void onBusEvent(HideRecentsEvent event) { - if (event.triggeredFromAltTab) { - dismissRecentsToLaunchTargetTaskOrHome(); - } else if (event.triggeredFromHomeKey) { - dismissRecentsToHome(); - } else { - // Fall through tap on the background view but not on any of the tasks. - dismissRecentsToHome(); - } - } - - public final void onBusEvent(ToggleRecentsEvent event) { - dismissRecentsToLaunchTargetTaskOrHome(); - } - - public final void onBusEvent(DismissTaskViewEvent event) { - int taskIndex = mTaskViews.indexOf(event.taskView); - if (taskIndex != -1) { - mTasks.remove(taskIndex); - ((ViewGroup) event.taskView.getParent()).removeView(event.taskView); - mTaskViews.remove(taskIndex); - EventBus.getDefault().send( - new TaskViewDismissedEvent(event.taskView.getTask(), event.taskView, null)); - } - } - - public final void onBusEvent(TaskViewDismissedEvent event) { - mRecentsView.announceForAccessibility(this.getString( - R.string.accessibility_recents_item_dismissed, event.task.title)); - updateControlVisibility(); - - EventBus.getDefault().send(new DeleteTaskDataEvent(event.task)); - - MetricsLogger.action(this, MetricsEvent.OVERVIEW_DISMISS, - event.task.key.getComponent().toString()); - } - - public final void onBusEvent(DeleteTaskDataEvent event) { - // Remove any stored data from the loader. - RecentsTaskLoader loader = Recents.getTaskLoader(); - loader.deleteTaskData(event.task, false); - - // Remove the task from activity manager. - SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.removeTask(event.task.key.id); - } - - public final void onBusEvent(final DismissAllTaskViewsEvent event) { - // Keep track of the tasks which will have their data removed. - ArrayList<Task> tasks = new ArrayList<>(mTaskStack.getStackTasks()); - mRecentsView.announceForAccessibility(this.getString( - R.string.accessibility_recents_all_items_dismissed)); - mTaskStack.removeAllTasks(); - for (int i = tasks.size() - 1; i >= 0; i--) { - EventBus.getDefault().send(new DeleteTaskDataEvent(tasks.get(i))); - } - mTasks = new ArrayList<>(); - updateModel(); - updateViews(); - - MetricsLogger.action(this, MetricsEvent.OVERVIEW_DISMISS_ALL); - } - - public final void onBusEvent(AllTaskViewsDismissedEvent event) { - SystemServicesProxy ssp = Recents.getSystemServices(); - if (!ssp.hasDockedTask()) { - dismissRecentsToHome(); - } - } - - public final void onBusEvent(LaunchNextTaskRequestEvent event) { - if (mTaskStack.getTaskCount() > 0) { - Task launchTask = mTaskStack.getNextLaunchTarget(); - TaskView launchTaskView = getChildViewForTask(launchTask); - if (launchTaskView != null) { - EventBus.getDefault().send(new LaunchTaskEvent(launchTaskView, - launchTask, null, INVALID_STACK_ID, false /* screenPinningRequested */)); - MetricsLogger.action(this, MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK, - launchTask.key.getComponent().toString()); - return; - } - } - // We couldn't find a matching task view, or there are no tasks. Just hide recents back - // to home. - EventBus.getDefault().send(new HideRecentsEvent(false, true)); - } - - public final void onBusEvent(LaunchTaskEvent event) { - event.taskView.bringToFront(); - startActivity(event.task.key.baseIntent); - // Eventually we should start blow-up animations here, but we need to make sure it's done - // in parallel with starting the activity so that we don't introduce unneeded latency. - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridImpl.java b/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridImpl.java deleted file mode 100644 index 41acaa0a619a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridImpl.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.recents.grid; - -import android.app.ActivityManager; -import android.content.Context; -import android.content.Intent; -import android.os.UserHandle; - -import com.android.systemui.recents.RecentsImpl; -import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; - -public class RecentsGridImpl extends RecentsImpl { - public static final String RECENTS_MOSAIC_ACTIVITY = - "com.android.systemui.recents.grid.RecentsGridActivity"; - - public RecentsGridImpl(Context context) { - super(context); - } - - @Override - protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask, - boolean isHomeStackVisible, boolean animate, int growTarget) { - Intent intent = new Intent(); - intent.setClassName(RECENTS_PACKAGE, RECENTS_MOSAIC_ACTIVITY); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - | Intent.FLAG_ACTIVITY_TASK_ON_HOME); - - mContext.startActivityAsUser(intent, UserHandle.CURRENT); - EventBus.getDefault().send(new RecentsActivityStartingEvent()); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java deleted file mode 100644 index 648f2f023fdf..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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.recents.grid; - -import android.graphics.Rect; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -class TaskGridLayoutAlgorithm { - - public enum VerticalGravity { - START, END, CENTER - } - - public static final List<Integer> ZERO_MARGIN = new ArrayList<>(); - static { - Integer[] zero = {0, 0, 0, 0}; - ZERO_MARGIN.addAll(Arrays.asList(zero)); - } - private static final String TAG = "TaskGridLayoutAlgorithm"; - - /** - * Calculates the adequate rectangles for the specified number of tasks to be layed out on - * the screen. - * @param count The number of task views to layout. - * @param containerWidth The width of the whole area containing those tasks. - * @param containerHeight The height of the whole area containing those tasks. - * @param screenRatio The ratio of the device's screen, so that tasks have the same aspect - * ratio (ignoring the title bar). - * @param padding The amount of padding, in pixels, in between task views. - * @param margins The amount of space to be left blank around the area on the left, top, right - * and bottom. - * @param titleBarHeight The height, in pixels, of the task views title bar. - * @return A list of rectangles to be used for layout. - */ - static ArrayList<Rect> getRectsForTaskCount(int count, int containerWidth, int containerHeight, - float screenRatio, int padding, List<Integer> margins, int titleBarHeight) { - return getRectsForTaskCount(count, containerWidth, containerHeight, screenRatio, padding, - margins, titleBarHeight, null, VerticalGravity.CENTER); - } - - private static ArrayList<Rect> getRectsForTaskCount(int count, int containerWidth, - int containerHeight, float screenRatio, int padding, List<Integer> margins, - int titleBarHeight, Rect preCalculatedTile, VerticalGravity gravity) { - ArrayList<Rect> rects = new ArrayList<>(count); - boolean landscape = (containerWidth > containerHeight); - containerWidth -= margins.get(0) + margins.get(2); - containerHeight -= margins.get(1) + margins.get(3); - - // We support at most 9 tasks in this layout. - count = Math.min(count, RecentsGridActivity.MAX_VISIBLE_TASKS); - - if (count == 0) { - return rects; - } - if (count <= 3) { - // Base case: single line. - int taskWidth, taskHeight; - if (preCalculatedTile != null) { - taskWidth = preCalculatedTile.width(); - taskHeight = preCalculatedTile.height(); - } else { - // Divide available width in equal parts. - int maxTaskWidth = (containerWidth - (count - 1) * padding) / count; - int maxTaskHeight = containerHeight; - if (maxTaskHeight >= maxTaskWidth / screenRatio + titleBarHeight) { - // Width bound. - taskWidth = maxTaskWidth; - taskHeight = (int) (maxTaskWidth / screenRatio + titleBarHeight); - } else { - // Height bound. - taskHeight = maxTaskHeight; - taskWidth = (int) ((taskHeight - titleBarHeight) * screenRatio); - } - } - int emptySpaceX = containerWidth - (count * taskWidth) - (count - 1) * padding; - int emptySpaceY = containerHeight - taskHeight; - for (int i = 0; i < count; i++) { - int left = emptySpaceX / 2 + i * taskWidth + i * padding; - int top; - switch (gravity) { - case CENTER: - top = emptySpaceY / 2; - break; - case END: - top = emptySpaceY; - break; - case START: - default: - top = 0; - break; - } - Rect rect = new Rect(left, top, left + taskWidth, top + taskHeight); - rect.offset(margins.get(0), margins.get(1)); - rects.add(rect); - } - } else if (count < 7) { - // Two lines. - int lineHeight = (containerHeight - padding) / 2; - int lineTaskCount = (int) Math.ceil((double) count / 2); - List<Rect> rectsA = getRectsForTaskCount(lineTaskCount, containerWidth, lineHeight, - screenRatio, padding, ZERO_MARGIN, titleBarHeight, null, VerticalGravity.END); - List<Rect> rectsB = getRectsForTaskCount(count - lineTaskCount, containerWidth, - lineHeight, screenRatio, padding, ZERO_MARGIN, titleBarHeight, rectsA.get(0), - VerticalGravity.START); - for (int i = 0; i < rectsA.size(); i++) { - rectsA.get(i).offset(margins.get(0), margins.get(1)); - } - for (int i = 0; i < rectsB.size(); i++) { - rectsB.get(i).offset(margins.get(0), margins.get(1) + lineHeight + padding); - } - rects.addAll(rectsA); - rects.addAll(rectsB); - } else { - // Three lines. - int lineHeight = (containerHeight - 2 * padding) / 3; - int lineTaskCount = (int) Math.ceil((double) count / 3); - List<Rect> rectsA = getRectsForTaskCount(lineTaskCount, containerWidth, lineHeight, - screenRatio, padding, ZERO_MARGIN, titleBarHeight, null, VerticalGravity.END); - List<Rect> rectsB = getRectsForTaskCount(lineTaskCount, containerWidth, lineHeight, - screenRatio, padding, ZERO_MARGIN, titleBarHeight, rectsA.get(0), - VerticalGravity.END); - List<Rect> rectsC = getRectsForTaskCount(count - (2 * lineTaskCount), containerWidth, - lineHeight, screenRatio, padding, ZERO_MARGIN, titleBarHeight, rectsA.get(0), - VerticalGravity.START); - for (int i = 0; i < rectsA.size(); i++) { - rectsA.get(i).offset(margins.get(0), margins.get(1)); - } - for (int i = 0; i < rectsB.size(); i++) { - rectsB.get(i).offset(margins.get(0), margins.get(1) + lineHeight + padding); - } - for (int i = 0; i < rectsC.size(); i++) { - rectsC.get(i).offset(margins.get(0), margins.get(1) + 2 * (lineHeight + padding)); - } - rects.addAll(rectsA); - rects.addAll(rectsB); - rects.addAll(rectsC); - } - return rects; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index ea50d890ae2e..ddffea2446a4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -74,6 +74,7 @@ import android.view.WindowManager; import android.view.WindowManager.KeyboardShortcutsReceiver; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; +import android.app.KeyguardManager; import com.android.internal.app.AssistUtils; import com.android.internal.os.BackgroundThread; @@ -124,6 +125,7 @@ public class SystemServicesProxy { AssistUtils mAssistUtils; WindowManager mWm; IWindowManager mIwm; + KeyguardManager mKgm; UserManager mUm; Display mDisplay; String mRecentsPackage; @@ -212,6 +214,7 @@ public class SystemServicesProxy { mAssistUtils = new AssistUtils(context); mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mIwm = WindowManagerGlobal.getWindowManagerService(); + mKgm = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); mUm = UserManager.get(context); mDisplay = mWm.getDefaultDisplay(); mRecentsPackage = context.getPackageName(); @@ -862,6 +865,16 @@ public class SystemServicesProxy { return label; } + /** + * Returns whether the provided {@param userId} is currently locked (and showing Keyguard). + */ + public boolean isDeviceLocked(int userId) { + if (mKgm == null) { + return false; + } + return mKgm.isDeviceLocked(userId); + } + /** Returns the package name of the home activity. */ public String getHomeActivityPackageName() { if (mPm == null) return null; diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java index 2c5c437bfab0..4349e30f60e0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java @@ -86,7 +86,7 @@ public class Utilities { public static <T extends View> T findParent(View v, Class<T> parentClass) { ViewParent parent = v.getParent(); while (parent != null) { - if (parent.getClass().equals(parentClass)) { + if (parentClass.isAssignableFrom(parent.getClass())) { return (T) parent; } parent = parent.getParent(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java index 9b48e4d02623..587744065d77 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -28,6 +28,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.ArraySet; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; import com.android.systemui.Prefs; @@ -130,6 +131,7 @@ public class RecentsTaskLoadPlan { SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>(); SparseIntArray affiliatedTaskCounts = new SparseIntArray(); + SparseBooleanArray lockedUsers = new SparseBooleanArray(); String dismissDescFormat = mContext.getString( R.string.accessibility_recents_item_will_be_dismissed); String appInfoDescFormat = mContext.getString( @@ -177,12 +179,17 @@ public class RecentsTaskLoadPlan { int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription); boolean isSystemApp = (info != null) && ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); + if (lockedUsers.indexOfKey(t.userId) < 0) { + lockedUsers.put(t.userId, Recents.getSystemServices().isDeviceLocked(t.userId)); + } + boolean isLocked = lockedUsers.get(t.userId); // Add the task to the stack Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon, thumbnail, title, titleDescription, dismissDescription, appInfoDescription, activityColor, backgroundColor, isLaunchTarget, isStackTask, isSystemApp, - t.isDockable, t.bounds, t.taskDescription, t.resizeMode, t.topActivity); + t.isDockable, t.bounds, t.taskDescription, t.resizeMode, t.topActivity, + isLocked); allTasks.add(task); affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java index 86a0315496a1..53f713a83cb0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -189,6 +189,9 @@ public class Task { @ViewDebug.ExportedProperty(category="recents") public ComponentName topActivity; + @ViewDebug.ExportedProperty(category="recents") + public boolean isLocked; + private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>(); public Task() { @@ -200,7 +203,7 @@ public class Task { String appInfoDescription, int colorPrimary, int colorBackground, boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp, boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription, - int resizeMode, ComponentName topActivity) { + int resizeMode, ComponentName topActivity, boolean isLocked) { boolean isInAffiliationGroup = (affiliationTaskId != key.id); boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0); this.key = key; @@ -224,6 +227,7 @@ public class Task { this.isDockable = isDockable; this.resizeMode = resizeMode; this.topActivity = topActivity; + this.isLocked = isLocked; } /** @@ -250,6 +254,7 @@ public class Task { this.isSystemApp = o.isSystemApp; this.isDockable = o.isDockable; this.resizeMode = o.resizeMode; + this.isLocked = o.isLocked; this.topActivity = o.topActivity; } @@ -355,6 +360,9 @@ public class Task { if (isFreeformTask()) { writer.print(" freeform=Y"); } + if (isLocked) { + writer.print(" locked=Y"); + } writer.print(" "); writer.print(title); writer.println(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java index 253d06a5f5f8..dba085e37427 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java @@ -30,17 +30,17 @@ public class AnimateableViewBounds extends ViewOutlineProvider { private static final float MIN_ALPHA = 0.1f; private static final float MAX_ALPHA = 0.8f; - View mSourceView; + protected View mSourceView; @ViewDebug.ExportedProperty(category="recents") - Rect mClipRect = new Rect(); + protected Rect mClipRect = new Rect(); @ViewDebug.ExportedProperty(category="recents") - Rect mClipBounds = new Rect(); + protected Rect mClipBounds = new Rect(); @ViewDebug.ExportedProperty(category="recents") - Rect mLastClipBounds = new Rect(); + protected Rect mLastClipBounds = new Rect(); @ViewDebug.ExportedProperty(category="recents") - int mCornerRadius; + protected int mCornerRadius; @ViewDebug.ExportedProperty(category="recents") - float mAlpha = 1f; + protected float mAlpha = 1f; public AnimateableViewBounds(View source, int cornerRadius) { mSourceView = source; @@ -110,7 +110,7 @@ public class AnimateableViewBounds extends ViewOutlineProvider { return mClipRect.bottom; } - private void updateClipBounds() { + protected void updateClipBounds() { mClipBounds.set(Math.max(0, mClipRect.left), Math.max(0, mClipRect.top), mSourceView.getWidth() - Math.max(0, mClipRect.right), mSourceView.getHeight() - Math.max(0, mClipRect.bottom)); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index 3c7012a16dfc..d8fdd7a281e8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -337,7 +337,8 @@ public class RecentsView extends FrameLayout { if (RecentsDebugFlags.Static.EnableStackActionButton) { // Measure the stack action button within the constraints of the space above the stack - Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect; + Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect( + mTaskStackView.useGridLayout()); measureChild(mStackActionButton, MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST)); @@ -772,7 +773,8 @@ public class RecentsView extends FrameLayout { * @return the bounds of the stack action button. */ private Rect getStackActionButtonBoundsFromStackLayout() { - Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect); + Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect( + mTaskStackView.useGridLayout())); int left = isLayoutRtl() ? actionButtonRect.left - mStackActionButton.getPaddingLeft() : actionButtonRect.right + mStackActionButton.getPaddingRight() diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java index 493e6187c83e..c1f4c8a4c1dd 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java @@ -157,7 +157,7 @@ public class TaskStackAnimationHelper { // Get the current transform for the task, which will be used to position it offscreen stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, - null); + null, mStackView.useGridLayout()); if (hideTask) { tv.setVisibility(View.INVISIBLE); @@ -230,7 +230,7 @@ public class TaskStackAnimationHelper { // Get the current transform for the task, which will be updated to the final transform // to animate to depending on how recents was invoked stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, - null); + null, mStackView.useGridLayout()); if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) { if (task.isLaunchTarget) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index 571c0f67e18e..052985647453 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -38,7 +38,7 @@ import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; - +import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -240,14 +240,14 @@ public class TaskStackLayoutAlgorithm { // This is the current system insets @ViewDebug.ExportedProperty(category="recents") public Rect mSystemInsets = new Rect(); - // This is the bounds of the stack action above the stack rect - @ViewDebug.ExportedProperty(category="recents") - public Rect mStackActionButtonRect = new Rect(); // The visible ranges when the stack is focused and unfocused private Range mUnfocusedRange; private Range mFocusedRange; + // This is the bounds of the stack action above the stack rect + @ViewDebug.ExportedProperty(category="recents") + private Rect mStackActionButtonRect = new Rect(); // The base top margin for the stack from the system insets @ViewDebug.ExportedProperty(category="recents") private int mBaseTopMargin; @@ -326,7 +326,7 @@ public class TaskStackLayoutAlgorithm { @ViewDebug.ExportedProperty(category="recents") int mMinTranslationZ; @ViewDebug.ExportedProperty(category="recents") - int mMaxTranslationZ; + public int mMaxTranslationZ; // Optimization, allows for quick lookup of task -> index private SparseIntArray mTaskIndexMap = new SparseIntArray(); @@ -334,6 +334,7 @@ public class TaskStackLayoutAlgorithm { // The freeform workspace layout FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm; + TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm; // The transform to place TaskViews at the front and back of the stack respectively TaskViewTransform mBackOfStackTransform = new TaskViewTransform(); @@ -344,6 +345,7 @@ public class TaskStackLayoutAlgorithm { mContext = context; mCb = cb; mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context); + mTaskGridLayoutAlgorithm = new TaskGridLayoutAlgorithm(context); reloadOnConfigurationChange(context); } @@ -368,6 +370,7 @@ public class TaskStackLayoutAlgorithm { R.dimen.recents_layout_initial_top_offset_tablet, R.dimen.recents_layout_initial_top_offset_tablet, R.dimen.recents_layout_initial_top_offset_tablet, + R.dimen.recents_layout_initial_top_offset_tablet, R.dimen.recents_layout_initial_top_offset_tablet); mBaseInitialBottomOffset = getDimensionForDevice(context, R.dimen.recents_layout_initial_bottom_offset_phone_port, @@ -375,17 +378,21 @@ public class TaskStackLayoutAlgorithm { R.dimen.recents_layout_initial_bottom_offset_tablet, R.dimen.recents_layout_initial_bottom_offset_tablet, R.dimen.recents_layout_initial_bottom_offset_tablet, + R.dimen.recents_layout_initial_bottom_offset_tablet, R.dimen.recents_layout_initial_bottom_offset_tablet); mFreeformLayoutAlgorithm.reloadOnConfigurationChange(context); + mTaskGridLayoutAlgorithm.reloadOnConfigurationChange(context); mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin); mBaseTopMargin = getDimensionForDevice(context, R.dimen.recents_layout_top_margin_phone, R.dimen.recents_layout_top_margin_tablet, - R.dimen.recents_layout_top_margin_tablet_xlarge); + R.dimen.recents_layout_top_margin_tablet_xlarge, + R.dimen.recents_layout_top_margin_tablet); mBaseSideMargin = getDimensionForDevice(context, R.dimen.recents_layout_side_margin_phone, R.dimen.recents_layout_side_margin_tablet, - R.dimen.recents_layout_side_margin_tablet_xlarge); + R.dimen.recents_layout_side_margin_tablet_xlarge, + R.dimen.recents_layout_side_margin_tablet); mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin); mFreeformStackGap = res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin); @@ -405,6 +412,7 @@ public class TaskStackLayoutAlgorithm { public boolean setSystemInsets(Rect systemInsets) { boolean changed = !mSystemInsets.equals(systemInsets); mSystemInsets.set(systemInsets); + mTaskGridLayoutAlgorithm.setSystemInsets(systemInsets); return changed; } @@ -470,6 +478,9 @@ public class TaskStackLayoutAlgorithm { updateFrontBackTransforms(); } + + // Initialize the grid layout + mTaskGridLayoutAlgorithm.initialize(displayRect, windowRect); } /** @@ -721,6 +732,11 @@ public class TaskStackLayoutAlgorithm { } } + public Rect getStackActionButtonRect(boolean useGridLayout) { + return useGridLayout + ? mTaskGridLayoutAlgorithm.getStackActionButtonRect() : mStackActionButtonRect; + } + /** * Returns the TaskViewTransform that would put the task just off the back of the stack. */ @@ -825,24 +841,30 @@ public class TaskStackLayoutAlgorithm { * is what the view is measured and laid out with. */ public TaskViewTransform getStackTransform(Task task, float stackScroll, - TaskViewTransform transformOut, TaskViewTransform frontTransform) { + TaskViewTransform transformOut, TaskViewTransform frontTransform, + boolean useGridLayout) { return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform, - false /* forceUpdate */, false /* ignoreTaskOverrides */); + false /* forceUpdate */, false /* ignoreTaskOverrides */, useGridLayout); } public TaskViewTransform getStackTransform(Task task, float stackScroll, TaskViewTransform transformOut, TaskViewTransform frontTransform, - boolean ignoreTaskOverrides) { + boolean ignoreTaskOverrides, boolean useGridLayout) { return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform, - false /* forceUpdate */, ignoreTaskOverrides); + false /* forceUpdate */, ignoreTaskOverrides, useGridLayout); } public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState, TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate, - boolean ignoreTaskOverrides) { + boolean ignoreTaskOverrides, boolean useGridLayout) { if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) { mFreeformLayoutAlgorithm.getTransform(task, transformOut, this); return transformOut; + } else if (useGridLayout) { + int taskIndex = mTaskIndexMap.get(task.key.id); + int taskCount = mTaskIndexMap.size(); + mTaskGridLayoutAlgorithm.getTransform(taskIndex, taskCount, transformOut, this); + return transformOut; } else { // Return early if we have an invalid index int nonOverrideTaskProgress = mTaskIndexMap.get(task.key.id, -1); @@ -864,10 +886,10 @@ public class TaskStackLayoutAlgorithm { */ public TaskViewTransform getStackTransformScreenCoordinates(Task task, float stackScroll, TaskViewTransform transformOut, TaskViewTransform frontTransform, - Rect windowOverrideRect) { + Rect windowOverrideRect, boolean useGridLayout) { TaskViewTransform transform = getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform, true /* forceUpdate */, - false /* ignoreTaskOverrides */); + false /* ignoreTaskOverrides */, useGridLayout); return transformToScreenCoordinates(transform, windowOverrideRect); } @@ -1088,9 +1110,9 @@ public class TaskStackLayoutAlgorithm { * Retrieves resources that are constant regardless of the current configuration of the device. */ public static int getDimensionForDevice(Context ctx, int phoneResId, - int tabletResId, int xlargeTabletResId) { + int tabletResId, int xlargeTabletResId, int gridLayoutResId) { return getDimensionForDevice(ctx, phoneResId, phoneResId, tabletResId, tabletResId, - xlargeTabletResId, xlargeTabletResId); + xlargeTabletResId, xlargeTabletResId, gridLayoutResId); } /** @@ -1098,12 +1120,14 @@ public class TaskStackLayoutAlgorithm { */ public static int getDimensionForDevice(Context ctx, int phonePortResId, int phoneLandResId, int tabletPortResId, int tabletLandResId, int xlargeTabletPortResId, - int xlargeTabletLandResId) { + int xlargeTabletLandResId, int gridLayoutResId) { RecentsConfiguration config = Recents.getConfiguration(); Resources res = ctx.getResources(); boolean isLandscape = Utilities.getAppConfiguration(ctx).orientation == Configuration.ORIENTATION_LANDSCAPE; - if (config.isXLargeScreen) { + if (config.isGridEnabled) { + return res.getDimensionPixelSize(gridLayoutResId); + } else if (config.isXLargeScreen) { return res.getDimensionPixelSize(isLandscape ? xlargeTabletLandResId : xlargeTabletPortResId); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 8c94c3578e96..4625ca76c5cc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -92,6 +92,7 @@ import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.recents.views.grid.GridTaskView; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -158,6 +159,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal private int mTaskCornerRadiusPx; private int mDividerSize; private int mStartTimerIndicatorDuration; + private boolean mDraggingOverDockState; @ViewDebug.ExportedProperty(category="recents") private boolean mTaskViewsClipDirty = true; @@ -499,13 +501,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Calculate the current and (if necessary) the target transform for the task transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll, - taskTransforms.get(i), frontTransform, ignoreTaskOverrides); + taskTransforms.get(i), frontTransform, ignoreTaskOverrides, useGridLayout()); if (useTargetStackScroll && !transform.visible) { // If we have a target stack scroll and the task is not currently visible, then we // just update the transform at the new scroll // TODO: Optimize this - transformAtTarget = mLayoutAlgorithm.getStackTransform(task, - targetStackScroll, new TaskViewTransform(), frontTransformAtTarget); + transformAtTarget = mLayoutAlgorithm.getStackTransform(task, targetStackScroll, + new TaskViewTransform(), frontTransformAtTarget, useGridLayout()); if (transformAtTarget.visible) { transform.copyFrom(transformAtTarget); } @@ -736,7 +738,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } else { mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), focusState, transform, null, true /* forceUpdate */, - false /* ignoreTaskOverrides */); + false /* ignoreTaskOverrides */, useGridLayout()); } transform.visible = true; } @@ -753,7 +755,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal Task task = tasks.get(i); TaskViewTransform transform = transformsOut.get(i); mLayoutAlgorithm.getStackTransform(task, stackScroll, focusState, transform, null, - true /* forceUpdate */, ignoreTaskOverrides); + true /* forceUpdate */, ignoreTaskOverrides, useGridLayout()); transform.visible = true; } } @@ -782,6 +784,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal * Updates the clip for each of the task views from back to front. */ private void clipTaskViews() { + // We never clip task views in grid layout + if (Recents.getConfiguration().isGridEnabled) { + return; + } + // Update the clip on each task child List<TaskView> taskViews = getTaskViews(); TaskView tmpTv = null; @@ -1338,14 +1345,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal setFocusedTask(focusedTaskIndex, false /* scrollToTask */, false /* requestViewFocus */); } - - // Update the stack action button visibility - if (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && - mStack.getTaskCount() > 0) { - EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */)); - } else { - EventBus.getDefault().send(new HideStackActionButtonEvent()); - } + updateStackActionButtonVisibility(); } public boolean isTouchPointInView(float x, float y, TaskView tv) { @@ -1506,7 +1506,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override public TaskView createView(Context context) { - return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); + if (Recents.getConfiguration().isGridEnabled) { + return (GridTaskView) mInflater.inflate(R.layout.recents_grid_task_view, this, false); + } else { + return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); + } } @Override @@ -1636,7 +1640,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal relayoutTaskViewsOnNextFrame(animation); } - if (mEnterAnimationComplete) { + // In grid layout, the stack action button always remains visible. + if (mEnterAnimationComplete && !useGridLayout()) { if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && mStack.getTaskCount() > 0) { @@ -1830,7 +1835,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Enlarge the dragged view slightly float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR; mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(), - mTmpTransform, null); + mTmpTransform, null, useGridLayout()); mTmpTransform.scale = finalScale; mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1; mTmpTransform.dimAlpha = 0f; @@ -1851,6 +1856,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal Interpolators.FAST_OUT_SLOW_IN); boolean ignoreTaskOverrides = false; if (event.dropTarget instanceof TaskStack.DockState) { + mDraggingOverDockState = true; // Calculate the new task stack bounds that matches the window size that Recents will // have after the drop final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; @@ -1870,6 +1876,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal updateLayoutAlgorithm(true /* boundScroll */); ignoreTaskOverrides = true; } else { + mDraggingOverDockState = false; // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging // task view, so add it back to the ignore set after updating the layout removeIgnoreTask(event.task); @@ -1880,6 +1887,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } public final void onBusEvent(final DragEndEvent event) { + mDraggingOverDockState = false; // We don't handle drops on the dock regions if (event.dropTarget instanceof TaskStack.DockState) { // However, we do need to reset the overrides, since the last state of this task stack @@ -2049,6 +2057,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } + // Update the Clear All button in case we're switching in or out of grid layout. + updateStackActionButtonVisibility(); + // Trigger a new layout and update to the initial state if necessary if (event.fromMultiWindow) { mInitialState = INITIAL_STATE_UPDATE_LAYOUT_ONLY; @@ -2119,6 +2130,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** + * Check whether we should use the grid layout. + * We use the grid layout for Recents iff all the following is true: + * 1. Grid-mode is enabled. + * 2. The activity is not in multi-window mode. + * 3. The user is not dragging a task view over the dock state. + * @return True if we should use the grid layout. + */ + public boolean useGridLayout() { + return Recents.getConfiguration().isGridEnabled + && !((RecentsActivity) mContext).isInMultiWindowMode() + && !mDraggingOverDockState; + } + + /** * Reads current system flags related to accessibility and screen pinning. */ private void readSystemFlags() { @@ -2128,6 +2153,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal Settings.System.LOCK_TO_APP_ENABLED) != 0; } + private void updateStackActionButtonVisibility() { + // Always show the button in grid layout. + if (useGridLayout() || + (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && + mStack.getTaskCount() > 0)) { + EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */)); + } else { + EventBus.getDefault().send(new HideStackActionButtonEvent()); + } + } + public void dump(String prefix, PrintWriter writer) { String innerPrefix = prefix + " "; String id = Integer.toHexString(System.identityHashCode(this)); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index 71f559be6775..33fa3b0eb10a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -168,7 +168,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { /** Touch preprocessing for handling below */ public boolean onInterceptTouchEvent(MotionEvent ev) { // Pass through to swipe helper if we are swiping - mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev); + mInterceptedBySwipeHelper = isSwipingEnabled() && mSwipeHelper.onInterceptTouchEvent(ev); if (mInterceptedBySwipeHelper) { return true; } @@ -680,4 +680,11 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { public float getScaledDismissSize() { return 1.5f * Math.max(mSv.getWidth(), mSv.getHeight()); } + + /** + * Returns whether swiping is enabled. + */ + private boolean isSwipingEnabled() { + return !mSv.useGridLayout(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index de7def6c95c9..2f4ad6a4d7ce 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -148,7 +148,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks private ArrayList<Animator> mTmpAnimators = new ArrayList<>(); @ViewDebug.ExportedProperty(deepExport=true, prefix="thumbnail_") - TaskViewThumbnail mThumbnailView; + protected TaskViewThumbnail mThumbnailView; @ViewDebug.ExportedProperty(deepExport=true, prefix="header_") TaskViewHeader mHeaderView; private View mActionButtonView; @@ -176,8 +176,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks super(context, attrs, defStyleAttr, defStyleRes); RecentsConfiguration config = Recents.getConfiguration(); Resources res = context.getResources(); - mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize( - R.dimen.recents_task_view_shadow_rounded_corners_radius)); + mViewBounds = createOutlineProvider(); if (config.fakeShadows) { setBackground(new FakeShadowDrawable(res, config)); } @@ -207,6 +206,12 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks return mTask; } + /* Create an outline provider to clip and outline the view */ + protected AnimateableViewBounds createOutlineProvider() { + return new AnimateableViewBounds(this, mContext.getResources().getDimensionPixelSize( + R.dimen.recents_task_view_shadow_rounded_corners_radius)); + } + /** Returns the view bounds. */ AnimateableViewBounds getViewBounds() { return mViewBounds; @@ -234,7 +239,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks /** * Update the task view when the configuration changes. */ - void onConfigurationChanged() { + protected void onConfigurationChanged() { mHeaderView.onConfigurationChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index 6be169117e5f..c0cc83fa0243 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -290,14 +290,16 @@ public class TaskViewHeader extends FrameLayout R.dimen.recents_task_view_header_height, R.dimen.recents_task_view_header_height_tablet_land, R.dimen.recents_task_view_header_height, - R.dimen.recents_task_view_header_height_tablet_land); + R.dimen.recents_task_view_header_height_tablet_land, + R.dimen.recents_grid_task_view_header_height); int headerButtonPadding = TaskStackLayoutAlgorithm.getDimensionForDevice(getContext(), R.dimen.recents_task_view_header_button_padding, R.dimen.recents_task_view_header_button_padding, R.dimen.recents_task_view_header_button_padding, R.dimen.recents_task_view_header_button_padding_tablet_land, R.dimen.recents_task_view_header_button_padding, - R.dimen.recents_task_view_header_button_padding_tablet_land); + R.dimen.recents_task_view_header_button_padding_tablet_land, + R.dimen.recents_grid_task_view_header_button_padding); if (headerBarHeight != mHeaderBarHeight || headerButtonPadding != mHeaderButtonPadding) { mHeaderBarHeight = headerBarHeight; mHeaderButtonPadding = headerButtonPadding; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java index c46adf15861f..d109807c3ca4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java @@ -60,6 +60,8 @@ public class TaskViewThumbnail extends View { @ViewDebug.ExportedProperty(category="recents") private float mThumbnailScale; private float mFullscreenThumbnailScale; + private boolean mSizeToFit = false; + private boolean mOverlayHeaderOnThumbnailActionBar = true; private ActivityManager.TaskThumbnailInfo mThumbnailInfo; private int mCornerRadius; @@ -67,6 +69,7 @@ public class TaskViewThumbnail extends View { private float mDimAlpha; private Matrix mScaleMatrix = new Matrix(); private Paint mDrawPaint = new Paint(); + private Paint mLockedPaint = new Paint(); private Paint mBgFillPaint = new Paint(); private BitmapShader mBitmapShader; private LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0); @@ -102,6 +105,7 @@ public class TaskViewThumbnail extends View { mCornerRadius = getResources().getDimensionPixelSize( R.dimen.recents_task_view_rounded_corners_radius); mBgFillPaint.setColor(Color.WHITE); + mLockedPaint.setColor(Color.WHITE); mFullscreenThumbnailScale = context.getResources().getFraction( com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1); } @@ -133,10 +137,15 @@ public class TaskViewThumbnail extends View { (int) (mThumbnailRect.width() * mThumbnailScale)); int thumbnailHeight = Math.min(viewHeight, (int) (mThumbnailRect.height() * mThumbnailScale)); - if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) { - int topOffset = mTaskBar != null - ? mTaskBar.getHeight() - mCornerRadius - : 0; + + if (mTask != null && mTask.isLocked) { + canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius, + mLockedPaint); + } else if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) { + int topOffset = 0; + if (mTaskBar != null && mOverlayHeaderOnThumbnailActionBar) { + topOffset = mTaskBar.getHeight() - mCornerRadius; + } // Draw the background, there will be some small overdraw with the thumbnail if (thumbnailWidth < viewWidth) { @@ -200,11 +209,13 @@ public class TaskViewThumbnail extends View { ColorMatrixColorFilter filter = new ColorMatrixColorFilter(TMP_FILTER_COLOR_MATRIX); mDrawPaint.setColorFilter(filter); mBgFillPaint.setColorFilter(filter); + mLockedPaint.setColorFilter(filter); } else { mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul)); mDrawPaint.setColorFilter(mLightingColorFilter); mDrawPaint.setColor(0xFFffffff); mBgFillPaint.setColorFilter(mLightingColorFilter); + mLockedPaint.setColorFilter(mLightingColorFilter); } } else { int grey = mul; @@ -230,7 +241,7 @@ public class TaskViewThumbnail extends View { // If we haven't measured or the thumbnail is invalid, skip the thumbnail drawing // and only draw the background color mThumbnailScale = 0f; - } else if (isStackTask) { + } else if (isStackTask && !mSizeToFit) { float invThumbnailScale = 1f / mFullscreenThumbnailScale; if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) { if (mThumbnailInfo.screenOrientation == Configuration.ORIENTATION_PORTRAIT) { @@ -262,6 +273,19 @@ public class TaskViewThumbnail extends View { } } + /** Sets whether the thumbnail should be resized to fit the task view in all orientations. */ + public void setSizeToFit(boolean flag) { + mSizeToFit = flag; + } + + /** + * Sets whether the header should overlap (and hide) the action bar in the thumbnail, or + * be stacked just above it. + */ + public void setOverlayHeaderOnThumbnailActionBar(boolean flag) { + mOverlayHeaderOnThumbnailActionBar = flag; + } + /** Updates the clip rect based on the given task bar. */ void updateClipToTaskBar(View taskBar) { mTaskBar = taskBar; @@ -299,6 +323,7 @@ public class TaskViewThumbnail extends View { if (t.colorBackground != 0) { mBgFillPaint.setColor(t.colorBackground); } + mLockedPaint.setColor(t.colorPrimary); } /** diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java new file mode 100644 index 000000000000..a029478c2045 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java @@ -0,0 +1,32 @@ +/* + * 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.recents.views.grid; + +import android.view.View; +import com.android.systemui.recents.views.AnimateableViewBounds; + +/* An outline provider for grid-based task views. */ +class AnimateableGridViewBounds extends AnimateableViewBounds { + + public AnimateableGridViewBounds(View source, int cornerRadius) { + super(source, cornerRadius); + } + + @Override + protected void updateClipBounds() { + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java new file mode 100644 index 000000000000..630040098d10 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java @@ -0,0 +1,71 @@ +/* + * 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.recents.views.grid; + +import android.content.Context; +import android.util.AttributeSet; +import com.android.systemui.R; +import com.android.systemui.recents.views.AnimateableViewBounds; +import com.android.systemui.recents.views.TaskView; + +public class GridTaskView extends TaskView { + + /** The height, in pixels, of the header view. */ + private int mHeaderHeight; + + public GridTaskView(Context context) { + this(context, null); + } + + public GridTaskView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public GridTaskView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public GridTaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mHeaderHeight = context.getResources().getDimensionPixelSize( + R.dimen.recents_grid_task_view_header_height); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + // Show the full thumbnail and don't overlap with the header. + mThumbnailView.setSizeToFit(true); + mThumbnailView.setOverlayHeaderOnThumbnailActionBar(false); + mThumbnailView.updateThumbnailScale(); + mThumbnailView.setTranslationY(mHeaderHeight); + } + + @Override + protected AnimateableViewBounds createOutlineProvider() { + return new AnimateableGridViewBounds(this, mContext.getResources().getDimensionPixelSize( + R.dimen.recents_task_view_shadow_rounded_corners_radius)); + } + + @Override + protected void onConfigurationChanged() { + super.onConfigurationChanged(); + mHeaderHeight = mContext.getResources().getDimensionPixelSize( + R.dimen.recents_grid_task_view_header_height); + mThumbnailView.setTranslationY(mHeaderHeight); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java new file mode 100644 index 000000000000..0d39f5b269a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java @@ -0,0 +1,157 @@ +/* + * 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.recents.views.grid; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.Rect; +import android.view.WindowManager; + +import com.android.systemui.R; +import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; +import com.android.systemui.recents.views.TaskViewTransform; + +public class TaskGridLayoutAlgorithm { + + private final String TAG = "TaskGridLayoutAlgorithm"; + private final int MAX_LAYOUT_TASK_COUNT = 8; + + /** The horizontal padding around the whole recents view. */ + private int mPaddingLeftRight; + /** The vertical padding around the whole recents view. */ + private int mPaddingTopBottom; + /** The padding between task views. */ + private int mPaddingTaskView; + + private Rect mDisplayRect; + private Rect mWindowRect; + private Point mScreenSize = new Point(); + + private Rect mTaskGridRect; + + /** The height, in pixels, of each task view's title bar. */ + private int mTitleBarHeight; + + /** The aspect ratio of each task thumbnail, without the title bar. */ + private float mAppAspectRatio; + private Rect mSystemInsets = new Rect(); + + public TaskGridLayoutAlgorithm(Context context) { + reloadOnConfigurationChange(context); + } + + public void reloadOnConfigurationChange(Context context) { + Resources res = context.getResources(); + mPaddingLeftRight = res.getDimensionPixelSize(R.dimen.recents_grid_padding_left_right); + mPaddingTopBottom = res.getDimensionPixelSize(R.dimen.recents_grid_padding_top_bottom); + mPaddingTaskView = res.getDimensionPixelSize(R.dimen.recents_grid_padding_task_view); + + mTaskGridRect = new Rect(); + mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_header_height); + + WindowManager windowManager = (WindowManager) context + .getSystemService(Context.WINDOW_SERVICE); + windowManager.getDefaultDisplay().getRealSize(mScreenSize); + + updateAppAspectRatio(); + } + + public TaskViewTransform getTransform(int taskIndex, int taskCount, + TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) { + + int layoutTaskCount = Math.min(MAX_LAYOUT_TASK_COUNT, taskCount); + + // We also need to invert the index in order to display the most recent tasks first. + int taskLayoutIndex = taskCount - taskIndex - 1; + + int tasksPerLine = layoutTaskCount < 2 ? 1 : ( + layoutTaskCount < 5 ? 2 : ( + layoutTaskCount < 7 ? 3 : 4)); + int lines = layoutTaskCount < 3 ? 1 : 2; + + int taskWidth, taskHeight; + int maxTaskWidth = (mDisplayRect.width() - 2 * mPaddingLeftRight + - (tasksPerLine - 1) * mPaddingTaskView) / tasksPerLine; + int maxTaskHeight = (mDisplayRect.height() - 2 * mPaddingTopBottom + - (lines - 1) * mPaddingTaskView) / lines; + + if (maxTaskHeight >= maxTaskWidth / mAppAspectRatio + mTitleBarHeight) { + // Width bound. + taskWidth = maxTaskWidth; + taskHeight = (int) (maxTaskWidth / mAppAspectRatio + mTitleBarHeight); + } else { + // Height bound. + taskHeight = maxTaskHeight; + taskWidth = (int) ((taskHeight - mTitleBarHeight) * mAppAspectRatio); + } + int emptySpaceX = mDisplayRect.width() - 2 * mPaddingLeftRight + - (tasksPerLine * taskWidth) - (tasksPerLine - 1) * mPaddingTaskView; + int emptySpaceY = mDisplayRect.height() - 2 * mPaddingTopBottom + - (lines * taskHeight) - (lines - 1) * mPaddingTaskView; + + mTaskGridRect.set(0, 0, taskWidth, taskHeight); + + int xIndex = taskLayoutIndex % tasksPerLine; + int yIndex = taskLayoutIndex / tasksPerLine; + int x = emptySpaceX / 2 + mPaddingLeftRight + (taskWidth + mPaddingTaskView) * xIndex; + int y = emptySpaceY / 2 + mPaddingTopBottom + (taskHeight + mPaddingTaskView) * yIndex; + float z = stackLayout.mMaxTranslationZ; + + float dimAlpha = 0f; + float viewOutlineAlpha = 0f; + boolean isTaskViewVisible = (taskLayoutIndex < MAX_LAYOUT_TASK_COUNT); + + // Fill out the transform + transformOut.scale = 1f; + transformOut.alpha = isTaskViewVisible ? 1f : 0f; + transformOut.translationZ = z; + transformOut.dimAlpha = dimAlpha; + transformOut.viewOutlineAlpha = viewOutlineAlpha; + transformOut.rect.set(mTaskGridRect); + transformOut.rect.offset(x, y); + Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale); + // We only show the 8 most recent tasks. + transformOut.visible = isTaskViewVisible; + return transformOut; + } + + public void initialize(Rect displayRect, Rect windowRect) { + mDisplayRect = displayRect; + mWindowRect = windowRect; + } + + public void setSystemInsets(Rect systemInsets) { + mSystemInsets = systemInsets; + updateAppAspectRatio(); + } + + private void updateAppAspectRatio() { + int usableWidth = mScreenSize.x - mSystemInsets.left - mSystemInsets.right; + int usableHeight = mScreenSize.y - mSystemInsets.top - mSystemInsets.bottom; + mAppAspectRatio = (float) usableWidth / (float) usableHeight; + } + + public Rect getStackActionButtonRect() { + Rect buttonRect = new Rect(mDisplayRect); + buttonRect.right -= mPaddingLeftRight; + buttonRect.left += mPaddingLeftRight; + buttonRect.bottom = buttonRect.top + mPaddingTopBottom; + return buttonRect; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java index 3db03d0abf11..cb9453b8846e 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java @@ -18,6 +18,7 @@ package com.android.systemui.stackdivider; import android.content.Context; import android.graphics.PixelFormat; +import android.os.Binder; import android.view.View; import android.view.WindowManager; @@ -51,6 +52,7 @@ public class DividerWindowManager { FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY, PixelFormat.TRANSLUCENT); + mLp.token = new Binder(); mLp.setTitle(WINDOW_TITLE); mLp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION; view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index e054bbe672af..0a138b8f7bf5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -62,6 +62,7 @@ public class NotificationShelf extends ActivatableNotificationView { private boolean mHasItemsInStableShelf; private NotificationIconContainer mCollapsedIcons; private int mScrollFastThreshold; + private int mStatusBarState; public NotificationShelf(Context context, AttributeSet attrs) { super(context, attrs); @@ -523,7 +524,10 @@ public class NotificationShelf extends ActivatableNotificationView { } private void setHasItemsInStableShelf(boolean hasItemsInStableShelf) { - mHasItemsInStableShelf = hasItemsInStableShelf; + if (mHasItemsInStableShelf != hasItemsInStableShelf) { + mHasItemsInStableShelf = hasItemsInStableShelf; + updateInteractiveness(); + } } /** @@ -538,6 +542,21 @@ public class NotificationShelf extends ActivatableNotificationView { mCollapsedIcons = collapsedIcons; } + public void setStatusBarState(int statusBarState) { + if (mStatusBarState != statusBarState) { + mStatusBarState = statusBarState; + updateInteractiveness(); + } + } + + private void updateInteractiveness() { + boolean interactive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf; + setClickable(interactive); + setFocusable(interactive); + setImportantForAccessibility(interactive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES + : View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } + private class ShelfState extends ExpandableViewState { private float openedAmount; private boolean hasItemsInStableShelf; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index ae9d06888432..ba931115932e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -36,6 +36,7 @@ import android.animation.AnimatorListenerAdapter; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityOptions; +import android.app.admin.DevicePolicyManager; import android.app.IActivityManager; import android.app.Notification; import android.app.NotificationManager; @@ -74,6 +75,7 @@ import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.net.Uri; import android.os.AsyncTask; +import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; @@ -663,12 +665,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, array.clear(); } - private final View.OnClickListener mShelfClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mState == StatusBarState.KEYGUARD) { - goToLockedShade(null); - } + private final View.OnClickListener mGoToLockedShadeListener = v -> { + if (mState == StatusBarState.KEYGUARD) { + goToLockedShade(null); } }; private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap @@ -1012,6 +1011,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); + filter.addAction(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG); context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); IntentFilter demoFilter = new IntentFilter(); @@ -1061,8 +1061,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, (NotificationShelf) LayoutInflater.from(mContext).inflate( R.layout.status_bar_notification_shelf, mStackScroller, false); mNotificationShelf.setOnActivatedListener(this); - mNotificationShelf.setOnClickListener(mShelfClickListener); mStackScroller.setShelf(mNotificationShelf); + mNotificationShelf.setOnClickListener(mGoToLockedShadeListener); + mNotificationShelf.setStatusBarState(mState); } @Override @@ -1531,6 +1532,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_SLIPPERY, PixelFormat.TRANSLUCENT); + lp.token = new Binder(); // this will allow the navbar to run in an overlay on devices that support this if (ActivityManager.isHighEndGfx()) { lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; @@ -2966,7 +2968,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, runPostCollapseRunnables(); setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); - showBouncer(); + showBouncerIfKeyguard(); recomputeDisableFlags(shouldAnimatIconHiding() /* animate */); // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in @@ -3642,6 +3644,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, else if (Intent.ACTION_SCREEN_ON.equals(action)) { notifyNavigationBarScreenOn(true); } + else if (DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG.equals(action)) { + mQSPanel.showDeviceMonitoringDialog(); + } } }; @@ -4540,13 +4545,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return false; } - protected void showBouncer() { + private void showBouncerIfKeyguard() { if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { - mWaitingForKeyguardExit = mStatusBarKeyguardViewManager.isShowing(); - mStatusBarKeyguardViewManager.dismiss(); + showBouncer(); } } + protected void showBouncer() { + mWaitingForKeyguardExit = mStatusBarKeyguardViewManager.isShowing(); + mStatusBarKeyguardViewManager.dismiss(); + } + private void instantExpandNotificationsPanel() { // Make our window larger and the panel expanded. @@ -4598,6 +4607,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStackScroller.setStatusBarState(state); updateReportRejectedTouchVisibility(); updateDozing(); + mNotificationShelf.setStatusBarState(state); } @Override @@ -4644,7 +4654,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, public void onTrackingStopped(boolean expand) { if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { if (!expand && !mUnlockMethodCache.canSkipBouncer()) { - showBouncer(); + showBouncerIfKeyguard(); } } } @@ -4739,7 +4749,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, || !mShowLockscreenNotifications || mFalsingManager.shouldEnforceBouncer(); if (isLockscreenPublicMode(userId) && fullShadeNeedsBouncer) { mLeaveOpenOnKeyguardHide = true; - showBouncer(); + showBouncerIfKeyguard(); mDraggedDownRow = row; mPendingRemoteInputView = null; } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java index 567ab3b5966d..227ebdfacbda 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java @@ -165,10 +165,6 @@ public class QSTileHost implements QSTile.Host, Tunable { mHeader = view; } - public PhoneStatusBar getPhoneStatusBar() { - return mStatusBar; - } - public void destroy() { mHandlerThread.quitSafely(); mTiles.values().forEach(tile -> tile.destroy()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java index 29c07053e169..06600541da71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Resources; import android.graphics.PixelFormat; +import android.os.Binder; import android.os.RemoteException; import android.os.SystemProperties; import android.os.Trace; @@ -97,6 +98,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback { | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, PixelFormat.TRANSLUCENT); + mLp.token = new Binder(); mLp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; mLp.gravity = Gravity.TOP; mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; diff --git a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java b/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java index f42092130af1..5cfe677efd57 100644 --- a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java @@ -81,11 +81,23 @@ public class LayoutInflaterBuilder { * @return Builder object post-modification. */ public LayoutInflaterBuilder replace(@NonNull Class from, @NonNull Class to) { + return replace(from.getName(), to); + } + + /** + * Instructs the Builder to configure the LayoutInflater such that all instances + * of one {@link View} will be replaced with instances of another during inflation. + * + * @param tag Instances of this tag will be replaced during inflation. + * @param to Instances of this class will be inflated as replacements. + * @return Builder object post-modification. + */ + public LayoutInflaterBuilder replace(@NonNull String tag, @NonNull Class to) { assertIfAlreadyBuilt(); if (mReplaceMap == null) { mReplaceMap = new ArrayMap<String, String>(); } - mReplaceMap.put(from.getName(), to.getName()); + mReplaceMap.put(tag, to.getName()); return this; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/SpTexts.java b/packages/SystemUI/src/com/android/systemui/volume/ConfigurableTexts.java index d8e53dbcbfae..20de5bb33462 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/SpTexts.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ConfigurableTexts.java @@ -25,19 +25,23 @@ import android.view.View.OnAttachStateChangeListener; import android.widget.TextView; /** - * Capture initial sp values for registered textviews, and update properly when configuration - * changes. + * Class for updating textviews on configuration change. */ -public class SpTexts { +public class ConfigurableTexts { private final Context mContext; private final ArrayMap<TextView, Integer> mTexts = new ArrayMap<>(); + private final ArrayMap<TextView, Integer> mTextLabels = new ArrayMap<>(); - public SpTexts(Context context) { + public ConfigurableTexts(Context context) { mContext = context; } public int add(final TextView text) { + return add(text, -1); + } + + public int add(final TextView text, final int labelResId) { if (text == null) return 0; final Resources res = mContext.getResources(); final float fontScale = res.getConfiguration().fontScale; @@ -55,6 +59,7 @@ public class SpTexts { setTextSizeH(text, sp); } }); + mTextLabels.put(text, labelResId); return sp; } @@ -67,12 +72,25 @@ public class SpTexts { text.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp); } + private void setTextLabelH(TextView text, int labelResId) { + try { + if (labelResId >= 0) { + Util.setText(text, mContext.getString(labelResId)); + } + } catch (Resources.NotFoundException e) { + // oh well. + } + } + private final Runnable mUpdateAll = new Runnable() { @Override public void run() { for (int i = 0; i < mTexts.size(); i++) { setTextSizeH(mTexts.keyAt(i), mTexts.valueAt(i)); } + for (int i = 0; i < mTextLabels.size(); i++) { + setTextLabelH(mTextLabels.keyAt(i), mTextLabels.valueAt(i)); + } } }; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java index c6e4356aa420..89bc75760b4b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java +++ b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java @@ -36,7 +36,7 @@ public class SegmentedButtons extends LinearLayout { private final Context mContext; protected final LayoutInflater mInflater; - private final SpTexts mSpTexts; + private final ConfigurableTexts mConfigurableTexts; private Callback mCallback; protected Object mSelectedValue; @@ -46,7 +46,7 @@ public class SegmentedButtons extends LinearLayout { mContext = context; mInflater = LayoutInflater.from(mContext); setOrientation(HORIZONTAL); - mSpTexts = new SpTexts(mContext); + mConfigurableTexts = new ConfigurableTexts(mContext); } public void setCallback(Callback callback) { @@ -97,15 +97,11 @@ public class SegmentedButtons extends LinearLayout { fireInteraction(); } }); - mSpTexts.add(b); + mConfigurableTexts.add(b, labelResId); } - public void updateLocale() { - for (int i = 0; i < getChildCount(); i++) { - final Button b = (Button) getChildAt(i); - final int labelResId = (Integer) b.getTag(LABEL_RES_KEY); - b.setText(labelResId); - } + public void update() { + mConfigurableTexts.update(); } private void fireOnSelected(boolean fromClick) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java index 42b06b2b52c9..d23ebc123e17 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java @@ -46,6 +46,7 @@ import android.transition.Transition; import android.transition.TransitionManager; import android.util.DisplayMetrics; import android.util.Log; +import android.util.Slog; import android.util.SparseBooleanArray; import android.view.Gravity; import android.view.MotionEvent; @@ -109,7 +110,7 @@ public class VolumeDialog implements TunerService.Tunable { private ViewGroup mDialogContentView; private ImageButton mExpandButton; private final List<VolumeRow> mRows = new ArrayList<>(); - private SpTexts mSpTexts; + private ConfigurableTexts mConfigurableTexts; private final SparseBooleanArray mDynamic = new SparseBooleanArray(); private final KeyguardManager mKeyguard; private final AudioManager mAudioManager; @@ -128,7 +129,6 @@ public class VolumeDialog implements TunerService.Tunable { private boolean mExpanded; private int mActiveStream; - private boolean mShowHeaders = VolumePrefs.DEFAULT_SHOW_HEADERS; private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE; private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE; private State mState; @@ -173,7 +173,7 @@ public class VolumeDialog implements TunerService.Tunable { private void initDialog() { mDialog = new CustomDialog(mContext); - mSpTexts = new SpTexts(mContext); + mConfigurableTexts = new ConfigurableTexts(mContext); mHovering = false; mShowing = false; mWindow = mDialog.getWindow(); @@ -294,12 +294,6 @@ public class VolumeDialog implements TunerService.Tunable { mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget(); } - public void setShowHeaders(boolean showHeaders) { - if (showHeaders == mShowHeaders) return; - mShowHeaders = showHeaders; - mHandler.sendEmptyMessage(H.RECHECK_ALL); - } - public void setAutomute(boolean automute) { if (mAutomute == automute) return; mAutomute = automute; @@ -357,7 +351,6 @@ public class VolumeDialog implements TunerService.Tunable { writer.println(mExpandButtonAnimationRunning); writer.print(" mActiveStream: "); writer.println(mActiveStream); writer.print(" mDynamic: "); writer.println(mDynamic); - writer.print(" mShowHeaders: "); writer.println(mShowHeaders); writer.print(" mAutomute: "); writer.println(mAutomute); writer.print(" mSilentMode: "); writer.println(mSilentMode); writer.print(" mCollapseTime: "); writer.println(mCollapseTime); @@ -385,11 +378,9 @@ public class VolumeDialog implements TunerService.Tunable { row.view.setTag(row); row.header = (TextView) row.view.findViewById(R.id.volume_row_header); row.header.setId(20 * row.stream); - mSpTexts.add(row.header); row.slider = (SeekBar) row.view.findViewById(R.id.volume_row_slider); row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row)); row.anim = null; - row.cachedShowHeaders = VolumePrefs.DEFAULT_SHOW_HEADERS; // forward events above the slider into the slider row.view.setOnTouchListener(new OnTouchListener() { @@ -617,8 +608,8 @@ public class VolumeDialog implements TunerService.Tunable { final boolean isActive = row == activeRow; final boolean shouldBeVisible = shouldBeVisibleH(row, isActive); Util.setVisOrGone(row.view, shouldBeVisible); + Util.setVisOrGone(row.header, shouldBeVisible); if (row.view.isShown()) { - updateVolumeRowHeaderVisibleH(row); updateVolumeRowSliderTintH(row, isActive); } } @@ -731,11 +722,9 @@ public class VolumeDialog implements TunerService.Tunable { row.slider.setMax(max); } - // update header visible - updateVolumeRowHeaderVisibleH(row); - // update header text - Util.setText(row.header, ss.name); + Util.setText(row.header, getStreamLabelH(ss)); + mConfigurableTexts.add(row.header, ss.name); // update icon final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted; @@ -768,31 +757,31 @@ public class VolumeDialog implements TunerService.Tunable { if (isRingVibrate) { row.icon.setContentDescription(mContext.getString( R.string.volume_stream_content_description_unmute, - ss.name)); + getStreamLabelH(ss))); } else { if (mController.hasVibrator()) { row.icon.setContentDescription(mContext.getString( R.string.volume_stream_content_description_vibrate, - ss.name)); + getStreamLabelH(ss))); } else { row.icon.setContentDescription(mContext.getString( R.string.volume_stream_content_description_mute, - ss.name)); + getStreamLabelH(ss))); } } } else { if (ss.muted || mAutomute && ss.level == 0) { row.icon.setContentDescription(mContext.getString( R.string.volume_stream_content_description_unmute, - ss.name)); + getStreamLabelH(ss))); } else { row.icon.setContentDescription(mContext.getString( R.string.volume_stream_content_description_mute, - ss.name)); + getStreamLabelH(ss))); } } } else { - row.icon.setContentDescription(ss.name); + row.icon.setContentDescription(getStreamLabelH(ss)); } // update slider @@ -802,15 +791,6 @@ public class VolumeDialog implements TunerService.Tunable { updateVolumeRowSliderH(row, enableSlider, vlevel); } - private void updateVolumeRowHeaderVisibleH(VolumeRow row) { - final boolean dynamic = row.ss != null && row.ss.dynamic; - final boolean showHeaders = mExpanded && (mShowHeaders || dynamic); - if (row.cachedShowHeaders != showHeaders) { - row.cachedShowHeaders = showHeaders; - Util.setVisOrGone(row.header, showHeaders); - } - } - private void updateVolumeRowSliderTintH(VolumeRow row, boolean isActive) { if (isActive && mExpanded) { row.slider.requestFocus(); @@ -920,6 +900,18 @@ public class VolumeDialog implements TunerService.Tunable { rescheduleTimeoutH(); } + private String getStreamLabelH(StreamState ss) { + if (ss.remoteLabel != null) { + return ss.remoteLabel; + } + try { + return mContext.getString(ss.name); + } catch (Resources.NotFoundException e) { + Slog.e(TAG, "Can't find translation for stream " + ss); + return ""; + } + } + private AutoTransition getTransistion() { AutoTransition transition = new AutoTransition(); transition.setDuration(mExpandButtonAnimationDuration); @@ -995,7 +987,7 @@ public class VolumeDialog implements TunerService.Tunable { mDensity = density; } updateWindowWidthH(); - mSpTexts.update(); + mConfigurableTexts.update(); mZenFooter.onConfigurationChanged(); } @@ -1125,7 +1117,7 @@ public class VolumeDialog implements TunerService.Tunable { if (mShowing) { event.getText().add(mContext.getString( R.string.volume_dialog_accessibility_shown_message, - getActiveRow().ss.name)); + getStreamLabelH(getActiveRow().ss))); return true; } } @@ -1253,7 +1245,6 @@ public class VolumeDialog implements TunerService.Tunable { private int cachedIconRes; private ColorStateList cachedSliderTint; private int iconState; // from Events - private boolean cachedShowHeaders = VolumePrefs.DEFAULT_SHOW_HEADERS; private ObjectAnimator anim; // slider progress animation for non-touch-related updates private int animTargetProgress; private int lastAudibleLevel = 1; diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java index bb5632b43d4a..17dd8d63b380 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java @@ -16,6 +16,7 @@ package com.android.systemui.volume; +import android.annotation.IntegerRes; import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -41,6 +42,7 @@ import android.os.RemoteException; import android.os.Vibrator; import android.provider.Settings; import android.service.notification.Condition; +import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; @@ -67,19 +69,20 @@ public class VolumeDialogController { private static final int DYNAMIC_STREAM_START_INDEX = 100; private static final int VIBRATE_HINT_DURATION = 50; - private static final int[] STREAMS = { - AudioSystem.STREAM_ALARM, - AudioSystem.STREAM_BLUETOOTH_SCO, - AudioSystem.STREAM_DTMF, - AudioSystem.STREAM_MUSIC, - AudioSystem.STREAM_NOTIFICATION, - AudioSystem.STREAM_RING, - AudioSystem.STREAM_SYSTEM, - AudioSystem.STREAM_SYSTEM_ENFORCED, - AudioSystem.STREAM_TTS, - AudioSystem.STREAM_VOICE_CALL, - AudioSystem.STREAM_ACCESSIBILITY, - }; + private static final ArrayMap<Integer, Integer> STREAMS = new ArrayMap<>(); + static { + STREAMS.put(AudioSystem.STREAM_ALARM, R.string.stream_alarm); + STREAMS.put(AudioSystem.STREAM_BLUETOOTH_SCO, R.string.stream_bluetooth_sco); + STREAMS.put(AudioSystem.STREAM_DTMF, R.string.stream_dtmf); + STREAMS.put(AudioSystem.STREAM_MUSIC, R.string.stream_music); + STREAMS.put(AudioSystem.STREAM_NOTIFICATION, R.string.stream_notification); + STREAMS.put(AudioSystem.STREAM_RING, R.string.stream_ring); + STREAMS.put(AudioSystem.STREAM_SYSTEM, R.string.stream_system); + STREAMS.put(AudioSystem.STREAM_SYSTEM_ENFORCED, R.string.stream_system_enforced); + STREAMS.put(AudioSystem.STREAM_TTS, R.string.stream_tts); + STREAMS.put(AudioSystem.STREAM_VOICE_CALL, R.string.stream_voice_call); + STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility); + } private final HandlerThread mWorkerThread; private final W mWorker; @@ -92,7 +95,6 @@ public class VolumeDialogController { private final MediaSessions mMediaSessions; private final C mCallbacks = new C(); private final State mState = new State(); - private final String[] mStreamTitles; private final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks(); private final Vibrator mVibrator; private final boolean mHasVibrator; @@ -120,26 +122,6 @@ public class VolumeDialogController { mObserver = new SettingObserver(mWorker); mObserver.init(); mReceiver.init(); - final String[] titles = - mContext.getResources().getStringArray(R.array.volume_stream_titles); - if (STREAMS.length == titles.length) { - mStreamTitles = titles; - } else if (STREAMS.length > titles.length) { - Log.e(TAG, String.format("Missing stream titles (found %d, expected %d): " - + " invalid resources for volume_stream_titles", - titles.length, STREAMS.length)); - mStreamTitles = new String[STREAMS.length]; - System.arraycopy(titles, 0, mStreamTitles, 0, titles.length); - for (int i = titles.length ; i < STREAMS.length ; i++) { - mStreamTitles[i] = ""; - } - } else { // STREAMS.length < titles.length - Log.e(TAG, String.format("Too many stream titles (found %d, expected %d): " - + " invalid resources for volume_stream_titles", - titles.length, STREAMS.length)); - mStreamTitles = new String[STREAMS.length]; - System.arraycopy(titles, 0, mStreamTitles, 0, STREAMS.length); - } mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); mHasVibrator = mVibrator != null && mVibrator.hasVibrator(); } @@ -385,14 +367,14 @@ public class VolumeDialogController { } private void onGetStateW() { - for (int stream : STREAMS) { + for (int stream : STREAMS.keySet()) { updateStreamLevelW(stream, getAudioManagerStreamVolume(stream)); streamStateW(stream).levelMin = getAudioManagerStreamMinVolume(stream); streamStateW(stream).levelMax = getAudioManagerStreamMaxVolume(stream); updateStreamMuteW(stream, mAudio.isStreamMute(stream)); final StreamState ss = streamStateW(stream); ss.muteSupported = mAudio.isStreamAffectedByMute(stream); - ss.name = mStreamTitles[stream]; + ss.name = STREAMS.get(stream); checkRoutedToBluetoothW(stream); } updateRingerModeExternalW(mAudio.getRingerMode()); @@ -912,8 +894,9 @@ public class VolumeDialogController { ss.level = pi.getCurrentVolume(); changed = true; } - if (!Objects.equals(ss.name, name)) { - ss.name = name; + if (!Objects.equals(ss.remoteLabel, name)) { + ss.name = -1; + ss.remoteLabel = name; changed = true; } if (changed) { @@ -975,7 +958,8 @@ public class VolumeDialogController { public int levelMax; public boolean muted; public boolean muteSupported; - public String name; + public @IntegerRes int name; + public String remoteLabel; public boolean routedToBluetooth; public StreamState copy() { @@ -987,6 +971,7 @@ public class VolumeDialogController { rt.muted = muted; rt.muteSupported = muteSupported; rt.name = name; + rt.remoteLabel = remoteLabel; rt.routedToBluetooth = routedToBluetooth; return rt; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java index 995ecaed6ecf..4e4832c5f33a 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java @@ -38,7 +38,7 @@ public class ZenFooter extends LinearLayout { private static final String TAG = Util.logTag(ZenFooter.class); private final Context mContext; - private final SpTexts mSpTexts; + private final ConfigurableTexts mConfigurableTexts; private ImageView mIcon; private TextView mSummaryLine1; @@ -51,7 +51,7 @@ public class ZenFooter extends LinearLayout { public ZenFooter(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; - mSpTexts = new SpTexts(mContext); + mConfigurableTexts = new ConfigurableTexts(mContext); final LayoutTransition layoutTransition = new LayoutTransition(); layoutTransition.setDuration(new ValueAnimator().getDuration() / 2); setLayoutTransition(layoutTransition); @@ -64,9 +64,9 @@ public class ZenFooter extends LinearLayout { mSummaryLine1 = (TextView) findViewById(R.id.volume_zen_summary_line_1); mSummaryLine2 = (TextView) findViewById(R.id.volume_zen_summary_line_2); mEndNowButton = (TextView) findViewById(R.id.volume_zen_end_now); - mSpTexts.add(mSummaryLine1); - mSpTexts.add(mSummaryLine2); - mSpTexts.add(mEndNowButton); + mConfigurableTexts.add(mSummaryLine1); + mConfigurableTexts.add(mSummaryLine2); + mConfigurableTexts.add(mEndNowButton, R.string.volume_zen_end_now); } public void init(final ZenModeController controller) { @@ -130,8 +130,7 @@ public class ZenFooter extends LinearLayout { } public void onConfigurationChanged() { - Util.setText(mEndNowButton, mContext.getString(R.string.volume_zen_end_now)); - mSpTexts.update(); + mConfigurableTexts.update(); } private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java index 12a00e9fe854..98e89e46964f 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java @@ -92,7 +92,7 @@ public class ZenModePanel extends LinearLayout { private final ZenPrefs mPrefs; private final TransitionHelper mTransitionHelper = new TransitionHelper(); private final Uri mForeverId; - private final SpTexts mSpTexts; + private final ConfigurableTexts mConfigurableTexts; private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this)); @@ -131,7 +131,7 @@ public class ZenModePanel extends LinearLayout { mPrefs = new ZenPrefs(); mInflater = LayoutInflater.from(mContext.getApplicationContext()); mForeverId = Condition.newId(mContext).appendPath("forever").build(); - mSpTexts = new SpTexts(mContext); + mConfigurableTexts = new ConfigurableTexts(mContext); mVoiceCapable = Util.isVoiceCapable(mContext); mZenModeConditionLayoutId = R.layout.zen_mode_condition; mZenModeButtonLayoutId = R.layout.zen_mode_button; @@ -175,7 +175,6 @@ public class ZenModePanel extends LinearLayout { createZenButtons(); mZenIntroduction = findViewById(R.id.zen_introduction); mZenIntroductionMessage = (TextView) findViewById(R.id.zen_introduction_message); - mSpTexts.add(mZenIntroductionMessage); mZenIntroductionConfirm = findViewById(R.id.zen_introduction_confirm); mZenIntroductionConfirm.setOnClickListener(new OnClickListener() { @Override @@ -193,7 +192,7 @@ public class ZenModePanel extends LinearLayout { } } }); - mSpTexts.add(mZenIntroductionCustomize); + mConfigurableTexts.add(mZenIntroductionCustomize, R.string.zen_priority_customize_button); mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions); mZenAlarmWarning = (TextView) findViewById(R.id.zen_alarm_warning); @@ -204,11 +203,9 @@ public class ZenModePanel extends LinearLayout { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + mConfigurableTexts.update(); if (mZenButtons != null) { - mZenButtons.updateLocale(); - } - if (mZenIntroductionCustomize != null) { - mZenIntroductionCustomize.setText(R.string.zen_priority_customize_button); + mZenButtons.update(); } } @@ -341,10 +338,6 @@ public class ZenModePanel extends LinearLayout { hideAllConditions(); } - public void updateLocale() { - mZenButtons.updateLocale(); - } - private void setExitCondition(Condition exitCondition) { if (Objects.equals(mExitCondition, exitCondition)) return; mExitCondition = exitCondition; @@ -439,9 +432,11 @@ public class ZenModePanel extends LinearLayout { mZenButtons.setVisibility(mHidden ? GONE : VISIBLE); mZenIntroduction.setVisibility(introduction ? VISIBLE : GONE); if (introduction) { - mZenIntroductionMessage.setText(zenImportant ? R.string.zen_priority_introduction + mConfigurableTexts.add(mZenIntroductionMessage, zenImportant + ? R.string.zen_priority_introduction : mVoiceCapable ? R.string.zen_silence_introduction_voice : R.string.zen_silence_introduction); + mConfigurableTexts.update(); mZenIntroductionCustomize.setVisibility(zenImportant ? VISIBLE : GONE); } final String warning = computeAlarmWarningText(zenNone); @@ -655,11 +650,11 @@ public class ZenModePanel extends LinearLayout { } if (tag.line1 == null) { tag.line1 = (TextView) row.findViewById(android.R.id.text1); - mSpTexts.add(tag.line1); + mConfigurableTexts.add(tag.line1); } if (tag.line2 == null) { tag.line2 = (TextView) row.findViewById(android.R.id.text2); - mSpTexts.add(tag.line2); + mConfigurableTexts.add(tag.line2); } final String line1 = !TextUtils.isEmpty(condition.line1) ? condition.line1 : condition.summary; diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk index ecf174ff4b0b..e96ea19f1236 100644 --- a/packages/SystemUI/tests/Android.mk +++ b/packages/SystemUI/tests/Android.mk @@ -44,7 +44,7 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \ LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-test \ - mockito-target-minus-junit4 \ + mockito-updated-target-minus-junit4 \ SystemUI-proto \ SystemUI-tags diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java index 008580ad6944..d1d752099d19 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java @@ -37,6 +37,7 @@ public abstract class SysuiTestCase { @Before public void SysuiSetup() throws Exception { + System.setProperty("dexmaker.share_classloader", "true"); mContext = new TestableContext(InstrumentationRegistry.getTargetContext(), this); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index 8b99d725fd73..c3948258f11c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -214,6 +214,16 @@ public class DozeMachineTest { @Test @UiThreadTest + public void testSuppressingPulse_doesntCrash() { + mMachine.requestState(INITIALIZED); + + mMachine.requestState(DOZE); + mMachine.requestState(DOZE_REQUEST_PULSE); + mMachine.requestState(DOZE_PULSE_DONE); + } + + @Test + @UiThreadTest public void testScreen_offInDoze() { mMachine.requestState(INITIALIZED); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java index 4c25c62e82a3..8acd6ba5e1d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java @@ -14,37 +14,31 @@ package com.android.systemui.qs; +import static junit.framework.Assert.assertEquals; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import android.content.Context; -import android.content.res.Resources; +import android.os.Handler; import android.os.Looper; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import android.text.SpannableStringBuilder; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; import android.widget.TextView; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.policy.SecurityController; +import com.android.systemui.util.LayoutInflaterBuilder; +import com.android.systemui.utils.TestableImageView; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import static junit.framework.Assert.assertEquals; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - @SmallTest @RunWith(AndroidJUnit4.class) public class QSFooterTest extends SysuiTestCase { @@ -53,28 +47,26 @@ public class QSFooterTest extends SysuiTestCase { private final String DEVICE_OWNER_PACKAGE = "TestDPC"; private final String VPN_PACKAGE = "TestVPN"; - private ViewGroup mRootView = mock(ViewGroup.class); - private TextView mFooterText = mock(TextView.class); - private ImageView mFooterIcon = mock(ImageView.class); - private ImageView mFooterIcon2 = mock(ImageView.class); + private ViewGroup mRootView; + private TextView mFooterText; + private TestableImageView mFooterIcon; + private TestableImageView mFooterIcon2; private QSFooter mFooter; - private Resources mResources; private SecurityController mSecurityController = mock(SecurityController.class); @Before public void setUp() { - when(mRootView.findViewById(R.id.footer_text)).thenReturn(mFooterText); - when(mRootView.findViewById(R.id.footer_icon)).thenReturn(mFooterIcon); - when(mRootView.findViewById(R.id.footer_icon2)).thenReturn(mFooterIcon2); - final LayoutInflater layoutInflater = mock(LayoutInflater.class); - when(layoutInflater.inflate(eq(R.layout.quick_settings_footer), anyObject(), anyBoolean())) - .thenReturn(mRootView); - final Context context = mock(Context.class); - when(context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).thenReturn(layoutInflater); - mResources = mContext.getResources(); - when(context.getResources()).thenReturn(mResources); - mFooter = new QSFooter(null, context); - reset(mRootView); + mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, + new LayoutInflaterBuilder(mContext) + .replace("ImageView", TestableImageView.class) + .build()); + Handler h = new Handler(Looper.getMainLooper()); + h.post(() -> mFooter = new QSFooter(null, mContext)); + waitForIdleSync(h); + mRootView = (ViewGroup) mFooter.getView(); + mFooterText = (TextView) mRootView.findViewById(R.id.footer_text); + mFooterIcon = (TestableImageView) mRootView.findViewById(R.id.footer_icon); + mFooterIcon2 = (TestableImageView) mRootView.findViewById(R.id.footer_icon2); mFooter.setHostEnvironment(null, mSecurityController, Looper.getMainLooper()); } @@ -86,8 +78,7 @@ public class QSFooterTest extends SysuiTestCase { mFooter.refreshState(); waitForIdleSync(mFooter.mHandler); - verify(mRootView).setVisibility(View.GONE); - verifyNoMoreInteractions(mRootView); + assertEquals(View.GONE, mRootView.getVisibility()); } @Test @@ -97,10 +88,8 @@ public class QSFooterTest extends SysuiTestCase { mFooter.refreshState(); waitForIdleSync(mFooter.mHandler); - verify(mFooterText).setText(mResources.getString(R.string.do_disclosure_generic)); - verifyNoMoreInteractions(mFooterText); - verify(mRootView).setVisibility(View.VISIBLE); - verifyNoMoreInteractions(mRootView); + assertEquals(mContext.getString(R.string.do_disclosure_generic), mFooterText.getText()); + assertEquals(View.VISIBLE, mRootView.getVisibility()); } @Test @@ -111,11 +100,9 @@ public class QSFooterTest extends SysuiTestCase { mFooter.refreshState(); waitForIdleSync(mFooter.mHandler); - verify(mFooterText).setText(mResources.getString(R.string.do_disclosure_with_name, - MANAGING_ORGANIZATION)); - verifyNoMoreInteractions(mFooterText); - verify(mRootView).setVisibility(View.VISIBLE); - verifyNoMoreInteractions(mRootView); + assertEquals(mContext.getString(R.string.do_disclosure_with_name, MANAGING_ORGANIZATION), + mFooterText.getText()); + assertEquals(View.VISIBLE, mRootView.getVisibility()); } @Test @@ -126,9 +113,9 @@ public class QSFooterTest extends SysuiTestCase { mFooter.refreshState(); waitForIdleSync(mFooter.mHandler); - verify(mFooterIcon).setVisibility(View.VISIBLE); - verify(mFooterIcon).setImageResource(R.drawable.ic_qs_network_logging); - verify(mFooterIcon2).setVisibility(View.INVISIBLE); + assertEquals(View.VISIBLE, mFooterIcon.getVisibility()); + assertEquals(R.drawable.ic_qs_network_logging, mFooterIcon.getLastImageResource()); + assertEquals(View.INVISIBLE, mFooterIcon2.getVisibility()); } @Test @@ -140,9 +127,10 @@ public class QSFooterTest extends SysuiTestCase { mFooter.refreshState(); waitForIdleSync(mFooter.mHandler); - verify(mFooterIcon).setVisibility(View.VISIBLE); - verify(mFooterIcon, never()).setImageResource(anyInt()); - verify(mFooterIcon2).setVisibility(View.INVISIBLE); + assertEquals(View.VISIBLE, mFooterIcon.getVisibility()); + // -1 == never set. + assertEquals(-1, mFooterIcon.getLastImageResource()); + assertEquals(View.INVISIBLE, mFooterIcon2.getVisibility()); } @Test @@ -154,10 +142,11 @@ public class QSFooterTest extends SysuiTestCase { mFooter.refreshState(); waitForIdleSync(mFooter.mHandler); - verify(mFooterIcon).setVisibility(View.VISIBLE); - verify(mFooterIcon, never()).setImageResource(anyInt()); - verify(mFooterIcon2).setVisibility(View.VISIBLE); - verify(mFooterIcon2, never()).setImageResource(anyInt()); + assertEquals(View.VISIBLE, mFooterIcon.getVisibility()); + assertEquals(View.VISIBLE, mFooterIcon2.getVisibility()); + // -1 == never set. + assertEquals(-1, mFooterIcon.getLastImageResource()); + assertEquals(-1, mFooterIcon2.getLastImageResource()); } @Test @@ -211,20 +200,15 @@ public class QSFooterTest extends SysuiTestCase { private CharSequence getExpectedMessage(boolean hasDeviceOwnerOrganization, boolean hasVPN) { final SpannableStringBuilder message = new SpannableStringBuilder(); message.append(hasDeviceOwnerOrganization ? - mResources.getString(R.string.monitoring_description_do_header_with_name, + mContext.getString(R.string.monitoring_description_do_header_with_name, MANAGING_ORGANIZATION, DEVICE_OWNER_PACKAGE) : - mResources.getString(R.string.monitoring_description_do_header_generic, + mContext.getString(R.string.monitoring_description_do_header_generic, DEVICE_OWNER_PACKAGE)); message.append("\n\n"); - message.append(mResources.getString(R.string.monitoring_description_do_body)); - if (hasVPN) { - message.append("\n\n"); - message.append(mResources.getString(R.string.monitoring_description_do_body_vpn, - VPN_PACKAGE)); - } - message.append(mResources.getString( + message.append(mContext.getString(R.string.monitoring_description_do_body)); + message.append(mContext.getString( R.string.monitoring_description_do_learn_more_separator)); - message.append(mResources.getString(R.string.monitoring_description_do_learn_more), + message.append(mContext.getString(R.string.monitoring_description_do_learn_more), mFooter.new EnterprisePrivacySpan(), 0); return message; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index 350a95ff66f4..c0d5bbd35690 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -63,7 +63,7 @@ public class QSFragmentTest extends FragmentTestCase { when(userSwitcher.getKeyguardMonitor()).thenReturn(keyguardMonitor); when(userSwitcher.getUsers()).thenReturn(new ArrayList<>()); QSTileHost host = new QSTileHost(mContext, - mock(PhoneStatusBar.class), + null, getLeakChecker(BluetoothController.class), getLeakChecker(LocationController.class), getLeakChecker(RotationLockController.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index 782a4890ba55..66ec7dd3f270 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -15,8 +15,10 @@ */ package com.android.systemui.qs.external; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; @@ -26,9 +28,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.content.pm.PackageInfo; import android.content.pm.ServiceInfo; import android.net.Uri; @@ -50,15 +50,12 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; @SmallTest @RunWith(AndroidJUnit4.class) public class TileLifecycleManagerTest extends SysuiTestCase { private static final int TEST_FAIL_TIMEOUT = 5000; - private final Context mMockContext = Mockito.mock(Context.class); private final PackageManagerAdapter mMockPackageManagerAdapter = Mockito.mock(PackageManagerAdapter.class); private final IQSTileService.Stub mMockTileService = Mockito.mock(IQSTileService.Stub.class); @@ -77,19 +74,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { // Stub.asInterface will just return itself. when(mMockTileService.queryLocalInterface(anyString())).thenReturn(mMockTileService); - // Default behavior for bind is success and connects as mMockTileService. - when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())) - .thenAnswer( - new Answer<Boolean>() { - @Override - public Boolean answer(InvocationOnMock invocation) { - ServiceConnection connection = - (ServiceConnection) invocation.getArguments()[1]; - connection.onServiceConnected( - mTileServiceComponentName, mMockTileService); - return true; - } - }); + mContext.addMockService(mTileServiceComponentName, mMockTileService); mTileServiceIntent = new Intent().setComponent(mTileServiceComponentName); @@ -97,7 +82,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mThread = new HandlerThread("TestThread"); mThread.start(); mHandler = new Handler(mThread.getLooper()); - mStateManager = new TileLifecycleManager(mHandler, mMockContext, + mStateManager = new TileLifecycleManager(mHandler, mContext, Mockito.mock(IQSService.class), new Tile(), mTileServiceIntent, mUser, @@ -126,11 +111,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { } private void verifyBind(int times) { - verify(mMockContext, times(times)).bindServiceAsUser( - mTileServiceIntent, - mStateManager, - Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, - mUser); + assertEquals(times > 0, mContext.isBound(mTileServiceComponentName)); } @Test @@ -143,7 +124,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { public void testUnbind() { mStateManager.setBindService(true); mStateManager.setBindService(false); - verify(mMockContext).unbindService(mStateManager); + assertFalse(mContext.isBound(mTileServiceComponentName)); } @Test @@ -203,7 +184,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { verifyBind(1); mStateManager.setBindService(false); - verify(mMockContext).unbindService(mStateManager); + assertFalse(mContext.isBound(mTileServiceComponentName)); verify(mMockTileService, never()).onStartListening(); } @@ -217,7 +198,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { verifyBind(1); mStateManager.setBindService(false); - verify(mMockContext).unbindService(mStateManager); + assertFalse(mContext.isBound(mTileServiceComponentName)); verify(mMockTileService, never()).onClick(null); } @@ -233,7 +214,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { // Package is re-enabled. setPackageEnabled(true); mStateManager.onReceive( - mMockContext, + mContext, new Intent( Intent.ACTION_PACKAGE_CHANGED, Uri.fromParts( diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithmTest.java deleted file mode 100644 index a9f811b2cee6..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithmTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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.recents.grid; - -import android.graphics.Rect; -import android.test.suitebuilder.annotation.SmallTest; -import com.android.systemui.SysuiTestCase; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertTrue; - -import org.junit.Test; - -@SmallTest -public class TaskGridLayoutAlgorithmTest extends SysuiTestCase { - - private static final List<Integer> ZERO_MARGIN = TaskGridLayoutAlgorithm.ZERO_MARGIN; - - @Test - public void testOneTile() { - List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - 1, 1000, 1000, 1 /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0); - assertEquals(1, rects.size()); - Rect singleRect = rects.get(0); - assertEquals(1000, singleRect.width()); - } - - @Test - public void testTwoTilesLandscape() { - List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - 2, 1200, 500, 1.2f /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0); - assertEquals(2, rects.size()); - for (Rect rect : rects) { - assertEquals(600, rect.width()); - assertEquals(499, rect.height()); - } - } - - @Test - public void testTwoTilesLandscapeWithPadding() { - List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - 2, 1200, 500, 1.19f /* screenRatio */, 10 /* padding */, ZERO_MARGIN, 0); - assertEquals(2, rects.size()); - Rect rectA = rects.get(0); - Rect rectB = rects.get(1); - assertEquals(595, rectA.width()); - assertEquals(595, rectB.width()); - assertEquals(605, rectB.left); - } - - @Test - public void testTwoTilesPortrait() { - List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - 2, 500, 1200, 1 /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0); - assertEquals(2, rects.size()); - for (Rect rect : rects) { - assertEquals(250, rect.width()); - assertEquals(250, rect.height()); - } - } - - @Test - public void testThreeTiles() { - List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - 3, 1200, 500, 2 /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0); - assertEquals(3, rects.size()); - for (Rect rect : rects) { - assertEquals(400, rect.width()); - assertEquals(200, rect.height()); - } - } - - @Test - public void testFourTiles() { - List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - 4, 1200, 500, 2.4f /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0); - assertEquals(4, rects.size()); - for (Rect rect : rects) { - assertEquals(600, rect.width()); - assertEquals(249, rect.height()); - } - Rect rectD = rects.get(3); - assertEquals(600, rectD.left); - assertEquals(250, rectD.top); - } - - @Test - public void testNineTiles() { - List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - 9, 1200, 600, 2 /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0); - assertEquals(9, rects.size()); - for (Rect rect : rects) { - assertEquals(400, rect.width()); - assertEquals(200, rect.height()); - } - Rect rectE = rects.get(4); - assertEquals(400, rectE.left); - assertEquals(200, rectE.top); - Rect rectI = rects.get(8); - assertEquals(800, rectI.left); - assertEquals(400, rectI.top); - }} - diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 639c8daf586b..7335af3fff9d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -16,15 +16,18 @@ package com.android.systemui.statusbar; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; -import android.content.ContentResolver; import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Resources; +import android.hardware.fingerprint.FingerprintManager; import android.os.Looper; import android.support.test.runner.AndroidJUnit4; -import android.telephony.SubscriptionManager; import android.test.suitebuilder.annotation.SmallTest; import android.view.View; import android.view.ViewGroup; @@ -37,22 +40,15 @@ import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; @SmallTest @RunWith(AndroidJUnit4.class) public class KeyguardIndicationControllerTest extends SysuiTestCase { private final String ORGANIZATION_NAME = "organization"; - private final String DISCLOSURE_WITH_ORGANIZATION_NAME = "managed by organization"; - private Context mContext = mock(Context.class); + private String mDisclosureWithOrganization; + private DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class); private ViewGroup mIndicationArea = mock(ViewGroup.class); private KeyguardIndicationTextView mDisclosure = mock(KeyguardIndicationTextView.class); @@ -61,19 +57,11 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - final Resources resources = mock(Resources.class); - when(mContext.getResources()).thenReturn(resources); - when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class)); - when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class)); - when(mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)).thenReturn( - mock(SubscriptionManager.class)); - when(mContext.getSystemService(Context.TRUST_SERVICE)).thenReturn( - mock(TrustManager.class)); - when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn( - mDevicePolicyManager); - - when(resources.getString(R.string.do_disclosure_with_name, ORGANIZATION_NAME)) - .thenReturn(DISCLOSURE_WITH_ORGANIZATION_NAME); + mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager); + mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class)); + mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class)); + mDisclosureWithOrganization = mContext.getString(R.string.do_disclosure_with_name, + ORGANIZATION_NAME); when(mIndicationArea.findViewById(R.id.keyguard_indication_enterprise_disclosure)) .thenReturn(mDisclosure); @@ -113,7 +101,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { createController(); verify(mDisclosure).setVisibility(View.VISIBLE); - verify(mDisclosure).switchIndication(DISCLOSURE_WITH_ORGANIZATION_NAME); + verify(mDisclosure).switchIndication(mDisclosureWithOrganization); verifyNoMoreInteractions(mDisclosure); } @@ -140,7 +128,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { monitor.onKeyguardVisibilityChanged(true); verify(mDisclosure).setVisibility(View.VISIBLE); - verify(mDisclosure).switchIndication(DISCLOSURE_WITH_ORGANIZATION_NAME); + verify(mDisclosure).switchIndication(mDisclosureWithOrganization); verifyNoMoreInteractions(mDisclosure); reset(mDisclosure); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index 136e7c0bc7a9..6fe776842eaf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -49,6 +49,11 @@ import java.util.ArrayList; import java.util.List; import static junit.framework.Assert.assertEquals; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -282,14 +287,11 @@ public class NetworkControllerBaseTest extends SysuiTestCase { ArgumentCaptor<Boolean> dataOutArg = ArgumentCaptor.forClass(Boolean.class); Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators( - ArgumentCaptor.forClass(IconState.class).capture(), + any(), iconArg.capture(), - ArgumentCaptor.forClass(Integer.class).capture(), + anyInt(), typeIconArg.capture(), dataInArg.capture(), dataOutArg.capture(), - ArgumentCaptor.forClass(String.class).capture(), - ArgumentCaptor.forClass(String.class).capture(), - ArgumentCaptor.forClass(Boolean.class).capture(), - ArgumentCaptor.forClass(Integer.class).capture()); + anyString(), anyString(), anyBoolean(), anyInt()); IconState iconState = iconArg.getValue(); assertEquals("Visibility in, quick settings", visible, iconState.visible); assertEquals("Signal icon in, quick settings", icon, iconState.icon); @@ -307,21 +309,50 @@ public class NetworkControllerBaseTest extends SysuiTestCase { // TODO: Verify all fields. Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators( iconArg.capture(), - ArgumentCaptor.forClass(IconState.class).capture(), + any(), typeIconArg.capture(), - ArgumentCaptor.forClass(Integer.class).capture(), - ArgumentCaptor.forClass(Boolean.class).capture(), - ArgumentCaptor.forClass(Boolean.class).capture(), - ArgumentCaptor.forClass(String.class).capture(), - ArgumentCaptor.forClass(String.class).capture(), - ArgumentCaptor.forClass(Boolean.class).capture(), - ArgumentCaptor.forClass(Integer.class).capture()); + anyInt(), anyBoolean(), anyBoolean(), anyString(), anyString(), anyBoolean(), + anyInt()); IconState iconState = iconArg.getValue(); assertEquals("Data icon in status bar", typeIcon, (int) typeIconArg.getValue()); assertEquals("Visibility in status bar", visible, iconState.visible); } + protected void verifyLastMobileDataIndicators(boolean visible, int icon, int typeIcon, + boolean qsVisible, int qsIcon, int qsTypeIcon, boolean dataIn, boolean dataOut) { + ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class); + ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor<IconState> qsIconArg = ArgumentCaptor.forClass(IconState.class); + ArgumentCaptor<Integer> qsTypeIconArg = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor<Boolean> dataInArg = ArgumentCaptor.forClass(Boolean.class); + ArgumentCaptor<Boolean> dataOutArg = ArgumentCaptor.forClass(Boolean.class); + + Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators( + iconArg.capture(), + qsIconArg.capture(), + typeIconArg.capture(), + qsTypeIconArg.capture(), + dataInArg.capture(), + dataOutArg.capture(), + anyString(), anyString(), anyBoolean(), anyInt()); + + IconState iconState = iconArg.getValue(); + + assertEquals("Data icon in status bar", typeIcon, (int) typeIconArg.getValue()); + assertEquals("Signal icon in status bar", icon, iconState.icon); + assertEquals("Visibility in status bar", visible, iconState.visible); + + iconState = qsIconArg.getValue(); + assertEquals("Visibility in quick settings", qsVisible, iconState.visible); + assertEquals("Signal icon in quick settings", qsIcon, iconState.icon); + assertEquals("Data icon in quick settings", qsTypeIcon, (int) qsTypeIconArg.getValue()); + assertEquals("Data direction in in quick settings", dataIn, + (boolean) dataInArg.getValue()); + assertEquals("Data direction out in quick settings", dataOut, + (boolean) dataOutArg.getValue()); + } + protected void assertNetworkNameEquals(String expected) { assertEquals("Network name", expected, mMobileSignalController.getState().networkName); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java index 55ec572518f9..4f961abffe6a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java @@ -29,8 +29,8 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { verifyLastMobileDataIndicators(true, TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH_ROAMING[1][DEFAULT_LEVEL], - TelephonyIcons.ROAMING_ICON); - verifyLastQsMobileDataIndicators(true, + TelephonyIcons.ROAMING_ICON, + true, TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH[1][DEFAULT_LEVEL], TelephonyIcons.QS_DATA_R, false, false); } @@ -183,14 +183,13 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { private void testDataActivity(int direction, boolean in, boolean out) { updateDataActivity(direction); - verifyLastMobileDataIndicators(true, DEFAULT_SIGNAL_STRENGTH, DEFAULT_ICON); - verifyLastQsMobileDataIndicators(true, DEFAULT_QS_SIGNAL_STRENGTH, - DEFAULT_QS_ICON, in, out); + verifyLastMobileDataIndicators(true, DEFAULT_SIGNAL_STRENGTH, DEFAULT_ICON, true, + DEFAULT_QS_SIGNAL_STRENGTH, DEFAULT_QS_ICON, in, out); } private void verifyDataIndicators(int dataIcon, int qsDataIcon) { - verifyLastMobileDataIndicators(true, DEFAULT_SIGNAL_STRENGTH, dataIcon); - verifyLastQsMobileDataIndicators(true, DEFAULT_QS_SIGNAL_STRENGTH, qsDataIcon, false, + verifyLastMobileDataIndicators(true, DEFAULT_SIGNAL_STRENGTH, dataIcon, + true, DEFAULT_QS_SIGNAL_STRENGTH, qsDataIcon, false, false); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java index fc7f94274e5a..ed32f65179c6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java @@ -17,6 +17,9 @@ import org.mockito.Mockito; import static junit.framework.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; + @SmallTest @RunWith(AndroidJUnit4.class) public class NetworkControllerWifiTest extends NetworkControllerBaseTest { @@ -78,6 +81,9 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { verifyLastQsWifiIcon(true, true, WifiIcons.QS_WIFI_SIGNAL_STRENGTH[1][testLevel], testSsid); + // Set to different activity state first to ensure a callback happens. + setWifiActivity(WifiManager.DATA_ACTIVITY_IN); + setWifiActivity(WifiManager.DATA_ACTIVITY_NONE); verifyLastQsDataDirection(false, false); setWifiActivity(WifiManager.DATA_ACTIVITY_IN); @@ -148,8 +154,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { ArgumentCaptor<Boolean> outArg = ArgumentCaptor.forClass(Boolean.class); Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators( - Mockito.anyBoolean(), Mockito.any(IconState.class), Mockito.any(IconState.class), - inArg.capture(), outArg.capture(), Mockito.anyString()); + anyBoolean(), any(), any(), inArg.capture(), outArg.capture(), any()); assertEquals("WiFi data in, in quick settings", in, (boolean) inArg.getValue()); assertEquals("WiFi data out, in quick settings", out, (boolean) outArg.getValue()); } @@ -161,9 +166,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { ArgumentCaptor<String> descArg = ArgumentCaptor.forClass(String.class); Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators( - enabledArg.capture(), Mockito.any(IconState.class), - iconArg.capture(), Mockito.anyBoolean(), - Mockito.anyBoolean(), + enabledArg.capture(), any(), iconArg.capture(), anyBoolean(), anyBoolean(), descArg.capture()); IconState iconState = iconArg.getValue(); assertEquals("WiFi enabled, in quick settings", enabled, (boolean) enabledArg.getValue()); @@ -176,8 +179,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class); Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators( - Mockito.anyBoolean(), iconArg.capture(), Mockito.any(IconState.class), - Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.anyString()); + anyBoolean(), iconArg.capture(), any(), anyBoolean(), anyBoolean(), any()); IconState iconState = iconArg.getValue(); assertEquals("WiFi visible, in status bar", visible, iconState.visible); assertEquals("WiFi signal, in status bar", icon, iconState.icon); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java index 9a697eedc2e1..87c4c664f352 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java @@ -16,26 +16,24 @@ package com.android.systemui.statusbar.policy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import android.app.admin.DevicePolicyManager; import android.content.Context; -import android.content.pm.UserInfo; import android.net.ConnectivityManager; -import android.os.UserManager; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import com.android.systemui.SysuiTestCase; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - @SmallTest @RunWith(AndroidJUnit4.class) @@ -45,16 +43,9 @@ public class SecurityControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - final Context context = mock(Context.class); - when(context.getSystemService(Context.DEVICE_POLICY_SERVICE)) - .thenReturn(mDevicePolicyManager); - when(context.getSystemService(Context.CONNECTIVITY_SERVICE)) - .thenReturn(mock(ConnectivityManager.class)); - final UserManager userManager = mock(UserManager.class); - when(userManager.getUserInfo(anyInt())).thenReturn(mock(UserInfo.class)); - when(context.getSystemService(Context.USER_SERVICE)) - .thenReturn(userManager); - mSecurityController = new SecurityControllerImpl(context); + mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager); + mContext.addMockSystemService(Context.CONNECTIVITY_SERVICE, mock(ConnectivityManager.class)); + mSecurityController = new SecurityControllerImpl(mContext); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java index bf73416ffcff..a95280641aa1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java @@ -16,15 +16,19 @@ package com.android.systemui.utils; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks; +import android.content.ComponentName; import android.content.ContentProviderClient; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.res.Resources; import android.os.Handler; +import android.os.IBinder; import android.os.UserHandle; import android.provider.Settings; +import android.util.ArrayMap; import com.android.systemui.utils.leaks.Tracker; import com.android.systemui.SysuiTestCase; @@ -34,6 +38,10 @@ public class TestableContext extends ContextWrapper { private final FakeContentResolver mFakeContentResolver; private final FakeSettingsProvider mSettingsProvider; + private ArrayMap<String, Object> mMockSystemServices; + private ArrayMap<ComponentName, IBinder> mMockServices; + private ArrayMap<ServiceConnection, ComponentName> mActiveServices; + private Tracker mReceiver; private Tracker mService; private Tracker mComponent; @@ -51,6 +59,33 @@ public class TestableContext extends ContextWrapper { mComponent = test.getTracker("component"); } + @Override + public Resources getResources() { + return super.getResources(); + } + + public void addMockSystemService(String name, Object service) { + mMockSystemServices = lazyInit(mMockSystemServices); + mMockSystemServices.put(name, service); + } + + public void addMockService(ComponentName component, IBinder service) { + mMockServices = lazyInit(mMockServices); + mMockServices.put(component, service); + } + + private <T, V> ArrayMap<T, V> lazyInit(ArrayMap<T, V> services) { + return services != null ? services : new ArrayMap<T, V>(); + } + + @Override + public Object getSystemService(String name) { + if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) { + return mMockSystemServices.get(name); + } + return super.getSystemService(name); + } + public FakeSettingsProvider getSettingsProvider() { return mSettingsProvider; } @@ -96,6 +131,7 @@ public class TestableContext extends ContextWrapper { @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); + if (checkMocks(service.getComponent(), conn)) return true; return super.bindService(service, conn, flags); } @@ -103,6 +139,7 @@ public class TestableContext extends ContextWrapper { public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user) { if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); + if (checkMocks(service.getComponent(), conn)) return true; return super.bindServiceAsUser(service, conn, flags, handler, user); } @@ -110,15 +147,35 @@ public class TestableContext extends ContextWrapper { public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, UserHandle user) { if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); + if (checkMocks(service.getComponent(), conn)) return true; return super.bindServiceAsUser(service, conn, flags, user); } + private boolean checkMocks(ComponentName component, ServiceConnection conn) { + if (mMockServices != null && component != null && mMockServices.containsKey(component)) { + mActiveServices = lazyInit(mActiveServices); + mActiveServices.put(conn, component); + conn.onServiceConnected(component, mMockServices.get(component)); + return true; + } + return false; + } + @Override public void unbindService(ServiceConnection conn) { if (mService != null) mService.getLeakInfo(conn).clearAllocations(); + if (mActiveServices != null && mActiveServices.containsKey(conn)) { + conn.onServiceDisconnected(mActiveServices.get(conn)); + mActiveServices.remove(conn); + return; + } super.unbindService(conn); } + public boolean isBound(ComponentName component) { + return mActiveServices != null && mActiveServices.containsValue(component); + } + @Override public void registerComponentCallbacks(ComponentCallbacks callback) { if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java new file mode 100644 index 000000000000..b131460e1eff --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java @@ -0,0 +1,40 @@ +/* + * 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.utils; + +import android.annotation.DrawableRes; +import android.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ImageView; + +public class TestableImageView extends ImageView { + + private int mLastResId = -1; + + public TestableImageView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setImageResource(@DrawableRes int resId) { + mLastResId = resId; + super.setImageResource(resId); + } + + public int getLastImageResource() { + return mLastResId; + } +} diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index ef9d8f2c22c1..ec8c67bbaedf 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -3105,6 +3105,26 @@ message MetricsEvent { // PACKAGE: The package name of the app the permission was revoked for ACTION_PERMISSION_REVOKE_READ_PHONE_NUMBER = 739; + // ACTION: A captive portal was detected during network validation + // CATEGORY: NOTIFICATION + // OS: N-MR2 + NOTIFICATION_NETWORK_SIGN_IN = 740; + + // ACTION: An unvalidated network without Internet was selected by the user + // CATEGORY: NOTIFICATION + // OS: N-MR2 + NOTIFICATION_NETWORK_NO_INTERNET = 741; + + // ACTION: A validated network failed revalidation and lost Internet access + // CATEGORY: NOTIFICATION + // OS: N-MR2 + NOTIFICATION_NETWORK_LOST_INTERNET = 742; + + // ACTION: The system default network switched to a different network + // CATEGORY: NOTIFICATION + // OS: N-MR2 + NOTIFICATION_NETWORK_SWITCH = 743; + // ---- End O Constants, all O constants go above this line ---- // Add new aosp constants above this line. diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index df71cedb0fef..386fbc92ef0d 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -560,7 +560,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { @Override public void interrupt(int userId) { - CopyOnWriteArrayList<Service> services; + List<IAccessibilityServiceClient> interfacesToInterrupt; synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -571,15 +571,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (resolvedUserId != mCurrentUserId) { return; } - services = getUserStateLocked(resolvedUserId).mBoundServices; + List<Service> services = getUserStateLocked(resolvedUserId).mBoundServices; + int numServices = services.size(); + interfacesToInterrupt = new ArrayList<>(numServices); + for (int i = 0; i < numServices; i++) { + Service service = services.get(i); + IBinder a11yServiceBinder = service.mService; + IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface; + if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) { + interfacesToInterrupt.add(a11yServiceInterface); + } + } } - for (int i = 0, count = services.size(); i < count; i++) { - Service service = services.get(i); + for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) { try { - service.mServiceInterface.onInterrupt(); + interfacesToInterrupt.get(i).onInterrupt(); } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error during sending interrupt request to " - + service.mService, re); + Slog.e(LOG_TAG, "Error sending interrupt request to " + + interfacesToInterrupt.get(i), re); } } } diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java index d70c4391e274..6a1613113c10 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java @@ -18,10 +18,13 @@ package com.android.server.autofill; import static android.Manifest.permission.MANAGE_AUTO_FILL; import static android.content.Context.AUTO_FILL_MANAGER_SERVICE; +import static android.view.View.ASSIST_FLAG_SANITIZED_TEXT; +import static android.view.View.ASSIST_FLAG_NON_SANITIZED_TEXT; import android.Manifest; import android.app.AppGlobals; import android.app.Notification; +import android.app.Notification.Action; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -248,13 +251,14 @@ public final class AutoFillManagerService extends SystemService { final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub { @Override - public void requestAutoFill(int userId, IBinder activityToken) { + public void requestAutoFill(IBinder activityToken, int userId, int flags) { + if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", userId=" + userId); mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); synchronized (mLock) { final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId); if (service != null) { - service.requestAutoFill(activityToken); + service.requestAutoFill(activityToken, flags); } } } @@ -307,7 +311,7 @@ public final class AutoFillManagerService extends SystemService { synchronized (mLock) { removeCachedServiceForUserLocked(userId); final ComponentName serviceComponent = getProviderForUser(userId); - if (serviceComponent== null) { + if (serviceComponent == null) { cancelNotificationLocked(userId); } else { showNotification(serviceComponent, userId); @@ -322,9 +326,10 @@ public final class AutoFillManagerService extends SystemService { //////////////////////////////////////////////////////////////////////////// // TODO: remove from frameworks/base/core/res/AndroidManifest.xml once it's not used anymore - private static final String NOTIFICATION_INTENT = + private static final String NOTIFICATION_AUTO_FILL_INTENT = "com.android.internal.autofill.action.REQUEST_AUTOFILL"; private static final String EXTRA_USER_ID = "user_id"; + private static final String EXTRA_FLAGS = "flags"; private static final int MSG_SHOW_ALL_NOTIFICATIONS = 42; private static final int SHOW_ALL_NOTIFICATIONS_DELAY_MS = 5000; @@ -335,13 +340,14 @@ public final class AutoFillManagerService extends SystemService { @Override public void onReceive(Context context, Intent intent) { final int userId = intent.getIntExtra(EXTRA_USER_ID, -1); + final int flags = intent.getIntExtra(EXTRA_FLAGS, 0); if (DEBUG) Slog.d(TAG, "Requesting autofill by notification for user " + userId); synchronized (mLock) { final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId); if (service == null) { Slog.w(TAG, "no auto-fill service for user " + userId); } else { - service.requestAutoFill(null); + service.requestAutoFill(null, flags); } } } @@ -393,14 +399,23 @@ public final class AutoFillManagerService extends SystemService { if (mNotificationReceiver == null) { mNotificationReceiver = new NotificationReceiver(); mContext.registerReceiver(mNotificationReceiver, - new IntentFilter(NOTIFICATION_INTENT)); + new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT)); } } - final Intent intent = new Intent(NOTIFICATION_INTENT); - intent.putExtra(EXTRA_USER_ID, userId); - final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT); + final Intent fillIntent = new Intent(NOTIFICATION_AUTO_FILL_INTENT); + fillIntent.putExtra(EXTRA_USER_ID, userId); + fillIntent.putExtra(EXTRA_FLAGS, ASSIST_FLAG_SANITIZED_TEXT); + final PendingIntent fillPendingIntent = PendingIntent.getBroadcast(mContext, + ASSIST_FLAG_SANITIZED_TEXT, fillIntent, PendingIntent.FLAG_UPDATE_CURRENT); + final Action fillAction = new Action.Builder(null, "FILL", fillPendingIntent).build(); + + final Intent saveIntent = new Intent(NOTIFICATION_AUTO_FILL_INTENT); + saveIntent.putExtra(EXTRA_USER_ID, userId); + saveIntent.putExtra(EXTRA_FLAGS, ASSIST_FLAG_NON_SANITIZED_TEXT); + final PendingIntent savePendingIntent = PendingIntent.getBroadcast(mContext, + ASSIST_FLAG_NON_SANITIZED_TEXT, saveIntent, PendingIntent.FLAG_UPDATE_CURRENT); + final Action saveAction = new Action.Builder(null, "SAVE", savePendingIntent).build(); final String packageName = serviceComponent.getPackageName(); String providerName = null; @@ -413,8 +428,8 @@ public final class AutoFillManagerService extends SystemService { } catch (Exception e) { providerName = packageName; } - final String title = "AutoFill by '" + providerName + "'"; - final String subTitle = "Tap notification to auto-fill top activity for user " + userId; + final String title = "AutoFill actions"; + final String subTitle = "Provider: " + providerName + "\n" + "User: " + userId; final Notification notification = new Notification.Builder(mContext) .setCategory(Notification.CATEGORY_SYSTEM) @@ -425,7 +440,7 @@ public final class AutoFillManagerService extends SystemService { com.android.internal.R.color.system_notification_accent_color)) .setContentTitle(title) .setStyle(new Notification.BigTextStyle().bigText(subTitle)) - .setContentIntent(pi) + .setActions(fillAction, saveAction) .build(); NotificationManager.from(mContext).notify(userId, notification); } diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java index e409cb072ef5..82356c8458b3 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java @@ -47,7 +47,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Date; import java.util.LinkedList; import java.util.List; @@ -70,13 +69,13 @@ final class AutoFillManagerServiceImpl { private final AutoFillServiceInfo mInfo; private final AutoFillManagerService mManagerService; - // TODO: improve its usage + // TODO(b/33197203): improve its usage // - set maximum number of entries // - disable on low-memory devices. private final List<String> mRequestHistory = new LinkedList<>(); @GuardedBy("mLock") - private final List<IBinder> mQueuedRequests = new LinkedList<>(); + private final List<QueuedRequest> mQueuedRequests = new LinkedList<>(); private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -84,7 +83,8 @@ final class AutoFillManagerServiceImpl { if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { final String reason = intent.getStringExtra("reason"); if (DEBUG) Slog.d(TAG, "close system dialogs: " + reason); - // TODO: close any pending UI like account selection (or remove this receiver) + // TODO(b/33197203): close any pending UI like account selection (or remove this + // receiver) } } }; @@ -104,8 +104,8 @@ final class AutoFillManagerServiceImpl { if (!mQueuedRequests.isEmpty()) { if (DEBUG) Log.d(TAG, "queued requests:" + mQueuedRequests.size()); } - for (IBinder activityToken : mQueuedRequests) { - requestAutoFillLocked(activityToken, false); + for (final QueuedRequest request: mQueuedRequests) { + requestAutoFillLocked(request.activityToken, request.flags, false); } } } @@ -180,7 +180,7 @@ final class AutoFillManagerServiceImpl { if (DEBUG) Slog.d(TAG, "Bound to " + mComponent); } - void requestAutoFill(IBinder activityToken) { + void requestAutoFill(IBinder activityToken, int flags) { synchronized (mLock) { if (!mBound) { Slog.w(TAG, "requestAutoFill() failed because it's not bound to service"); @@ -188,14 +188,14 @@ final class AutoFillManagerServiceImpl { } } - // TODO: activityToken should probably not be null, but we need to wait until the UI is - // triggering the call (for now it's trough 'adb shell cmd autofill request' + // TODO(b/33197203): activityToken should probably not be null, but we need to wait until + // the UI is triggering the call (for now it's trough 'adb shell cmd autofill request' if (activityToken == null) { // Let's get top activities from all visible stacks. - // TODO: overload getTopVisibleActivities() to take userId, otherwise it could return - // activities for different users when a work profile app is displayed in another - // window (in a multi-window environment). + // TODO(b/33197203): overload getTopVisibleActivities() to take userId, otherwise it + // could return activities for different users when a work profile app is displayed in + // another window (in a multi-window environment). final List<IBinder> topActivities = LocalServices .getService(ActivityManagerInternal.class).getTopVisibleActivities(); if (DEBUG) @@ -211,32 +211,34 @@ final class AutoFillManagerServiceImpl { DateFormat.getDateTimeInstance().format(new Date()) + " - " + activityToken; synchronized (mLock) { mRequestHistory.add(historyItem); - requestAutoFillLocked(activityToken, true); + requestAutoFillLocked(activityToken, flags, true); } } - private void requestAutoFillLocked(IBinder activityToken, boolean queueIfNecessary) { + private void requestAutoFillLocked(IBinder activityToken, int flags, boolean queueIfNecessary) { if (mService == null) { if (!queueIfNecessary) { Slog.w(TAG, "requestAutoFillLocked(): service is null"); return; } if (DEBUG) Slog.d(TAG, "requestAutoFill(): service not set yet, queuing it"); - mQueuedRequests.add(activityToken); + mQueuedRequests.add(new QueuedRequest(activityToken, flags)); return; } /* - * TODO: apply security checks below: + * TODO(b/33197203): apply security checks below: * - checks if disabled by secure settings / device policy * - log operation using noteOp() * - check flags * - display disclosure if needed */ try { - // TODO: add MetricsLogger call - if (!mAm.requestAutoFillData(mService.getAssistReceiver(), null, activityToken)) { - // TODO: might need a way to warn user (perhaps a new method on AutoFillService). + // TODO(b/33197203): add MetricsLogger call + if (!mAm.requestAutoFillData(mService.getAssistReceiver(), null, activityToken, + flags)) { + // TODO(b/33197203): might need a way to warn user (perhaps a new method on + // AutoFillService). Slog.w(TAG, "failed to request auto-fill data for " + activityToken); } } catch (RemoteException e) { @@ -322,4 +324,19 @@ final class AutoFillManagerServiceImpl { return "[AutoFillManagerServiceImpl: userId=" + mUserId + ", uid=" + mUid + ", component=" + mComponent.flattenToShortString() + "]"; } + + private static final class QueuedRequest { + final IBinder activityToken; + final int flags; + + QueuedRequest(IBinder activityToken, int flags) { + this.activityToken = activityToken; + this.flags = flags; + } + + @Override + public String toString() { + return "flags: " + flags + " token: " + activityToken; + } + } } diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java index 6406b8ac9a31..aa3503b9a6d3 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java @@ -16,6 +16,9 @@ package com.android.server.autofill; +import static android.view.View.ASSIST_FLAG_SANITIZED_TEXT; +import static android.view.View.ASSIST_FLAG_NON_SANITIZED_TEXT; + import android.app.ActivityManager; import android.os.RemoteException; import android.os.ShellCommand; @@ -40,8 +43,10 @@ public final class AutoFillManagerServiceShellCommand extends ShellCommand { final PrintWriter pw = getOutPrintWriter(); try { switch (cmd) { - case "request": - return requestAutoFill(); + case "fill": + return requestAutoFill(ASSIST_FLAG_SANITIZED_TEXT); + case "save": + return requestAutoFill(ASSIST_FLAG_NON_SANITIZED_TEXT); default: return handleDefaultCommands(cmd); } @@ -58,15 +63,17 @@ public final class AutoFillManagerServiceShellCommand extends ShellCommand { pw.println(" help"); pw.println(" Prints this help text."); pw.println(""); - pw.println(" request [--user USER_ID]"); - pw.println(" Request auto-fill on the top activity. "); + pw.println(" fill [--user USER_ID]"); + pw.println(" Request provider to auto-fill the top activity. "); + pw.println(" save [--user USER_ID]"); + pw.println(" Request provider to save contents of the top activity. "); pw.println(""); } } - private int requestAutoFill() throws RemoteException { + private int requestAutoFill(int flags) throws RemoteException { final int userId = getUserIdFromArgs(); - mService.requestAutoFill(userId, null); + mService.requestAutoFill(null, userId, flags); return 0; } diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index b0c560353d8a..0ea4722bdc66 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -105,6 +105,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private static final int MESSAGE_USER_UNLOCKED = 301; private static final int MESSAGE_ADD_PROXY_DELAYED = 400; private static final int MESSAGE_BIND_PROFILE_SERVICE = 401; + private static final int MAX_SAVE_RETRIES = 3; private static final int MAX_ERROR_RESTART_RETRIES = 6; @@ -229,7 +230,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } finally { mBluetoothLock.readLock().unlock(); } - Slog.d(TAG, "Airplane Mode change - current state: " + st); + Slog.d(TAG, "Airplane Mode change - current state: " + + BluetoothAdapter.nameForState(st)); if (isAirplaneModeOn()) { // Clear registered LE apps to force shut-off @@ -337,13 +339,15 @@ class BluetoothManagerService extends IBluetoothManager.Stub { /** * Save the Bluetooth on/off state - * */ private void persistBluetoothSetting(int value) { if (DBG) Slog.d(TAG, "Persisting Bluetooth Setting: " + value); + // waive WRITE_SECURE_SETTINGS permission check + long callingIdentity = Binder.clearCallingIdentity(); Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.BLUETOOTH_ON, value); + Binder.restoreCallingIdentity(callingIdentity); } /** @@ -426,6 +430,10 @@ class BluetoothManagerService extends IBluetoothManager.Stub { public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + if (callback == null) { + Slog.w(TAG, "registerStateChangeCallback: Callback is null!"); + return; + } Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_STATE_CHANGE_CALLBACK); msg.obj = callback; mHandler.sendMessage(msg); @@ -434,6 +442,10 @@ class BluetoothManagerService extends IBluetoothManager.Stub { public void unregisterStateChangeCallback(IBluetoothStateChangeCallback callback) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + if (callback == null) { + Slog.w(TAG, "unregisterStateChangeCallback: Callback is null!"); + return; + } Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK); msg.obj = callback; mHandler.sendMessage(msg); @@ -460,7 +472,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { public int getState() { if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) { - Slog.w(TAG, "getState(): not allowed for non-active and non system user"); + Slog.w(TAG, "getState(): report OFF for non-active and non system user"); return BluetoothAdapter.STATE_OFF; } @@ -600,20 +612,26 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } /** - * Action taken when GattService is turned off + * Action taken when GattService is turned on */ private void onBluetoothGattServiceUp() { if (DBG) Slog.d(TAG,"BluetoothGatt Service is Up"); try { mBluetoothLock.readLock().lock(); - if (isBleAppPresent() == false && mBluetooth != null - && mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) { + if (mBluetooth == null) { + if (DBG) Slog.w(TAG, "onBluetoothServiceUp: mBluetooth is null!"); + return; + } + int st = mBluetooth.getState(); + if (st != BluetoothAdapter.STATE_BLE_ON) { + if (DBG) Slog.v(TAG, "onBluetoothServiceUp: state isn't BLE_ON: " + + BluetoothAdapter.nameForState(st)); + return; + } + if (isBluetoothPersistedStateOnBluetooth() || !isBleAppPresent()) { + // This triggers transition to STATE_ON mBluetooth.onLeServiceUp(); - - // waive WRITE_SECURE_SETTINGS permission check - long callingIdentity = Binder.clearCallingIdentity(); persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); - Binder.restoreCallingIdentity(callingIdentity); } } catch (RemoteException e) { Slog.e(TAG,"Unable to call onServiceUp", e); @@ -712,7 +730,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (DBG) { Slog.d(TAG,"enable(): mBluetooth =" + mBluetooth + - " mBinding = " + mBinding + " mState = " + mState); + " mBinding = " + mBinding + " mState = " + + BluetoothAdapter.nameForState(mState)); } synchronized(mReceiver) { @@ -752,10 +771,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { synchronized(mReceiver) { if (persist) { - // waive WRITE_SECURE_SETTINGS permission check - long callingIdentity = Binder.clearCallingIdentity(); persistBluetoothSetting(BLUETOOTH_OFF); - Binder.restoreCallingIdentity(callingIdentity); } mEnableExternal = false; sendDisableMsg(); @@ -800,7 +816,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { public void unbindAndFinish() { if (DBG) { Slog.d(TAG,"unbindAndFinish(): " + mBluetooth + - " mBinding = " + mBinding); + " mBinding = " + mBinding + " mUnbinding = " + mUnbinding); } try { @@ -816,16 +832,13 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } catch (RemoteException re) { Slog.e(TAG, "Unable to unregister BluetoothCallback",re); } - - if (DBG) Slog.d(TAG, "Sending unbind request."); mBluetoothBinder = null; mBluetooth = null; - //Unbind mContext.unbindService(mConnection); mUnbinding = false; mBinding = false; } else { - mUnbinding=false; + mUnbinding = false; } mBluetoothGatt = null; } finally { @@ -1101,7 +1114,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { * Inform BluetoothAdapter instances that Adapter service is up */ private void sendBluetoothServiceUpCallback() { - if (DBG) Slog.d(TAG,"Calling onBluetoothServiceUp callbacks"); try { int n = mCallbacks.beginBroadcast(); Slog.d(TAG,"Broadcasting onBluetoothServiceUp() to " + n + " receivers."); @@ -1120,7 +1132,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { * Inform BluetoothAdapter instances that Adapter service is down */ private void sendBluetoothServiceDownCallback() { - if (DBG) Slog.d(TAG,"Calling onBluetoothServiceDown callbacks"); try { int n = mCallbacks.beginBroadcast(); Slog.d(TAG,"Broadcasting onBluetoothServiceDown() to " + n + " receivers."); @@ -1192,34 +1203,33 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } private class BluetoothServiceConnection implements ServiceConnection { - public void onServiceConnected(ComponentName className, IBinder service) { - if (DBG) Slog.d(TAG, "BluetoothServiceConnection: " + className.getClassName()); + public void onServiceConnected(ComponentName componentName, IBinder service) { + String name = componentName.getClassName(); + if (DBG) Slog.d(TAG, "BluetoothServiceConnection: " + name); Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED); - // TBD if (className.getClassName().equals(IBluetooth.class.getName())) { - if (className.getClassName().equals("com.android.bluetooth.btservice.AdapterService")) { + if (name.equals("com.android.bluetooth.btservice.AdapterService")) { msg.arg1 = SERVICE_IBLUETOOTH; - // } else if (className.getClassName().equals(IBluetoothGatt.class.getName())) { - } else if (className.getClassName().equals("com.android.bluetooth.gatt.GattService")) { + } else if (name.equals("com.android.bluetooth.gatt.GattService")) { msg.arg1 = SERVICE_IBLUETOOTHGATT; } else { - Slog.e(TAG, "Unknown service connected: " + className.getClassName()); + Slog.e(TAG, "Unknown service connected: " + name); return; } msg.obj = service; mHandler.sendMessage(msg); } - public void onServiceDisconnected(ComponentName className) { - // Called if we unexpected disconnected. - if (DBG) Slog.d(TAG, "BluetoothServiceConnection, disconnected: " + - className.getClassName()); + public void onServiceDisconnected(ComponentName componentName) { + // Called if we unexpectedly disconnect. + String name = componentName.getClassName(); + if (DBG) Slog.d(TAG, "BluetoothServiceConnection, disconnected: " + name); Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED); - if (className.getClassName().equals("com.android.bluetooth.btservice.AdapterService")) { + if (name.equals("com.android.bluetooth.btservice.AdapterService")) { msg.arg1 = SERVICE_IBLUETOOTH; - } else if (className.getClassName().equals("com.android.bluetooth.gatt.GattService")) { + } else if (name.equals("com.android.bluetooth.gatt.GattService")) { msg.arg1 = SERVICE_IBLUETOOTHGATT; } else { - Slog.e(TAG, "Unknown service disconnected: " + className.getClassName()); + Slog.e(TAG, "Unknown service disconnected: " + name); return; } mHandler.sendMessage(msg); @@ -1237,7 +1247,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { @Override public void handleMessage(Message msg) { - if (DBG) Slog.d (TAG, "Message: " + msg.what); switch (msg.what) { case MESSAGE_GET_NAME_AND_ADDRESS: if (DBG) Slog.d(TAG, "MESSAGE_GET_NAME_AND_ADDRESS"); @@ -1275,7 +1284,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { case MESSAGE_ENABLE: if (DBG) { - Slog.d(TAG, "MESSAGE_ENABLE: mBluetooth = " + mBluetooth); + Slog.d(TAG, "MESSAGE_ENABLE(" + msg.arg1 + "): mBluetooth = " + mBluetooth); } mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE); mEnable = true; @@ -1325,6 +1334,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { break; case MESSAGE_DISABLE: + if (DBG) Slog.d(TAG, "MESSAGE_DISABLE: mBluetooth = " + mBluetooth); mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE); if (mEnable && mBluetooth != null) { waitForOnOff(true, false); @@ -1340,31 +1350,25 @@ class BluetoothManagerService extends IBluetoothManager.Stub { case MESSAGE_REGISTER_ADAPTER: { IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj; - boolean added = mCallbacks.register(callback); - Slog.d(TAG,"Added callback: " + (callback == null? "null": callback) +":" +added ); - } + mCallbacks.register(callback); break; + } case MESSAGE_UNREGISTER_ADAPTER: { IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj; - boolean removed = mCallbacks.unregister(callback); - Slog.d(TAG,"Removed callback: " + (callback == null? "null": callback) +":" + removed); + mCallbacks.unregister(callback); break; } case MESSAGE_REGISTER_STATE_CHANGE_CALLBACK: { IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj; - if (callback != null) { - mStateChangeCallbacks.register(callback); - } + mStateChangeCallbacks.register(callback); break; } case MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK: { IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj; - if (callback != null) { - mStateChangeCallbacks.unregister(callback); - } + mStateChangeCallbacks.unregister(callback); break; } case MESSAGE_ADD_PROXY_DELAYED: @@ -1438,13 +1442,11 @@ class BluetoothManagerService extends IBluetoothManager.Stub { //Do enable request try { if (mQuietEnable == false) { - if(!mBluetooth.enable()) { + if (!mBluetooth.enable()) { Slog.e(TAG,"IBluetooth.enable() returned false"); } - } - else - { - if(!mBluetooth.enableNoAutoConnect()) { + } else { + if (!mBluetooth.enableNoAutoConnect()) { Slog.e(TAG,"IBluetooth.enableNoAutoConnect() returned false"); } } @@ -1462,19 +1464,14 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } break; } - case MESSAGE_TIMEOUT_BIND: { - Slog.e(TAG, "MESSAGE_TIMEOUT_BIND"); - mBluetoothLock.writeLock().lock(); - mBinding = false; - mBluetoothLock.writeLock().unlock(); - - break; - } case MESSAGE_BLUETOOTH_STATE_CHANGE: { int prevState = msg.arg1; int newState = msg.arg2; - if (DBG) Slog.d(TAG, "MESSAGE_BLUETOOTH_STATE_CHANGE: prevState = " + prevState + ", newState =" + newState); + if (DBG) { + Slog.d(TAG, "MESSAGE_BLUETOOTH_STATE_CHANGE: " + BluetoothAdapter.nameForState(prevState) + " > " + + BluetoothAdapter.nameForState(newState)); + } mState = newState; bluetoothStateChangeHandler(prevState, newState); // handle error state transition case from TURNING_ON to OFF @@ -1514,7 +1511,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } case MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED: { - Slog.e(TAG, "MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED: " + msg.arg1); + Slog.e(TAG, "MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED(" + msg.arg1 + ")"); try { mBluetoothLock.writeLock().lock(); if (msg.arg1 == SERVICE_IBLUETOOTH) { @@ -1525,7 +1522,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { mBluetoothGatt = null; break; } else { - Slog.e(TAG, "Bad msg.arg1: " + msg.arg1); + Slog.e(TAG, "Unknown argument for service disconnect!"); break; } } finally { @@ -1562,8 +1559,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } case MESSAGE_RESTART_BLUETOOTH_SERVICE: { - Slog.d(TAG, "MESSAGE_RESTART_BLUETOOTH_SERVICE:" - +" Restart IBluetooth service"); + Slog.d(TAG, "MESSAGE_RESTART_BLUETOOTH_SERVICE"); /* Enable without persisting the setting as it doesnt change when IBluetooth service restarts */ @@ -1571,7 +1567,13 @@ class BluetoothManagerService extends IBluetoothManager.Stub { handleEnable(mQuietEnable); break; } - + case MESSAGE_TIMEOUT_BIND: { + Slog.e(TAG, "MESSAGE_TIMEOUT_BIND"); + mBluetoothLock.writeLock().lock(); + mBinding = false; + mBluetoothLock.writeLock().unlock(); + break; + } case MESSAGE_TIMEOUT_UNBIND: { Slog.e(TAG, "MESSAGE_TIMEOUT_UNBIND"); @@ -1657,11 +1659,10 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } else if (mBinding || mBluetooth != null) { Message userMsg = mHandler.obtainMessage(MESSAGE_USER_SWITCHED); userMsg.arg2 = 1 + msg.arg2; - // if user is switched when service is being binding - // delay sending MESSAGE_USER_SWITCHED + // if user is switched when service is binding retry after a delay mHandler.sendMessageDelayed(userMsg, USER_SWITCHED_TIME_MS); if (DBG) { - Slog.d(TAG, "delay MESSAGE_USER_SWITCHED " + userMsg.arg2); + Slog.d(TAG, "Retry MESSAGE_USER_SWITCHED " + userMsg.arg2); } } break; @@ -1762,7 +1763,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { parentUser == foregroundUser || callingAppId == Process.NFC_UID || callingAppId == mSystemUiUid; - if (DBG) { + if (DBG && !valid) { Slog.d(TAG, "checkIfCallerIsForegroundUser: valid=" + valid + " callingUser=" + callingUser + " parentUser=" + parentUser @@ -1775,7 +1776,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } private void sendBleStateChanged(int prevState, int newState) { - if (DBG) Slog.d(TAG,"BLE State Change Intent: " + prevState + " -> " + newState); + if (DBG) Slog.d(TAG,"Sending BLE State Change: " + BluetoothAdapter.nameForState(prevState) + + " > " + BluetoothAdapter.nameForState(newState)); // Send broadcast message to everyone else Intent intent = new Intent(BluetoothAdapter.ACTION_BLE_STATE_CHANGED); intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState); @@ -1786,76 +1788,76 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private void bluetoothStateChangeHandler(int prevState, int newState) { boolean isStandardBroadcast = true; - if (DBG) Slog.d(TAG, "bluetoothStateChangeHandler: " + prevState + " -> " + newState); - if (prevState != newState) { - //Notify all proxy objects first of adapter state change - if (newState == BluetoothAdapter.STATE_BLE_ON || - newState == BluetoothAdapter.STATE_OFF) { - boolean intermediate_off = (prevState == BluetoothAdapter.STATE_TURNING_OFF - && newState == BluetoothAdapter.STATE_BLE_ON); - - if (newState == BluetoothAdapter.STATE_OFF) { - // If Bluetooth is off, send service down event to proxy objects, and unbind - if (DBG) Slog.d(TAG, "Bluetooth is complete turn off"); - sendBluetoothServiceDownCallback(); - unbindAndFinish(); - sendBleStateChanged(prevState, newState); - // Don't broadcast as it has already been broadcast before - isStandardBroadcast = false; - - } else if (!intermediate_off) { - // connect to GattService - if (DBG) Slog.d(TAG, "Bluetooth is in LE only mode"); - if (mBluetoothGatt != null) { - if (DBG) Slog.d(TAG, "Calling BluetoothGattServiceUp"); - onBluetoothGattServiceUp(); - } else { - if (DBG) Slog.d(TAG, "Binding Bluetooth GATT service"); - if (mContext.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_BLUETOOTH_LE)) { - Intent i = new Intent(IBluetoothGatt.class.getName()); - doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.CURRENT); - } - } - sendBleStateChanged(prevState, newState); - //Don't broadcase this as std intent - isStandardBroadcast = false; - - } else if (intermediate_off){ - if (DBG) Slog.d(TAG, "Intermediate off, back to LE only mode"); - // For LE only mode, broadcast as is - sendBleStateChanged(prevState, newState); - sendBluetoothStateCallback(false); // BT is OFF for general users - // Broadcast as STATE_OFF - newState = BluetoothAdapter.STATE_OFF; - sendBrEdrDownCallback(); - } - } else if (newState == BluetoothAdapter.STATE_ON) { - boolean isUp = (newState==BluetoothAdapter.STATE_ON); - sendBluetoothStateCallback(isUp); + if (prevState == newState) { // No change. Nothing to do. + return; + } + // Notify all proxy objects first of adapter state change + if (newState == BluetoothAdapter.STATE_BLE_ON || + newState == BluetoothAdapter.STATE_OFF) { + boolean intermediate_off = (prevState == BluetoothAdapter.STATE_TURNING_OFF + && newState == BluetoothAdapter.STATE_BLE_ON); + + if (newState == BluetoothAdapter.STATE_OFF) { + // If Bluetooth is off, send service down event to proxy objects, and unbind + if (DBG) Slog.d(TAG, "Bluetooth is complete send Service Down"); + sendBluetoothServiceDownCallback(); + unbindAndFinish(); sendBleStateChanged(prevState, newState); + // Don't broadcast as it has already been broadcast before + isStandardBroadcast = false; - } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON || - newState == BluetoothAdapter.STATE_BLE_TURNING_OFF ) { + } else if (!intermediate_off) { + // connect to GattService + if (DBG) Slog.d(TAG, "Bluetooth is in LE only mode"); + if (mBluetoothGatt != null) { + if (DBG) Slog.d(TAG, "Calling BluetoothGattServiceUp"); + onBluetoothGattServiceUp(); + } else { + if (DBG) Slog.d(TAG, "Binding Bluetooth GATT service"); + if (mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_BLUETOOTH_LE)) { + Intent i = new Intent(IBluetoothGatt.class.getName()); + doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.CURRENT); + } + } sendBleStateChanged(prevState, newState); + //Don't broadcase this as std intent isStandardBroadcast = false; - } else if (newState == BluetoothAdapter.STATE_TURNING_ON || - newState == BluetoothAdapter.STATE_TURNING_OFF) { + } else if (intermediate_off) { + if (DBG) Slog.d(TAG, "Intermediate off, back to LE only mode"); + // For LE only mode, broadcast as is sendBleStateChanged(prevState, newState); + sendBluetoothStateCallback(false); // BT is OFF for general users + // Broadcast as STATE_OFF + newState = BluetoothAdapter.STATE_OFF; + sendBrEdrDownCallback(); } + } else if (newState == BluetoothAdapter.STATE_ON) { + boolean isUp = (newState == BluetoothAdapter.STATE_ON); + sendBluetoothStateCallback(isUp); + sendBleStateChanged(prevState, newState); - if (isStandardBroadcast) { - if (prevState == BluetoothAdapter.STATE_BLE_ON) { - // Show prevState of BLE_ON as OFF to standard users - prevState = BluetoothAdapter.STATE_OFF; - } - Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); - intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState); - intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_PERM); + } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON || + newState == BluetoothAdapter.STATE_BLE_TURNING_OFF ) { + sendBleStateChanged(prevState, newState); + isStandardBroadcast = false; + + } else if (newState == BluetoothAdapter.STATE_TURNING_ON || + newState == BluetoothAdapter.STATE_TURNING_OFF) { + sendBleStateChanged(prevState, newState); + } + + if (isStandardBroadcast) { + if (prevState == BluetoothAdapter.STATE_BLE_ON) { + // Show prevState of BLE_ON as OFF to standard users + prevState = BluetoothAdapter.STATE_OFF; } + Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); + intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_PERM); } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index c9fd5685e11e..7572dfe0c53d 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -720,16 +720,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mHandler = new InternalHandler(mHandlerThread.getLooper()); mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper()); - // setup our unique device name - if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) { - String id = Settings.Secure.getString(context.getContentResolver(), - Settings.Secure.ANDROID_ID); - if (id != null && id.length() > 0) { - String name = new String("android-").concat(id); - SystemProperties.set("net.hostname", name); - } - } - mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000); @@ -866,6 +856,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mAvoidBadWifiTracker = createAvoidBadWifiTracker( mContext, mHandler, () -> rematchForAvoidBadWifiUpdate()); + mAvoidBadWifiTracker.start(); } private NetworkRequest createInternetRequestForTransport( diff --git a/services/core/java/com/android/server/ConsumerIrService.java b/services/core/java/com/android/server/ConsumerIrService.java index ea6812d2df85..2ed6c77baa0d 100644 --- a/services/core/java/com/android/server/ConsumerIrService.java +++ b/services/core/java/com/android/server/ConsumerIrService.java @@ -29,13 +29,13 @@ public class ConsumerIrService extends IConsumerIrService.Stub { private static final int MAX_XMIT_TIME = 2000000; /* in microseconds */ - private static native long halOpen(); - private static native int halTransmit(long halObject, int carrierFrequency, int[] pattern); - private static native int[] halGetCarrierFrequencies(long halObject); + private static native boolean halOpen(); + private static native int halTransmit(int carrierFrequency, int[] pattern); + private static native int[] halGetCarrierFrequencies(); private final Context mContext; private final PowerManager.WakeLock mWakeLock; - private final long mNativeHal; + private final boolean mHasNativeHal; private final Object mHalLock = new Object(); ConsumerIrService(Context context) { @@ -45,23 +45,23 @@ public class ConsumerIrService extends IConsumerIrService.Stub { mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mWakeLock.setReferenceCounted(true); - mNativeHal = halOpen(); + mHasNativeHal = halOpen(); if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONSUMER_IR)) { - if (mNativeHal == 0) { + if (!mHasNativeHal) { throw new RuntimeException("FEATURE_CONSUMER_IR present, but no IR HAL loaded!"); } - } else if (mNativeHal != 0) { + } else if (mHasNativeHal) { throw new RuntimeException("IR HAL present, but FEATURE_CONSUMER_IR is not set!"); } } @Override public boolean hasIrEmitter() { - return mNativeHal != 0; + return mHasNativeHal; } private void throwIfNoIrEmitter() { - if (mNativeHal == 0) { + if (!mHasNativeHal) { throw new UnsupportedOperationException("IR emitter not available"); } } @@ -91,7 +91,7 @@ public class ConsumerIrService extends IConsumerIrService.Stub { // Right now there is no mechanism to ensure fair queing of IR requests synchronized (mHalLock) { - int err = halTransmit(mNativeHal, carrierFrequency, pattern); + int err = halTransmit(carrierFrequency, pattern); if (err < 0) { Slog.e(TAG, "Error transmitting: " + err); @@ -109,7 +109,7 @@ public class ConsumerIrService extends IConsumerIrService.Stub { throwIfNoIrEmitter(); synchronized(mHalLock) { - return halGetCarrierFrequencies(mNativeHal); + return halGetCarrierFrequencies(); } } } diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index 13985304bd87..4d6ffe6e99c6 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -16,12 +16,14 @@ package com.android.server; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; +import android.app.admin.PasswordMetrics; import android.app.backup.BackupManager; import android.app.trust.IStrongAuthTracker; import android.app.trust.TrustManager; @@ -931,6 +933,7 @@ public class LockSettingsService extends ILockSettings.Stub { synchronized (mSeparateChallengeLock) { setLockPatternInternal(pattern, savedCredential, userId); setSeparateProfileChallengeEnabled(userId, true, null); + notifyPasswordChanged(userId); } } @@ -945,6 +948,7 @@ public class LockSettingsService extends ILockSettings.Stub { setKeystorePassword(null, userId); fixateNewestUserKeyAuth(userId); onUserLockChanged(userId); + notifyActivePasswordMetricsAvailable(null, userId); return; } @@ -994,6 +998,7 @@ public class LockSettingsService extends ILockSettings.Stub { synchronized (mSeparateChallengeLock) { setLockPasswordInternal(password, savedCredential, userId); setSeparateProfileChallengeEnabled(userId, true, null); + notifyPasswordChanged(userId); } } @@ -1007,6 +1012,7 @@ public class LockSettingsService extends ILockSettings.Stub { setKeystorePassword(null, userId); fixateNewestUserKeyAuth(userId); onUserLockChanged(userId); + notifyActivePasswordMetricsAvailable(null, userId); return; } @@ -1420,6 +1426,7 @@ public class LockSettingsService extends ILockSettings.Stub { // migrate credential to GateKeeper credentialUtil.setCredential(credential, null, userId); if (!hasChallenge) { + notifyActivePasswordMetricsAvailable(credential, userId); return VerifyCredentialResponse.OK; } // Fall through to get the auth token. Technically this should never happen, @@ -1459,6 +1466,7 @@ public class LockSettingsService extends ILockSettings.Stub { if (progressCallback != null) { progressCallback.onCredentialVerified(); } + notifyActivePasswordMetricsAvailable(credential, userId); unlockKeystore(credential, userId); Slog.i(TAG, "Unlocking user " + userId + @@ -1482,6 +1490,36 @@ public class LockSettingsService extends ILockSettings.Stub { return response; } + private void notifyActivePasswordMetricsAvailable(String password, @UserIdInt int userId) { + final PasswordMetrics metrics; + if (password == null) { + metrics = new PasswordMetrics(); + } else { + metrics = PasswordMetrics.computeForPassword(password); + metrics.quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(userId); + } + + // Asynchronous to avoid dead lock + mHandler.post(() -> { + DevicePolicyManager dpm = (DevicePolicyManager) + mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + dpm.setActivePasswordState(metrics, userId); + }); + } + + /** + * Call after {@link #notifyActivePasswordMetricsAvailable} so metrics are updated before + * reporting the password changed. + */ + private void notifyPasswordChanged(@UserIdInt int userId) { + // Same handler as notifyActivePasswordMetricsAvailable to ensure correct ordering + mHandler.post(() -> { + DevicePolicyManager dpm = (DevicePolicyManager) + mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + dpm.reportPasswordChanged(userId); + }); + } + @Override public boolean checkVoldPassword(int userId) throws RemoteException { if (!mFirstCallToVold) { diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java index 6f8edecb1777..26e471e4fc95 100644 --- a/services/core/java/com/android/server/MasterClearReceiver.java +++ b/services/core/java/com/android/server/MasterClearReceiver.java @@ -42,12 +42,21 @@ public class MasterClearReceiver extends BroadcastReceiver { return; } } + if (Intent.ACTION_MASTER_CLEAR.equals(intent.getAction())) { + Slog.w(TAG, "The request uses the deprecated Intent#ACTION_MASTER_CLEAR, " + + "Intent#ACTION_FACTORY_RESET should be used instead."); + } + if (intent.hasExtra(Intent.EXTRA_FORCE_MASTER_CLEAR)) { + Slog.w(TAG, "The request uses the deprecated Intent#EXTRA_FORCE_MASTER_CLEAR, " + + "Intent#EXTRA_FORCE_FACTORY_RESET should be used instead."); + } final boolean shutdown = intent.getBooleanExtra("shutdown", false); final String reason = intent.getStringExtra(Intent.EXTRA_REASON); final boolean wipeExternalStorage = intent.getBooleanExtra( Intent.EXTRA_WIPE_EXTERNAL_STORAGE, false); - final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false); + final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false) + || intent.getBooleanExtra(Intent.EXTRA_FORCE_FACTORY_RESET, false); Slog.w(TAG, "!!! FACTORY RESET !!!"); // The reboot call is blocking, so we need to do it on another thread. diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index 27e4aa48c047..f712f121f1d7 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -16,7 +16,11 @@ package com.android.server; +import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT; +import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE; + import android.Manifest.permission; +import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -25,26 +29,29 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.net.INetworkRecommendationProvider; import android.net.INetworkScoreCache; import android.net.INetworkScoreService; -import android.net.NetworkScoreManager; +import android.net.NetworkKey; import android.net.NetworkScorerAppManager; import android.net.NetworkScorerAppManager.NetworkScorerAppData; import android.net.RecommendationRequest; import android.net.RecommendationResult; import android.net.ScoredNetwork; +import android.net.Uri; import android.net.wifi.WifiConfiguration; -import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; -import android.provider.Settings; -import android.text.TextUtils; +import android.provider.Settings.Global; import android.util.ArrayMap; import android.util.Log; +import android.util.TimedRemoteCaller; -import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; @@ -58,6 +65,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; /** @@ -66,17 +74,20 @@ import java.util.function.Consumer; */ public class NetworkScoreService extends INetworkScoreService.Stub { private static final String TAG = "NetworkScoreService"; - private static final boolean DBG = false; + private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); private final Context mContext; private final NetworkScorerAppManager mNetworkScorerAppManager; + private final RequestRecommendationCaller mRequestRecommendationCaller; @GuardedBy("mScoreCaches") private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches; /** Lock used to update mPackageMonitor when scorer package changes occur. */ private final Object mPackageMonitorLock = new Object[0]; + private final Object mServiceConnectionLock = new Object[0]; @GuardedBy("mPackageMonitorLock") private NetworkScorerPackageMonitor mPackageMonitor; + @GuardedBy("mServiceConnectionLock") private ScoringServiceConnection mServiceConnection; private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() { @@ -98,10 +109,10 @@ public class NetworkScoreService extends INetworkScoreService.Stub { * manages the service connection. */ private class NetworkScorerPackageMonitor extends PackageMonitor { - final String mRegisteredPackage; + final List<String> mPackagesToWatch; - private NetworkScorerPackageMonitor(String mRegisteredPackage) { - this.mRegisteredPackage = mRegisteredPackage; + private NetworkScorerPackageMonitor(List<String> packagesToWatch) { + mPackagesToWatch = packagesToWatch; } @Override @@ -135,7 +146,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } private void evaluateBinding(String scorerPackageName, boolean forceUnbind) { - if (mRegisteredPackage.equals(scorerPackageName)) { + if (mPackagesToWatch.contains(scorerPackageName)) { if (DBG) { Log.d(TAG, "Evaluating binding for: " + scorerPackageName + ", forceUnbind=" + forceUnbind); @@ -145,13 +156,14 @@ public class NetworkScoreService extends INetworkScoreService.Stub { if (activeScorer == null) { // Package change has invalidated a scorer, this will also unbind any service // connection. - Log.i(TAG, "Package " + mRegisteredPackage + - " is no longer valid, disabling scoring."); - setScorerInternal(null); - } else if (activeScorer.mScoringServiceClassName == null) { - // The scoring service is not available, make sure it's unbound. + if (DBG) Log.d(TAG, "No active scorers available."); unbindFromScoringServiceIfNeeded(); - } else { // The scoring service changed in some way. + } else if (activeScorer.packageName.equals(scorerPackageName)) { + if (DBG) { + Log.d(TAG, "Possible change to the active scorer: " + + activeScorer.packageName); + } + // The scoring service changed in some way. if (forceUnbind) { unbindFromScoringServiceIfNeeded(); } @@ -161,6 +173,27 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } } + /** + * Reevaluates the service binding when the Settings toggle is changed. + */ + private class SettingsObserver extends ContentObserver { + + public SettingsObserver() { + super(null /*handler*/); + } + + @Override + public void onChange(boolean selfChange) { + onChange(selfChange, null); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri)); + bindToScoringServiceIfNeeded(); + } + } + public NetworkScoreService(Context context) { this(context, new NetworkScorerAppManager(context)); } @@ -175,24 +208,16 @@ public class NetworkScoreService extends INetworkScoreService.Stub { mContext.registerReceiverAsUser( mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/, null /* scheduler */); + // TODO(jjoslin): 12/15/16 - Make timeout configurable. + mRequestRecommendationCaller = + new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); } /** Called when the system is ready to run third-party code but before it actually does so. */ void systemReady() { if (DBG) Log.d(TAG, "systemReady"); - ContentResolver cr = mContext.getContentResolver(); - if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) { - // On first run, we try to initialize the scorer to the one configured at build time. - // This will be a no-op if the scorer isn't actually valid. - String defaultPackage = mContext.getResources().getString( - R.string.config_defaultNetworkScorerPackageName); - if (!TextUtils.isEmpty(defaultPackage)) { - mNetworkScorerAppManager.setActiveScorer(defaultPackage); - } - Settings.Global.putInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 1); - } - registerPackageMonitorIfNeeded(); + registerRecommendationSettingObserverIfNeeded(); } /** Called when the system is ready for us to start third-party code. */ @@ -206,29 +231,40 @@ public class NetworkScoreService extends INetworkScoreService.Stub { bindToScoringServiceIfNeeded(); } + private void registerRecommendationSettingObserverIfNeeded() { + final List<String> providerPackages = + mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); + if (!providerPackages.isEmpty()) { + final ContentResolver resolver = mContext.getContentResolver(); + final Uri uri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED); + resolver.registerContentObserver(uri, false, new SettingsObserver()); + } + } + private void registerPackageMonitorIfNeeded() { if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded"); - NetworkScorerAppData scorer = mNetworkScorerAppManager.getActiveScorer(); + final List<String> providerPackages = + mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); synchronized (mPackageMonitorLock) { // Unregister the current monitor if needed. if (mPackageMonitor != null) { if (DBG) { Log.d(TAG, "Unregistering package monitor for " - + mPackageMonitor.mRegisteredPackage); + + mPackageMonitor.mPackagesToWatch); } mPackageMonitor.unregister(); mPackageMonitor = null; } - // Create and register the monitor if a scorer is active. - if (scorer != null) { - mPackageMonitor = new NetworkScorerPackageMonitor(scorer.mPackageName); + // Create and register the monitor if there are packages that could be providers. + if (!providerPackages.isEmpty()) { + mPackageMonitor = new NetworkScorerPackageMonitor(providerPackages); // TODO: Need to update when we support per-user scorers. http://b/23422763 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM, false /* externalStorage */); if (DBG) { Log.d(TAG, "Registered package monitor for " - + mPackageMonitor.mRegisteredPackage); + + mPackageMonitor.mPackagesToWatch); } } } @@ -242,22 +278,24 @@ public class NetworkScoreService extends INetworkScoreService.Stub { private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) { if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")"); - if (scorerData != null && scorerData.mScoringServiceClassName != null) { - ComponentName componentName = - new ComponentName(scorerData.mPackageName, scorerData.mScoringServiceClassName); - // If we're connected to a different component then drop it. - if (mServiceConnection != null - && !mServiceConnection.mComponentName.equals(componentName)) { - unbindFromScoringServiceIfNeeded(); - } + if (scorerData != null && scorerData.recommendationServiceClassName != null) { + ComponentName componentName = new ComponentName(scorerData.packageName, + scorerData.recommendationServiceClassName); + synchronized (mServiceConnectionLock) { + // If we're connected to a different component then drop it. + if (mServiceConnection != null + && !mServiceConnection.mComponentName.equals(componentName)) { + unbindFromScoringServiceIfNeeded(); + } - // If we're not connected at all then create a new connection. - if (mServiceConnection == null) { - mServiceConnection = new ScoringServiceConnection(componentName); - } + // If we're not connected at all then create a new connection. + if (mServiceConnection == null) { + mServiceConnection = new ScoringServiceConnection(componentName); + } - // Make sure the connection is connected (idempotent) - mServiceConnection.connect(mContext); + // Make sure the connection is connected (idempotent) + mServiceConnection.connect(mContext); + } } else { // otherwise make sure it isn't bound. unbindFromScoringServiceIfNeeded(); } @@ -265,10 +303,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub { private void unbindFromScoringServiceIfNeeded() { if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded"); - if (mServiceConnection != null) { - mServiceConnection.disconnect(mContext); + synchronized (mServiceConnectionLock) { + if (mServiceConnection != null) { + mServiceConnection.disconnect(mContext); + } + mServiceConnection = null; } - mServiceConnection = null; + clearInternal(); } @Override @@ -348,7 +389,8 @@ public class NetworkScoreService extends INetworkScoreService.Stub { // mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG); mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG); - return setScorerInternal(packageName); + // Scorers (recommendation providers) are selected and no longer set. + return false; } @Override @@ -358,56 +400,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub { if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) || mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) == PackageManager.PERMISSION_GRANTED) { - // The return value is discarded here because at this point, the call should always - // succeed. The only reason for failure is if the new package is not a valid scorer, but - // we're disabling scoring altogether here. - setScorerInternal(null /* packageName */); + // no-op for now but we could write to the setting if needed. } else { throw new SecurityException( "Caller is neither the active scorer nor the scorer manager."); } } - /** Set the active scorer. Callers are responsible for checking permissions as appropriate. */ - private boolean setScorerInternal(String packageName) { - if (DBG) Log.d(TAG, "setScorerInternal(" + packageName + ")"); - long token = Binder.clearCallingIdentity(); - try { - unbindFromScoringServiceIfNeeded(); - // Preemptively clear scores even though the set operation could fail. We do this for - // safety as scores should never be compared across apps; in practice, Settings should - // only be allowing valid apps to be set as scorers, so failure here should be rare. - clearInternal(); - // Get the scorer that is about to be replaced, if any, so we can notify it directly. - NetworkScorerAppData prevScorer = mNetworkScorerAppManager.getActiveScorer(); - boolean result = mNetworkScorerAppManager.setActiveScorer(packageName); - // Unconditionally attempt to bind to the current scorer. If setActiveScorer() failed - // then we'll attempt to restore the previous binding (if any), otherwise an attempt - // will be made to bind to the new scorer. - bindToScoringServiceIfNeeded(); - if (result) { // new scorer successfully set - registerPackageMonitorIfNeeded(); - - Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED); - if (prevScorer != null) { // Directly notify the old scorer. - intent.setPackage(prevScorer.mPackageName); - // TODO: Need to update when we support per-user scorers. http://b/23422763 - mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM); - } - - if (packageName != null) { // Then notify the new scorer - intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName); - intent.setPackage(packageName); - // TODO: Need to update when we support per-user scorers. http://b/23422763 - mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM); - } - } - return result; - } finally { - Binder.restoreCallingIdentity(token); - } - } - /** Clear scores. Callers are responsible for checking permissions as appropriate. */ private void clearInternal() { sendCallback(new Consumer<INetworkScoreCache>() { @@ -463,7 +462,22 @@ public class NetworkScoreService extends INetworkScoreService.Stub { @Override public RecommendationResult requestRecommendation(RecommendationRequest request) { - // TODO(jjoslin): 11/25/16 - Update with real impl. + mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG); + throwIfCalledOnMainThread(); + final INetworkRecommendationProvider provider = getRecommendationProvider(); + if (provider != null) { + try { + return mRequestRecommendationCaller.getRecommendationResult(provider, request); + } catch (RemoteException | TimeoutException e) { + Log.w(TAG, "Failed to request a recommendation.", e); + // TODO(jjoslin): 12/15/16 - Keep track of failures. + } + } + + if (DBG) { + Log.d(TAG, "Returning the default network recommendation."); + } + WifiConfiguration selectedConfig = null; if (request != null) { selectedConfig = request.getCurrentSelectedConfig(); @@ -472,6 +486,24 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } @Override + public boolean requestScores(NetworkKey[] networks) { + mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG); + final INetworkRecommendationProvider provider = getRecommendationProvider(); + if (provider != null) { + try { + provider.requestScores(networks); + // TODO(jjoslin): 12/15/16 - Consider pushing null scores into the cache to prevent + // repeated requests for the same scores. + return true; + } catch (RemoteException e) { + Log.w(TAG, "Failed to request scores.", e); + // TODO(jjoslin): 12/15/16 - Keep track of failures. + } + } + return false; + } + + @Override protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) { mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG); NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer(); @@ -479,7 +511,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { writer.println("Scoring is disabled."); return; } - writer.println("Current scorer: " + currentScorer.mPackageName); + writer.println("Current scorer: " + currentScorer.packageName); sendCallback(new Consumer<INetworkScoreCache>() { @Override @@ -492,10 +524,12 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } }, getScoreCacheLists()); - if (mServiceConnection != null) { - mServiceConnection.dump(fd, writer, args); - } else { - writer.println("ScoringServiceConnection: null"); + synchronized (mServiceConnectionLock) { + if (mServiceConnection != null) { + mServiceConnection.dump(fd, writer, args); + } else { + writer.println("ScoringServiceConnection: null"); + } } writer.flush(); } @@ -528,10 +562,27 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } } + private void throwIfCalledOnMainThread() { + if (Thread.currentThread() == mContext.getMainLooper().getThread()) { + throw new RuntimeException("Cannot invoke on the main thread"); + } + } + + @Nullable + private INetworkRecommendationProvider getRecommendationProvider() { + synchronized (mServiceConnectionLock) { + if (mServiceConnection != null) { + return mServiceConnection.getRecommendationProvider(); + } + } + return null; + } + private static class ScoringServiceConnection implements ServiceConnection { private final ComponentName mComponentName; - private boolean mBound = false; - private boolean mConnected = false; + private volatile boolean mBound = false; + private volatile boolean mConnected = false; + private volatile INetworkRecommendationProvider mRecommendationProvider; ScoringServiceConnection(ComponentName componentName) { mComponentName = componentName; @@ -562,12 +613,19 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } catch (RuntimeException e) { Log.e(TAG, "Unbind failed.", e); } + + mRecommendationProvider = null; + } + + INetworkRecommendationProvider getRecommendationProvider() { + return mRecommendationProvider; } @Override public void onServiceConnected(ComponentName name, IBinder service) { if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString()); mConnected = true; + mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service); } @Override @@ -576,6 +634,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString()); } mConnected = false; + mRecommendationProvider = null; } public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { @@ -583,4 +642,43 @@ public class NetworkScoreService extends INetworkScoreService.Stub { + ", connected: " + mConnected); } } + + /** + * Executes the async requestRecommendation() call with a timeout. + */ + private static final class RequestRecommendationCaller + extends TimedRemoteCaller<RecommendationResult> { + private final IRemoteCallback mCallback; + + RequestRecommendationCaller(long callTimeoutMillis) { + super(callTimeoutMillis); + mCallback = new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) throws RemoteException { + final RecommendationResult result = + data.getParcelable(EXTRA_RECOMMENDATION_RESULT); + final int sequence = data.getInt(EXTRA_SEQUENCE, -1); + onRemoteMethodResult(result, sequence); + } + }; + } + + /** + * Runs the requestRecommendation() call on the given {@link INetworkRecommendationProvider} + * instance. + * + * @param target the {@link INetworkRecommendationProvider} to request a recommendation + * from + * @param request the {@link RecommendationRequest} from the calling client + * @return a {@link RecommendationResult} from the provider + * @throws RemoteException if the call failed + * @throws TimeoutException if the call took longer than the set timeout + */ + RecommendationResult getRecommendationResult(INetworkRecommendationProvider target, + RecommendationRequest request) throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.requestRecommendation(request, mCallback, sequence); + return getResultTimed(sequence); + } + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2a8ad87f9d89..eae4905f27e4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -17,9 +17,11 @@ package com.android.server.am; import android.annotation.Nullable; +import android.app.ActivityManagerInternal.PictureInPictureArguments; import android.app.ApplicationThreadConstants; import android.app.ContentProviderHolder; import android.app.IActivityManager; +import android.app.RemoteAction; import android.app.WaitResult; import android.os.IDeviceIdentifiersPolicyService; @@ -202,6 +204,7 @@ import android.os.storage.StorageManagerInternal; import android.os.storage.StorageManager; import android.provider.Downloads; import android.provider.Settings; +import android.service.autofill.AutoFillService; import android.service.voice.IVoiceInteractionSession; import android.service.voice.VoiceInteractionManagerInternal; import android.service.voice.VoiceInteractionSession; @@ -366,6 +369,7 @@ import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME; import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS; import static com.android.server.am.ActivityStackSupervisor.ON_TOP; import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; +import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS; import static com.android.server.am.ActivityStackSupervisor.RESTORE_FROM_RECENTS; import static com.android.server.am.TaskRecord.INVALID_TASK_ID; import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK; @@ -507,6 +511,9 @@ public class ActivityManagerService extends IActivityManager.Stub // on getting this result before starting to launch its UI). static final int PENDING_ASSIST_EXTRAS_LONG_TIMEOUT = 2000; + // How long to wait in getAutoFillAssistStructure() for the activity to respond with the result. + static final int PENDING_AUTO_FILL_ASSIST_STRUCTURE_TIMEOUT = 2000; + // Maximum number of persisted Uri grants a package is allowed static final int MAX_PERSISTED_URI_GRANTS = 128; @@ -533,8 +540,6 @@ public class ActivityManagerService extends IActivityManager.Stub private static final String INTENT_REMOTE_BUGREPORT_FINISHED = "android.intent.action.REMOTE_BUGREPORT_FINISHED"; - // Used to indicate that a task is removed it should also be removed from recents. - private static final boolean REMOVE_FROM_RECENTS = true; // Used to indicate that an app transition should be animated. static final boolean ANIMATE = true; @@ -683,15 +688,18 @@ public class ActivityManagerService extends IActivityManager.Stub public AssistStructure structure = null; public AssistContent content = null; public Bundle receiverExtras; + public int flags; public PendingAssistExtras(ActivityRecord _activity, Bundle _extras, Intent _intent, - String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _userHandle) { + String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _flags, + int _userHandle) { activity = _activity; extras = _extras; intent = _intent; hint = _hint; receiver = _receiver; receiverExtras = _receiverExtras; + flags = _flags; userHandle = _userHandle; } @Override @@ -1213,6 +1221,15 @@ public class ActivityManagerService extends IActivityManager.Stub /** * Set while we are wanting to sleep, to prevent any * activities from being started/resumed. + * + * TODO(b/33594039): Clarify the actual state transitions represented by mSleeping. + * + * Currently mSleeping is set to true when transitioning into the sleep state, and remains true + * while in the sleep state until there is a pending transition out of sleep, in which case + * mSleeping is set to false, and remains false while awake. + * + * Whether mSleeping can quickly toggled between true/false without the device actually + * display changing states is undefined. */ private boolean mSleeping = false; @@ -1362,6 +1379,7 @@ public class ActivityManagerService extends IActivityManager.Stub boolean mAlwaysFinishActivities = false; boolean mForceResizableActivities; boolean mSupportsMultiWindow; + boolean mSupportsSplitScreenMultiWindow; boolean mSupportsFreeformWindowManagement; boolean mSupportsPictureInPicture; boolean mSupportsLeanbackOnly; @@ -1544,10 +1562,10 @@ public class ActivityManagerService extends IActivityManager.Stub static final int SYSTEM_USER_UNLOCK_MSG = 59; static final int LOG_STACK_STATE = 60; static final int VR_MODE_CHANGE_MSG = 61; - static final int VR_MODE_APPLY_IF_NEEDED_MSG = 62; - static final int SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG = 63; - static final int HANDLE_TRUST_STORAGE_UPDATE_MSG = 64; - static final int REPORT_LOCKED_BOOT_COMPLETE_MSG = 65; + static final int SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG = 62; + static final int HANDLE_TRUST_STORAGE_UPDATE_MSG = 63; + static final int REPORT_LOCKED_BOOT_COMPLETE_MSG = 64; + static final int NOTIFY_VR_SLEEPING_MSG = 65; static final int START_USER_SWITCH_FG_MSG = 712; static final int FIRST_ACTIVITY_STACK_MSG = 100; @@ -2282,14 +2300,8 @@ public class ActivityManagerService extends IActivityManager.Stub } } vrService.setVrMode(vrMode, requestedPackage, userId, callingPackage); - } break; - case VR_MODE_APPLY_IF_NEEDED_MSG: { - final ActivityRecord r = (ActivityRecord) msg.obj; - final boolean needsVrMode = r != null && r.requestedVrComponent != null; - if (needsVrMode) { - applyVrMode(msg.arg1 == 1, r.requestedVrComponent, r.userId, - r.info.getComponentName(), false); - } + } case NOTIFY_VR_SLEEPING_MSG: { + notifyVrManagerOfSleepState(msg.arg1 != 0); } break; case HANDLE_TRUST_STORAGE_UPDATE_MSG: { synchronized (ActivityManagerService.this) { @@ -3063,20 +3075,17 @@ public class ActivityManagerService extends IActivityManager.Stub mHandler.obtainMessage(VR_MODE_CHANGE_MSG, 0, 0, r)); } - void applyVrModeIfNeededLocked(ActivityRecord r, boolean enable) { + private void sendNotifyVrManagerOfSleepState(boolean isSleeping) { mHandler.sendMessage( - mHandler.obtainMessage(VR_MODE_APPLY_IF_NEEDED_MSG, enable ? 1 : 0, 0, r)); + mHandler.obtainMessage(NOTIFY_VR_SLEEPING_MSG, isSleeping ? 1 : 0, 0)); } - private void applyVrMode(boolean enabled, ComponentName packageName, int userId, - ComponentName callingPackage, boolean immediate) { - VrManagerInternal vrService = - LocalServices.getService(VrManagerInternal.class); - if (immediate) { - vrService.setVrModeImmediate(enabled, packageName, userId, callingPackage); - } else { - vrService.setVrMode(enabled, packageName, userId, callingPackage); + private void notifyVrManagerOfSleepState(boolean isSleeping) { + final VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class); + if (vrService == null) { + return; } + vrService.onSleepStateChanged(isSleeping); } final void showAskCompatModeDialogLocked(ActivityRecord r) { @@ -3674,6 +3683,15 @@ public class ActivityManagerService extends IActivityManager.Stub mNativeDebuggingApp = null; } + String invokeWith = null; + if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { + // Debuggable apps may include a wrapper script with their library directory. + String wrapperFileName = app.info.nativeLibraryDir + "/wrap.sh"; + if (new File(wrapperFileName).exists()) { + invokeWith = "/system/bin/logwrapper " + wrapperFileName; + } + } + String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi; if (requiredAbi == null) { requiredAbi = Build.SUPPORTED_ABIS[0]; @@ -3700,12 +3718,12 @@ public class ActivityManagerService extends IActivityManager.Stub startResult = Process.startWebView(entryPoint, app.processName, uid, uid, gids, debugFlags, mountExternal, app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet, - app.info.dataDir, entryPointArgs); + app.info.dataDir, null, entryPointArgs); } else { startResult = Process.start(entryPoint, app.processName, uid, uid, gids, debugFlags, mountExternal, app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet, - app.info.dataDir, entryPointArgs); + app.info.dataDir, invokeWith, entryPointArgs); } checkTime(startTime, "startProcess: returned from zygote!"); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); @@ -4807,7 +4825,7 @@ public class ActivityManagerService extends IActivityManager.Stub // because we don't support returning them across task boundaries. Also, to // keep backwards compatibility we remove the task from recents when finishing // task with root activity. - res = removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity); + res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity); if (!res) { Slog.i(TAG, "Removing task failed to finish activity"); } @@ -5452,7 +5470,7 @@ public class ActivityManagerService extends IActivityManager.Stub tr.getBaseIntent().getComponent().getPackageName(); if (tr.userId != userId) continue; if (!taskPackageName.equals(packageName)) continue; - removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS); + mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS); } } @@ -7514,6 +7532,23 @@ public class ActivityManagerService extends IActivityManager.Stub enterPictureInPictureMode(token, DEFAULT_DISPLAY, aspectRatio, true /* checkAspectRatio */); } + @Override + public void enterPictureInPictureModeOnMoveToBackground(IBinder token, + boolean enterPictureInPictureOnMoveToBg) { + final long origId = Binder.clearCallingIdentity(); + try { + synchronized(this) { + final ActivityRecord r = ensureValidPictureInPictureActivityLocked( + "enterPictureInPictureModeOnMoveToBackground", token, -1f /* aspectRatio */, + false /* checkAspectRatio */, false /* checkActivityVisibility */); + + r.supportsPipOnMoveToBackground = enterPictureInPictureOnMoveToBg; + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + private void enterPictureInPictureMode(IBinder token, int displayId, float aspectRatio, boolean checkAspectRatio) { final long origId = Binder.clearCallingIdentity(); @@ -7523,7 +7558,8 @@ public class ActivityManagerService extends IActivityManager.Stub "enterPictureInPictureMode", token, aspectRatio, checkAspectRatio, true /* checkActivityVisibility */); - enterPictureInPictureModeLocked(r, displayId, aspectRatio, + r.pictureInPictureArgs.aspectRatio = aspectRatio; + enterPictureInPictureModeLocked(r, displayId, r.pictureInPictureArgs, true /* moveHomeStackToFront */, "enterPictureInPictureMode"); } } finally { @@ -7531,25 +7567,29 @@ public class ActivityManagerService extends IActivityManager.Stub } } - void enterPictureInPictureModeLocked(ActivityRecord r, int displayId, float aspectRatio, - boolean moveHomeStackToFront, String reason) { - final Rect bounds = isValidPictureInPictureAspectRatio(aspectRatio) - ? mWindowManager.getPictureInPictureBounds(displayId, aspectRatio) + void enterPictureInPictureModeLocked(ActivityRecord r, int displayId, + PictureInPictureArguments pipArgs, boolean moveHomeStackToFront, String reason) { + final Rect bounds = isValidPictureInPictureAspectRatio(pipArgs.aspectRatio) + ? mWindowManager.getPictureInPictureBounds(displayId, pipArgs.aspectRatio) : mWindowManager.getPictureInPictureDefaultBounds(displayId); mStackSupervisor.moveActivityToPinnedStackLocked(r, reason, bounds, moveHomeStackToFront); + mWindowManager.setPictureInPictureActions(pipArgs.userActions); } @Override - public void enterPictureInPictureModeOnMoveToBackground(IBinder token, - boolean enterPictureInPictureOnMoveToBg) { + public void setPictureInPictureAspectRatio(IBinder token, float aspectRatio) { final long origId = Binder.clearCallingIdentity(); try { synchronized(this) { final ActivityRecord r = ensureValidPictureInPictureActivityLocked( - "requestAutoEnterPictureInPicture", token, -1f /* aspectRatio */, - false /* checkAspectRatio */, false /* checkActivityVisibility */); + "setPictureInPictureAspectRatio", token, aspectRatio, + true /* checkAspectRatio */, false /* checkActivityVisibility */); - r.supportsPipOnMoveToBackground = enterPictureInPictureOnMoveToBg; + r.pictureInPictureArgs.aspectRatio = aspectRatio; + if (r.getStack().getStackId() == PINNED_STACK_ID) { + // If the activity is already in picture-in-picture, update the pinned stack now + mWindowManager.setPictureInPictureAspectRatio(aspectRatio); + } } } finally { Binder.restoreCallingIdentity(origId); @@ -7557,19 +7597,27 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void setPictureInPictureAspectRatio(IBinder token, float aspectRatio) { + public void setPictureInPictureActions(IBinder token, ParceledListSlice actionsList) { final long origId = Binder.clearCallingIdentity(); try { synchronized(this) { final ActivityRecord r = ensureValidPictureInPictureActivityLocked( - "setPictureInPictureAspectRatio", token, aspectRatio, - true /* checkAspectRatio */, false /* checkActivityVisibility */); + "setPictureInPictureActions", token, -1 /* aspectRatio */, + false /* checkAspectRatio */, false /* checkActivityVisibility */); + final List<RemoteAction> actions = actionsList.getList(); + if (actions.size() > ActivityManager.getMaxNumPictureInPictureActions()) { + throw new IllegalArgumentException("setPictureInPictureActions: Invalid number" + + " of picture-in-picture actions. Only a maximum of " + + ActivityManager.getMaxNumPictureInPictureActions() + + " actions allowed"); + } + + r.pictureInPictureArgs.userActions = actions; if (r.getStack().getStackId() == PINNED_STACK_ID) { // If the activity is already in picture-in-picture, update the pinned stack now - mWindowManager.setPictureInPictureAspectRatio(aspectRatio); + mWindowManager.setPictureInPictureActions(actions); } - r.pictureInPictureAspectRatio = aspectRatio; } } finally { Binder.restoreCallingIdentity(origId); @@ -7605,7 +7653,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (!r.canEnterPictureInPicture(checkActivityVisibility)) { throw new IllegalArgumentException(caller - + "Current activity does not support picture-in-picture or is not " + + ": Current activity does not support picture-in-picture or is not " + "visible r=" + r); } @@ -9536,79 +9584,6 @@ public class ActivityManagerService extends IActivityManager.Stub mWindowManager.executeAppTransition(); } - private void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess, - boolean removeFromRecents) { - if (removeFromRecents) { - mRecentTasks.remove(tr); - tr.removedFromRecents(); - } - ComponentName component = tr.getBaseIntent().getComponent(); - if (component == null) { - Slog.w(TAG, "No component for base intent of task: " + tr); - return; - } - - // Find any running services associated with this app and stop if needed. - mServices.cleanUpRemovedTaskLocked(tr, component, new Intent(tr.getBaseIntent())); - - if (!killProcess) { - return; - } - - // Determine if the process(es) for this task should be killed. - final String pkg = component.getPackageName(); - ArrayList<ProcessRecord> procsToKill = new ArrayList<>(); - ArrayMap<String, SparseArray<ProcessRecord>> pmap = mProcessNames.getMap(); - for (int i = 0; i < pmap.size(); i++) { - - SparseArray<ProcessRecord> uids = pmap.valueAt(i); - for (int j = 0; j < uids.size(); j++) { - ProcessRecord proc = uids.valueAt(j); - if (proc.userId != tr.userId) { - // Don't kill process for a different user. - continue; - } - if (proc == mHomeProcess) { - // Don't kill the home process along with tasks from the same package. - continue; - } - if (!proc.pkgList.containsKey(pkg)) { - // Don't kill process that is not associated with this task. - continue; - } - - for (int k = 0; k < proc.activities.size(); k++) { - TaskRecord otherTask = proc.activities.get(k).task; - if (tr.taskId != otherTask.taskId && otherTask.inRecents) { - // Don't kill process(es) that has an activity in a different task that is - // also in recents. - return; - } - } - - if (proc.foregroundServices) { - // Don't kill process(es) with foreground service. - return; - } - - // Add process to kill list. - procsToKill.add(proc); - } - } - - // Kill the running processes. - for (int i = 0; i < procsToKill.size(); i++) { - ProcessRecord pr = procsToKill.get(i); - if (pr.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND - && pr.curReceivers.isEmpty()) { - pr.kill("remove task", true); - } else { - // We delay killing processes that are not in the background or running a receiver. - pr.waitingToKill = "remove task"; - } - } - } - private void removeTasksByPackageNameLocked(String packageName, int userId) { // Remove all tasks with activities in the specified package from the list of recent tasks for (int i = mRecentTasks.size() - 1; i >= 0; i--) { @@ -9618,7 +9593,7 @@ public class ActivityManagerService extends IActivityManager.Stub ComponentName cn = tr.intent.getComponent(); if (cn != null && cn.getPackageName().equals(packageName)) { // If the package name matches, remove the task. - removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS); + mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS); } } } @@ -9636,35 +9611,11 @@ public class ActivityManagerService extends IActivityManager.Stub final boolean sameComponent = cn != null && cn.getPackageName().equals(packageName) && (filterByClasses == null || filterByClasses.contains(cn.getClassName())); if (sameComponent) { - removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS); + mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS); } } } - /** - * Removes the task with the specified task id. - * - * @param taskId Identifier of the task to be removed. - * @param killProcess Kill any process associated with the task if possible. - * @param removeFromRecents Whether to also remove the task from recents. - * @return Returns true if the given task was found and removed. - */ - private boolean removeTaskByIdLocked(int taskId, boolean killProcess, - boolean removeFromRecents) { - final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked( - taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID); - if (tr != null) { - tr.removeTaskActivitiesLocked(); - cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents); - if (tr.isPersistable) { - notifyTaskPersisterLocked(null, true); - } - return true; - } - Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId); - return false; - } - @Override public void removeStack(int stackId) { enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS, "removeStack()"); @@ -9675,15 +9626,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { final long ident = Binder.clearCallingIdentity(); try { - final ActivityStack stack = mStackSupervisor.getStack(stackId); - if (stack == null) { - return; - } - final ArrayList<TaskRecord> tasks = stack.getAllTasks(); - for (int i = tasks.size() - 1; i >= 0; i--) { - removeTaskByIdLocked( - tasks.get(i).taskId, true /* killProcess */, REMOVE_FROM_RECENTS); - } + mStackSupervisor.removeStackLocked(stackId); } finally { Binder.restoreCallingIdentity(ident); } @@ -9712,7 +9655,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { final long ident = Binder.clearCallingIdentity(); try { - return removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS); + return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS); } finally { Binder.restoreCallingIdentity(ident); } @@ -11685,6 +11628,7 @@ public class ActivityManagerService extends IActivityManager.Stub startTimeTrackingFocusedActivityLocked(); mTopProcessState = ActivityManager.PROCESS_STATE_TOP; mStackSupervisor.comeOutOfSleepIfNeededLocked(); + sendNotifyVrManagerOfSleepState(false); updateOomAdjLocked(); } else if (!mSleeping && shouldSleepLocked()) { mSleeping = true; @@ -11693,6 +11637,7 @@ public class ActivityManagerService extends IActivityManager.Stub } mTopProcessState = ActivityManager.PROCESS_STATE_TOP_SLEEPING; mStackSupervisor.goingToSleepLocked(); + sendNotifyVrManagerOfSleepState(true); updateOomAdjLocked(); // Initialize the wake times of all processes. @@ -12178,7 +12123,7 @@ public class ActivityManagerService extends IActivityManager.Stub public Bundle getAssistContextExtras(int requestType) { PendingAssistExtras pae = enqueueAssistContext(requestType, null, null, null, null, null, true /* focused */, true /* newSessionId */, - UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_TIMEOUT); + UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_TIMEOUT, 0); if (pae == null) { return null; } @@ -12246,22 +12191,23 @@ public class ActivityManagerService extends IActivityManager.Stub IBinder activityToken, boolean focused, boolean newSessionId) { return enqueueAssistContext(requestType, null, null, receiver, receiverExtras, activityToken, focused, newSessionId, - UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT) + UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT, 0) != null; } @Override public boolean requestAutoFillData(IResultReceiver receiver, Bundle receiverExtras, - IBinder activityToken) { - return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_AUTOFILL, null, null, receiver, + IBinder activityToken, int flags) { + return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_FULL, null, null, receiver, receiverExtras, activityToken, true, true, - UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT) - != null; + UserHandle.getCallingUserId(), null, PENDING_AUTO_FILL_ASSIST_STRUCTURE_TIMEOUT, + flags) != null; } private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint, IResultReceiver receiver, Bundle receiverExtras, IBinder activityToken, - boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout) { + boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout, + int flags) { enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO, "enqueueAssistContext()"); synchronized (this) { @@ -12300,14 +12246,14 @@ public class ActivityManagerService extends IActivityManager.Stub extras.putString(Intent.EXTRA_ASSIST_PACKAGE, activity.packageName); extras.putInt(Intent.EXTRA_ASSIST_UID, activity.app.uid); pae = new PendingAssistExtras(activity, extras, intent, hint, receiver, receiverExtras, - userHandle); + flags, userHandle); // Increment the sessionId if necessary if (newSessionId) { mViSessionId++; } try { activity.app.thread.requestAssistContextExtras(activity.appToken, pae, - requestType, mViSessionId); + requestType, mViSessionId, flags); mPendingAssistExtras.add(pae); mUiHandler.postDelayed(pae, timeout); } catch (RemoteException e) { @@ -12383,10 +12329,13 @@ public class ActivityManagerService extends IActivityManager.Stub sendBundle.putParcelable(VoiceInteractionSession.KEY_CONTENT, pae.content); sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS, pae.receiverExtras); + if (pae.flags > 0) { + sendBundle.putInt(VoiceInteractionSession.KEY_FLAGS, pae.flags); + } IBinder autoFillCallback = - extras.getBinder(VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK); + extras.getBinder(AutoFillService.KEY_CALLBACK); if (autoFillCallback != null) { - sendBundle.putBinder(VoiceInteractionSession.KEY_AUTO_FILL_CALLBACK, + sendBundle.putBinder(AutoFillService.KEY_CALLBACK, autoFillCallback); } } @@ -12420,7 +12369,7 @@ public class ActivityManagerService extends IActivityManager.Stub Bundle args) { return enqueueAssistContext(requestType, intent, hint, null, null, null, true /* focused */, true /* newSessionId */, - userHandle, args, PENDING_ASSIST_EXTRAS_TIMEOUT) != null; + userHandle, args, PENDING_ASSIST_EXTRAS_TIMEOUT, 0) != null; } public void registerProcessObserver(IProcessObserver observer) { @@ -13136,6 +13085,8 @@ public class ActivityManagerService extends IActivityManager.Stub mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE); final boolean supportsMultiWindow = ActivityManager.supportsMultiWindow(); + final boolean supportsSplitScreenMultiWindow = + ActivityManager.supportsSplitScreenMultiWindow(); final String debugApp = Settings.Global.getString(resolver, DEBUG_APP); final boolean waitForDebugger = Settings.Global.getInt(resolver, WAIT_FOR_DEBUGGER, 0) != 0; final boolean alwaysFinishActivities = @@ -13171,6 +13122,7 @@ public class ActivityManagerService extends IActivityManager.Stub mSupportsFreeformWindowManagement = false; mSupportsPictureInPicture = false; } + mSupportsSplitScreenMultiWindow = supportsSplitScreenMultiWindow; mWindowManager.setForceResizableTasks(mForceResizableActivities); mWindowManager.setSupportsPictureInPicture(mSupportsPictureInPicture); // This happens before any activities are started, so we can change global configuration @@ -17860,6 +17812,7 @@ public class ActivityManagerService extends IActivityManager.Stub || Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) || Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(action) || Intent.ACTION_MASTER_CLEAR.equals(action) + || Intent.ACTION_FACTORY_RESET.equals(action) || AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action) || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action) || LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION.equals(action) @@ -22222,22 +22175,6 @@ public class ActivityManagerService extends IActivityManager.Stub public SleepToken acquireSleepToken(String tag) { Preconditions.checkNotNull(tag); - ComponentName requestedVrService = null; - ComponentName callingVrActivity = null; - int userId = -1; - synchronized (ActivityManagerService.this) { - final ActivityRecord resumedActivity = mStackSupervisor.getResumedActivityLocked(); - if (resumedActivity != null) { - requestedVrService = resumedActivity.requestedVrComponent; - callingVrActivity = resumedActivity.info.getComponentName(); - userId = resumedActivity.userId; - } - } - - if (requestedVrService != null) { - applyVrMode(false, requestedVrService, userId, callingVrActivity, true); - } - synchronized (ActivityManagerService.this) { SleepTokenImpl token = new SleepTokenImpl(tag); mSleepTokens.add(token); @@ -22477,7 +22414,8 @@ public class ActivityManagerService extends IActivityManager.Stub long origId = Binder.clearCallingIdentity(); try { // We remove the task from recents to preserve backwards - if (!removeTaskByIdLocked(mTaskId, false, REMOVE_FROM_RECENTS)) { + if (!mStackSupervisor.removeTaskByIdLocked(mTaskId, false, + REMOVE_FROM_RECENTS)) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } } finally { diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 14b843a8f638..814b05a95936 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -233,6 +233,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runAttachAgent(pw); case "supports-multiwindow": return runSupportsMultiwindow(pw); + case "supports-split-screen-multi-window": + return runSupportsSplitScreenMultiwindow(pw); default: return handleDefaultCommands(cmd); } @@ -2300,20 +2302,36 @@ final class ActivityManagerShellCommand extends ShellCommand { } int runSupportsMultiwindow(PrintWriter pw) throws RemoteException { + final Resources res = getResources(pw); + if (res == null) { + return -1; + } + pw.println(res.getBoolean(com.android.internal.R.bool.config_supportsMultiWindow)); + return 0; + } + + int runSupportsSplitScreenMultiwindow(PrintWriter pw) throws RemoteException { + final Resources res = getResources(pw); + if (res == null) { + return -1; + } + pw.println( + res.getBoolean(com.android.internal.R.bool.config_supportsSplitScreenMultiWindow)); + return 0; + } + + private Resources getResources(PrintWriter pw) throws RemoteException { // system resources does not contain all the device configuration, construct it manually. Configuration config = mInterface.getConfiguration(); if (config == null) { pw.println("Error: Activity manager has no configuration"); - return -1; + return null; } final DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); - Resources res = new Resources(AssetManager.getSystem(), metrics, config); - - pw.println(res.getBoolean(com.android.internal.R.bool.config_supportsMultiWindow)); - return 0; + return new Resources(AssetManager.getSystem(), metrics, config); } @Override @@ -2495,6 +2513,8 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Rtrieve the configuration and any recent configurations of the device."); pw.println(" supports-multiwindow"); pw.println(" Returns true if the device supports multiwindow."); + pw.println(" supports-split-screen-multi-window"); + pw.println(" Returns true if the device supports split screen multiwindow."); pw.println(" suppress-resize-config-changes <true|false>"); pw.println(" Suppresses configuration changes due to user resizing an activity/task."); pw.println(" set-inactive [--user <USER_ID>] <PACKAGE> true|false"); diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 13c422b22509..ef197002f0a7 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -54,6 +54,7 @@ import static com.android.server.am.TaskRecord.INVALID_TASK_ID; import android.annotation.NonNull; import android.app.ActivityManager.TaskDescription; +import android.app.ActivityManagerInternal.PictureInPictureArguments; import android.app.ActivityOptions; import android.app.PendingIntent; import android.app.ResultInfo; @@ -217,13 +218,13 @@ final class ActivityRecord { boolean immersive; // immersive mode (don't interrupt if possible) boolean forceNewConfig; // force re-create with new config next time boolean supportsPipOnMoveToBackground; // Supports automatically entering picture-in-picture - // when this activity is hidden. This flag is requested by the activity. + // when this activity is hidden. This flag is requested by the activity. private boolean enterPipOnMoveToBackground; // Flag to enter picture in picture when this - // activity is made invisible. This flag is set specifically when another task is being - // launched or moved to the front which may cause this activity to try and enter PiP - // when it is next made invisible. - float pictureInPictureAspectRatio; // The aspect ratio to use when auto-entering - // picture-in-picture + // activity is made invisible. This flag is set specifically when another task is being + // launched or moved to the front which may cause this activity to try and enter PiP + // when it is next made invisible. + PictureInPictureArguments pictureInPictureArgs = new PictureInPictureArguments(); // The PiP + // arguments used when deferring the entering of picture-in-picture. int launchCount; // count of launches since last state long lastLaunchTime; // time of last launch of this activity ComponentName requestedVrComponent; // the requested component for handling VR mode. @@ -441,9 +442,10 @@ final class ActivityRecord { pw.println(prefix + "resizeMode=" + ActivityInfo.resizeModeToString(info.resizeMode)); } if (supportsPipOnMoveToBackground) { - pw.println(prefix + "supportsPipOnMoveToBackground=1 " - + "enterPipOnMoveToBackground=" - + (enterPipOnMoveToBackground ? 1 : 0)); + pw.println(prefix + "supportsPipOnMoveToBackground=1"); + pw.println(prefix + "enterPipOnMoveToBackground=" + + (enterPipOnMoveToBackground ? 1 : 0)); + pictureInPictureArgs.dump(pw, prefix); } } @@ -967,7 +969,7 @@ final class ActivityRecord { return supportsPictureInPicture(); } - if (supportsPictureInPicture() && visible) { + if (supportsPictureInPicture()) { switch (state) { case RESUMED: case PAUSING: diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index d160a4644baa..5bdae574421b 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -1895,7 +1895,7 @@ final class ActivityStack extends ConfigurationContainer { // since it will affect the focused stack's visibility and occlude // starting activities mService.enterPictureInPictureModeLocked(r, r.getDisplayId(), - r.pictureInPictureAspectRatio, false /* moveHomeStackToFront */, + r.pictureInPictureArgs, false /* moveHomeStackToFront */, "ensureActivitiesVisibleLocked"); return true; } @@ -2150,9 +2150,7 @@ final class ActivityStack extends ConfigurationContainer { mStackSupervisor.allResumedActivitiesComplete()) { // Make sure we have executed any pending transitions, since there // should be nothing left to do at this point. - mWindowManager.executeAppTransition(); - mNoAnimActivities.clear(); - ActivityOptions.abort(options); + executeAppTransition(options); if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Top activity resumed " + next); if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); @@ -2187,9 +2185,7 @@ final class ActivityStack extends ConfigurationContainer { && mStackSupervisor.allPausedActivitiesComplete()) { // Make sure we have executed any pending transitions, since there // should be nothing left to do at this point. - mWindowManager.executeAppTransition(); - mNoAnimActivities.clear(); - ActivityOptions.abort(options); + executeAppTransition(options); if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Going to sleep and all paused"); if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); @@ -2252,9 +2248,7 @@ final class ActivityStack extends ConfigurationContainer { // So, nothing else to-do except: // Make sure we have executed any pending transitions, since there // should be nothing left to do at this point. - mWindowManager.executeAppTransition(); - mNoAnimActivities.clear(); - ActivityOptions.abort(options); + executeAppTransition(options); if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Top activity resumed (dontWaitForPause) " + next); if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); @@ -2857,7 +2851,7 @@ final class ActivityStack extends ConfigurationContainer { } else { targetTask = createTaskRecord( mStackSupervisor.getNextTaskIdForUserLocked(target.userId), - target.info, null, null, null, false); + target.info, null, null, null, false, target.mActivityType); targetTask.affinityIntent = target.intent; if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + target + " out to new task " + target.task); @@ -4897,9 +4891,9 @@ final class ActivityStack extends ConfigurationContainer { TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, - boolean toTop) { + boolean toTop, int type) { TaskRecord task = new TaskRecord(mService, taskId, info, intent, voiceSession, - voiceInteractor); + voiceInteractor, type); // add the task to stack first, mTaskPositioner might need the stack association addTask(task, toTop, "createTaskRecord"); final boolean isLockscreenShown = @@ -4908,6 +4902,11 @@ final class ActivityStack extends ConfigurationContainer { && !isLockscreenShown) { task.updateOverrideConfiguration(mBounds); } + final Rect bounds = task.updateOverrideConfigurationFromLaunchBounds(); + final boolean showForAllUsers = (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0; + mWindowManager.addTask(taskId, mStackId, task.userId, bounds, + task.getOverrideConfiguration(), task.mResizeMode, task.isHomeTask(), + task.isOnTopLauncher(), toTop, showForAllUsers); return task; } @@ -4974,15 +4973,13 @@ final class ActivityStack extends ConfigurationContainer { } void addConfigOverride(ActivityRecord r, TaskRecord task) { - final Rect bounds = task.updateOverrideConfigurationFromLaunchBounds(); + task.updateOverrideConfigurationFromLaunchBounds(); // TODO: VI deal with activity mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, - r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, - (r.info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges, - task.voiceSession != null, r.mLaunchTaskBehind, bounds, - task.getOverrideConfiguration(), task.mResizeMode, r.isAlwaysFocusable(), - task.isHomeTask(), r.appInfo.targetSdkVersion, r.mRotationAnimationHint, - task.isOnTopLauncher()); + r.task.taskId, r.info.screenOrientation, r.fullscreen, + (r.info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, r.info.configChanges, + task.voiceSession != null, r.mLaunchTaskBehind, r.isAlwaysFocusable(), + r.appInfo.targetSdkVersion, r.mRotationAnimationHint); r.onOverrideConfigurationSent(); } @@ -5021,7 +5018,7 @@ final class ActivityStack extends ConfigurationContainer { final TaskRecord task = createTaskRecord( mStackSupervisor.getNextTaskIdForUserLocked(r.userId), - r.info, r.intent, null, null, true); + r.info, r.intent, null, null, true, r.mActivityType); r.setTask(task, null); task.addActivityToTop(r); setAppTask(r, task); @@ -5033,10 +5030,8 @@ final class ActivityStack extends ConfigurationContainer { } private void setAppTask(ActivityRecord r, TaskRecord task) { - final Rect bounds = task.updateOverrideConfigurationFromLaunchBounds(); - mWindowManager.setAppTask(r.appToken, task.taskId, mStackId, bounds, - task.getOverrideConfiguration(), task.mResizeMode, task.isHomeTask(), - task.isOnTopLauncher()); + task.updateOverrideConfigurationFromLaunchBounds(); + mWindowManager.addAppToTask(r.appToken, task.taskId); r.onOverrideConfigurationSent(); } @@ -5055,4 +5050,10 @@ final class ActivityStack extends ConfigurationContainer { mTaskHistory.get(taskNdx).setLockTaskAuth(); } } + + void executeAppTransition(ActivityOptions options) { + mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); + ActivityOptions.abort(options); + } } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 281812ca08c5..b4b346576507 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -249,6 +249,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer // Don't execute any calls to resume. static final boolean DEFER_RESUME = true; + // Used to indicate that a task is removed it should also be removed from recents. + static final boolean REMOVE_FROM_RECENTS = true; + // Activity actions an app cannot start if it uses a permission which is not granted. private static final ArrayMap<String, String> ACTION_TO_RUNTIME_PERMISSION = new ArrayMap<>(); @@ -1844,6 +1847,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer final ActivityRecord r = mFocusedStack.topRunningActivityLocked(); if (r == null || r.state != RESUMED) { mFocusedStack.resumeTopActivityUncheckedLocked(null, null); + } else if (r.state == RESUMED) { + // Kick off any lingering app transitions form the MoveTaskToFront operation. + mFocusedStack.executeAppTransition(targetOptions); } return false; } @@ -2180,7 +2186,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer // Update the return-to to reflect where the pinned stack task was moved // from so that we retain the stack that was previously visible if the // pinned stack is recreated. See moveActivityToPinnedStackLocked(). - task.setTaskToReturnTo(getFocusedStack().getStackId() == HOME_STACK_ID + final int focusedStackId = getFocusedStack().getStackId(); + task.setTaskToReturnTo(focusedStackId == HOME_STACK_ID || !onTop ? HOME_ACTIVITY_TYPE : APPLICATION_ACTIVITY_TYPE); } moveTaskToStackLocked(tasks.get(i).taskId, @@ -2371,6 +2378,141 @@ public class ActivityStackSupervisor extends ConfigurationContainer return activityContainer.mStack; } + /** + * Removes the stack associed with the given {@param stackId}. If the {@param stackId} is the + * pinned stack, then its tasks are not explicitly removed when the stack is destroyed, but + * instead moved back onto the fullscreen stack. + */ + void removeStackLocked(int stackId) { + final ActivityStack stack = getStack(stackId); + if (stack == null) { + return; + } + + final ArrayList<TaskRecord> tasks = stack.getAllTasks(); + if (stack.getStackId() == PINNED_STACK_ID) { + final ActivityStack fullscreenStack = getStack(FULLSCREEN_WORKSPACE_STACK_ID); + if (fullscreenStack != null) { + final boolean isFullscreenStackVisible = + fullscreenStack.getStackVisibilityLocked(null) == STACK_VISIBLE; + for (int i = 0; i < tasks.size(); i++) { + // Insert the task either at the top of the fullscreen stack if it is hidden, + // or just under the top task if it is currently visible + final int insertPosition = isFullscreenStackVisible + ? Math.max(0, fullscreenStack.getChildCount() - 1) + : fullscreenStack.getChildCount(); + positionTaskInStackLocked(tasks.get(i).taskId, FULLSCREEN_WORKSPACE_STACK_ID, + insertPosition); + } + ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); + resumeFocusedStackTopActivityLocked(); + } else { + // If there is no fullscreen stack, then create the stack and move all the tasks + // onto the stack + moveTasksToFullscreenStackLocked(PINNED_STACK_ID, false /* onTop */); + } + } else { + for (int i = tasks.size() - 1; i >= 0; i--) { + removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */, + REMOVE_FROM_RECENTS); + } + } + } + + /** + * Removes the task with the specified task id. + * + * @param taskId Identifier of the task to be removed. + * @param killProcess Kill any process associated with the task if possible. + * @param removeFromRecents Whether to also remove the task from recents. + * @return Returns true if the given task was found and removed. + */ + boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents) { + final TaskRecord tr = anyTaskForIdLocked(taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID); + if (tr != null) { + tr.removeTaskActivitiesLocked(); + cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents); + if (tr.isPersistable) { + mService.notifyTaskPersisterLocked(null, true); + } + return true; + } + Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId); + return false; + } + + void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess, boolean removeFromRecents) { + if (removeFromRecents) { + mRecentTasks.remove(tr); + tr.removedFromRecents(); + } + ComponentName component = tr.getBaseIntent().getComponent(); + if (component == null) { + Slog.w(TAG, "No component for base intent of task: " + tr); + return; + } + + // Find any running services associated with this app and stop if needed. + mService.mServices.cleanUpRemovedTaskLocked(tr, component, new Intent(tr.getBaseIntent())); + + if (!killProcess) { + return; + } + + // Determine if the process(es) for this task should be killed. + final String pkg = component.getPackageName(); + ArrayList<ProcessRecord> procsToKill = new ArrayList<>(); + ArrayMap<String, SparseArray<ProcessRecord>> pmap = mService.mProcessNames.getMap(); + for (int i = 0; i < pmap.size(); i++) { + + SparseArray<ProcessRecord> uids = pmap.valueAt(i); + for (int j = 0; j < uids.size(); j++) { + ProcessRecord proc = uids.valueAt(j); + if (proc.userId != tr.userId) { + // Don't kill process for a different user. + continue; + } + if (proc == mService.mHomeProcess) { + // Don't kill the home process along with tasks from the same package. + continue; + } + if (!proc.pkgList.containsKey(pkg)) { + // Don't kill process that is not associated with this task. + continue; + } + + for (int k = 0; k < proc.activities.size(); k++) { + TaskRecord otherTask = proc.activities.get(k).task; + if (tr.taskId != otherTask.taskId && otherTask.inRecents) { + // Don't kill process(es) that has an activity in a different task that is + // also in recents. + return; + } + } + + if (proc.foregroundServices) { + // Don't kill process(es) with foreground service. + return; + } + + // Add process to kill list. + procsToKill.add(proc); + } + } + + // Kill the running processes. + for (int i = 0; i < procsToKill.size(); i++) { + ProcessRecord pr = procsToKill.get(i); + if (pr.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND + && pr.curReceivers.isEmpty()) { + pr.kill("remove task", true); + } else { + // We delay killing processes that are not in the background or running a receiver. + pr.waitingToKill = "remove task"; + } + } + } + int getNextStackId() { while (true) { if (mNextFreeStackId >= FIRST_DYNAMIC_STACK_ID @@ -2424,7 +2566,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer return false; } - stack.addTask(task, false, "restoreRecentTask"); + stack.addTask(task, false /* toTop */, "restoreRecentTask"); + final Rect bounds = task.updateOverrideConfigurationFromLaunchBounds(); + mWindowManager.addTask(task.taskId, stack.mStackId, task.userId, bounds, + task.getOverrideConfiguration(), task.mResizeMode, task.isHomeTask(), + task.isOnTopLauncher(), false /* toTop */, true /* showForAllUsers */); if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Added restored task=" + task + " to stack=" + stack); final ArrayList<ActivityRecord> activities = task.mActivities; diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index d0960a0c73a3..3bb9ccc22a87 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -1642,8 +1642,8 @@ class ActivityStarter { final TaskRecord task = mTargetStack.createTaskRecord( mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info, - mNewTaskIntent != null ? mNewTaskIntent : mIntent, - mVoiceSession, mVoiceInteractor, !mLaunchTaskBehind /* toTop */); + mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession, + mVoiceInteractor, !mLaunchTaskBehind /* toTop */, mStartActivity.mActivityType); mStartActivity.setTask(task, taskToAffiliate); if (mLaunchBounds != null) { final int stackId = mTargetStack.mStackId; @@ -1817,8 +1817,8 @@ class ActivityStarter { } final ActivityRecord prev = mTargetStack.topActivity(); final TaskRecord task = (prev != null) ? prev.task : mTargetStack.createTaskRecord( - mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), - mStartActivity.info, mIntent, null, null, true); + mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mStartActivity.info, + mIntent, null, null, true, mStartActivity.mActivityType); mStartActivity.setTask(task, null); mWindowManager.moveTaskToTop(mStartActivity.task.taskId); if (DEBUG_TASKS) Slog.v(TAG_TASKS, diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java index 19bf5369a57b..cfe2eb075530 100644 --- a/services/core/java/com/android/server/am/KeyguardController.java +++ b/services/core/java/com/android/server/am/KeyguardController.java @@ -121,8 +121,6 @@ class KeyguardController { // Some stack visibility might change (e.g. docked stack) mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); mWindowManager.executeAppTransition(); - mService.applyVrModeIfNeededLocked(mStackSupervisor.getResumedActivityLocked(), - true /* enable */); } finally { mWindowManager.continueSurfaceLayout(); } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 383f1068793f..9e28068b8b73 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -281,7 +281,7 @@ final class TaskRecord extends ConfigurationContainer { private Configuration mTmpConfig = new Configuration(); TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent, - IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) { + IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor, int type) { mService = service; mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + TaskPersister.IMAGE_EXTENSION; @@ -296,6 +296,7 @@ final class TaskRecord extends ConfigurationContainer { mActivities = new ArrayList<>(); mCallingUid = info.applicationInfo.uid; mCallingPackage = info.packageName; + taskType = type; setIntent(_intent, info); setMinDimensions(info); touchActiveTime(); @@ -321,7 +322,6 @@ final class TaskRecord extends ConfigurationContainer { setIntent(_intent, info); setMinDimensions(info); - taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE; isPersistable = true; // Clamp to [1, max]. maxRecents = Math.min(Math.max(info.maxRecents, 1), @@ -1104,7 +1104,7 @@ final class TaskRecord extends ConfigurationContainer { } boolean canGoInDockedStack() { - return isResizeable() && + return isResizeable() && mService.mSupportsSplitScreenMultiWindow && !ActivityInfo.isPreserveOrientationMode(mResizeMode); } diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java index 372b2d8e566e..d5fa26c49251 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java @@ -43,10 +43,10 @@ final public class IpConnectivityEventBuilder { private IpConnectivityEventBuilder() { } - public static byte[] serialize(int dropped, List<ConnectivityMetricsEvent> events) + public static byte[] serialize(int dropped, List<IpConnectivityEvent> events) throws IOException { final IpConnectivityLog log = new IpConnectivityLog(); - log.events = toProto(events); + log.events = events.toArray(new IpConnectivityEvent[events.size()]); log.droppedEvents = dropped; if ((log.events.length > 0) || (dropped > 0)) { // Only write version number if log has some information at all. @@ -55,7 +55,7 @@ final public class IpConnectivityEventBuilder { return IpConnectivityLog.toByteArray(log); } - public static IpConnectivityEvent[] toProto(List<ConnectivityMetricsEvent> eventsIn) { + public static List<IpConnectivityEvent> toProto(List<ConnectivityMetricsEvent> eventsIn) { final ArrayList<IpConnectivityEvent> eventsOut = new ArrayList<>(eventsIn.size()); for (ConnectivityMetricsEvent in : eventsIn) { final IpConnectivityEvent out = toProto(in); @@ -64,7 +64,7 @@ final public class IpConnectivityEventBuilder { } eventsOut.add(out); } - return eventsOut.toArray(new IpConnectivityEvent[eventsOut.size()]); + return eventsOut; } public static IpConnectivityEvent toProto(ConnectivityMetricsEvent ev) { diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java index 42f439c242f8..445f60601312 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java @@ -36,14 +36,14 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.TokenBucket; import com.android.server.SystemService; +import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; import java.util.function.ToIntFunction; -import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; - /** {@hide} */ final public class IpConnectivityMetrics extends SystemService { private static final String TAG = IpConnectivityMetrics.class.getSimpleName(); @@ -63,6 +63,8 @@ final public class IpConnectivityMetrics extends SystemService { // Maximum size of the event buffer. private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10; + private static final int MAXIMUM_CONNECT_LATENCY_RECORDS = 20000; + private static final int ERROR_RATE_LIMITED = -1; // Lock ensuring that concurrent manipulations of the event buffer are correct. @@ -160,9 +162,15 @@ final public class IpConnectivityMetrics extends SystemService { initBuffer(); } + final List<IpConnectivityEvent> protoEvents = IpConnectivityEventBuilder.toProto(events); + + if (mNetdListener != null) { + mNetdListener.flushStatistics(protoEvents); + } + final byte[] data; try { - data = IpConnectivityEventBuilder.serialize(dropped, events); + data = IpConnectivityEventBuilder.serialize(dropped, protoEvents); } catch (IOException e) { Log.e(TAG, "could not serialize events", e); return ""; diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java index 74a1f4fccb4b..f555f0817bd9 100644 --- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java +++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java @@ -19,25 +19,28 @@ package com.android.server.connectivity; import android.content.Context; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; -import android.net.Network; import android.net.INetdEventCallback; +import android.net.Network; import android.net.NetworkRequest; +import android.net.metrics.ConnectStats; import android.net.metrics.DnsEvent; -import android.net.metrics.IpConnectivityLog; import android.net.metrics.INetdEventListener; +import android.net.metrics.IpConnectivityLog; import android.os.RemoteException; +import android.text.format.DateUtils; import android.util.Log; - import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; - +import com.android.internal.util.TokenBucket; +import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.ConnectStatistics; +import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; import java.io.PrintWriter; import java.util.Arrays; +import java.util.List; import java.util.SortedMap; import java.util.TreeMap; - /** * Implementation of the INetdEventListener interface. */ @@ -52,6 +55,12 @@ public class NetdEventListenerService extends INetdEventListener.Stub { // TODO: read this constant from system property private static final int MAX_LOOKUPS_PER_DNS_EVENT = 100; + // Rate limit connect latency logging to 1 measurement per 15 seconds (5760 / day) with maximum + // bursts of 5000 measurements. + private static final int CONNECT_LATENCY_BURST_LIMIT = 5000; + private static final int CONNECT_LATENCY_FILL_RATE = 15 * (int) DateUtils.SECOND_IN_MILLIS; + private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000; + // Stores the results of a number of consecutive DNS lookups on the same network. // This class is not thread-safe and it is the responsibility of the service to call its methods // on one thread at a time. @@ -121,6 +130,12 @@ public class NetdEventListenerService extends INetdEventListener.Stub { } }; + @GuardedBy("this") + private final TokenBucket mConnectTb = + new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT); + @GuardedBy("this") + private ConnectStats mConnectStats = makeConnectStats(); + // Callback should only be registered/unregistered when logging is being enabled/disabled in DPM // by the device owner. It's DevicePolicyManager's responsibility to ensure that. @GuardedBy("this") @@ -173,15 +188,30 @@ public class NetdEventListenerService extends INetdEventListener.Stub { @Override // Called concurrently by multiple binder threads. // This method must not block or perform long-running operations. - public synchronized void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, - int uid) throws RemoteException { + public synchronized void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, + int port, int uid) throws RemoteException { maybeVerboseLog("onConnectEvent(%d, %d, %dms)", netId, error, latencyMs); + mConnectStats.addEvent(error, latencyMs, ipAddr); + if (mNetdEventCallback != null) { mNetdEventCallback.onConnectEvent(ipAddr, port, System.currentTimeMillis(), uid); } } + public synchronized void flushStatistics(List<IpConnectivityEvent> events) { + events.add(flushConnectStats()); + // TODO: migrate DnsEventBatch to IpConnectivityLogClass.DNSLatencies + } + + private IpConnectivityEvent flushConnectStats() { + IpConnectivityEvent ev = new IpConnectivityEvent(); + ev.setConnectStatistics(mConnectStats.toProto()); + // TODO: add transport information + mConnectStats = makeConnectStats(); + return ev; + } + public synchronized void dump(PrintWriter writer) { IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); pw.println(TAG + ":"); @@ -189,9 +219,14 @@ public class NetdEventListenerService extends INetdEventListener.Stub { for (DnsEventBatch batch : mEventBatches.values()) { pw.println(batch.toString()); } + // TODO: also dump ConnectStats pw.decreaseIndent(); } + private ConnectStats makeConnectStats() { + return new ConnectStats(mConnectTb, CONNECT_LATENCY_MAXIMUM_RECORDS); + } + private static void maybeLog(String s, Object... args) { if (DBG) Log.d(TAG, String.format(s, args)); } diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index 5e9885914169..9ffe2b78a095 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -433,6 +433,8 @@ public class NetworkMonitor extends StateMachine { })); intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL, mLastPortalProbeResult.detectUrl); + intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT, + getCaptivePortalUserAgent(mContext)); intent.setFlags( Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivityAsUser(intent, UserHandle.CURRENT); diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index c6bf4c5fcd6a..c0550c6c7876 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -19,7 +19,6 @@ package com.android.server.connectivity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.widget.Toast; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -27,17 +26,40 @@ import android.net.NetworkCapabilities; import android.os.UserHandle; import android.telephony.TelephonyManager; import android.util.Slog; - +import android.util.SparseArray; +import android.util.SparseIntArray; +import android.widget.Toast; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import static android.net.NetworkCapabilities.*; - +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; public class NetworkNotificationManager { - public static enum NotificationType { SIGN_IN, NO_INTERNET, LOST_INTERNET, NETWORK_SWITCH }; + public static enum NotificationType { + LOST_INTERNET(MetricsEvent.NOTIFICATION_NETWORK_LOST_INTERNET), + NETWORK_SWITCH(MetricsEvent.NOTIFICATION_NETWORK_SWITCH), + NO_INTERNET(MetricsEvent.NOTIFICATION_NETWORK_NO_INTERNET), + SIGN_IN(MetricsEvent.NOTIFICATION_NETWORK_SIGN_IN); + + public final int eventId; + + NotificationType(int eventId) { + this.eventId = eventId; + Holder.sIdToTypeMap.put(eventId, this); + } + + private static class Holder { + private static SparseArray<NotificationType> sIdToTypeMap = new SparseArray<>(); + } - private static final String NOTIFICATION_ID = "Connectivity.Notification"; + public static NotificationType getFromId(int id) { + return Holder.sIdToTypeMap.get(id); + } + }; private static final String TAG = NetworkNotificationManager.class.getSimpleName(); private static final boolean DBG = true; @@ -46,11 +68,14 @@ public class NetworkNotificationManager { private final Context mContext; private final TelephonyManager mTelephonyManager; private final NotificationManager mNotificationManager; + // Tracks the types of notifications managed by this instance, from creation to cancellation. + private final SparseIntArray mNotificationTypeMap; public NetworkNotificationManager(Context c, TelephonyManager t, NotificationManager n) { mContext = c; mTelephonyManager = t; mNotificationManager = n; + mNotificationTypeMap = new SparseIntArray(); } // TODO: deal more gracefully with multi-transport networks. @@ -100,8 +125,10 @@ public class NetworkNotificationManager { */ public void showNotification(int id, NotificationType notifyType, NetworkAgentInfo nai, NetworkAgentInfo switchToNai, PendingIntent intent, boolean highPriority) { - int transportType; - String extraInfo; + final String tag = tagFor(id); + final int eventId = notifyType.eventId; + final int transportType; + final String extraInfo; if (nai != null) { transportType = getFirstTransportType(nai); extraInfo = nai.networkInfo.getExtraInfo(); @@ -114,9 +141,10 @@ public class NetworkNotificationManager { } if (DBG) { - Slog.d(TAG, "showNotification id=" + id + " " + notifyType - + " transportType=" + getTransportName(transportType) - + " extraInfo=" + extraInfo + " highPriority=" + highPriority); + Slog.d(TAG, String.format( + "showNotification tag=%s event=%s transport=%s extraInfo=%s highPrioriy=%s", + tag, nameOf(eventId), getTransportName(transportType), extraInfo, + highPriority)); } Resources r = Resources.getSystem(); @@ -184,22 +212,32 @@ public class NetworkNotificationManager { Notification notification = builder.build(); + mNotificationTypeMap.put(id, eventId); try { - mNotificationManager.notifyAsUser(NOTIFICATION_ID, id, notification, UserHandle.ALL); + mNotificationManager.notifyAsUser(tag, eventId, notification, UserHandle.ALL); } catch (NullPointerException npe) { Slog.d(TAG, "setNotificationVisible: visible notificationManager error", npe); } } public void clearNotification(int id) { + final String tag = tagFor(id); + if (mNotificationTypeMap.indexOfKey(id) < 0) { + Slog.e(TAG, "cannot clear unknown notification with tag=" + tag); + return; + } + final int eventId = mNotificationTypeMap.get(id); if (DBG) { - Slog.d(TAG, "clearNotification id=" + id); + Slog.d(TAG, String.format("clearing notification tag=%s event=%s", tag, + nameOf(eventId))); } try { - mNotificationManager.cancelAsUser(NOTIFICATION_ID, id, UserHandle.ALL); + mNotificationManager.cancelAsUser(tag, eventId, UserHandle.ALL); } catch (NullPointerException npe) { - Slog.d(TAG, "setNotificationVisible: cancel notificationManager error", npe); + Slog.d(TAG, String.format( + "failed to clear notification tag=%s event=%s", tag, nameOf(eventId)), npe); } + mNotificationTypeMap.delete(id); } /** @@ -222,4 +260,15 @@ public class NetworkNotificationManager { R.string.network_switch_metered_toast, fromTransport, toTransport); Toast.makeText(mContext, text, Toast.LENGTH_LONG).show(); } + + @VisibleForTesting + static String tagFor(int id) { + return String.format("ConnectivityNotification:%d", id); + } + + @VisibleForTesting + static String nameOf(int eventId) { + NotificationType t = NotificationType.getFromId(eventId); + return (t != null) ? t.name() : "UNKNOWN"; + } } diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 132967c6b26a..3f0ebf28bbfd 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -46,7 +46,11 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.service.fingerprint.FingerprintActionStatsProto; +import android.service.fingerprint.FingerprintServiceDumpProto; +import android.service.fingerprint.FingerprintUserStatsProto; import android.util.Slog; +import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; @@ -968,7 +972,11 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe final long ident = Binder.clearCallingIdentity(); try { - dumpInternal(pw); + if (args.length > 0 && "--proto".equals(args[0])) { + dumpProto(fd); + } else { + dumpInternal(pw); + } } finally { Binder.restoreCallingIdentity(ident); } @@ -1027,6 +1035,45 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe pw.println(dump); } + private void dumpProto(FileDescriptor fd) { + final ProtoOutputStream proto = new ProtoOutputStream(fd); + for (UserInfo user : UserManager.get(getContext()).getUsers()) { + final int userId = user.getUserHandle().getIdentifier(); + + final long userToken = proto.start(FingerprintServiceDumpProto.USERS); + + proto.write(FingerprintUserStatsProto.USER_ID, userId); + proto.write(FingerprintUserStatsProto.NUM_FINGERPRINTS, + mFingerprintUtils.getFingerprintsForUser(mContext, userId).size()); + + // Normal fingerprint authentications (e.g. lockscreen) + final PerformanceStats normal = mPerformanceMap.get(userId); + if (normal != null) { + final long countsToken = proto.start(FingerprintUserStatsProto.NORMAL); + proto.write(FingerprintActionStatsProto.ACCEPT, normal.accept); + proto.write(FingerprintActionStatsProto.REJECT, normal.reject); + proto.write(FingerprintActionStatsProto.ACQUIRE, normal.acquire); + proto.write(FingerprintActionStatsProto.LOCKOUT, normal.lockout); + proto.end(countsToken); + } + + // Statistics about secure fingerprint transactions (e.g. to unlock password + // storage, make secure purchases, etc.) + final PerformanceStats crypto = mPerformanceMap.get(userId); + if (crypto != null) { + final long countsToken = proto.start(FingerprintUserStatsProto.CRYPTO); + proto.write(FingerprintActionStatsProto.ACCEPT, crypto.accept); + proto.write(FingerprintActionStatsProto.REJECT, crypto.reject); + proto.write(FingerprintActionStatsProto.ACQUIRE, crypto.acquire); + proto.write(FingerprintActionStatsProto.LOCKOUT, crypto.lockout); + proto.end(countsToken); + } + + proto.end(userToken); + } + proto.flush(); + } + @Override public void onStart() { publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper()); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 44aafa8a33d8..e5f4282eefe0 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -888,13 +888,42 @@ abstract class HdmiCecLocalDevice { } /** - * Send a key event to other device. + * Send a key event to other CEC device. The logical address of target device will be given by + * {@link #findKeyReceiverAddress}. * * @param keyCode key code defined in {@link android.view.KeyEvent} * @param isPressed {@code true} for key down event + * @see #findKeyReceiverAddress() */ + @ServiceThreadOnly protected void sendKeyEvent(int keyCode, boolean isPressed) { - Slog.w(TAG, "sendKeyEvent not implemented"); + assertRunOnServiceThread(); + if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) { + Slog.w(TAG, "Unsupported key: " + keyCode); + return; + } + List<SendKeyAction> action = getActions(SendKeyAction.class); + int logicalAddress = findKeyReceiverAddress(); + if (logicalAddress == Constants.ADDR_INVALID || logicalAddress == mAddress) { + // Don't send key event to invalid device or itself. + Slog.w(TAG, "Discard key event: " + keyCode + ", pressed:" + isPressed + + ", receiverAddr=" + logicalAddress); + } else if (!action.isEmpty()) { + action.get(0).processKeyEvent(keyCode, isPressed); + } else if (isPressed) { + addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode)); + } + } + + /** + * Returns the logical address of the device which will receive key events via + * {@link #sendKeyEvent}. + * + * @see #sendKeyEvent(int, boolean) + */ + protected int findKeyReceiverAddress() { + Slog.w(TAG, "findKeyReceiverAddress is not implemented"); + return Constants.ADDR_INVALID; } void sendUserControlPressedAndReleased(int targetAddress, int cecKeycode) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 69c012e3893d..d45b00bd904b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -355,6 +355,11 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { } @Override + protected int findKeyReceiverAddress() { + return Constants.ADDR_TV; + } + + @Override @ServiceThreadOnly protected void sendStandby(int deviceId) { assertRunOnServiceThread(); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index c85d97962a22..4526ab74387b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -432,40 +432,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return mService.getPowerStatus(); } - /** - * Sends key to a target CEC device. - * - * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}. - * @param isPressed true if this is key press event - */ @Override - @ServiceThreadOnly - protected void sendKeyEvent(int keyCode, boolean isPressed) { - assertRunOnServiceThread(); - if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) { - Slog.w(TAG, "Unsupported key: " + keyCode); - return; - } - List<SendKeyAction> action = getActions(SendKeyAction.class); - int logicalAddress = findKeyReceiverAddress(); - if (logicalAddress == mAddress) { - Slog.w(TAG, "Discard key event to itself :" + keyCode + " pressed:" + isPressed); - return; - } - if (!action.isEmpty()) { - action.get(0).processKeyEvent(keyCode, isPressed); - } else { - if (isPressed) { - if (logicalAddress != Constants.ADDR_INVALID) { - addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode)); - return; - } - } - Slog.w(TAG, "Discard key event: " + keyCode + " pressed:" + isPressed); - } - } - - private int findKeyReceiverAddress() { + protected int findKeyReceiverAddress() { if (getActiveSource().isValid()) { return getActiveSource().logicalAddress; } diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 54366e6c587c..fb2b961fce77 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -457,6 +457,8 @@ public class GnssLocationProvider implements LocationProviderInterface { if (mDownloadXtraDataPending == STATE_PENDING_NETWORK) { xtraDownloadRequest(); } + // Always on, notify HAL so it can get data it needs + sendMessage(UPDATE_NETWORK_STATE, 0 /*arg*/, network); } }; @@ -469,6 +471,7 @@ public class GnssLocationProvider implements LocationProviderInterface { new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { + // Specific to a change to a SUPL enabled network becoming ready sendMessage(UPDATE_NETWORK_STATE, 0 /*arg*/, network); } diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 8ca608693b3e..386e78b5181d 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -553,15 +553,21 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStats getSummaryForAllUid( NetworkTemplate template, long start, long end, boolean includeTags) { - @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage); - final NetworkStats stats = - getUidComplete().getSummary(template, start, end, accessLevel); - if (includeTags) { - final NetworkStats tagStats = getUidTagComplete() - .getSummary(template, start, end, accessLevel); - stats.combineAllValues(tagStats); + try { + @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage); + final NetworkStats stats = + getUidComplete().getSummary(template, start, end, accessLevel); + if (includeTags) { + final NetworkStats tagStats = getUidTagComplete() + .getSummary(template, start, end, accessLevel); + stats.combineAllValues(tagStats); + } + return stats; + } catch (NullPointerException e) { + // TODO: Track down and fix the cause of this crash and remove this catch block. + Slog.wtf(TAG, "NullPointerException in getSummaryForAllUid", e); + throw e; } - return stats; } @Override diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java index 7dff2c1bdd23..1e0035d65939 100644 --- a/services/core/java/com/android/server/notification/NotificationComparator.java +++ b/services/core/java/com/android/server/notification/NotificationComparator.java @@ -15,7 +15,25 @@ */ package com.android.server.notification; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; +import android.telecom.TelecomManager; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Slog; + import java.util.Comparator; +import java.util.Objects; /** * Sorts notifications individually into attention-relevant order. @@ -23,8 +41,45 @@ import java.util.Comparator; public class NotificationComparator implements Comparator<NotificationRecord> { + private final String DEFAULT_SMS_APP_SETTING = Settings.Secure.SMS_DEFAULT_APPLICATION; + + private final Context mContext; + private String mDefaultPhoneApp; + private ArrayMap<Integer, String> mDefaultSmsApp = new ArrayMap<>(); + + public NotificationComparator(Context context) { + mContext = context; + mContext.registerReceiver(mPhoneAppBroadcastReceiver, + new IntentFilter(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED)); + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(DEFAULT_SMS_APP_SETTING), false, mSmsContentObserver); + } + @Override public int compare(NotificationRecord left, NotificationRecord right) { + // First up: sufficiently important ongoing notifications of certain categories + boolean leftImportantOngoing = isImportantOngoing(left); + boolean rightImportantOngoing = isImportantOngoing(right); + + if (leftImportantOngoing != rightImportantOngoing) { + // by ongoing, ongoing higher than non-ongoing + return -1 * Boolean.compare(leftImportantOngoing, rightImportantOngoing); + } + + // Next: sufficiently import person to person communication + boolean leftPeople = isImportantMessaging(left); + boolean rightPeople = isImportantMessaging(right); + + if (leftPeople && rightPeople){ + // by contact proximity, close to far. if same proximity, check further fields. + if (Float.compare(left.getContactAffinity(), right.getContactAffinity()) != 0) { + return -1 * Float.compare(left.getContactAffinity(), right.getContactAffinity()); + } + } else if (leftPeople != rightPeople) { + // People, messaging higher than non-messaging + return -1 * Boolean.compare(leftPeople, rightPeople); + } + final int leftImportance = left.getImportance(); final int rightImportance = right.getImportance(); if (leftImportance != rightImportance) { @@ -47,14 +102,112 @@ public class NotificationComparator return -1 * Integer.compare(leftPriority, rightPriority); } - final float leftPeople = left.getContactAffinity(); - final float rightPeople = right.getContactAffinity(); - if (leftPeople != rightPeople) { - // by contact proximity, close to far - return -1 * Float.compare(leftPeople, rightPeople); - } - // then break ties by time, most recent first return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs()); } + + private boolean isImportantOngoing(NotificationRecord record) { + if (!isOngoing(record)) { + return false; + } + + if (record.getImportance() < NotificationManager.IMPORTANCE_LOW) { + return false; + } + + // TODO: add whitelist + + return isCall(record) || isMediaNotification(record); + } + + protected boolean isImportantMessaging(NotificationRecord record) { + if (record.getImportance() < NotificationManager.IMPORTANCE_LOW) { + return false; + } + + Class<? extends Notification.Style> style = getNotificationStyle(record); + if (Notification.MessagingStyle.class.equals(style)) { + return true; + } + + if (record.getContactAffinity() > ValidateNotificationPeople.NONE) { + return true; + } + + if (record.getNotification().category == Notification.CATEGORY_MESSAGE + && isDefaultMessagingApp(record)) { + return true; + } + + return false; + } + + private boolean isOngoing(NotificationRecord record) { + final int ongoingFlags = + Notification.FLAG_FOREGROUND_SERVICE | Notification.FLAG_ONGOING_EVENT; + return (record.getNotification().flags & ongoingFlags) != 0; + } + + + private Class<? extends Notification.Style> getNotificationStyle(NotificationRecord record) { + String templateClass = + record.getNotification().extras.getString(Notification.EXTRA_TEMPLATE); + + if (!TextUtils.isEmpty(templateClass)) { + return Notification.getNotificationStyleClass(templateClass); + } + return null; + } + + private boolean isMediaNotification(NotificationRecord record) { + return record.getNotification().extras.getParcelable( + Notification.EXTRA_MEDIA_SESSION) != null; + } + + private boolean isCall(NotificationRecord record) { + return record.getNotification().category == Notification.CATEGORY_CALL + && isDefaultPhoneApp(record.sbn.getPackageName()); + } + + private boolean isDefaultPhoneApp(String pkg) { + if (mDefaultPhoneApp == null) { + final TelecomManager telecomm = + (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); + mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultDialerPackage() : null; + } + return Objects.equals(pkg, mDefaultPhoneApp); + } + + @SuppressWarnings("deprecation") + private boolean isDefaultMessagingApp(NotificationRecord record) { + final int userId = record.getUserId(); + if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false; + if (mDefaultSmsApp.get(userId) == null) { + mDefaultSmsApp.put(userId, Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.SMS_DEFAULT_APPLICATION, userId)); + } + return Objects.equals(mDefaultSmsApp.get(userId), record.sbn.getPackageName()); + } + + private final BroadcastReceiver mPhoneAppBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mDefaultPhoneApp = + intent.getStringExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME); + } + }; + + private final ContentObserver mSmsContentObserver = new ContentObserver( + new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange, Uri uri, int userId) { + if (Settings.Secure.getUriFor(DEFAULT_SMS_APP_SETTING).equals(uri)) { + mDefaultSmsApp.put(userId, Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.SMS_DEFAULT_APPLICATION, userId)); + + } + } + }; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f58522b52dea..a6fb4589f677 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1552,9 +1552,6 @@ public class NotificationManagerService extends SystemService { @Override public void createNotificationChannel(String pkg, NotificationChannel channel, IOnNotificationChannelCreatedListener listener) throws RemoteException { - Preconditions.checkNotNull(channel); - Preconditions.checkNotNull(channel.getId()); - Preconditions.checkNotNull(channel.getName()); checkCallerIsSystemOrSameApp(pkg); mRankingHelper.createNotificationChannel(pkg, Binder.getCallingUid(), channel); savePolicyFile(); @@ -1563,7 +1560,6 @@ public class NotificationManagerService extends SystemService { @Override public NotificationChannel getNotificationChannel(String pkg, String channelId) { - Preconditions.checkNotNull(channelId); checkCallerIsSystemOrSameApp(pkg); return mRankingHelper.getNotificationChannel(pkg, Binder.getCallingUid(), channelId); } @@ -1571,14 +1567,12 @@ public class NotificationManagerService extends SystemService { @Override public NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId) { - Preconditions.checkNotNull(channelId); checkCallerIsSystem(); return mRankingHelper.getNotificationChannel(pkg, uid, channelId); } @Override public void deleteNotificationChannel(String pkg, String channelId) { - Preconditions.checkNotNull(channelId); checkCallerIsSystemOrSameApp(pkg); if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) { throw new IllegalArgumentException("Cannot delete default channel"); @@ -1592,8 +1586,6 @@ public class NotificationManagerService extends SystemService { @Override public void updateNotificationChannelForPackage(String pkg, int uid, NotificationChannel channel) { - Preconditions.checkNotNull(channel); - Preconditions.checkNotNull(channel.getId()); checkCallerIsSystem(); if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) { // cancel @@ -2383,7 +2375,7 @@ public class NotificationManagerService extends SystemService { } @Override - public void applyAdjustmentFromAssistantService(INotificationListener token, + public void applyAdjustmentFromAssistant(INotificationListener token, Adjustment adjustment) throws RemoteException { final long identity = Binder.clearCallingIdentity(); try { @@ -2398,7 +2390,7 @@ public class NotificationManagerService extends SystemService { } @Override - public void applyAdjustmentsFromAssistantService(INotificationListener token, + public void applyAdjustmentsFromAssistant(INotificationListener token, List<Adjustment> adjustments) throws RemoteException { final long identity = Binder.clearCallingIdentity(); @@ -2414,6 +2406,52 @@ public class NotificationManagerService extends SystemService { Binder.restoreCallingIdentity(identity); } } + + @Override + public void createNotificationChannelFromAssistant(INotificationListener token, String pkg, + NotificationChannel channel) throws RemoteException { + ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token); + int uid = mPackageManager.getPackageUid(pkg, 0, info.userid); + mRankingHelper.createNotificationChannel(pkg, uid, channel); + savePolicyFile(); + } + + @Override + public void deleteNotificationChannelFromAssistant(INotificationListener token, String pkg, + String channelId) throws RemoteException { + ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token); + if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) { + throw new IllegalArgumentException("Cannot delete default channel"); + } + + int uid = mPackageManager.getPackageUid(pkg, 0, info.userid); + cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true, + info.userid, REASON_CHANNEL_BANNED, null); + mRankingHelper.deleteNotificationChannel(pkg, uid, channelId); + savePolicyFile(); + } + + @Override + public void updateNotificationChannelFromAssistant(INotificationListener token, String pkg, + NotificationChannel channel) throws RemoteException { + ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token); + if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) { + // cancel + cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true, + info.userid, REASON_CHANNEL_BANNED, null); + } + int uid = mPackageManager.getPackageUid(pkg, 0, info.userid); + mRankingHelper.updateNotificationChannelFromAssistant(pkg, uid, channel); + savePolicyFile(); + } + + @Override + public ParceledListSlice<NotificationChannel> getNotificationChannelsFromAssistant( + INotificationListener token, String pkg) throws RemoteException { + ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token); + int uid = mPackageManager.getPackageUid(pkg, 0, info.userid); + return mRankingHelper.getNotificationChannels(pkg, uid); + } }; private void applyAdjustmentLocked(Adjustment adjustment) { @@ -3924,7 +3962,7 @@ public class NotificationManagerService extends SystemService { } } - private static boolean isUidSystem(int uid) { + protected static boolean isUidSystem(int uid) { final int appid = UserHandle.getAppId(uid); return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0); } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 4fcc98764ac8..dbd719b9167f 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -25,6 +25,7 @@ import android.app.Notification; import android.app.NotificationChannel; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.Bitmap; @@ -136,9 +137,9 @@ public final class NotificationRecord { private boolean isPreChannelsNotification() { try { if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(getChannel().getId())) { - final ApplicationInfo applicationInfo = + final ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfoAsUser(sbn.getPackageName(), - 0, sbn.getUserId()); + 0, UserHandle.getUserId(sbn.getUid())); if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) { return true; } diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index cb5fb0d0c6f0..882e84c4a3b6 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -34,7 +34,7 @@ public interface RankingConfig { void createNotificationChannel(String pkg, int uid, NotificationChannel channel); void updateNotificationChannel(String pkg, int uid, NotificationChannel channel); - void updateNotificationChannelFromRanker(String pkg, int uid, NotificationChannel channel); + void updateNotificationChannelFromAssistant(String pkg, int uid, NotificationChannel channel); NotificationChannel getNotificationChannel(String pkg, int uid, String channelId); NotificationChannel getNotificationChannelWithFallback(String pkg, int uid, String channelId); void deleteNotificationChannel(String pkg, int uid, String channelId); diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 5073b1bf69a8..98d4c6902f74 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -18,6 +18,7 @@ package com.android.server.notification; import static android.app.NotificationManager.IMPORTANCE_NONE; import com.android.internal.R; +import com.android.internal.util.Preconditions; import android.app.Notification; import android.app.NotificationChannel; @@ -72,7 +73,7 @@ public class RankingHelper implements RankingConfig { private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED; private final NotificationSignalExtractor[] mSignalExtractors; - private final NotificationComparator mPreliminaryComparator = new NotificationComparator(); + private final NotificationComparator mPreliminaryComparator; private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator(); private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record @@ -89,6 +90,8 @@ public class RankingHelper implements RankingConfig { mRankingHandler = rankingHandler; mPm = pm; + mPreliminaryComparator = new NotificationComparator(mContext); + final int N = extractorNames.length; mSignalExtractors = new NotificationSignalExtractor[N]; for (int i = 0; i < N; i++) { @@ -451,7 +454,14 @@ public class RankingHelper implements RankingConfig { @Override public void createNotificationChannel(String pkg, int uid, NotificationChannel channel) { + Preconditions.checkNotNull(pkg); + Preconditions.checkNotNull(channel); + Preconditions.checkNotNull(channel.getId()); + Preconditions.checkNotNull(channel.getName()); Record r = getOrCreateRecord(pkg, uid); + if (r == null) { + throw new IllegalArgumentException("Invalid package"); + } if (IMPORTANCE_NONE == r.importance) { throw new IllegalArgumentException("Package blocked"); } @@ -472,7 +482,12 @@ public class RankingHelper implements RankingConfig { @Override public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel) { + Preconditions.checkNotNull(updatedChannel); + Preconditions.checkNotNull(updatedChannel.getId()); Record r = getOrCreateRecord(pkg, uid); + if (r == null) { + throw new IllegalArgumentException("Invalid package"); + } NotificationChannel channel = r.channels.get(updatedChannel.getId()); if (channel == null) { throw new IllegalArgumentException("Channel does not exist"); @@ -485,9 +500,12 @@ public class RankingHelper implements RankingConfig { } @Override - public void updateNotificationChannelFromRanker(String pkg, int uid, + public void updateNotificationChannelFromAssistant(String pkg, int uid, NotificationChannel updatedChannel) { Record r = getOrCreateRecord(pkg, uid); + if (r == null) { + throw new IllegalArgumentException("Invalid package"); + } NotificationChannel channel = r.channels.get(updatedChannel.getId()); if (channel == null) { throw new IllegalArgumentException("Channel does not exist"); @@ -538,7 +556,11 @@ public class RankingHelper implements RankingConfig { @Override public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId) { + Preconditions.checkNotNull(pkg); Record r = getOrCreateRecord(pkg, uid); + if (r == null) { + throw new IllegalArgumentException("Invalid package"); + } if (channelId == null) { channelId = NotificationChannel.DEFAULT_CHANNEL_ID; } @@ -547,7 +569,12 @@ public class RankingHelper implements RankingConfig { @Override public void deleteNotificationChannel(String pkg, int uid, String channelId) { + Preconditions.checkNotNull(pkg); + Preconditions.checkNotNull(channelId); Record r = getRecord(pkg, uid); + if (r == null) { + throw new IllegalArgumentException("Invalid package"); + } if (r != null) { r.channels.remove(channelId); } @@ -555,8 +582,12 @@ public class RankingHelper implements RankingConfig { @Override public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid) { + Preconditions.checkNotNull(pkg); List<NotificationChannel> channels = new ArrayList<>(); - Record r = getOrCreateRecord(pkg, uid); + Record r = getRecord(pkg, uid); + if (r == null) { + throw new IllegalArgumentException("Invalid package"); + } int N = r.channels.size(); for (int i = 0; i < N; i++) { channels.add(r.channels.valueAt(i)); @@ -753,11 +784,6 @@ public class RankingHelper implements RankingConfig { } } - private static boolean isUidSystem(int uid) { - final int appid = UserHandle.getAppId(uid); - return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0); - } - private static class Record { static int UNKNOWN_UID = UserHandle.USER_NULL; diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 31939749ee66..203f841bb305 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -105,11 +105,11 @@ public class Installer extends SystemService { } } - public void createAppData(String uuid, String packageName, int userId, int flags, int appId, + public long createAppData(String uuid, String packageName, int userId, int flags, int appId, String seInfo, int targetSdkVersion) throws InstallerException { - if (!checkBeforeRemote()) return; + if (!checkBeforeRemote()) return -1; try { - mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo, + return mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo, targetSdkVersion); } catch (Exception e) { throw InstallerException.from(e); @@ -182,16 +182,6 @@ public class Installer extends SystemService { } } - public long getAppDataInode(String uuid, String packageName, int userId, int flags) - throws InstallerException { - if (!checkBeforeRemote()) return -1; - try { - return mInstalld.getAppDataInode(uuid, packageName, userId, flags); - } catch (Exception e) { - throw InstallerException.from(e); - } - } - public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet, int dexoptNeeded, @Nullable String outputPath, int dexFlags, String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f7cccc8f7eea..4937662b91d4 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -5524,9 +5524,21 @@ public class PackageManagerService extends IPackageManager.Stub { final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); final ActivityInfo ai = getActivityInfo(comp, flags, userId); if (ai != null) { - final ResolveInfo ri = new ResolveInfo(); - ri.activityInfo = ai; - list.add(ri); + // When specifying an explicit component, we prevent the activity from being + // used when either 1) the calling package is normal and the activity is within + // an ephemeral application or 2) the calling package is ephemeral and the + // activity is not visible to ephemeral applications. + boolean blockResolution = + (ephemeralPkgName == null + && (ai.applicationInfo.privateFlags + & ApplicationInfo.PRIVATE_FLAG_EPHEMERAL) != 0) + || (ephemeralPkgName != null + && (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL) == 0); + if (!blockResolution) { + final ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = ai; + list.add(ri); + } } return list; } @@ -5604,10 +5616,10 @@ public class PackageManagerService extends IPackageManager.Stub { } else { final PackageParser.Package pkg = mPackages.get(pkgName); if (pkg != null) { - result = filterIfNotSystemUser( + result = filterForEphemeral(filterIfNotSystemUser( mActivities.queryIntentForPackage( intent, resolvedType, flags, pkg.activities, userId), - userId); + userId), ephemeralPkgName); } else { // the caller wants to resolve for a particular package; however, there // were no installed results, so, try to find an ephemeral result @@ -7587,19 +7599,6 @@ public class PackageManagerService extends IPackageManager.Stub { compilerFilter = getNonProfileGuidedCompilerFilter(compilerFilter); } - // If the OTA updates a system app which was previously preopted to a non-preopted state - // the app might end up being verified at runtime. That's because by default the apps - // are verify-profile but for preopted apps there's no profile. - // Do a hacky check to ensure that if we have no profiles (a reasonable indication - // that before the OTA the app was preopted) the app gets compiled with a non-profile - // filter (by default interpret-only). - // Note that at this stage unused apps are already filtered. - if (isSystemApp(pkg) && - DexFile.isProfileGuidedCompilerFilter(compilerFilter) && - !Environment.getReferenceProfile(pkg.packageName).exists()) { - compilerFilter = getNonProfileGuidedCompilerFilter(compilerFilter); - } - // checkProfiles is false to avoid merging profiles during boot which // might interfere with background compilation (b/28612421). // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will @@ -7640,6 +7639,11 @@ public class PackageManagerService extends IPackageManager.Stub { } } + @Override + public void notifyDexLoad(String loadingPackageName, List<String> dexPaths, String loaderIsa) { + // TODO(calin): b/32871170 + } + // TODO: this is not used nor needed. Delete it. @Override public boolean performDexOptIfNeeded(String packageName) { @@ -20455,8 +20459,9 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); Preconditions.checkNotNull(app.seinfo); + long ceDataInode = -1; try { - mInstaller.createAppData(volumeUuid, packageName, userId, flags, + ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags, appId, app.seinfo, app.targetSdkVersion); } catch (InstallerException e) { if (app.isSystemApp()) { @@ -20464,7 +20469,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); + ", but trying to recover: " + e); destroyAppDataLeafLIF(pkg, userId, flags); try { - mInstaller.createAppData(volumeUuid, packageName, userId, flags, + ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags, appId, app.seinfo, app.targetSdkVersion); logCriticalInfo(Log.DEBUG, "Recovery succeeded!"); } catch (InstallerException e2) { @@ -20475,21 +20480,13 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } } - if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) { - try { - // CE storage is unlocked right now, so read out the inode and - // remember for use later when it's locked - // TODO: mark this structure as dirty so we persist it! - final long ceDataInode = mInstaller.getAppDataInode(volumeUuid, packageName, userId, - StorageManager.FLAG_STORAGE_CE); - synchronized (mPackages) { - final PackageSetting ps = mSettings.mPackages.get(packageName); - if (ps != null) { - ps.setCeDataInode(ceDataInode, userId); - } + if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) { + // TODO: mark this structure as dirty so we persist it! + synchronized (mPackages) { + final PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps != null) { + ps.setCeDataInode(ceDataInode, userId); } - } catch (InstallerException e) { - Slog.e(TAG, "Failed to find inode for " + packageName + ": " + e); } } diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java index 442a4f406e81..922291790271 100644 --- a/services/core/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java @@ -59,8 +59,9 @@ public final class SELinuxMMAC { private static List<Policy> sPolicies = new ArrayList<>(); /** Path to MAC permissions on system image */ - private static final File MAC_PERMISSIONS = new File(Environment.getRootDirectory(), - "/etc/security/mac_permissions.xml"); + private static final File[] MAC_PERMISSIONS = + { new File(Environment.getRootDirectory(), "/etc/security/plat_mac_permissions.xml"), + new File(Environment.getRootDirectory(), "/etc/security/nonplat_mac_permissions.xml") }; // Append privapp to existing seinfo label private static final String PRIVILEGED_APP_STR = ":privapp"; @@ -87,49 +88,51 @@ public final class SELinuxMMAC { FileReader policyFile = null; XmlPullParser parser = Xml.newPullParser(); - try { - policyFile = new FileReader(MAC_PERMISSIONS); - Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS); - - parser.setInput(policyFile); - parser.nextTag(); - parser.require(XmlPullParser.START_TAG, null, "policy"); - - while (parser.next() != XmlPullParser.END_TAG) { - if (parser.getEventType() != XmlPullParser.START_TAG) { - continue; - } - - switch (parser.getName()) { - case "signer": - policies.add(readSignerOrThrow(parser)); - break; - default: - skip(parser); + for (int i = 0; i < MAC_PERMISSIONS.length; i++) { + try { + policyFile = new FileReader(MAC_PERMISSIONS[i]); + Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS[i]); + + parser.setInput(policyFile); + parser.nextTag(); + parser.require(XmlPullParser.START_TAG, null, "policy"); + + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + + switch (parser.getName()) { + case "signer": + policies.add(readSignerOrThrow(parser)); + break; + default: + skip(parser); + } } + } catch (IllegalStateException | IllegalArgumentException | + XmlPullParserException ex) { + StringBuilder sb = new StringBuilder("Exception @"); + sb.append(parser.getPositionDescription()); + sb.append(" while parsing "); + sb.append(MAC_PERMISSIONS[i]); + sb.append(":"); + sb.append(ex); + Slog.w(TAG, sb.toString()); + return false; + } catch (IOException ioe) { + Slog.w(TAG, "Exception parsing " + MAC_PERMISSIONS[i], ioe); + return false; + } finally { + IoUtils.closeQuietly(policyFile); } - } catch (IllegalStateException | IllegalArgumentException | - XmlPullParserException ex) { - StringBuilder sb = new StringBuilder("Exception @"); - sb.append(parser.getPositionDescription()); - sb.append(" while parsing "); - sb.append(MAC_PERMISSIONS); - sb.append(":"); - sb.append(ex); - Slog.w(TAG, sb.toString()); - return false; - } catch (IOException ioe) { - Slog.w(TAG, "Exception parsing " + MAC_PERMISSIONS, ioe); - return false; - } finally { - IoUtils.closeQuietly(policyFile); } // Now sort the policy stanzas PolicyComparator policySort = new PolicyComparator(); Collections.sort(policies, policySort); if (policySort.foundDuplicate()) { - Slog.w(TAG, "ERROR! Duplicate entries found parsing " + MAC_PERMISSIONS); + Slog.w(TAG, "ERROR! Duplicate entries found parsing mac_permissions.xml files"); return false; } diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java index 2af1bcb425ec..30608403ca63 100644 --- a/services/core/java/com/android/server/pm/ShortcutLauncher.java +++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java @@ -16,6 +16,7 @@ package com.android.server.pm; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.pm.PackageInfo; import android.content.pm.ShortcutInfo; @@ -103,6 +104,9 @@ class ShortcutLauncher extends ShortcutPackageItem { // Nothing to do. } + /** + * Pin the given shortcuts, replacing the current pinned ones. + */ public void pinShortcuts(@UserIdInt int packageUserId, @NonNull String packageName, @NonNull List<String> ids) { final ShortcutPackage packageShortcuts = @@ -143,11 +147,39 @@ class ShortcutLauncher extends ShortcutPackageItem { /** * Return the pinned shortcut IDs for the publisher package. */ + @Nullable public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName, @UserIdInt int packageUserId) { return mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName)); } + /** + * Return true if the given shortcut is pinned by this launcher. + */ + public boolean hasPinned(ShortcutInfo shortcut) { + final ArraySet<String> pinned = + getPinnedShortcutIds(shortcut.getPackage(), shortcut.getUserId()); + return (pinned != null) && pinned.contains(shortcut.getId()); + } + + /** + * Additionally pin a shortcut. c.f. {@link #pinShortcuts(int, String, List)} + */ + public void addPinnedShortcut(@NonNull String packageName, @UserIdInt int packageUserId, + String id) { + final ArraySet<String> pinnedSet = getPinnedShortcutIds(packageName, packageUserId); + final ArrayList<String> pinnedList; + if (pinnedSet != null) { + pinnedList = new ArrayList<>(pinnedSet.size() + 1); + pinnedList.addAll(pinnedSet); + } else { + pinnedList = new ArrayList<>(1); + } + pinnedList.add(id); + + pinShortcuts(packageUserId, packageName, pinnedList); + } + boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) { return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null; } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 2eb077876b98..b74506229ff7 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -179,7 +179,7 @@ class ShortcutPackage extends ShortcutPackageItem { } } - private void ensureNotImmutable(@NonNull String id) { + public void ensureNotImmutable(@NonNull String id) { ensureNotImmutable(mShortcuts.get(id)); } @@ -706,6 +706,7 @@ class ShortcutPackage extends ShortcutPackageItem { for (int i = mShortcuts.size() - 1; i >= 0; i--) { final ShortcutInfo si = mShortcuts.valueAt(i); + // Disable dynamic shortcuts whose target activity is gone. if (si.isDynamic()) { if (!s.injectIsMainActivity(si.getActivity(), getPackageUserId())) { Slog.w(TAG, String.format( diff --git a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java new file mode 100644 index 000000000000..cdb69ce813a7 --- /dev/null +++ b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java @@ -0,0 +1,355 @@ +/* + * 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.server.pm; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.IPinItemRequest; +import android.content.pm.LauncherApps; +import android.content.pm.LauncherApps.PinItemRequest; +import android.content.pm.ShortcutInfo; +import android.os.Bundle; +import android.os.UserHandle; +import android.util.Log; +import android.util.Pair; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; + +/** + * Handles {@link android.content.pm.ShortcutManager#requestPinShortcut} related tasks. + */ +class ShortcutRequestPinProcessor { + private static final String TAG = ShortcutService.TAG; + private static final boolean DEBUG = ShortcutService.DEBUG; + + private final ShortcutService mService; + private final Object mLock; + + /** + * Internal for {@link android.content.pm.LauncherApps.PinItemRequest} which receives callbacks. + */ + private static class PinShortcutRequestInner extends IPinItemRequest.Stub { + private final ShortcutRequestPinProcessor mProcessor; + /** Original shortcut passed by the app. */ + public final ShortcutInfo shortcutOriginal; + + /** + * Cloned shortcut that's passed to the launcher. The notable difference from + * {@link #shortcutOriginal} is it must not have the intent. + */ + public final ShortcutInfo shortcutForLauncher; + + private final IntentSender mResultIntent; + + public final String launcherPackage; + public final int launcherUserId; + public final boolean preExisting; + + @GuardedBy("this") + private boolean mAccepted; + + private PinShortcutRequestInner(ShortcutRequestPinProcessor processor, + ShortcutInfo shortcutOriginal, ShortcutInfo shortcutForLauncher, + IntentSender resultIntent, + String launcherPackage, int launcherUserId, boolean preExisting) { + mProcessor = processor; + this.shortcutOriginal = shortcutOriginal; + this.shortcutForLauncher = shortcutForLauncher; + mResultIntent = resultIntent; + this.launcherPackage = launcherPackage; + this.launcherUserId = launcherUserId; + this.preExisting = preExisting; + } + + @Override + public boolean isValid() { + // TODO When an app calls requestPinShortcut(), all pending requests should be + // invalidated. + synchronized (this) { + return !mAccepted; + } + } + + /** + * Called when the launcher calls {@link PinItemRequest#accept}. + */ + @Override + public boolean accept(Bundle options) { + // Make sure the options are unparcellable by the FW. (e.g. not containing unknown + // classes.) + if (options != null) { + try { + options.size(); + } catch (RuntimeException e) { + throw new IllegalArgumentException("options cannot be unparceled", e); + } + } + synchronized (this) { + if (mAccepted) { + throw new IllegalStateException("accept() called already"); + } + mAccepted = true; + } + if (DEBUG) { + Slog.d(TAG, "Launcher accepted shortcut. ID=" + shortcutOriginal.getId() + + " package=" + shortcutOriginal.getPackage() + + " options=" + options); + } + + // Pin it and send the result intent. + if (mProcessor.directPinShortcut(this)) { + mProcessor.sendResultIntent(mResultIntent); + return true; + } else { + return false; + } + } + } + + public ShortcutRequestPinProcessor(ShortcutService service, Object lock) { + mService = service; + mLock = lock; + } + + public boolean isRequestPinnedShortcutSupported(int callingUserId) { + return getRequestPinShortcutConfirmationActivity(callingUserId) != null; + } + + /** + * Handle {@link android.content.pm.ShortcutManager#requestPinShortcut)}. + */ + public boolean requestPinShortcutLocked(ShortcutInfo inShortcut, IntentSender resultIntent) { + + // First, make sure the launcher supports it. + + // Find the confirmation activity in the default launcher. + final Pair<ComponentName, Integer> confirmActivity = + getRequestPinShortcutConfirmationActivity(inShortcut.getUserId()); + + // If the launcher doesn't support it, just return a rejected result and finish. + if (confirmActivity == null) { + Log.w(TAG, "Launcher doesn't support requestPinnedShortcut(). Shortcut not created."); + return false; + } + + final ComponentName launcherComponent = confirmActivity.first; + final String launcherPackage = confirmActivity.first.getPackageName(); + final int launcherUserId = confirmActivity.second; + + // Make sure the launcher user is unlocked. (it's always the parent profile, so should + // really be unlocked here though.) + mService.throwIfUserLockedL(launcherUserId); + + // Next, validate the incoming shortcut, etc. + + final ShortcutPackage ps = mService.getPackageShortcutsForPublisherLocked( + inShortcut.getPackage(), inShortcut.getUserId()); + + final ShortcutInfo existing = ps.findShortcutById(inShortcut.getId()); + final boolean existsAlready = existing != null; + + if (DEBUG) { + Slog.d(TAG, "requestPinnedShortcut package=" + inShortcut.getPackage() + + " existsAlready=" + existsAlready + + " shortcut=" + inShortcut.toInsecureString()); + } + + // This is the shortcut that'll be sent to the launcher. + final ShortcutInfo shortcutForLauncher; + + if (existsAlready) { + validateExistingShortcut(existing); + + // See if it's already pinned. + if (mService.getLauncherShortcutsLocked( + launcherPackage, existing.getUserId(), launcherUserId).hasPinned(existing)) { + Log.i(TAG, "Launcher's already pinning shortcut " + existing.getId() + + " for package " + existing.getPackage()); + sendResultIntent(resultIntent); + return true; + } + + // Pass a clone, not the original. + // Note this will remove the intent and icons. + shortcutForLauncher = existing.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); + + // FLAG_PINNED is still set, if it's pinned by other launchers. + shortcutForLauncher.clearFlags(ShortcutInfo.FLAG_PINNED); + } else { + // It doesn't exist, so it must have all mandatory fields. + mService.validateShortcutForPinRequest(inShortcut); + + // Initialize the ShortcutInfo for pending approval. + inShortcut.resolveResourceStrings(mService.injectGetResourcesForApplicationAsUser( + inShortcut.getPackage(), inShortcut.getUserId())); + if (DEBUG) { + Slog.d(TAG, "resolved shortcut=" + inShortcut.toInsecureString()); + } + // We should strip out the intent, but should preserve the icon. + shortcutForLauncher = inShortcut.clone( + ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER_APPROVAL); + } + + // Create a request object. + final PinShortcutRequestInner inner = + new PinShortcutRequestInner(this, inShortcut, shortcutForLauncher, resultIntent, + launcherPackage, launcherUserId, existsAlready); + + final PinItemRequest outer = new PinItemRequest(PinItemRequest.REQUEST_TYPE_SHORTCUT, + shortcutForLauncher, inner); + + return startRequestConfirmActivity(launcherComponent, launcherUserId, outer); + } + + private void validateExistingShortcut(ShortcutInfo shortcutInfo) { + // Make sure it's enabled. + // (Because we can't always force enable it automatically as it may be a stale + // manifest shortcut.) + Preconditions.checkArgument(shortcutInfo.isEnabled(), + "Shortcut ID=" + shortcutInfo + " already exists but disabled."); + + } + + private boolean startRequestConfirmActivity(ComponentName activity, int launcherUserId, + PinItemRequest request) { + // Start the activity. + final Intent confirmIntent = new Intent(LauncherApps.ACTION_CONFIRM_PIN_ITEM); + confirmIntent.setComponent(activity); + confirmIntent.putExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST, request); + confirmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + + final long token = mService.injectClearCallingIdentity(); + try { + mService.mContext.startActivityAsUser( + confirmIntent, UserHandle.of(launcherUserId)); + } catch (RuntimeException e) { // ActivityNotFoundException, etc. + Log.e(TAG, "Unable to start activity " + activity, e); + return false; + } finally { + mService.injectRestoreCallingIdentity(token); + } + return true; + } + + /** + * Find the activity that handles {@link LauncherApps#ACTION_CONFIRM_PIN_ITEM} in the + * default launcher. + */ + @Nullable + @VisibleForTesting + Pair<ComponentName, Integer> getRequestPinShortcutConfirmationActivity( + int callingUserId) { + // Find the default launcher. + final int launcherUserId = mService.getParentOrSelfUserId(callingUserId); + final ComponentName defaultLauncher = mService.getDefaultLauncher(launcherUserId); + + if (defaultLauncher == null) { + Log.e(TAG, "Default launcher not found."); + return null; + } + final ComponentName activity = mService.injectGetPinConfirmationActivity( + defaultLauncher.getPackageName(), launcherUserId); + return (activity == null) ? null : Pair.create(activity, launcherUserId); + } + + public void sendResultIntent(@Nullable IntentSender intent) { + if (DEBUG) { + Slog.d(TAG, "Sending result intent."); + } + mService.injectSendIntentSender(intent); + } + + /** + * The last step of the "request pin shortcut" flow. Called when the launcher accepted a + * request. + */ + public boolean directPinShortcut(PinShortcutRequestInner request) { + + final ShortcutInfo original = request.shortcutOriginal; + final int appUserId = original.getUserId(); + final String appPackageName = original.getPackage(); + final int launcherUserId = request.launcherUserId; + final String launcherPackage = request.launcherPackage; + final String shortcutId = original.getId(); + + synchronized (mLock) { + if (!(mService.isUserUnlockedL(appUserId) + && mService.isUserUnlockedL(request.launcherUserId))) { + Log.w(TAG, "User is locked now."); + return false; + } + + final ShortcutPackage ps = mService.getPackageShortcutsForPublisherLocked( + appPackageName, appUserId); + final ShortcutInfo current = ps.findShortcutById(shortcutId); + + // The shortcut might have been changed, so we need to do the same validation again. + try { + if (current == null) { + // It doesn't exist, so it must have all necessary fields. + mService.validateShortcutForPinRequest(original); + } else { + validateExistingShortcut(current); + } + } catch (RuntimeException e) { + Log.w(TAG, "Unable to pin shortcut: " + e.getMessage()); + return false; + } + + // If the shortcut doesn't exist, need to create it. + // First, create it as a dynamic shortcut. + if (current == null) { + if (DEBUG) { + Slog.d(TAG, "Temporarily adding " + shortcutId + " as dynamic"); + } + // Add as a dynamic shortcut. + if (original.getActivity() == null) { + original.setActivity(mService.getDummyMainActivity(appPackageName)); + } + ps.addOrUpdateDynamicShortcut(original); + } + + // Pin the shortcut. + if (DEBUG) { + Slog.d(TAG, "Pinning " + shortcutId); + } + + final ShortcutLauncher launcher = mService.getLauncherShortcutsLocked( + launcherPackage, appUserId, launcherUserId); + launcher.attemptToRestoreIfNeededAndSave(); + launcher.addPinnedShortcut(appPackageName, appUserId, shortcutId); + + if (current == null) { + if (DEBUG) { + Slog.d(TAG, "Removing " + shortcutId + " as dynamic"); + } + ps.deleteDynamicWithId(shortcutId); + } + + ps.adjustRanks(); // Shouldn't be needed, but just in case. + } + + mService.verifyStates(); + mService.packageShortcutsChanged(appPackageName, appUserId); + + return true; + } +} diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index c5c1c0cac43c..436a53c77580 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -29,6 +29,8 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.IntentSender.SendIntentException; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; @@ -42,8 +44,10 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; import android.content.pm.ShortcutServiceInternal; import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener; +import android.content.pm.UserInfo; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.Bitmap; @@ -190,6 +194,8 @@ public class ShortcutService extends IShortcutService.Stub { private static final String KEY_LOW_RAM = "lowRam"; private static final String KEY_ICON_SIZE = "iconSize"; + private static final String DUMMY_MAIN_ACTIVITY = "android.__dummy__"; + @VisibleForTesting interface ConfigConstants { /** @@ -298,6 +304,8 @@ public class ShortcutService extends IShortcutService.Stub { private final UsageStatsManagerInternal mUsageStatsManagerInternal; private final ActivityManagerInternal mActivityManagerInternal; + private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor; + @GuardedBy("mLock") final SparseIntArray mUidState = new SparseIntArray(); @@ -336,8 +344,9 @@ public class ShortcutService extends IShortcutService.Stub { int IS_ACTIVITY_ENABLED = 13; int PACKAGE_UPDATE_CHECK = 14; int ASYNC_PRELOAD_USER_DELAY = 15; + int GET_DEFAULT_LAUNCHER = 16; - int COUNT = ASYNC_PRELOAD_USER_DELAY + 1; + int COUNT = GET_DEFAULT_LAUNCHER + 1; } private static final String[] STAT_LABELS = { @@ -356,7 +365,8 @@ public class ShortcutService extends IShortcutService.Stub { "checkLauncherActivity", "isActivityEnabled", "packageUpdateCheck", - "asyncPreloadUserDelay" + "asyncPreloadUserDelay", + "getDefaultLauncher()" }; final Object mStatLock = new Object(); @@ -417,6 +427,8 @@ public class ShortcutService extends IShortcutService.Stub { mActivityManagerInternal = Preconditions.checkNotNull( LocalServices.getService(ActivityManagerInternal.class)); + mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock); + if (onlyForPackageManagerApis) { return; // Don't do anything further. For unit tests only. } @@ -1591,12 +1603,11 @@ public class ShortcutService extends IShortcutService.Stub { * - Make sure the intent's extras are persistable, and them to set * {@link ShortcutInfo#mIntentPersistableExtrases}. Also clear its extras. * - Clear flags. - * - * TODO Detailed unit tests */ - private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) { + private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate, + boolean forPinRequest) { Preconditions.checkNotNull(shortcut, "Null shortcut detected"); - if (shortcut.getActivity() != null) { + if (!forPinRequest && shortcut.getActivity() != null) { Preconditions.checkState( shortcut.getPackage().equals(shortcut.getActivity().getPackageName()), "Cannot publish shortcut: activity " + shortcut.getActivity() + " does not" @@ -1608,10 +1619,13 @@ public class ShortcutService extends IShortcutService.Stub { } if (!forUpdate) { - shortcut.enforceMandatoryFields(/* forPinned= */ false); - Preconditions.checkArgument( - injectIsMainActivity(shortcut.getActivity(), shortcut.getUserId()), - "Cannot publish shortcut: " + shortcut.getActivity() + " is not main activity"); + shortcut.enforceMandatoryFields(/* forPinned= */ forPinRequest); + if (!forPinRequest) { + Preconditions.checkArgument( + injectIsMainActivity(shortcut.getActivity(), shortcut.getUserId()), + "Cannot publish shortcut: " + shortcut.getActivity() + + " is not main activity"); + } } if (shortcut.getIcon() != null) { ShortcutInfo.validateIcon(shortcut.getIcon()); @@ -1620,11 +1634,18 @@ public class ShortcutService extends IShortcutService.Stub { shortcut.replaceFlags(0); } + private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) { + fixUpIncomingShortcutInfo(shortcut, forUpdate, /*forPinRequest=*/ false); + } + + public void validateShortcutForPinRequest(@NonNull ShortcutInfo shortcut) { + fixUpIncomingShortcutInfo(shortcut, /* forUpdate= */ false, /*forPinRequest=*/ true); + } + /** * When a shortcut has no target activity, set the default one from the package. */ private void fillInDefaultActivity(List<ShortcutInfo> shortcuts) { - ComponentName defaultActivity = null; for (int i = shortcuts.size() - 1; i >= 0; i--) { final ShortcutInfo si = shortcuts.get(i); @@ -1834,6 +1855,31 @@ public class ShortcutService extends IShortcutService.Stub { } @Override + public boolean requestPinShortcut(String packageName, ShortcutInfo shortcut, + IntentSender resultIntent, int userId) { + verifyCaller(packageName, userId); + Preconditions.checkNotNull(shortcut); + Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); + + final boolean ret; + synchronized (mLock) { + throwIfUserLockedL(userId); + + Preconditions.checkState(isUidForegroundLocked(injectBinderCallingUid()), + "Calling application must have a foreground activity or a foreground service"); + + // TODO Cancel all pending requests from the caller. + + // Send request to the launcher, if supported. + ret = mShortcutRequestPinProcessor.requestPinShortcutLocked(shortcut, resultIntent); + } + + verifyStates(); + + return ret; + } + + @Override public void disableShortcuts(String packageName, List shortcutIds, CharSequence disabledMessage, int disabledMessageResId, @UserIdInt int userId) { verifyCaller(packageName, userId); @@ -2049,6 +2095,16 @@ public class ShortcutService extends IShortcutService.Stub { } } + @Override + public boolean isRequestPinShortcutSupported(int callingUserId) { + final long token = injectClearCallingIdentity(); + try { + return mShortcutRequestPinProcessor.isRequestPinnedShortcutSupported(callingUserId); + } finally { + injectRestoreCallingIdentity(token); + } + } + /** * Reset all throttling, for developer options and command line. Only system/shell can call * it. @@ -2113,89 +2169,108 @@ public class ShortcutService extends IShortcutService.Stub { // This method is extracted so we can directly call this method from unit tests, // even when hasShortcutPermission() is overridden. @VisibleForTesting - boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) { + boolean hasShortcutHostPermissionInner(@NonNull String packageName, int userId) { synchronized (mLock) { throwIfUserLockedL(userId); final ShortcutUser user = getUserShortcutsLocked(userId); - // Always trust the in-memory cache. + // Always trust the cached component. final ComponentName cached = user.getCachedLauncher(); if (cached != null) { - if (cached.getPackageName().equals(callingPackage)) { + if (cached.getPackageName().equals(packageName)) { return true; } } // If the cached one doesn't match, then go ahead - final List<ResolveInfo> allHomeCandidates = new ArrayList<>(); - - // Default launcher from package manager. - final long startGetHomeActivitiesAsUser = injectElapsedRealtime(); - final ComponentName defaultLauncher = mPackageManagerInternal - .getHomeActivitiesAsUser(allHomeCandidates, userId); - logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser); + final ComponentName detected = getDefaultLauncher(userId); - ComponentName detected; - if (defaultLauncher != null) { - detected = defaultLauncher; + // Update the cache. + user.setLauncher(detected); + if (detected != null) { if (DEBUG) { - Slog.v(TAG, "Default launcher from PM: " + detected); + Slog.v(TAG, "Detected launcher: " + detected); } + return detected.getPackageName().equals(packageName); } else { - detected = user.getLastKnownLauncher(); - - if (detected != null) { - if (injectIsActivityEnabledAndExported(detected, userId)) { - if (DEBUG) { - Slog.v(TAG, "Cached launcher: " + detected); - } - } else { - Slog.w(TAG, "Cached launcher " + detected + " no longer exists"); - detected = null; - user.clearLauncher(); - } - } + // Default launcher not found. + return false; } + } + } - if (detected == null) { - // If we reach here, that means it's the first check since the user was created, - // and there's already multiple launchers and there's no default set. - // Find the system one with the highest priority. - // (We need to check the priority too because of FallbackHome in Settings.) - // If there's no system launcher yet, then no one can access shortcuts, until - // the user explicitly - final int size = allHomeCandidates.size(); - - int lastPriority = Integer.MIN_VALUE; - for (int i = 0; i < size; i++) { - final ResolveInfo ri = allHomeCandidates.get(i); - if (!ri.activityInfo.applicationInfo.isSystemApp()) { - continue; - } + @Nullable + ComponentName getDefaultLauncher(@UserIdInt int userId) { + final long start = injectElapsedRealtime(); + final long token = injectClearCallingIdentity(); + try { + synchronized (mLock) { + throwIfUserLockedL(userId); + + final ShortcutUser user = getUserShortcutsLocked(userId); + + final List<ResolveInfo> allHomeCandidates = new ArrayList<>(); + + // Default launcher from package manager. + final long startGetHomeActivitiesAsUser = injectElapsedRealtime(); + final ComponentName defaultLauncher = mPackageManagerInternal + .getHomeActivitiesAsUser(allHomeCandidates, userId); + logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser); + + ComponentName detected = null; + if (defaultLauncher != null) { + detected = defaultLauncher; if (DEBUG) { - Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d", - ri.activityInfo.getComponentName(), ri.priority)); + Slog.v(TAG, "Default launcher from PM: " + detected); } - if (ri.priority < lastPriority) { - continue; + } else { + detected = user.getLastKnownLauncher(); + + if (detected != null) { + if (injectIsActivityEnabledAndExported(detected, userId)) { + if (DEBUG) { + Slog.v(TAG, "Cached launcher: " + detected); + } + } else { + Slog.w(TAG, "Cached launcher " + detected + " no longer exists"); + detected = null; + user.clearLauncher(); + } } - detected = ri.activityInfo.getComponentName(); - lastPriority = ri.priority; } - } - // Update the cache. - user.setLauncher(detected); - if (detected != null) { - if (DEBUG) { - Slog.v(TAG, "Detected launcher: " + detected); + if (detected == null) { + // If we reach here, that means it's the first check since the user was created, + // and there's already multiple launchers and there's no default set. + // Find the system one with the highest priority. + // (We need to check the priority too because of FallbackHome in Settings.) + // If there's no system launcher yet, then no one can access shortcuts, until + // the user explicitly + final int size = allHomeCandidates.size(); + + int lastPriority = Integer.MIN_VALUE; + for (int i = 0; i < size; i++) { + final ResolveInfo ri = allHomeCandidates.get(i); + if (!ri.activityInfo.applicationInfo.isSystemApp()) { + continue; + } + if (DEBUG) { + Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d", + ri.activityInfo.getComponentName(), ri.priority)); + } + if (ri.priority < lastPriority) { + continue; + } + detected = ri.activityInfo.getComponentName(); + lastPriority = ri.priority; + } } - return detected.getPackageName().equals(callingPackage); - } else { - // Default launcher not found. - return false; + return detected; } + } finally { + injectRestoreCallingIdentity(token); + logDurationStat(Stats.GET_DEFAULT_LAUNCHER, start); } } @@ -3034,10 +3109,21 @@ public class ShortcutService extends IShortcutService.Stub { if (activity != null) { baseIntent.setComponent(activity); } + return queryActivities(baseIntent, userId, /* exportedOnly =*/ true); + } - final List<ResolveInfo> resolved = - mContext.getPackageManager().queryIntentActivitiesAsUser( - baseIntent, PACKAGE_MATCH_FLAGS, userId); + @NonNull + List<ResolveInfo> queryActivities(@NonNull Intent intent, int userId, + boolean exportedOnly) { + final List<ResolveInfo> resolved; + final long token = injectClearCallingIdentity(); + try { + resolved = + mContext.getPackageManager().queryIntentActivitiesAsUser( + intent, PACKAGE_MATCH_FLAGS, userId); + } finally { + injectRestoreCallingIdentity(token); + } if (resolved == null || resolved.size() == 0) { return EMPTY_RESOLVE_INFO; } @@ -3045,7 +3131,9 @@ public class ShortcutService extends IShortcutService.Stub { if (!isInstalled(resolved.get(0).activityInfo)) { return EMPTY_RESOLVE_INFO; } - resolved.removeIf(ACTIVITY_NOT_EXPORTED); + if (exportedOnly) { + resolved.removeIf(ACTIVITY_NOT_EXPORTED); + } return resolved; } @@ -3056,14 +3144,11 @@ public class ShortcutService extends IShortcutService.Stub { @Nullable ComponentName injectGetDefaultMainActivity(@NonNull String packageName, int userId) { final long start = injectElapsedRealtime(); - final long token = injectClearCallingIdentity(); try { final List<ResolveInfo> resolved = queryActivities(getMainActivityIntent(), packageName, null, userId); return resolved.size() == 0 ? null : resolved.get(0).activityInfo.getComponentName(); } finally { - injectRestoreCallingIdentity(token); - logDurationStat(Stats.GET_LAUNCHER_ACTIVITY, start); } } @@ -3073,31 +3158,36 @@ public class ShortcutService extends IShortcutService.Stub { */ boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) { final long start = injectElapsedRealtime(); - final long token = injectClearCallingIdentity(); try { - final List<ResolveInfo> resolved = - queryActivities(getMainActivityIntent(), activity.getPackageName(), - activity, userId); + if (DUMMY_MAIN_ACTIVITY.equals(activity.getClassName())) { + return true; + } + final List<ResolveInfo> resolved = queryActivities( + getMainActivityIntent(), activity.getPackageName(), activity, userId); return resolved.size() > 0; } finally { - injectRestoreCallingIdentity(token); - logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start); } } /** + * Create a dummy "main activity" component name which is used to create a dynamic shortcut + * with no main activity temporarily. + */ + @NonNull + ComponentName getDummyMainActivity(@NonNull String packageName) { + return new ComponentName(packageName, DUMMY_MAIN_ACTIVITY); + } + + /** * Return all the enabled, exported and main activities from a package. */ @NonNull List<ResolveInfo> injectGetMainActivities(@NonNull String packageName, int userId) { final long start = injectElapsedRealtime(); - final long token = injectClearCallingIdentity(); try { return queryActivities(getMainActivityIntent(), packageName, null, userId); } finally { - injectRestoreCallingIdentity(token); - logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start); } } @@ -3109,17 +3199,33 @@ public class ShortcutService extends IShortcutService.Stub { boolean injectIsActivityEnabledAndExported( @NonNull ComponentName activity, @UserIdInt int userId) { final long start = injectElapsedRealtime(); - final long token = injectClearCallingIdentity(); try { return queryActivities(new Intent(), activity.getPackageName(), activity, userId) .size() > 0; } finally { - injectRestoreCallingIdentity(token); - logDurationStat(Stats.IS_ACTIVITY_ENABLED, start); } } + /** + * Get the {@link LauncherApps#ACTION_CONFIRM_PIN_ITEM} activity in a given package. + */ + @Nullable + ComponentName injectGetPinConfirmationActivity(@NonNull String launcherPackageName, + int launcherUserId) { + Preconditions.checkNotNull(launcherPackageName); + + final Intent confirmIntent = new Intent(LauncherApps.ACTION_CONFIRM_PIN_ITEM); + confirmIntent.setPackage(launcherPackageName); + + final List<ResolveInfo> candidates = queryActivities( + confirmIntent, launcherUserId, /* exportedOnly =*/ false); + for (ResolveInfo ri : candidates) { + return ri.activityInfo.getComponentName(); + } + return null; + } + boolean injectIsSafeModeEnabled() { final long token = injectClearCallingIdentity(); try { @@ -3133,6 +3239,32 @@ public class ShortcutService extends IShortcutService.Stub { } } + /** + * If {@code userId} is of a managed profile, return the parent user ID. Otherwise return + * itself. + */ + int getParentOrSelfUserId(int userId) { + final long token = injectClearCallingIdentity(); + try { + final UserInfo parent = mUserManager.getProfileParent(userId); + return (parent != null) ? parent.id : userId; + } finally { + injectRestoreCallingIdentity(token); + } + } + + void injectSendIntentSender(IntentSender intentSender) { + if (intentSender == null) { + return; + } + try { + intentSender.sendIntent(mContext, /* code= */ 0, /* intent= */ null, + /* onFinished=*/ null, /* handler= */ null); + } catch (SendIntentException e) { + Slog.w(TAG, "sendIntent failed().", e); + } + } + // === Backup & restore === boolean shouldBackupApp(String packageName, int userId) { @@ -3749,6 +3881,11 @@ public class ShortcutService extends IShortcutService.Stub { } } + @VisibleForTesting + ShortcutRequestPinProcessor getShortcutRequestPinProcessorForTest() { + return mShortcutRequestPinProcessor; + } + /** * Control whether {@link #verifyStates} should be performed. We always perform it during unit * tests. diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index bac7a76e01cc..05228ec50e5d 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -2269,8 +2269,11 @@ public class UserManagerService extends IUserManager.Stub { private UserInfo createUserInternal(String name, int flags, int parentId, String[] disallowedPackages) { - if (hasUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.getCallingUserId())) { - Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled."); + String restriction = ((flags & UserInfo.FLAG_MANAGED_PROFILE) != 0) + ? UserManager.DISALLOW_ADD_MANAGED_PROFILE + : UserManager.DISALLOW_ADD_USER; + if (hasUserRestriction(restriction, UserHandle.getCallingUserId())) { + Log.w(LOG_TAG, "Cannot add user. " + restriction + " is enabled."); return null; } return createUserInternalUnchecked(name, flags, parentId, disallowedPackages); @@ -2541,9 +2544,16 @@ public class UserManagerService extends IUserManager.Stub { public boolean removeUser(int userHandle) { Slog.i(LOG_TAG, "removeUser u" + userHandle); checkManageOrCreateUsersPermission("Only the system can remove users"); - if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean( - UserManager.DISALLOW_REMOVE_USER, false)) { - Log.w(LOG_TAG, "Cannot remove user. DISALLOW_REMOVE_USER is enabled."); + + final boolean isManagedProfile; + synchronized (mUsersLock) { + UserInfo userInfo = getUserInfoLU(userHandle); + isManagedProfile = userInfo != null && userInfo.isManagedProfile(); + } + String restriction = isManagedProfile + ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE : UserManager.DISALLOW_REMOVE_USER; + if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) { + Log.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled."); return false; } return removeUserUnchecked(userHandle); diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 7ec3c19bbae3..e91cce11f7df 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -75,12 +75,14 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_USB_FILE_TRANSFER, UserManager.DISALLOW_CONFIG_CREDENTIALS, UserManager.DISALLOW_REMOVE_USER, + UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, UserManager.DISALLOW_DEBUGGING_FEATURES, UserManager.DISALLOW_CONFIG_VPN, UserManager.DISALLOW_CONFIG_TETHERING, UserManager.DISALLOW_NETWORK_RESET, UserManager.DISALLOW_FACTORY_RESET, UserManager.DISALLOW_ADD_USER, + UserManager.DISALLOW_ADD_MANAGED_PROFILE, UserManager.ENSURE_VERIFY_APPS, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, @@ -124,6 +126,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_NETWORK_RESET, UserManager.DISALLOW_FACTORY_RESET, UserManager.DISALLOW_ADD_USER, + UserManager.DISALLOW_ADD_MANAGED_PROFILE, + UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, @@ -155,6 +159,13 @@ public class UserRestrictionsUtils { ); /** + * User restrictions that default to {@code true} for device owners. + */ + private static final Set<String> DEFAULT_ENABLED_FOR_DEVICE_OWNERS = Sets.newArraySet( + UserManager.DISALLOW_ADD_MANAGED_PROFILE + ); + + /** * Throws {@link IllegalArgumentException} if the given restriction name is invalid. */ public static boolean isValidRestriction(@NonNull String restriction) { @@ -249,6 +260,13 @@ public class UserRestrictionsUtils { } /** + * Returns the user restrictions that default to {@code true} for device owners. + */ + public static @NonNull Set<String> getDefaultEnabledForDeviceOwner() { + return DEFAULT_ENABLED_FOR_DEVICE_OWNERS; + } + + /** * Takes restrictions that can be set by device owner, and sort them into what should be applied * globally and what should be applied only on the current user. */ diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index afad32846a3f..9d4b51fd66bf 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -229,6 +229,7 @@ import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener; import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.wm.AppTransition; +import com.android.server.vr.VrManagerInternal; import java.io.File; import java.io.FileReader; @@ -6455,6 +6456,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mKeyguardDelegate.onScreenTurnedOff(); } } + reportScreenStateToVrManager(false); } // Called on the DisplayManager's DisplayPowerController thread. @@ -6490,6 +6492,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { mKeyguardDelegate.onScreenTurnedOn(); } } + reportScreenStateToVrManager(true); + } + + private void reportScreenStateToVrManager(boolean isScreenOn) { + VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class); + if (vrService == null) { + return; + } + vrService.onScreenStateChanged(isScreenOn); } private void finishWindowsDrawn() { @@ -6674,9 +6685,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Navigation bar and status bar. getNonDecorInsetsLw(displayRotation, displayWidth, displayHeight, outInsets); - if (mStatusBar != null) { - outInsets.top = mStatusBarHeight; - } + outInsets.top = mStatusBarHeight; } @Override @@ -6685,7 +6694,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { outInsets.setEmpty(); // Only navigation bar - if (mNavigationBar != null) { + if (mHasNavigationBar) { int position = navigationBarPosition(displayWidth, displayHeight, displayRotation); if (position == NAV_BAR_BOTTOM) { outInsets.bottom = getNavigationBarHeight(displayRotation, mUiMode); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 1790554a258b..f2ccac5197aa 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -134,6 +134,8 @@ public final class PowerManagerService extends SystemService private static final int DIRTY_DOCK_STATE = 1 << 10; // Dirty bit: brightness boost changed private static final int DIRTY_SCREEN_BRIGHTNESS_BOOST = 1 << 11; + // Dirty bit: sQuiescent changed + private static final int DIRTY_QUIESCENT = 1 << 12; // Summarizes the state of all active wakelocks. private static final int WAKE_LOCK_CPU = 1 << 0; @@ -169,6 +171,9 @@ public final class PowerManagerService extends SystemService // Default setting for double tap to wake. private static final int DEFAULT_DOUBLE_TAP_TO_WAKE = 0; + // System property indicating that the screen should remain off until an explicit user action + private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent"; + /** Constants for {@link #shutdownOrRebootInternal} */ @Retention(RetentionPolicy.SOURCE) @IntDef({HALT_MODE_SHUTDOWN, HALT_MODE_REBOOT, HALT_MODE_REBOOT_SAFE_MODE}) @@ -398,6 +403,9 @@ public final class PowerManagerService extends SystemService // True if the device should stay on. private boolean mStayOn; + // True if the lights should stay off until an explicit user action. + private static boolean sQuiescent; + // True if the proximity sensor reads a positive result. private boolean mProximityPositive; @@ -530,6 +538,8 @@ public final class PowerManagerService extends SystemService mWakefulness = WAKEFULNESS_AWAKE; + sQuiescent = SystemProperties.get(SYSTEM_PROPERTY_QUIESCENT, "0").equals("1"); + nativeInit(); nativeSetAutoSuspend(false); nativeSetInteractive(true); @@ -1190,12 +1200,19 @@ public final class PowerManagerService extends SystemService && eventTime > mLastUserActivityTime) { mLastUserActivityTimeNoChangeLights = eventTime; mDirty |= DIRTY_USER_ACTIVITY; + if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) { + mDirty |= DIRTY_QUIESCENT; + } + return true; } } else { if (eventTime > mLastUserActivityTime) { mLastUserActivityTime = eventTime; mDirty |= DIRTY_USER_ACTIVITY; + if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) { + mDirty |= DIRTY_QUIESCENT; + } return true; } } @@ -2096,7 +2113,7 @@ public final class PowerManagerService extends SystemService final boolean oldDisplayReady = mDisplayReady; if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED - | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST)) != 0) { + | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST | DIRTY_QUIESCENT)) != 0) { mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(); // Determine appropriate screen brightness and auto-brightness adjustments. @@ -2163,6 +2180,9 @@ public final class PowerManagerService extends SystemService mRequestWaitForNegativeProximity); mRequestWaitForNegativeProximity = false; + if ((dirty & DIRTY_QUIESCENT) != 0) { + sQuiescent = false; + } if (DEBUG_SPEW) { Slog.d(TAG, "updateDisplayPowerStateLocked: mDisplayReady=" + mDisplayReady + ", policy=" + mDisplayPowerRequest.policy @@ -2170,8 +2190,8 @@ public final class PowerManagerService extends SystemService + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary) + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary) + ", mBootCompleted=" + mBootCompleted - + ", mScreenBrightnessBoostInProgress=" - + mScreenBrightnessBoostInProgress); + + ", mScreenBrightnessBoostInProgress=" + mScreenBrightnessBoostInProgress + + ", sQuiescent=" + sQuiescent); } } return mDisplayReady && !oldDisplayReady; @@ -2210,7 +2230,7 @@ public final class PowerManagerService extends SystemService } private int getDesiredScreenPolicyLocked() { - if (mWakefulness == WAKEFULNESS_ASLEEP) { + if (mWakefulness == WAKEFULNESS_ASLEEP || sQuiescent) { return DisplayPowerRequest.POLICY_OFF; } @@ -2899,10 +2919,25 @@ public final class PowerManagerService extends SystemService } if (reason.equals(PowerManager.REBOOT_RECOVERY) || reason.equals(PowerManager.REBOOT_RECOVERY_UPDATE)) { - SystemProperties.set("sys.powerctl", "reboot,recovery"); - } else { - SystemProperties.set("sys.powerctl", "reboot," + reason); + reason = "recovery"; + } + + // If the reason is "quiescent", it means that the boot process should proceed + // without turning on the screen/lights. + // The "quiescent" property is sticky, meaning that any number + // of subsequent reboots should honor the property until it is reset. + if (reason.equals(PowerManager.REBOOT_QUIESCENT)) { + sQuiescent = true; + reason = ""; } + + if (sQuiescent) { + // Pass the optional "quiescent" argument to the bootloader to let it know + // that it should not turn the screen/lights on. + reason = reason + ",quiescent"; + } + + SystemProperties.set("sys.powerctl", "reboot," + reason); try { Thread.sleep(20 * 1000L); } catch (InterruptedException e) { diff --git a/services/core/java/com/android/server/vr/VrManagerInternal.java b/services/core/java/com/android/server/vr/VrManagerInternal.java index ad87a885348e..0fc1900166e2 100644 --- a/services/core/java/com/android/server/vr/VrManagerInternal.java +++ b/services/core/java/com/android/server/vr/VrManagerInternal.java @@ -56,25 +56,27 @@ public abstract class VrManagerInternal { int userId, @NonNull ComponentName calling); /** - * Set the current VR mode state immediately. + * Set whether the system has acquired a sleep token. * - * @param enabled {@code true} to enable VR mode. - * @param packageName The package name of the requested VrListenerService to bind. - * @param userId the user requesting the VrListenerService component. - * @param calling the component currently using VR mode, or null to leave unchanged. + * @param isAsleep is {@code true} if the device is asleep, or {@code false} otherwise. */ - public abstract void setVrModeImmediate(boolean enabled, @NonNull ComponentName packageName, - int userId, @NonNull ComponentName calling); + public abstract void onSleepStateChanged(boolean isAsleep); + /** + * Set whether the display used for VR output is on. + * + * @param isScreenOn is {@code true} if the display is on and can receive commands, + * or {@code false} otherwise. + */ + public abstract void onScreenStateChanged(boolean isScreenOn); - /** - * Return NO_ERROR if the given package is installed on the device and enabled as a - * VrListenerService for the given current user, or a negative error code indicating a failure. - * - * @param packageName the name of the package to check, or null to select the default package. - * @return NO_ERROR if the given package is installed and is enabled, or a negative error code - * given in {@link android.service.vr.VrModeException} on failure. - */ + /** + * Return NO_ERROR if the given package is installed on the device and enabled as a + * VrListenerService for the given current user, or a negative error code indicating a failure. + * + * @param packageName the name of the package to check, or null to select the default package. + * @return NO_ERROR if the given package is installed and is enabled, or a negative error code + * given in {@link android.service.vr.VrModeException} on failure. + */ public abstract int hasVrPackage(@NonNull ComponentName packageName, int userId); - } diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index fdadc8de717f..1083e0ada55d 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -101,6 +101,14 @@ public class VrManagerService extends SystemService implements EnabledComponentC private static final int PENDING_STATE_DELAY_MS = 300; private static final int EVENT_LOG_SIZE = 32; private static final int INVALID_APPOPS_MODE = -1; + /** Null set of sleep sleep flags. */ + private static final int FLAG_NONE = 0; + /** Flag set when the device is not sleeping. */ + private static final int FLAG_AWAKE = 1; + /** Flag set when the screen has been turned on. */ + private static final int FLAG_SCREEN_ON = 2; + /** Flag indicating that all system sleep flags have been set.*/ + private static final int FLAG_ALL = FLAG_AWAKE | FLAG_SCREEN_ON; private static native void initializeNative(); private static native void setVrModeNative(boolean enabled); @@ -110,6 +118,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC private final IBinder mOverlayToken = new Binder(); // State protected by mLock + private boolean mVrModeAllowed; private boolean mVrModeEnabled; private EnabledComponentsObserver mComponentObserver; private ManagedApplicationService mCurrentVrService; @@ -125,10 +134,64 @@ public class VrManagerService extends SystemService implements EnabledComponentC private VrState mPendingState; private final ArrayDeque<VrState> mLoggingDeque = new ArrayDeque<>(EVENT_LOG_SIZE); private final NotificationAccessManager mNotifAccessManager = new NotificationAccessManager(); + /** Tracks the state of the screen and keyguard UI.*/ + private int mSystemSleepFlags = FLAG_NONE; private static final int MSG_VR_STATE_CHANGE = 0; private static final int MSG_PENDING_VR_STATE_CHANGE = 1; + /** + * Set whether VR mode may be enabled. + * <p/> + * If VR mode is not allowed to be enabled, calls to set VR mode will be cached. When VR mode + * is again allowed to be enabled, the most recent cached state will be applied. + * + * @param allowed {@code true} if calling any of the setVrMode methods may cause the device to + * enter VR mode. + */ + private void setVrModeAllowedLocked(boolean allowed) { + if (mVrModeAllowed != allowed) { + mVrModeAllowed = allowed; + Slog.i(TAG, "VR mode is " + ((allowed) ? "allowed" : "disallowed")); + if (mVrModeAllowed) { + consumeAndApplyPendingStateLocked(); + } else { + // Set pending state to current state. + mPendingState = (mVrModeEnabled && mCurrentVrService != null) + ? new VrState(mVrModeEnabled, mCurrentVrService.getComponent(), + mCurrentVrService.getUserId(), mCurrentVrModeComponent) + : null; + + // Unbind current VR service and do necessary callbacks. + updateCurrentVrServiceLocked(false, null, 0, null); + } + } + } + + private void setSleepState(boolean isAsleep) { + synchronized(mLock) { + + if (!isAsleep) { + mSystemSleepFlags |= FLAG_AWAKE; + } else { + mSystemSleepFlags &= ~FLAG_AWAKE; + } + + setVrModeAllowedLocked(mSystemSleepFlags == FLAG_ALL); + } + } + + private void setScreenOn(boolean isScreenOn) { + synchronized(mLock) { + if (isScreenOn) { + mSystemSleepFlags |= FLAG_SCREEN_ON; + } else { + mSystemSleepFlags &= ~FLAG_SCREEN_ON; + } + setVrModeAllowedLocked(mSystemSleepFlags == FLAG_ALL); + } + } + private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -148,7 +211,9 @@ public class VrManagerService extends SystemService implements EnabledComponentC } break; case MSG_PENDING_VR_STATE_CHANGE : { synchronized(mLock) { - VrManagerService.this.consumeAndApplyPendingStateLocked(); + if (mVrModeAllowed) { + VrManagerService.this.consumeAndApplyPendingStateLocked(); + } } } break; default : @@ -268,8 +333,8 @@ public class VrManagerService extends SystemService implements EnabledComponentC } mNotifAccessManager.update(enabledPackages); - if (mCurrentVrService == null) { - return; // No active services + if (!mVrModeAllowed) { + return; // Don't do anything, we shouldn't be in VR mode. } // If there is a pending state change, we'd better deal with that first @@ -321,6 +386,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC return; } pw.println("********* Dump of VrManagerService *********"); + pw.println("VR mode is currently: " + ((mVrModeAllowed) ? "allowed" : "disallowed")); pw.println("Previous state transitions:\n"); String tab = " "; dumpStateTransitions(pw); @@ -374,13 +440,17 @@ public class VrManagerService extends SystemService implements EnabledComponentC @Override public void setVrMode(boolean enabled, ComponentName packageName, int userId, ComponentName callingPackage) { - VrManagerService.this.setVrMode(enabled, packageName, userId, callingPackage, false); + VrManagerService.this.setVrMode(enabled, packageName, userId, callingPackage); } @Override - public void setVrModeImmediate(boolean enabled, ComponentName packageName, int userId, - ComponentName callingPackage) { - VrManagerService.this.setVrMode(enabled, packageName, userId, callingPackage, true); + public void onSleepStateChanged(boolean isAsleep) { + VrManagerService.this.setSleepState(isAsleep); + } + + @Override + public void onScreenStateChanged(boolean isScreenOn) { + VrManagerService.this.setScreenOn(isScreenOn); } @Override @@ -424,6 +494,10 @@ public class VrManagerService extends SystemService implements EnabledComponentC mComponentObserver.rebuildAll(); } + } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { + synchronized (mLock) { + mVrModeAllowed = true; + } } } @@ -466,12 +540,16 @@ public class VrManagerService extends SystemService implements EnabledComponentC false, mOverlayToken, null, oldUserId); } + if (!mVrModeEnabled) { + return; + } + // Apply the restrictions for the current user based on vr state String[] exemptions = (exemptedPackage == null) ? new String[0] : new String[] { exemptedPackage }; appOpsManager.setUserRestrictionForUser(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, - mVrModeEnabled, mOverlayToken, exemptions, newUserId); + true, mOverlayToken, exemptions, newUserId); } private void updateDependentAppOpsLocked(String newVrServicePackage, int newUserId, @@ -512,7 +590,8 @@ public class VrManagerService extends SystemService implements EnabledComponentC boolean validUserComponent = (mComponentObserver.isValid(component, userId) == EnabledComponentsObserver.NO_ERROR); - if (!mVrModeEnabled && !enabled) { + boolean goingIntoVrMode = validUserComponent && enabled; + if (!mVrModeEnabled && !goingIntoVrMode) { return validUserComponent; // Disabled -> Disabled transition does nothing. } @@ -520,29 +599,37 @@ public class VrManagerService extends SystemService implements EnabledComponentC ? mCurrentVrService.getComponent().getPackageName() : null; final int oldUserId = mCurrentVrModeUser; - // Always send mode change events. - changeVrModeLocked(enabled); + // Notify system services and VR HAL of mode change. + changeVrModeLocked(goingIntoVrMode); - if (!enabled || !validUserComponent) { - // Unbind whatever is running + boolean nothingChanged = false; + if (!goingIntoVrMode) { + // Not going into VR mode, unbind whatever is running if (mCurrentVrService != null) { Slog.i(TAG, "Disconnecting " + mCurrentVrService.getComponent() + " for user " + mCurrentVrService.getUserId()); mCurrentVrService.disconnect(); mCurrentVrService = null; + } else { + nothingChanged = true; } } else { + // Going into VR mode if (mCurrentVrService != null) { - // Unbind any running service that doesn't match the component/user selection + // Unbind any running service that doesn't match the latest component/user + // selection. if (mCurrentVrService.disconnectIfNotMatching(component, userId)) { Slog.i(TAG, "Disconnecting " + mCurrentVrService.getComponent() + " for user " + mCurrentVrService.getUserId()); createAndConnectService(component, userId); sendUpdatedCaller = true; + } else { + nothingChanged = true; } - // The service with the correct component/user is bound + // The service with the correct component/user is already bound, do nothing. } else { - // Nothing was previously running, bind a new service + // Nothing was previously running, bind a new service for the latest + // component/user selection. createAndConnectService(component, userId); sendUpdatedCaller = true; } @@ -577,7 +664,10 @@ public class VrManagerService extends SystemService implements EnabledComponentC } }); } - logStateLocked(); + + if (!nothingChanged) { + logStateLocked(); + } return validUserComponent; } finally { @@ -663,16 +753,28 @@ public class VrManagerService extends SystemService implements EnabledComponentC private void grantCoarseLocationPermissionIfNeeded(String pkg, int userId) { // Don't clobber the user if permission set in current state explicitly if (!isPermissionUserUpdated(Manifest.permission.ACCESS_COARSE_LOCATION, pkg, userId)) { - mContext.getPackageManager().grantRuntimePermission(pkg, - Manifest.permission.ACCESS_COARSE_LOCATION, new UserHandle(userId)); + try { + mContext.getPackageManager().grantRuntimePermission(pkg, + Manifest.permission.ACCESS_COARSE_LOCATION, new UserHandle(userId)); + } catch (IllegalArgumentException e) { + // Package was removed during update. + Slog.w(TAG, "Could not grant coarse location permission, package " + pkg + + " was removed."); + } } } private void revokeCoarseLocationPermissionIfNeeded(String pkg, int userId) { // Don't clobber the user if permission set in current state explicitly if (!isPermissionUserUpdated(Manifest.permission.ACCESS_COARSE_LOCATION, pkg, userId)) { - mContext.getPackageManager().revokeRuntimePermission(pkg, - Manifest.permission.ACCESS_COARSE_LOCATION, new UserHandle(userId)); + try { + mContext.getPackageManager().revokeRuntimePermission(pkg, + Manifest.permission.ACCESS_COARSE_LOCATION, new UserHandle(userId)); + } catch (IllegalArgumentException e) { + // Package was removed during update. + Slog.w(TAG, "Could not revoke coarse location permission, package " + pkg + + " was removed."); + } } } @@ -772,7 +874,10 @@ public class VrManagerService extends SystemService implements EnabledComponentC mPendingState.targetPackageName, mPendingState.userId, mPendingState.callingPackage); mPendingState = null; + } else { + updateCurrentVrServiceLocked(false, null, 0, null); } + } private void logStateLocked() { @@ -822,13 +927,20 @@ public class VrManagerService extends SystemService implements EnabledComponentC /* * Implementation of VrManagerInternal calls. These are callable from system services. */ - private void setVrMode(boolean enabled, @NonNull ComponentName targetPackageName, - int userId, @NonNull ComponentName callingPackage, boolean immediate) { + int userId, @NonNull ComponentName callingPackage) { synchronized (mLock) { + VrState pending = new VrState(enabled, targetPackageName, userId, callingPackage); + if (!mVrModeAllowed) { + // We're not allowed to be in VR mode. Make this state pending. This will be + // applied the next time we are allowed to enter VR mode unless it is superseded by + // another call. + mPendingState = pending; + return; + } - if (!enabled && mCurrentVrService != null && !immediate) { + if (!enabled && mCurrentVrService != null) { // If we're transitioning out of VR mode, delay briefly to avoid expensive HAL calls // and service bind/unbind in case we are immediately switching to another VR app. if (mPendingState == null) { @@ -836,7 +948,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC PENDING_STATE_DELAY_MS); } - mPendingState = new VrState(enabled, targetPackageName, userId, callingPackage); + mPendingState = pending; return; } else { mHandler.removeMessages(MSG_PENDING_VR_STATE_CHANGE); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 3645c24043ca..8f64353a362d 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1556,6 +1556,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); } final long ident = Binder.clearCallingIdentity(); + + // Live wallpapers can't be specified for keyguard. If we're using a static + // system+lock image currently, migrate the system wallpaper to be a lock-only + // image as part of making a different live component active as the system + // wallpaper. + if (mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + if (mLockWallpaperMap.get(userId) == null) { + // We're using the static imagery and there is no lock-specific image in place, + // therefore it's a shared system+lock image that we need to migrate. + migrateSystemToLockWallpaperLocked(userId); + } + } + try { wallpaper.imageWallpaperPending = false; if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) { diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 00c37d2d899a..6147885a4a2e 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -17,6 +17,8 @@ package com.android.server.wm; import static android.app.ActivityManager.StackId; +import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; +import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; @@ -45,6 +47,7 @@ import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; import static com.android.server.wm.WindowManagerService.logWithStack; +import android.content.pm.ActivityInfo; import android.os.Debug; import com.android.internal.util.ToBooleanFunction; import com.android.server.input.InputApplicationHandle; @@ -81,18 +84,18 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree @NonNull final AppWindowAnimator mAppAnimator; - final boolean voiceInteraction; + final boolean mVoiceInteraction; // TODO: Use getParent instead? Task mTask; /** @see WindowContainer#fillsParent() */ private boolean mFillsParent; boolean layoutConfigChanges; - boolean showForAllUsers; - int targetSdk; + boolean mShowForAllUsers; + int mTargetSdk; // The input dispatching timeout for this application token in nanoseconds. - long inputDispatchingTimeoutNanos; + long mInputDispatchingTimeoutNanos; // These are used for determining when all windows associated with // an activity have been drawn, so they can be made visible together @@ -152,7 +155,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree boolean mLaunchTaskBehind; boolean mEnteringAnimation; - boolean mAlwaysFocusable; + private boolean mAlwaysFocusable; boolean mAppStopped; int mRotationAnimationHint; @@ -167,12 +170,31 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>(); ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>(); - AppWindowToken(WindowManagerService service, IApplicationToken token, boolean _voiceInteraction, - DisplayContent displayContent) { - super(service, token != null ? token.asBinder() : null, TYPE_APPLICATION, true, - displayContent); + AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction, + DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen, + boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint, + int configChanges, boolean launchTaskBehind, boolean alwaysFocusable) { + this(service, token, voiceInteraction, dc); + mInputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos; + mFillsParent = fullscreen; + mShowForAllUsers = showForAllUsers; + mTargetSdk = targetSdk; + mOrientation = orientation; + layoutConfigChanges = (configChanges & (CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION)) != 0; + mLaunchTaskBehind = launchTaskBehind; + mAlwaysFocusable = alwaysFocusable; + mRotationAnimationHint = rotationAnimationHint; + + // Application tokens start out hidden. + hidden = true; + hiddenRequested = true; + } + + AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction, + DisplayContent dc) { + super(service, token != null ? token.asBinder() : null, TYPE_APPLICATION, true, dc); appToken = token; - voiceInteraction = _voiceInteraction; + mVoiceInteraction = voiceInteraction; mInputApplicationHandle = new InputApplicationHandle(this); mAppAnimator = new AppWindowAnimator(this, service); } @@ -407,7 +429,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Removing app token: " + this); - boolean delayed = setVisibility(null, false, TRANSIT_UNSET, true, voiceInteraction); + boolean delayed = setVisibility(null, false, TRANSIT_UNSET, true, mVoiceInteraction); mService.mOpeningApps.remove(this); mService.mUnknownAppVisibilityController.appRemoved(this); @@ -1350,7 +1372,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree void dump(PrintWriter pw, String prefix) { super.dump(pw, prefix); if (appToken != null) { - pw.print(prefix); pw.print("app=true voiceInteraction="); pw.println(voiceInteraction); + pw.println(prefix + "app=true mVoiceInteraction=" + mVoiceInteraction); } pw.print(prefix); pw.print("task="); pw.println(mTask); pw.print(prefix); pw.print(" mFillsParent="); pw.print(mFillsParent); diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index f8b461eba741..e6bc7f44ab0c 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -390,9 +390,8 @@ public class DockedStackDividerController implements DimLayerUser { inputMethodManagerInternal.hideCurrentInputMethod(); mImeHideRequested = true; } - } else if (setMinimizedDockedStack(false)) { - mService.mWindowPlacerLocked.performSurfacePlacement(); } + setMinimizedDockedStack(false, false /* animate */); } /** diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index 0e4add8afc46..36520a9642a1 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -327,7 +327,7 @@ class DragState { // Global drags are limited to system windows, and windows for apps that are targeting N and // above. return targetWin.mAppToken == null - || targetWin.mAppToken.targetSdk >= Build.VERSION_CODES.N; + || targetWin.mAppToken.mTargetSdk >= Build.VERSION_CODES.N; } /* helper - send a ACTION_DRAG_STARTED event only if the window has not diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 495be094c982..3fbe36f78112 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -258,7 +258,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { if (! abort) { // The activity manager declined to abort dispatching. // Wait a bit longer and timeout again later. - return appWindowToken.inputDispatchingTimeoutNanos; + return appWindowToken.mInputDispatchingTimeoutNanos; } } catch (RemoteException ex) { } @@ -504,7 +504,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { } else { final InputApplicationHandle handle = newApp.mInputApplicationHandle; handle.name = newApp.toString(); - handle.dispatchingTimeoutNanos = newApp.inputDispatchingTimeoutNanos; + handle.dispatchingTimeoutNanos = newApp.mInputDispatchingTimeoutNanos; mService.mInputManager.setFocusedApplication(handle); } @@ -591,8 +591,6 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { private void updateInputWindows(boolean inDrag) { - clearInputWindowHandlesLw(); - // TODO: multi-display navInputConsumer = getInputConsumer(INPUT_CONSUMER_NAVIGATION, DEFAULT_DISPLAY); pipInputConsumer = getInputConsumer(INPUT_CONSUMER_PIP, DEFAULT_DISPLAY); @@ -614,6 +612,8 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { // Send windows to native code. mService.mInputManager.setInputWindows(mInputWindowHandles); + + clearInputWindowHandlesLw(); } @Override diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java index 0ad4e0a64791..34633c243d65 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedStackController.java @@ -18,12 +18,13 @@ package com.android.server.wm; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.util.TypedValue.COMPLEX_UNIT_DIP; -import static android.view.Display.DEFAULT_DISPLAY; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.animation.ValueAnimator; +import android.app.RemoteAction; +import android.content.pm.ParceledListSlice; import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; @@ -41,12 +42,13 @@ import android.view.Gravity; import android.view.IPinnedStackController; import android.view.IPinnedStackListener; -import com.android.internal.os.BackgroundThread; import com.android.internal.policy.PipMotionHelper; import com.android.internal.policy.PipSnapAlgorithm; import com.android.server.UiThread; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; /** * Holds the common state of the pinned stack between the system and SystemUI. @@ -70,10 +72,14 @@ class PinnedStackController { // States that affect how the PIP can be manipulated private boolean mInInteractiveMode; private boolean mIsMinimized; + private boolean mIsSnappingToEdge; private boolean mIsImeShowing; private int mImeHeight; private ValueAnimator mBoundsAnimator = null; + // The set of actions that are currently allowed on the PiP activity + private ArrayList<RemoteAction> mActions = new ArrayList<>(); + // Used to calculate stack bounds across rotations private final DisplayInfo mDisplayInfo = new DisplayInfo(); @@ -113,6 +119,7 @@ class PinnedStackController { @Override public void setSnapToEdge(final boolean snapToEdge) { mHandler.post(() -> { + mIsSnappingToEdge = snapToEdge; mSnapAlgorithm.setSnapToEdge(snapToEdge); }); } @@ -171,6 +178,9 @@ class PinnedStackController { listener.onListenerRegistered(mCallbacks); mPinnedStackListener = listener; notifyBoundsChanged(mIsImeShowing); + notifyMinimizeChanged(mIsMinimized); + notifySnapToEdgeChanged(mIsSnappingToEdge); + notifyActionsChanged(mActions); } catch (RemoteException e) { Log.e(TAG, "Failed to register pinned stack listener", e); } @@ -315,7 +325,16 @@ class PinnedStackController { } /** - * Sends a broadcast that the PIP movement bounds have changed. + * Sets the current set of actions. + */ + void setActions(List<RemoteAction> actions) { + mActions.clear(); + mActions.addAll(actions); + notifyActionsChanged(mActions); + } + + /** + * Notifies listeners that the PIP movement bounds have changed. */ private void notifyBoundsChanged(boolean adjustedForIme) { if (mPinnedStackListener != null) { @@ -328,6 +347,45 @@ class PinnedStackController { } /** + * Notifies listeners that the PIP minimized state has changed. + */ + private void notifyMinimizeChanged(boolean isMinimized) { + if (mPinnedStackListener != null) { + try { + mPinnedStackListener.onMinimizedStateChanged(isMinimized); + } catch (RemoteException e) { + Slog.e(TAG_WM, "Error delivering minimize changed event.", e); + } + } + } + + /** + * Notifies listeners that the PIP snap-to-edge state has changed. + */ + private void notifySnapToEdgeChanged(boolean isSnappingToEdge) { + if (mPinnedStackListener != null) { + try { + mPinnedStackListener.onSnapToEdgeStateChanged(isSnappingToEdge); + } catch (RemoteException e) { + Slog.e(TAG_WM, "Error delivering snap-to-edge changed event.", e); + } + } + } + + /** + * Notifies listeners that the PIP actions have changed. + */ + private void notifyActionsChanged(List<RemoteAction> actions) { + if (mPinnedStackListener != null) { + try { + mPinnedStackListener.onActionsChanged(new ParceledListSlice(actions)); + } catch (RemoteException e) { + Slog.e(TAG_WM, "Error delivering actions changed event.", e); + } + } + } + + /** * @return the bounds on the screen that the PIP can be visible in. */ private void getInsetBounds(Rect outRect) { @@ -355,5 +413,16 @@ class PinnedStackController { pw.println(prefix + " mIsImeShowing=" + mIsImeShowing); pw.println(prefix + " mInInteractiveMode=" + mInInteractiveMode); pw.println(prefix + " mIsMinimized=" + mIsMinimized); + if (mActions.isEmpty()) { + pw.println(prefix + " mActions=[]"); + } else { + pw.println(prefix + " mActions=["); + for (int i = 0; i < mActions.size(); i++) { + RemoteAction action = mActions.get(i); + pw.print(prefix + " Action[" + i + "]: "); + action.dump("", pw); + } + pw.println(prefix + " ]"); + } } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 612af755a0f9..aa6e3c7c3e45 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -88,12 +88,15 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU private boolean mIsOnTopLauncher; Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds, - Configuration overrideConfig, boolean isOnTopLauncher) { + Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode, + boolean homeTask) { mTaskId = taskId; mStack = stack; mUserId = userId; mService = service; mIsOnTopLauncher = isOnTopLauncher; + mResizeMode = resizeMode; + mHomeTask = homeTask; setBounds(bounds, overrideConfig); } @@ -101,7 +104,8 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU return mStack.getDisplayContent(); } - void addAppToken(int addPos, AppWindowToken wtoken, int resizeMode, boolean homeTask) { + @Override + void addChild(AppWindowToken wtoken, int addPos) { final int lastPos = mChildren.size(); if (addPos >= lastPos) { addPos = lastPos; @@ -118,11 +122,9 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU if (parent != null) { parent.removeChild(wtoken); } - addChild(wtoken, addPos); + super.addChild(wtoken, addPos); wtoken.mTask = this; mDeferRemoval = false; - mResizeMode = resizeMode; - mHomeTask = homeTask; } private boolean hasWindowsAlive() { @@ -536,7 +538,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU boolean showForAllUsers() { final int tokensCount = mChildren.size(); - return (tokensCount != 0) && mChildren.get(tokensCount - 1).showForAllUsers; + return (tokensCount != 0) && mChildren.get(tokensCount - 1).mShowForAllUsers; } boolean inFreeformWorkspace() { diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 02376e9c47eb..4a1c0678a661 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -129,7 +129,7 @@ class WindowContainer<E extends WindowContainer> implements Comparable<WindowCon /** Adds the input window container has a child of this container at the input index. */ @CallSuper - protected void addChild(E child, int index) { + void addChild(E child, int index) { if (child.getParent() != null) { throw new IllegalArgumentException("addChild: container=" + child.getName() + " is already a child of container=" + child.getParent().getName() diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index e68960cc141d..577e5a0c2e20 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -26,13 +26,13 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.IActivityManager; +import android.app.RemoteAction; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; @@ -238,6 +238,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_VERBOSE_TRANSA import static com.android.server.wm.WindowManagerDebugConfig.TAG_KEEP_SCREEN_ON; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static java.lang.Integer.MAX_VALUE; /** {@hide} */ public class WindowManagerService extends IWindowManager.Stub @@ -1480,7 +1481,7 @@ public class WindowManagerService extends IWindowManager.Stub // Try using the target SDK of the root window if (attachedWindow != null) { return attachedWindow.mAppToken != null - && attachedWindow.mAppToken.targetSdk > Build.VERSION_CODES.N_MR1; + && attachedWindow.mAppToken.mTargetSdk > Build.VERSION_CODES.N_MR1; } else { // Otherwise, look at the package try { @@ -2439,28 +2440,11 @@ public class WindowManagerService extends IWindowManager.Stub } } - private Task createTaskLocked(int taskId, int stackId, int userId, AppWindowToken atoken, - Rect bounds, Configuration overrideConfig, boolean isOnTopLauncher) { - if (DEBUG_STACK) Slog.i(TAG_WM, "createTaskLocked: taskId=" + taskId + " stackId=" + stackId - + " atoken=" + atoken + " bounds=" + bounds); - final TaskStack stack = mStackIdToStack.get(stackId); - if (stack == null) { - throw new IllegalArgumentException("createTaskLocked: invalid stackId=" + stackId); - } - EventLog.writeEvent(WM_TASK_CREATED, taskId, stackId); - Task task = new Task(taskId, stack, userId, this, bounds, overrideConfig, isOnTopLauncher); - mTaskIdToTask.put(taskId, task); - stack.addTask(task, !atoken.mLaunchTaskBehind /* toTop */, atoken.showForAllUsers); - return task; - } - @Override - public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId, - int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int userId, + public void addAppToken(int addPos, IApplicationToken token, int taskId, + int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges, boolean voiceInteraction, boolean launchTaskBehind, - Rect taskBounds, Configuration overrideConfig, int taskResizeMode, - boolean alwaysFocusable, boolean homeTask, int targetSdkVersion, - int rotationAnimationHint, boolean isOnTopLauncher) { + boolean alwaysFocusable, int targetSdkVersion, int rotationAnimationHint) { if (!checkCallingPermission(MANAGE_APP_TOKENS, "addAppToken()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); } @@ -2486,42 +2470,24 @@ public class WindowManagerService extends IWindowManager.Stub return; } - final TaskStack stack = mStackIdToStack.get(stackId); - if (stack == null) { - throw new IllegalArgumentException("addAppToken: invalid stackId=" + stackId); - } - - atoken = new AppWindowToken(this, token, voiceInteraction, stack.getDisplayContent()); - atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos; - atoken.setFillsParent(fullscreen); - atoken.showForAllUsers = showForAllUsers; - atoken.targetSdk = targetSdkVersion; - atoken.setOrientation(requestedOrientation); - atoken.layoutConfigChanges = (configChanges & - (ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0; - atoken.mLaunchTaskBehind = launchTaskBehind; - atoken.mAlwaysFocusable = alwaysFocusable; - if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken - + " to stack=" + stackId + " task=" + taskId + " at " + addPos); - atoken.mRotationAnimationHint = rotationAnimationHint; - Task task = mTaskIdToTask.get(taskId); if (task == null) { - task = createTaskLocked(taskId, stackId, userId, atoken, taskBounds, overrideConfig, - isOnTopLauncher); + throw new IllegalArgumentException("addAppToken: invalid taskId=" + taskId); } - task.addAppToken(addPos, atoken, taskResizeMode, homeTask); - // Application tokens start out hidden. - atoken.hidden = true; - atoken.hiddenRequested = true; + atoken = new AppWindowToken(this, token, voiceInteraction, task.getDisplayContent(), + inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion, + requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind, + alwaysFocusable); + if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken + + " task=" + taskId + " at " + addPos); + + task.addChild(atoken, addPos); } } @Override - public void setAppTask(IBinder token, int taskId, int stackId, Rect taskBounds, - Configuration overrideConfig, int taskResizeMode, boolean homeTask, - boolean isOnTopLauncher) { + public void addAppToTask(IBinder token, int taskId) { if (!checkCallingPermission(MANAGE_APP_TOKENS, "setAppTask()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); } @@ -2533,12 +2499,40 @@ public class WindowManagerService extends IWindowManager.Stub return; } - Task newTask = mTaskIdToTask.get(taskId); - if (newTask == null) { - newTask = createTaskLocked(taskId, stackId, atoken.mTask.mUserId, atoken, - taskBounds, overrideConfig, isOnTopLauncher); + Task task = mTaskIdToTask.get(taskId); + if (task == null) { + throw new IllegalArgumentException("setAppTask: invalid taskId=" + taskId); } - newTask.addAppToken(Integer.MAX_VALUE /* at top */, atoken, taskResizeMode, homeTask); + task.addChild(atoken, MAX_VALUE /* at top */); + } + } + + public void addTask(int taskId, int stackId, int userId, Rect bounds, + Configuration overrideConfig, int resizeMode, boolean homeTask, boolean isOnTopLauncher, + boolean toTop, boolean showForAllUsers) { + if (!checkCallingPermission(MANAGE_APP_TOKENS, "addTask()")) { + throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); + } + + synchronized(mWindowMap) { + Task task = mTaskIdToTask.get(taskId); + if (task != null) { + throw new IllegalArgumentException( + "addTask: Attempt to add already existing task=" + task); + } + + if (DEBUG_STACK) Slog.i(TAG_WM, "createTaskLocked: taskId=" + taskId + + " stackId=" + stackId + " bounds=" + bounds); + + final TaskStack stack = mStackIdToStack.get(stackId); + if (stack == null) { + throw new IllegalArgumentException("addTask: invalid stackId=" + stackId); + } + EventLog.writeEvent(WM_TASK_CREATED, taskId, stackId); + task = new Task(taskId, stack, userId, this, bounds, overrideConfig, isOnTopLauncher, + resizeMode, homeTask); + mTaskIdToTask.put(taskId, task); + stack.addTask(task, toTop, showForAllUsers); } } @@ -3203,7 +3197,7 @@ public class WindowManagerService extends IWindowManager.Stub } final long origId = Binder.clearCallingIdentity(); - wtoken.setVisibility(null, visible, TRANSIT_UNSET, true, wtoken.voiceInteraction); + wtoken.setVisibility(null, visible, TRANSIT_UNSET, true, wtoken.mVoiceInteraction); wtoken.updateReportedVisibilityLocked(); Binder.restoreCallingIdentity(origId); } @@ -3412,22 +3406,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - public void setPictureInPictureAspectRatio(float aspectRatio) { - synchronized (mWindowMap) { - if (!mSupportsPictureInPicture) { - return; - } - - final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID); - if (stack == null) { - return; - } - - animateResizePinnedStack(getPictureInPictureBounds( - stack.getDisplayContent().getDisplayId(), aspectRatio), -1); - } - } - public Rect getPictureInPictureBounds(int displayId, float aspectRatio) { synchronized (mWindowMap) { if (!mSupportsPictureInPicture) { @@ -3454,6 +3432,36 @@ public class WindowManagerService extends IWindowManager.Stub } /** + * Sets the current picture-in-picture aspect ratio. + */ + public void setPictureInPictureAspectRatio(float aspectRatio) { + synchronized (mWindowMap) { + final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID); + if (!mSupportsPictureInPicture || stack == null) { + return; + } + + final int displayId = stack.getDisplayContent().getDisplayId(); + final Rect toBounds = getPictureInPictureBounds(displayId, aspectRatio); + animateResizePinnedStack(toBounds, -1 /* duration */); + } + } + + /** + * Sets the current picture-in-picture actions. + */ + public void setPictureInPictureActions(List<RemoteAction> actions) { + synchronized (mWindowMap) { + final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID); + if (!mSupportsPictureInPicture || stack == null) { + return; + } + + stack.getDisplayContent().getPinnedStackController().setActions(actions); + } + } + + /** * Place a TaskStack on a DisplayContent. Will create a new TaskStack if none is found with * specified stackId. * @param stackId The unique identifier of the new stack. diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index e2027fd8764b..3daad4373d06 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -124,7 +124,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION; @@ -634,7 +633,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } mIsFloatingLayer = mIsImWindow || mIsWallpaper; - if (mAppToken != null && mAppToken.showForAllUsers) { + if (mAppToken != null && mAppToken.mShowForAllUsers) { // Windows for apps that can show for all users should also show when the device is // locked. mAttrs.flags |= FLAG_SHOW_WHEN_LOCKED; @@ -1035,7 +1034,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public boolean isVoiceInteraction() { - return mAppToken != null && mAppToken.voiceInteraction; + return mAppToken != null && mAppToken.mVoiceInteraction; } boolean setReportResizeHints() { @@ -1225,7 +1224,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP public long getInputDispatchingTimeoutNanos() { return mAppToken != null - ? mAppToken.inputDispatchingTimeoutNanos + ? mAppToken.mInputDispatchingTimeoutNanos : WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; } @@ -2887,7 +2886,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Child windows are evaluated based on their parent window. final WindowState win = getTopParentWindow(); if (win.mAttrs.type < FIRST_SYSTEM_WINDOW - && win.mAppToken != null && win.mAppToken.showForAllUsers) { + && win.mAppToken != null && win.mAppToken.mShowForAllUsers) { // All window frames that are fullscreen extend above status bar, but some don't extend // below navigation bar. Thus, check for display frame for top/left and stable frame for diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index de808377e880..1aabd5ea66f9 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -5,8 +5,6 @@ import static android.app.ActivityManagerInternal.APP_TRANSITION_SAVED_SURFACE; import static android.app.ActivityManagerInternal.APP_TRANSITION_STARTING_WINDOW; import static android.app.ActivityManagerInternal.APP_TRANSITION_TIMEOUT; import static android.app.ActivityManagerInternal.APP_TRANSITION_WINDOWS_DRAWN; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; -import static android.view.WindowManager.LayoutParams.TYPE_DREAM; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE; @@ -27,7 +25,6 @@ import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN; import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_OPEN; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -35,7 +32,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.DO_TRAVERSAL; import static com.android.server.wm.WindowManagerService.H.NOTIFY_APP_TRANSITION_STARTING; import static com.android.server.wm.WindowManagerService.H.REPORT_WINDOWS_CHANGE; -import static com.android.server.wm.WindowManagerService.H.UPDATE_DOCKED_STACK_DIVIDER; import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD; import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES; @@ -53,7 +49,6 @@ import android.view.Display; import android.view.DisplayInfo; import android.view.Surface; import android.view.SurfaceControl; -import android.view.View; import android.view.WindowManager.LayoutParams; import android.view.animation.Animation; @@ -293,7 +288,7 @@ class WindowSurfacePlacer { } } - voiceInteraction |= wtoken.voiceInteraction; + voiceInteraction |= wtoken.mVoiceInteraction; if (wtoken.fillsParent()) { final WindowState ws = wtoken.findMainWindow(); diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index 50a6095912e2..3e8e42063bc1 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -74,6 +74,7 @@ LOCAL_SHARED_LIBRARIES += \ libutils \ android.hardware.audio.common@2.0 \ android.hardware.gnss@1.0 \ + android.hardware.ir@1.0 \ android.hardware.light@2.0 \ android.hardware.power@1.0 \ android.hardware.thermal@1.0 \ diff --git a/services/core/jni/com_android_server_ConsumerIrService.cpp b/services/core/jni/com_android_server_ConsumerIrService.cpp index 7104870349b5..1f7bf4a05ad4 100644 --- a/services/core/jni/com_android_server_ConsumerIrService.cpp +++ b/services/core/jni/com_android_server_ConsumerIrService.cpp @@ -23,87 +23,70 @@ #include <stdlib.h> #include <utils/misc.h> #include <utils/Log.h> -#include <hardware/hardware.h> -#include <hardware/consumerir.h> +#include <android/hardware/ir/1.0/IConsumerIr.h> #include <ScopedPrimitiveArray.h> -namespace android { - -static jlong halOpen(JNIEnv* /* env */, jobject /* obj */) { - hw_module_t const* module; - consumerir_device_t *dev; - int err; +using ::android::hardware::ir::V1_0::IConsumerIr; +using ::android::hardware::ir::V1_0::ConsumerIrFreqRange; +using ::android::hardware::hidl_vec; - err = hw_get_module(CONSUMERIR_HARDWARE_MODULE_ID, &module); - if (err != 0) { - ALOGE("Can't open consumer IR HW Module, error: %d", err); - return 0; - } +namespace android { - err = module->methods->open(module, CONSUMERIR_TRANSMITTER, - (hw_device_t **) &dev); - if (err < 0) { - ALOGE("Can't open consumer IR transmitter, error: %d", err); - return 0; - } +static sp<IConsumerIr> mHal; - return reinterpret_cast<jlong>(dev); +static jboolean halOpen(JNIEnv* /* env */, jobject /* obj */) { + // TODO(b/31632518) + mHal = IConsumerIr::getService("consumerir"); + return mHal != nullptr; } -static jint halTransmit(JNIEnv *env, jobject /* obj */, jlong halObject, - jint carrierFrequency, jintArray pattern) { - int ret; - - consumerir_device_t *dev = reinterpret_cast<consumerir_device_t*>(halObject); +static jint halTransmit(JNIEnv *env, jobject /* obj */, jint carrierFrequency, + jintArray pattern) { ScopedIntArrayRO cPattern(env, pattern); if (cPattern.get() == NULL) { return -EINVAL; } - jsize patternLength = cPattern.size(); - - ret = dev->transmit(dev, carrierFrequency, cPattern.get(), patternLength); + hidl_vec<int32_t> patternVec; + patternVec.setToExternal(const_cast<int32_t*>(cPattern.get()), cPattern.size()); - return reinterpret_cast<jint>(ret); + bool success = mHal->transmit(carrierFrequency, patternVec); + return success ? 0 : -1; } -static jintArray halGetCarrierFrequencies(JNIEnv *env, jobject /* obj */, - jlong halObject) { - consumerir_device_t *dev = reinterpret_cast<consumerir_device_t*>(halObject); - consumerir_freq_range_t *ranges; +static jintArray halGetCarrierFrequencies(JNIEnv *env, jobject /* obj */) { int len; + hidl_vec<ConsumerIrFreqRange> ranges; + bool success; - len = dev->get_num_carrier_freqs(dev); - if (len <= 0) - return NULL; - - ranges = new consumerir_freq_range_t[len]; + auto cb = [&](bool s, hidl_vec<ConsumerIrFreqRange> vec) { + ranges = vec; + success = s; + }; + mHal->getCarrierFreqs(cb); - len = dev->get_carrier_freqs(dev, len, ranges); - if (len <= 0) { - delete[] ranges; + if (!success) { return NULL; } + len = ranges.size(); int i; ScopedIntArrayRW freqsOut(env, env->NewIntArray(len*2)); jint *arr = freqsOut.get(); if (arr == NULL) { - delete[] ranges; return NULL; } for (i = 0; i < len; i++) { - arr[i*2] = ranges[i].min; - arr[i*2+1] = ranges[i].max; + arr[i*2] = static_cast<jint>(ranges[i].min); + arr[i*2+1] = static_cast<jint>(ranges[i].max); } - delete[] ranges; return freqsOut.getJavaArray(); } static const JNINativeMethod method_table[] = { - { "halOpen", "()J", (void *)halOpen }, - { "halTransmit", "(JI[I)I", (void *)halTransmit }, - { "halGetCarrierFrequencies", "(J)[I", (void *)halGetCarrierFrequencies}, + { "halOpen", "()Z", (void *)halOpen }, + { "halTransmit", "(I[I)I", (void *)halTransmit }, + { "halGetCarrierFrequencies", "()[I", (void *)halGetCarrierFrequencies}, }; int register_android_server_ConsumerIrService(JNIEnv *env) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index aafc432f9a55..050f25d364f5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -17,7 +17,9 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; +import static android.app.admin.DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG; import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY; +import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED; import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED; import static android.app.admin.DevicePolicyManager.CODE_HAS_DEVICE_OWNER; @@ -153,7 +155,6 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; import com.android.internal.util.ParcelableString; @@ -245,6 +246,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final int MONITORING_CERT_NOTIFICATION_ID = R.plurals.ssl_ca_cert_warning; private static final int PROFILE_WIPED_NOTIFICATION_ID = 1001; + private static final int NETWORK_LOGGING_NOTIFICATION_ID = 1002; private static final String ATTR_PERMISSION_PROVIDER = "permission-provider"; private static final String ATTR_SETUP_COMPLETE = "setup-complete"; @@ -253,7 +255,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED = "device-provisioning-config-applied"; private static final String ATTR_DEVICE_PAIRED = "device-paired"; - private static final String ATTR_DELEGATED_CERT_INSTALLER = "delegated-cert-installer"; private static final String ATTR_APPLICATION_RESTRICTIONS_MANAGER = "application-restrictions-manager"; @@ -636,6 +637,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String TAG_PARENT_ADMIN = "parent-admin"; private static final String TAG_ORGANIZATION_COLOR = "organization-color"; private static final String TAG_ORGANIZATION_NAME = "organization-name"; + private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification"; + private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications"; final DeviceAdminInfo info; @@ -686,6 +689,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { boolean forceEphemeralUsers = false; // Can only be set by a device owner. boolean isNetworkLoggingEnabled = false; // Can only be set by a device owner. + // one notification after enabling + 3 more after reboots + static final int DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN = 4; + int numNetworkLoggingNotifications = 0; + long lastNetworkLoggingNotificationTimeMs = 0; // Time in milliseconds since epoch + ActiveAdmin parentAdmin; final boolean isParent; @@ -904,6 +912,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (isNetworkLoggingEnabled) { out.startTag(null, TAG_IS_NETWORK_LOGGING_ENABLED); out.attribute(null, ATTR_VALUE, Boolean.toString(isNetworkLoggingEnabled)); + out.attribute(null, ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS, + Integer.toString(numNetworkLoggingNotifications)); + out.attribute(null, ATTR_LAST_NETWORK_LOGGING_NOTIFICATION, + Long.toString(lastNetworkLoggingNotificationTimeMs)); out.endTag(null, TAG_IS_NETWORK_LOGGING_ENABLED); } if (disabledKeyguardFeatures != DEF_KEYGUARD_FEATURES_DISABLED) { @@ -1095,6 +1107,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if (TAG_IS_NETWORK_LOGGING_ENABLED.equals(tag)) { isNetworkLoggingEnabled = Boolean.parseBoolean( parser.getAttributeValue(null, ATTR_VALUE)); + lastNetworkLoggingNotificationTimeMs = Long.parseLong( + parser.getAttributeValue(null, ATTR_LAST_NETWORK_LOGGING_NOTIFICATION)); + numNetworkLoggingNotifications = Integer.parseInt( + parser.getAttributeValue(null, ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS)); } else if (TAG_DISABLE_KEYGUARD_FEATURES.equals(tag)) { disabledKeyguardFeatures = Integer.parseInt( parser.getAttributeValue(null, ATTR_VALUE)); @@ -1690,9 +1706,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mSecurityLogMonitor = new SecurityLogMonitor(this); - mHasFeature = mContext.getPackageManager() + mHasFeature = mInjector.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN); - mIsWatch = mContext.getPackageManager() + mIsWatch = mInjector.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_WATCH); if (!mHasFeature) { // Skip the rest of the initialization @@ -2342,20 +2358,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.endTag(null, "failed-password-attempts"); } - final PasswordMetrics metrics = policy.mActivePasswordMetrics; - if (!metrics.isDefault()) { - out.startTag(null, "active-password"); - out.attribute(null, "quality", Integer.toString(metrics.quality)); - out.attribute(null, "length", Integer.toString(metrics.length)); - out.attribute(null, "uppercase", Integer.toString(metrics.upperCase)); - out.attribute(null, "lowercase", Integer.toString(metrics.lowerCase)); - out.attribute(null, "letters", Integer.toString(metrics.letters)); - out.attribute(null, "numeric", Integer.toString(metrics.numeric)); - out.attribute(null, "symbols", Integer.toString(metrics.symbols)); - out.attribute(null, "nonletter", Integer.toString(metrics.nonLetter)); - out.endTag(null, "active-password"); - } - for (int i = 0; i < policy.mAcceptedCaCertificates.size(); i++) { out.startTag(null, TAG_ACCEPTED_CA_CERTIFICATES); out.attribute(null, ATTR_NAME, policy.mAcceptedCaCertificates.valueAt(i)); @@ -2456,6 +2458,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { JournaledFile journal = makeJournaledFile(userHandle); FileInputStream stream = null; File file = journal.chooseForRead(); + boolean needsRewrite = false; try { stream = new FileInputStream(file); XmlPullParser parser = Xml.newPullParser(); @@ -2542,16 +2545,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if ("password-owner".equals(tag)) { policy.mPasswordOwner = Integer.parseInt( parser.getAttributeValue(null, "value")); - } else if ("active-password".equals(tag)) { - final PasswordMetrics m = policy.mActivePasswordMetrics; - m.quality = Integer.parseInt(parser.getAttributeValue(null, "quality")); - m.length = Integer.parseInt(parser.getAttributeValue(null, "length")); - m.upperCase = Integer.parseInt(parser.getAttributeValue(null, "uppercase")); - m.lowerCase = Integer.parseInt(parser.getAttributeValue(null, "lowercase")); - m.letters = Integer.parseInt(parser.getAttributeValue(null, "letters")); - m.numeric = Integer.parseInt(parser.getAttributeValue(null, "numeric")); - m.symbols = Integer.parseInt(parser.getAttributeValue(null, "symbols")); - m.nonLetter = Integer.parseInt(parser.getAttributeValue(null, "nonletter")); } else if (TAG_ACCEPTED_CA_CERTIFICATES.equals(tag)) { policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME)); } else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) { @@ -2577,6 +2570,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { policy.mAdminBroadcastPending = Boolean.toString(true).equals(pending); } else if (TAG_INITIALIZATION_BUNDLE.equals(tag)) { policy.mInitBundle = PersistableBundle.restoreFromXml(parser); + } else if ("active-password".equals(tag)) { + needsRewrite = true; } else { Slog.w(LOG_TAG, "Unknown tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -2596,27 +2591,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Ignore } + // Might need to upgrade the file by rewriting it + if (needsRewrite) { + saveSettingsLocked(userHandle); + } + // Generate a list of admins from the admin map policy.mAdminList.addAll(policy.mAdminMap.values()); - // Validate that what we stored for the password quality matches - // sufficiently what is currently set. Note that this is only - // a sanity check in case the two get out of sync; this should - // never normally happen. - final long identity = mInjector.binderClearCallingIdentity(); - try { - int actualPasswordQuality = mLockPatternUtils.getActivePasswordQuality(userHandle); - if (actualPasswordQuality < policy.mActivePasswordMetrics.quality) { - Slog.w(LOG_TAG, "Active password quality 0x" - + Integer.toHexString(policy.mActivePasswordMetrics.quality) - + " does not match actual quality 0x" - + Integer.toHexString(actualPasswordQuality)); - policy.mActivePasswordMetrics = new PasswordMetrics(); - } - } finally { - mInjector.binderRestoreCallingIdentity(identity); - } - validatePasswordOwnerLocked(policy); updateMaximumTimeToLockLocked(userHandle); updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle); @@ -3850,6 +3832,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private boolean isActivePasswordSufficientForUserLocked( DevicePolicyData policy, int userHandle, boolean parent) { + enforceUserUnlocked(userHandle, parent); + final int requiredPasswordQuality = getPasswordQuality(null, userHandle, parent); if (policy.mActivePasswordMetrics.quality < requiredPasswordQuality) { return false; @@ -4461,7 +4445,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } try { - int uid = mContext.getPackageManager().getPackageUidAsUser( + int uid = mInjector.getPackageManager().getPackageUidAsUser( policy.mDelegatedCertInstallerPackage, userHandle); return uid == callingUid; } catch (NameNotFoundException e) { @@ -4806,6 +4790,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long ident = mInjector.binderClearCallingIdentity(); try { + final String restriction; + if (userHandle == UserHandle.USER_SYSTEM) { + restriction = UserManager.DISALLOW_FACTORY_RESET; + } else if (isManagedProfile(userHandle)) { + restriction = UserManager.DISALLOW_REMOVE_MANAGED_PROFILE; + } else { + restriction = UserManager.DISALLOW_REMOVE_USER; + } + if (isAdminAffectedByRestriction( + admin.info.getComponent(), restriction, userHandle)) { + throw new SecurityException("Cannot wipe data. " + restriction + + " restriction is set for user " + userHandle); + } + if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) { if (!isDeviceOwner(admin.info.getComponent(), userHandle)) { throw new SecurityException( @@ -4817,34 +4815,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { manager.wipe(); } } + boolean wipeExtRequested = (flags & WIPE_EXTERNAL_STORAGE) != 0; - // If the admin is the only one who has set the restriction: force wipe, even if - // {@link UserManager.DISALLOW_FACTORY_RESET} is set. Reason is that the admin - // could remove this user restriction anyway. - boolean force = (userHandle == UserHandle.USER_SYSTEM) - && isAdminOnlyOneWhoSetRestriction(admin, - UserManager.DISALLOW_FACTORY_RESET, UserHandle.USER_SYSTEM); wipeDeviceOrUserLocked(wipeExtRequested, userHandle, - "DevicePolicyManager.wipeData() from " + source, force); + "DevicePolicyManager.wipeData() from " + source, /*force=*/ true); } finally { mInjector.binderRestoreCallingIdentity(ident); } } } - private boolean isAdminOnlyOneWhoSetRestriction(ActiveAdmin admin, String userRestriction, - int userId) { - int source = mUserManager.getUserRestrictionSource(userRestriction, UserHandle.of(userId)); - if (isDeviceOwner(admin.info.getComponent(), userId)) { - return source == UserManager.RESTRICTION_SOURCE_DEVICE_OWNER; - } else if (isProfileOwner(admin.info.getComponent(), userId)) { - return source == UserManager.RESTRICTION_SOURCE_PROFILE_OWNER; - } - return false; - } - - private void wipeDeviceOrUserLocked(boolean wipeExtRequested, final int userHandle, - String reason, boolean force) { + private void wipeDeviceOrUserLocked( + boolean wipeExtRequested, final int userHandle, String reason, boolean force) { + // TODO If split user is enabled and the device owner is set in the primary user (rather + // than system), we should probably trigger factory reset. Current code just remove + // that user (but still clears FRP...) if (userHandle == UserHandle.USER_SYSTEM) { wipeDataLocked(wipeExtRequested, reason, force); } else { @@ -4857,10 +4842,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { am.switchUser(UserHandle.USER_SYSTEM); } - boolean isManagedProfile = isManagedProfile(userHandle); - if (!mUserManager.removeUser(userHandle)) { + boolean userRemoved = force + ? mUserManagerInternal.removeUserEvenWhenDisallowed(userHandle) + : mUserManager.removeUser(userHandle); + if (!userRemoved) { Slog.w(LOG_TAG, "Couldn't remove user " + userHandle); - } else if (isManagedProfile) { + } else if (isManagedProfile(userHandle)) { sendWipeProfileNotification(); } } catch (RemoteException re) { @@ -4921,33 +4908,52 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } enforceFullCrossUsersPermission(userHandle); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BIND_DEVICE_ADMIN, null); + + // If the managed profile doesn't have a separate password, set the metrics to default + if (isManagedProfile(userHandle) && !isSeparateProfileChallengeEnabled(userHandle)) { + metrics = new PasswordMetrics(); + } + + validateQualityConstant(metrics.quality); + DevicePolicyData policy = getUserData(userHandle); + synchronized (this) { + policy.mActivePasswordMetrics = metrics; + } + } + + @Override + public void reportPasswordChanged(@UserIdInt int userId) { + if (!mHasFeature) { + return; + } + enforceFullCrossUsersPermission(userId); // Managed Profile password can only be changed when it has a separate challenge. - if (!isSeparateProfileChallengeEnabled(userHandle)) { - enforceNotManagedProfile(userHandle, "set the active password"); + if (!isSeparateProfileChallengeEnabled(userId)) { + enforceNotManagedProfile(userId, "set the active password"); } mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - validateQualityConstant(metrics.quality); - DevicePolicyData policy = getUserData(userHandle); + DevicePolicyData policy = getUserData(userId); long ident = mInjector.binderClearCallingIdentity(); try { synchronized (this) { - policy.mActivePasswordMetrics = metrics; policy.mFailedPasswordAttempts = 0; - saveSettingsLocked(userHandle); - updatePasswordExpirationsLocked(userHandle); - setExpirationAlarmCheckLocked(mContext, userHandle, /* parent */ false); + saveSettingsLocked(userId); + updatePasswordExpirationsLocked(userId); + setExpirationAlarmCheckLocked(mContext, userId, /* parent */ false); // Send a broadcast to each profile using this password as its primary unlock. sendAdminCommandForLockscreenPoliciesLocked( DeviceAdminReceiver.ACTION_PASSWORD_CHANGED, - DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userHandle); + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userId); } - removeCaApprovalsIfNeeded(userHandle); + removeCaApprovalsIfNeeded(userId); } finally { mInjector.binderRestoreCallingIdentity(ident); } @@ -5948,7 +5954,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } synchronized (this) { enforceCanSetDeviceOwnerLocked(admin, userId); - if (getActiveAdminUncheckedLocked(admin, userId) == null + final ActiveAdmin activeAdmin = getActiveAdminUncheckedLocked(admin, userId); + if (activeAdmin == null || getUserData(userId).mRemovingAdmins.contains(admin)) { throw new IllegalArgumentException("Not active admin: " + admin); } @@ -5975,12 +5982,23 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mOwners.writeDeviceOwner(); updateDeviceOwnerLocked(); setDeviceOwnerSystemPropertyLocked(); - Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED); + + // STOPSHIP(b/31952368) Also set this restriction for existing DOs on OTA to Android OC. + final Set<String> restrictions = + UserRestrictionsUtils.getDefaultEnabledForDeviceOwner(); + if (!restrictions.isEmpty()) { + for (String restriction : restrictions) { + activeAdmin.ensureUserRestrictions().putBoolean(restriction, true); + } + saveUserRestrictionsLocked(userId); + } ident = mInjector.binderClearCallingIdentity(); try { // TODO Send to system too? - mContext.sendBroadcastAsUser(intent, new UserHandle(userId)); + mContext.sendBroadcastAsUser( + new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED), + UserHandle.of(userId)); } finally { mInjector.binderRestoreCallingIdentity(ident); } @@ -6007,6 +6025,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private boolean isDeviceOwnerPackage(String packageName, int userId) { + synchronized (this) { + return mOwners.hasDeviceOwner() + && mOwners.getDeviceOwnerUserId() == userId + && mOwners.getDeviceOwnerPackageName().equals(packageName); + } + } + public boolean isProfileOwner(ComponentName who, int userId) { final ComponentName profileOwner = getProfileOwner(userId); return who != null && who.equals(profileOwner); @@ -6089,7 +6115,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkNotNull(packageName, "packageName is null"); final int callingUid = mInjector.binderGetCallingUid(); try { - int uid = mContext.getPackageManager().getPackageUidAsUser(packageName, + int uid = mInjector.getPackageManager().getPackageUidAsUser(packageName, UserHandle.getUserId(callingUid)); if (uid != callingUid) { throw new SecurityException("Invalid packageName"); @@ -6176,6 +6202,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mOwners.setProfileOwner(who, ownerName, userHandle); mOwners.writeProfileOwner(userHandle); Slog.i(LOG_TAG, "Profile owner set: " + who + " on user " + userHandle); + return true; } } @@ -6574,6 +6601,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "User must be running and unlocked"); } + private void enforceUserUnlocked(@UserIdInt int userId, boolean parent) { + if (parent) { + enforceUserUnlocked(getProfileParentId(userId)); + } else { + enforceUserUnlocked(userId); + } + } + private void enforceManageUsers() { final int callingUid = mInjector.binderGetCallingUid(); if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) { @@ -6832,7 +6867,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } try { - int uid = mContext.getPackageManager().getPackageUidAsUser( + int uid = mInjector.getPackageManager().getPackageUidAsUser( policy.mApplicationRestrictionsManagingPackage, userHandle); return uid == callingUid; } catch (NameNotFoundException e) { @@ -7486,28 +7521,41 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean removeUser(ComponentName who, UserHandle userHandle) { Preconditions.checkNotNull(who, "ComponentName is null"); - UserHandle callingUserHandle = mInjector.binderGetCallingUserHandle(); synchronized (this) { getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); } + + final int callingUserId = mInjector.userHandleGetCallingUserId(); final long id = mInjector.binderClearCallingIdentity(); try { - int restrictionSource = mUserManager.getUserRestrictionSource( - UserManager.DISALLOW_REMOVE_USER, callingUserHandle); - if (restrictionSource != UserManager.RESTRICTION_NOT_SET - && restrictionSource != UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) { + String restriction = isManagedProfile(userHandle.getIdentifier()) + ? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE + : UserManager.DISALLOW_REMOVE_USER; + if (isAdminAffectedByRestriction(who, restriction, callingUserId)) { Log.w(LOG_TAG, "The device owner cannot remove a user because " - + "DISALLOW_REMOVE_USER is enabled, and was not set by the device " - + "owner"); + + restriction + " is enabled, and was not set by the device owner"); return false; } - return mUserManagerInternal.removeUserEvenWhenDisallowed( - userHandle.getIdentifier()); + return mUserManagerInternal.removeUserEvenWhenDisallowed(userHandle.getIdentifier()); } finally { mInjector.binderRestoreCallingIdentity(id); } } + private boolean isAdminAffectedByRestriction( + ComponentName admin, String userRestriction, int userId) { + switch(mUserManager.getUserRestrictionSource(userRestriction, UserHandle.of(userId))) { + case UserManager.RESTRICTION_NOT_SET: + return false; + case UserManager.RESTRICTION_SOURCE_DEVICE_OWNER: + return !isDeviceOwner(admin, userId); + case UserManager.RESTRICTION_SOURCE_PROFILE_OWNER: + return !isProfileOwner(admin, userId); + default: + return true; + } + } + @Override public boolean switchUser(ComponentName who, UserHandle userHandle) { Preconditions.checkNotNull(who, "ComponentName is null"); @@ -7613,14 +7661,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Save the restriction to ActiveAdmin. activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner); - saveSettingsLocked(userHandle); - - pushUserRestrictions(userHandle); - - sendChangedNotification(userHandle); + saveUserRestrictionsLocked(userHandle); } } + private void saveUserRestrictionsLocked(int userId) { + saveSettingsLocked(userId); + pushUserRestrictions(userId); + sendChangedNotification(userId); + } + private void pushUserRestrictions(int userId) { synchronized (this) { final Bundle global; @@ -8605,7 +8655,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final String deviceOwnerPackageName = mOwners.getDeviceOwnerComponent() .getPackageName(); - final String[] pkgs = mContext.getPackageManager().getPackagesForUid(callerUid); + final String[] pkgs = mInjector.getPackageManager().getPackagesForUid(callerUid); for (String pkg : pkgs) { if (deviceOwnerPackageName.equals(pkg)) { @@ -8642,7 +8692,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { ActivityInfo[] receivers = null; try { - receivers = mContext.getPackageManager().getPackageInfo( + receivers = mInjector.getPackageManager().getPackageInfo( deviceOwnerPackage, PackageManager.GET_RECEIVERS).receivers; } catch (NameNotFoundException e) { Log.e(LOG_TAG, "Cannot find device owner package", e); @@ -8698,7 +8748,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { < android.os.Build.VERSION_CODES.M) { return false; } - final PackageManager packageManager = mContext.getPackageManager(); + final PackageManager packageManager = mInjector.getPackageManager(); switch (grantState) { case DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED: { mInjector.getPackageManagerInternal().grantRuntimePermission(packageName, @@ -8733,7 +8783,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public int getPermissionGrantState(ComponentName admin, String packageName, String permission) throws RemoteException { - PackageManager packageManager = mContext.getPackageManager(); + PackageManager packageManager = mInjector.getPackageManager(); UserHandle user = mInjector.binderGetCallingUserHandle(); synchronized (this) { @@ -8770,17 +8820,33 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public boolean isProvisioningAllowed(String action) { - return checkProvisioningPreConditionSkipPermission(action) == CODE_OK; + public boolean isProvisioningAllowed(String action, String packageName) { + Preconditions.checkNotNull(packageName); + + final int callingUid = mInjector.binderGetCallingUid(); + final long ident = mInjector.binderClearCallingIdentity(); + try { + final int uidForPackage = mInjector.getPackageManager().getPackageUidAsUser( + packageName, UserHandle.getUserId(callingUid)); + Preconditions.checkArgument(callingUid == uidForPackage, + "Caller uid doesn't match the one for the provided package."); + } catch (NameNotFoundException e) { + throw new IllegalArgumentException("Invalid package provided " + packageName, e); + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } + + return checkProvisioningPreConditionSkipPermission(action, packageName) == CODE_OK; } @Override - public int checkProvisioningPreCondition(String action) { + public int checkProvisioningPreCondition(String action, String packageName) { + Preconditions.checkNotNull(packageName); enforceCanManageProfileAndDeviceOwners(); - return checkProvisioningPreConditionSkipPermission(action); + return checkProvisioningPreConditionSkipPermission(action, packageName); } - private int checkProvisioningPreConditionSkipPermission(String action) { + private int checkProvisioningPreConditionSkipPermission(String action, String packageName) { if (!mHasFeature) { return CODE_DEVICE_ADMIN_NOT_SUPPORTED; } @@ -8789,7 +8855,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (action != null) { switch (action) { case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE: - return checkManagedProfileProvisioningPreCondition(callingUserId); + return checkManagedProfileProvisioningPreCondition(packageName, callingUserId); case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE: return checkDeviceOwnerProvisioningPreCondition(callingUserId); case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER: @@ -8858,30 +8924,38 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private int checkManagedProfileProvisioningPreCondition(int callingUserId) { + private int checkManagedProfileProvisioningPreCondition(String packageName, int callingUserId) { if (!hasFeatureManagedUsers()) { return CODE_MANAGED_USERS_NOT_SUPPORTED; } - synchronized (this) { - if (mOwners.hasDeviceOwner()) { - // STOPSHIP Only allow creating a managed profile if allowed by the device - // owner. http://b/31952368 - if (mInjector.userManagerIsSplitSystemUser()) { - if (callingUserId == UserHandle.USER_SYSTEM) { - // Managed-profiles cannot be setup on the system user. - return CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER; - } - } - } + if (callingUserId == UserHandle.USER_SYSTEM + && mInjector.userManagerIsSplitSystemUser()) { + // Managed-profiles cannot be setup on the system user. + return CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER; } if (getProfileOwner(callingUserId) != null) { // Managed user cannot have a managed profile. return CODE_USER_HAS_PROFILE_OWNER; } - boolean canRemoveProfile = - !mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_USER); + final long ident = mInjector.binderClearCallingIdentity(); try { + final UserHandle callingUserHandle = UserHandle.of(callingUserId); + if (mUserManager.hasUserRestriction( + UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle)) { + // The DO can initiate provisioning if the restriction was set by the DO. + if (!isDeviceOwnerPackage(packageName, callingUserId) + || isAdminAffectedByRestriction(mOwners.getDeviceOwnerComponent(), + UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) { + // Caller is not DO or the restriction was set by the system. + return CODE_ADD_MANAGED_PROFILE_DISALLOWED; + } + } + + // TODO: Allow it if the caller is the DO? DO could just call removeUser() before + // provisioning, so not strictly required... + boolean canRemoveProfile = !mUserManager.hasUserRestriction( + UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, callingUserHandle); if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) { return CODE_CANNOT_ADD_MANAGED_PROFILE; } @@ -9702,9 +9776,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int callingUserId = mInjector.userHandleGetCallingUserId(); final boolean isCallerDeviceOwner = isDeviceOwner(callingOwner); final boolean isCallerManagedProfile = isManagedProfile(callingUserId); - if (!isCallerDeviceOwner && !isCallerManagedProfile - /* STOPSHIP(b/32326223) Reinstate when setAffiliationIds is public - || !isAffiliatedUser(callingUserId) */) { + if ((!isCallerDeviceOwner && !isCallerManagedProfile) + || !isUserAffiliatedWithDevice(callingUserId)) { return targetUsers; } @@ -9724,8 +9797,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Both must be the same package and be affiliated in order to bind. if (callingOwnerPackage.equals(targetOwnerPackage) - /* STOPSHIP(b/32326223) Reinstate when setAffiliationIds is public - && isAffiliatedUser(userId)*/) { + && isUserAffiliatedWithDevice(userId)) { targetUsers.add(UserHandle.of(userId)); } } @@ -9832,7 +9904,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // already in the requested state return; } - getDeviceOwnerAdminLocked().isNetworkLoggingEnabled = enabled; + ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + deviceOwner.isNetworkLoggingEnabled = enabled; + if (!enabled) { + deviceOwner.numNetworkLoggingNotifications = 0; + deviceOwner.lastNetworkLoggingNotificationTimeMs = 0; + } saveSettingsLocked(mInjector.userHandleGetCallingUserId()); setNetworkLoggingActiveInternal(enabled); @@ -9848,6 +9925,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Slog.wtf(LOG_TAG, "Network logging could not be started due to the logging" + " service not being available yet."); } + sendNetworkLoggingNotificationLocked(); } else { if (mNetworkLogger != null && !mNetworkLogger.stopNetworkLogging()) { mNetworkLogger = null; @@ -9913,6 +9991,41 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mNetworkLogger.retrieveLogs(batchToken); } + private void sendNetworkLoggingNotificationLocked() { + final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + if (deviceOwner == null || !deviceOwner.isNetworkLoggingEnabled) { + return; + } + if (deviceOwner.numNetworkLoggingNotifications >= + ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) { + return; + } + final long now = System.currentTimeMillis(); + if (now - deviceOwner.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) { + return; + } + deviceOwner.numNetworkLoggingNotifications++; + if (deviceOwner.numNetworkLoggingNotifications + >= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) { + deviceOwner.lastNetworkLoggingNotificationTimeMs = 0; + } else { + deviceOwner.lastNetworkLoggingNotificationTimeMs = now; + } + final Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG); + intent.setPackage("com.android.systemui"); + final PendingIntent pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0, + UserHandle.CURRENT); + Notification notification = new Notification.Builder(mContext) + .setSmallIcon(R.drawable.ic_qs_network_logging) + .setContentTitle(mContext.getString(R.string.network_logging_notification_title)) + .setContentText(mContext.getString(R.string.network_logging_notification_text)) + .setShowWhen(true) + .setContentIntent(pendingIntent) + .build(); + mInjector.getNotificationManager().notify(NETWORK_LOGGING_NOTIFICATION_ID, notification); + saveSettingsLocked(mOwners.getDeviceOwnerUserId()); + } + /** * Return the package name of owner in a given user. */ diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java index 8cb13da07c16..49ae2bc3523a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java @@ -34,6 +34,7 @@ import com.android.server.ServiceThread; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; /** * A class for managing network logging. @@ -45,17 +46,17 @@ final class NetworkLogger { private final DevicePolicyManagerService mDpm; private final PackageManagerInternal mPm; + private final AtomicBoolean mIsLoggingEnabled = new AtomicBoolean(false); private IIpConnectivityMetrics mIpConnectivityMetrics; private ServiceThread mHandlerThread; private NetworkLoggingHandler mNetworkLoggingHandler; - private boolean mIsLoggingEnabled; private final INetdEventCallback mNetdEventCallback = new INetdEventCallback.Stub() { @Override public void onDnsEvent(String hostname, String[] ipAddresses, int ipAddressesCount, long timestamp, int uid) { - if (!mIsLoggingEnabled) { + if (!mIsLoggingEnabled.get()) { return; } DnsEvent dnsEvent = new DnsEvent(hostname, ipAddresses, ipAddressesCount, @@ -65,7 +66,7 @@ final class NetworkLogger { @Override public void onConnectEvent(String ipAddr, int port, long timestamp, int uid) { - if (!mIsLoggingEnabled) { + if (!mIsLoggingEnabled.get()) { return; } ConnectEvent connectEvent = new ConnectEvent(ipAddr, port, mPm.getNameForUid(uid), @@ -116,7 +117,7 @@ final class NetworkLogger { mDpm); mNetworkLoggingHandler.scheduleBatchFinalization( NetworkLoggingHandler.BATCH_FINALIZATION_TIMEOUT_MS); - mIsLoggingEnabled = true; + mIsLoggingEnabled.set(true); return true; } else { return false; @@ -130,7 +131,7 @@ final class NetworkLogger { boolean stopNetworkLogging() { Log.d(TAG, "Stopping network logging"); // stop the logging regardless of whether we fail to unregister listener - mIsLoggingEnabled = false; + mIsLoggingEnabled.set(false); try { if (!checkIpConnectivityMetricsService()) { // the IIpConnectivityMetrics service should have been present at this point diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 02dc5fb6f5d1..be13499e2c18 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -32,6 +32,7 @@ import android.os.Build; import android.os.Environment; import android.os.FactoryTest; import android.os.FileUtils; +import android.os.IIncidentManager; import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; @@ -273,7 +274,7 @@ public final class SystemServer { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, uptimeMillis); MetricsLogger.histogram(null, "boot_system_server_init", uptimeMillis); // Also report when first stage of init has started - long initStartNs = SystemProperties.getLong("init.start", -1); + long initStartNs = SystemProperties.getLong("ro.boottime.init", -1); if (initStartNs >= 0) { MetricsLogger.histogram(null, "boot_android_init", (int)(initStartNs / 1000000)); } @@ -1635,6 +1636,19 @@ public final class SystemServer { } traceEnd(); + traceBeginAndSlog("IncidentDaemonReady"); + try { + // TODO: Switch from checkService to getService once it's always + // in the build and should reliably be there. + final IIncidentManager incident = IIncidentManager.Stub.asInterface( + ServiceManager.checkService("incident")); + if (incident != null) incident.systemRunning(); + } catch (Throwable e) { + reportWtf("Notifying incident daemon running", e); + } + traceEnd(); + + traceEnd(); // PhaseActivityManagerReady } }); diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java index ef4bc024c153..d90a4a235972 100644 --- a/services/net/java/android/net/dhcp/DhcpPacket.java +++ b/services/net/java/android/net/dhcp/DhcpPacket.java @@ -7,6 +7,7 @@ import android.net.metrics.DhcpErrorEvent; import android.os.Build; import android.os.SystemProperties; import android.system.OsConstants; +import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; import java.io.UnsupportedEncodingException; @@ -25,8 +26,10 @@ import java.util.List; * Defines basic data and operations needed to build and use packets for the * DHCP protocol. Subclasses create the specific packets used at each * stage of the negotiation. + * + * @hide */ -abstract class DhcpPacket { +public abstract class DhcpPacket { protected static final String TAG = "DhcpPacket"; // dhcpcd has a minimum lease of 20 seconds, but DhcpStateMachine would refuse to wake up the @@ -629,7 +632,8 @@ abstract class DhcpPacket { protected void addCommonClientTlvs(ByteBuffer buf) { addTlv(buf, DHCP_MAX_MESSAGE_SIZE, (short) MAX_LENGTH); addTlv(buf, DHCP_VENDOR_CLASS_ID, getVendorId()); - addTlv(buf, DHCP_HOST_NAME, getHostname()); + final String hn = getHostname(); + if (!TextUtils.isEmpty(hn)) addTlv(buf, DHCP_HOST_NAME, hn); } /** diff --git a/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/services/net/java/android/net/ip/ConnectivityPacketTracker.java new file mode 100644 index 000000000000..884a8a754266 --- /dev/null +++ b/services/net/java/android/net/ip/ConnectivityPacketTracker.java @@ -0,0 +1,148 @@ +/* + * 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.net.ip; + +import static android.system.OsConstants.*; + +import android.net.NetworkUtils; +import android.net.util.BlockingSocketReader; +import android.net.util.ConnectivityPacketSummary; +import android.os.Handler; +import android.system.ErrnoException; +import android.system.Os; +import android.system.PacketSocketAddress; +import android.util.Log; +import android.util.LocalLog; + +import libcore.io.IoBridge; +import libcore.util.HexEncoding; + +import java.io.FileDescriptor; +import java.io.InterruptedIOException; +import java.io.IOException; +import java.net.NetworkInterface; +import java.net.SocketException; + + +/** + * Critical connectivity packet tracking daemon. + * + * Tracks ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets. + * + * This class's constructor, start() and stop() methods must only be called + * from the same thread on which the passed in |log| is accessed. + * + * Log lines include a hexdump of the packet, which can be decoded via: + * + * echo -n H3XSTR1NG | sed -e 's/\([0-9A-F][0-9A-F]\)/\1 /g' -e 's/^/000000 /' + * | text2pcap - - + * | tcpdump -n -vv -e -r - + * + * @hide + */ +public class ConnectivityPacketTracker { + private static final String TAG = ConnectivityPacketTracker.class.getSimpleName(); + private static final boolean DBG = false; + private static final String MARK_START = "--- START ---"; + private static final String MARK_STOP = "--- STOP ---"; + + private final String mTag; + private final Handler mHandler; + private final LocalLog mLog; + private final BlockingSocketReader mPacketListener; + + public ConnectivityPacketTracker(NetworkInterface netif, LocalLog log) { + final String ifname; + final int ifindex; + final byte[] hwaddr; + final int mtu; + + try { + ifname = netif.getName(); + ifindex = netif.getIndex(); + hwaddr = netif.getHardwareAddress(); + mtu = netif.getMTU(); + } catch (NullPointerException|SocketException e) { + throw new IllegalArgumentException("bad network interface", e); + } + + mTag = TAG + "." + ifname; + mHandler = new Handler(); + mLog = log; + mPacketListener = new PacketListener(ifindex, hwaddr, mtu); + } + + public void start() { + mLog.log(MARK_START); + mPacketListener.start(); + } + + public void stop() { + mPacketListener.stop(); + mLog.log(MARK_STOP); + } + + private final class PacketListener extends BlockingSocketReader { + private final int mIfIndex; + private final byte mHwAddr[]; + + PacketListener(int ifindex, byte[] hwaddr, int mtu) { + super(mtu); + mIfIndex = ifindex; + mHwAddr = hwaddr; + } + + @Override + protected FileDescriptor createSocket() { + FileDescriptor s = null; + try { + // TODO: Evaluate switching to SOCK_DGRAM and changing the + // BlockingSocketReader's read() to recvfrom(), so that this + // might work on non-ethernet-like links (via SLL). + s = Os.socket(AF_PACKET, SOCK_RAW, 0); + NetworkUtils.attachControlPacketFilter(s, ARPHRD_ETHER); + Os.bind(s, new PacketSocketAddress((short) ETH_P_ALL, mIfIndex)); + } catch (ErrnoException | IOException e) { + logError("Failed to create packet tracking socket: ", e); + closeSocket(s); + return null; + } + return s; + } + + @Override + protected void handlePacket(byte[] recvbuf, int length) { + final String summary = ConnectivityPacketSummary.summarize( + mHwAddr, recvbuf, length); + if (summary == null) return; + + if (DBG) Log.d(mTag, summary); + addLogEntry(summary + + "\n[" + new String(HexEncoding.encode(recvbuf, 0, length)) + "]"); + } + + @Override + protected void logError(String msg, Exception e) { + Log.e(mTag, msg, e); + addLogEntry(msg + e); + } + + private void addLogEntry(String entry) { + mHandler.post(() -> mLog.log(entry)); + } + } +} diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java index 39f14e5ee287..87018ec5c342 100644 --- a/services/net/java/android/net/ip/IpManager.java +++ b/services/net/java/android/net/ip/IpManager.java @@ -374,6 +374,7 @@ public class IpManager extends StateMachine { private static final int EVENT_DHCPACTION_TIMEOUT = 10; private static final int MAX_LOG_RECORDS = 500; + private static final int MAX_PACKET_RECORDS = 100; private static final boolean NO_CALLBACKS = false; private static final boolean SEND_CALLBACKS = true; @@ -399,6 +400,7 @@ public class IpManager extends StateMachine { private final WakeupMessage mDhcpActionTimeoutAlarm; private final AvoidBadWifiTracker mAvoidBadWifiTracker; private final LocalLog mLocalLog; + private final LocalLog mConnectivityPacketLog; private final MessageHandlingLogger mMsgStateLogger; private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); @@ -438,6 +440,10 @@ public class IpManager extends StateMachine { mCallback = new LoggingCallbackWrapper(callback); mNwService = nwService; + mLocalLog = new LocalLog(MAX_LOG_RECORDS); + mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS); + mMsgStateLogger = new MessageHandlingLogger(); + mNetlinkTracker = new NetlinkTracker( mInterfaceName, new NetlinkTracker.Callback() { @@ -451,48 +457,79 @@ public class IpManager extends StateMachine { super.interfaceAdded(iface); if (mClatInterfaceName.equals(iface)) { mCallback.setNeighborDiscoveryOffload(false); + } else if (!mInterfaceName.equals(iface)) { + return; } + + final String msg = "interfaceAdded(" + iface +")"; + logMsg(msg); } @Override public void interfaceRemoved(String iface) { super.interfaceRemoved(iface); + // TODO: Also observe mInterfaceName going down and take some + // kind of appropriate action. if (mClatInterfaceName.equals(iface)) { // TODO: consider sending a message to the IpManager main // StateMachine thread, in case "NDO enabled" state becomes // tied to more things that 464xlat operation. mCallback.setNeighborDiscoveryOffload(true); + } else if (!mInterfaceName.equals(iface)) { + return; } + + final String msg = "interfaceRemoved(" + iface +")"; + logMsg(msg); } - }; - try { - mNwService.registerObserver(mNetlinkTracker); - } catch (RemoteException e) { - Log.e(mTag, "Couldn't register NetlinkTracker: " + e.toString()); - } + private void logMsg(String msg) { + Log.d(mTag, msg); + getHandler().post(() -> { mLocalLog.log("OBSERVED " + msg); }); + } + }; - mAvoidBadWifiTracker = new AvoidBadWifiTracker(mContext, getHandler()); + mLinkProperties = new LinkProperties(); + mLinkProperties.setInterfaceName(mInterfaceName); - resetLinkProperties(); + mAvoidBadWifiTracker = new AvoidBadWifiTracker(mContext, getHandler(), + () -> { mLocalLog.log("OBSERVED AvoidBadWifi changed"); }); mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(), mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT); mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(), mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT); - // Super simple StateMachine. + // Anything the StateMachine may access must have been instantiated + // before this point. + configureAndStartStateMachine(); + + // Anything that may send messages to the StateMachine must only be + // configured to do so after the StateMachine has started (above). + startStateMachineUpdaters(); + } + + private void configureAndStartStateMachine() { addState(mStoppedState); addState(mStartedState); addState(mRunningState, mStartedState); addState(mStoppingState); setInitialState(mStoppedState); - mLocalLog = new LocalLog(MAX_LOG_RECORDS); - mMsgStateLogger = new MessageHandlingLogger(); + super.start(); } + private void startStateMachineUpdaters() { + try { + mNwService.registerObserver(mNetlinkTracker); + } catch (RemoteException e) { + Log.e(mTag, "Couldn't register NetlinkTracker: " + e.toString()); + } + + mAvoidBadWifiTracker.start(); + } + @Override protected void onQuitting() { mCallback.onQuit(); @@ -501,6 +538,7 @@ public class IpManager extends StateMachine { // Shut down this IpManager instance altogether. public void shutdown() { stop(); + mAvoidBadWifiTracker.shutdown(); quit(); } @@ -574,7 +612,7 @@ public class IpManager extends StateMachine { } IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - pw.println("APF dump:"); + pw.println(mTag + " APF dump:"); pw.increaseIndent(); // Thread-unsafe access to mApfFilter but just used for debugging. ApfFilter apfFilter = mApfFilter; @@ -590,6 +628,12 @@ public class IpManager extends StateMachine { pw.increaseIndent(); mLocalLog.readOnlyLocalLog().dump(fd, pw, args); pw.decreaseIndent(); + + pw.println(); + pw.println(mTag + " connectivity packet log:"); + pw.increaseIndent(); + mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args); + pw.decreaseIndent(); } @@ -1185,6 +1229,7 @@ public class IpManager extends StateMachine { } class RunningState extends State { + private ConnectivityPacketTracker mPacketTracker; private boolean mDhcpActionInFlight; @Override @@ -1197,6 +1242,9 @@ public class IpManager extends StateMachine { mCallback.setFallbackMulticastFilter(mMulticastFiltering); } + mPacketTracker = createPacketTracker(); + if (mPacketTracker != null) mPacketTracker.start(); + if (mConfiguration.mEnableIPv6 && !startIPv6()) { doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6); transitionTo(mStoppingState); @@ -1231,6 +1279,11 @@ public class IpManager extends StateMachine { mDhcpClient.doQuit(); } + if (mPacketTracker != null) { + mPacketTracker.stop(); + mPacketTracker = null; + } + if (mApfFilter != null) { mApfFilter.shutdown(); mApfFilter = null; @@ -1239,6 +1292,14 @@ public class IpManager extends StateMachine { resetLinkProperties(); } + private ConnectivityPacketTracker createPacketTracker() { + try { + return new ConnectivityPacketTracker(mNetworkInterface, mConnectivityPacketLog); + } catch (IllegalArgumentException e) { + return null; + } + } + private void ensureDhcpAction() { if (!mDhcpActionInFlight) { mCallback.onPreDhcpAction(); diff --git a/services/net/java/android/net/util/AvoidBadWifiTracker.java b/services/net/java/android/net/util/AvoidBadWifiTracker.java index c14e811e0584..2abaeb1ae35b 100644 --- a/services/net/java/android/net/util/AvoidBadWifiTracker.java +++ b/services/net/java/android/net/util/AvoidBadWifiTracker.java @@ -57,7 +57,11 @@ public class AvoidBadWifiTracker { private final Context mContext; private final Handler mHandler; private final Runnable mReevaluateRunnable; + private final Uri mUri; + private final ContentResolver mResolver; private final SettingObserver mSettingObserver; + private final BroadcastReceiver mBroadcastReceiver; + private volatile boolean mAvoidBadWifi = true; public AvoidBadWifiTracker(Context ctx, Handler handler) { @@ -68,19 +72,36 @@ public class AvoidBadWifiTracker { mContext = ctx; mHandler = handler; mReevaluateRunnable = () -> { if (update() && cb != null) cb.run(); }; + mUri = Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI); + mResolver = mContext.getContentResolver(); mSettingObserver = new SettingObserver(); - - final IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - mContext.registerReceiverAsUser(new BroadcastReceiver() { + mBroadcastReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { reevaluate(); } - }, UserHandle.ALL, intentFilter, null, null); + }; update(); } + public void start() { + mResolver.registerContentObserver(mUri, false, mSettingObserver); + + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + mContext.registerReceiverAsUser( + mBroadcastReceiver, UserHandle.ALL, intentFilter, null, null); + + reevaluate(); + } + + public void shutdown() { + mResolver.unregisterContentObserver(mSettingObserver); + + mContext.unregisterReceiver(mBroadcastReceiver); + } + public boolean currentValue() { return mAvoidBadWifi; } @@ -100,8 +121,7 @@ public class AvoidBadWifiTracker { } public String getSettingsValue() { - final ContentResolver resolver = mContext.getContentResolver(); - return Settings.Global.getString(resolver, NETWORK_AVOID_BAD_WIFI); + return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI); } @VisibleForTesting @@ -117,12 +137,8 @@ public class AvoidBadWifiTracker { } private class SettingObserver extends ContentObserver { - private final Uri mUri = Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI); - public SettingObserver() { super(null); - final ContentResolver resolver = mContext.getContentResolver(); - resolver.registerContentObserver(mUri, false, this); } @Override diff --git a/services/net/java/android/net/util/BlockingSocketReader.java b/services/net/java/android/net/util/BlockingSocketReader.java new file mode 100644 index 000000000000..12fa1e57653a --- /dev/null +++ b/services/net/java/android/net/util/BlockingSocketReader.java @@ -0,0 +1,155 @@ +/* + * 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.net.util; + +import android.annotation.Nullable; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; + +import libcore.io.IoBridge; + +import java.io.FileDescriptor; +import java.io.InterruptedIOException; +import java.io.IOException; + + +/** + * A thread that reads from a socket and passes the received packets to a + * subclass's handlePacket() method. The packet receive buffer is recycled + * on every read call, so subclasses should make any copies they would like + * inside their handlePacket() implementation. + * + * All public methods may be called from any thread. + * + * @hide + */ +public abstract class BlockingSocketReader { + public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024; + + private final byte[] mPacket; + private final Thread mThread; + private volatile FileDescriptor mSocket; + private volatile boolean mRunning; + private volatile long mPacketsReceived; + + // Make it slightly easier for subclasses to properly close a socket + // without having to know this incantation. + public static final void closeSocket(@Nullable FileDescriptor fd) { + try { + IoBridge.closeAndSignalBlockedThreads(fd); + } catch (IOException ignored) {} + } + + protected BlockingSocketReader() { + this(DEFAULT_RECV_BUF_SIZE); + } + + protected BlockingSocketReader(int recvbufsize) { + if (recvbufsize < DEFAULT_RECV_BUF_SIZE) { + recvbufsize = DEFAULT_RECV_BUF_SIZE; + } + mPacket = new byte[recvbufsize]; + mThread = new Thread(() -> { mainLoop(); }); + } + + public final boolean start() { + if (mSocket != null) return false; + + try { + mSocket = createSocket(); + } catch (Exception e) { + logError("Failed to create socket: ", e); + return false; + } + + if (mSocket == null) return false; + + mRunning = true; + mThread.start(); + return true; + } + + public final void stop() { + mRunning = false; + closeSocket(mSocket); + mSocket = null; + } + + public final boolean isRunning() { return mRunning; } + + public final long numPacketsReceived() { return mPacketsReceived; } + + /** + * Subclasses MUST create the listening socket here, including setting + * all desired socket options, interface or address/port binding, etc. + */ + protected abstract FileDescriptor createSocket(); + + /** + * Called by the main loop for every packet. Any desired copies of + * |recvbuf| should be made in here, and the underlying byte array is + * reused across all reads. + */ + protected void handlePacket(byte[] recvbuf, int length) {} + + /** + * Called by the main loop to log errors. In some cases |e| may be null. + */ + protected void logError(String msg, Exception e) {} + + /** + * Called by the main loop just prior to exiting. + */ + protected void onExit() {} + + private final void mainLoop() { + while (isRunning()) { + final int bytesRead; + + try { + // Blocking read. + // TODO: See if this can be converted to recvfrom. + bytesRead = Os.read(mSocket, mPacket, 0, mPacket.length); + if (bytesRead < 1) { + if (isRunning()) logError("Socket closed, exiting", null); + break; + } + mPacketsReceived++; + } catch (ErrnoException e) { + if (e.errno != OsConstants.EINTR) { + if (isRunning()) logError("read error: ", e); + break; + } + continue; + } catch (IOException ioe) { + if (isRunning()) logError("read error: ", ioe); + continue; + } + + try { + handlePacket(mPacket, bytesRead); + } catch (Exception e) { + logError("Unexpected exception: ", e); + break; + } + } + + stop(); + onExit(); + } +} diff --git a/services/net/java/android/net/util/ConnectivityPacketSummary.java b/services/net/java/android/net/util/ConnectivityPacketSummary.java new file mode 100644 index 000000000000..699ba5b6c4ad --- /dev/null +++ b/services/net/java/android/net/util/ConnectivityPacketSummary.java @@ -0,0 +1,375 @@ +/* + * 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.net.util; + +import android.net.dhcp.DhcpPacket; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.StringJoiner; + +import static android.system.OsConstants.*; +import static android.net.util.NetworkConstants.*; + + +/** + * Critical connectivity packet summarizing class. + * + * Outputs short descriptions of ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets. + * + * @hide + */ +public class ConnectivityPacketSummary { + private static final String TAG = ConnectivityPacketSummary.class.getSimpleName(); + + private final byte[] mHwAddr; + private final byte[] mBytes; + private final int mLength; + private final ByteBuffer mPacket; + private final String mSummary; + + public static String summarize(byte[] hwaddr, byte[] buffer) { + return summarize(hwaddr, buffer, buffer.length); + } + + // Methods called herein perform some but by no means all error checking. + // They may throw runtime exceptions on malformed packets. + public static String summarize(byte[] hwaddr, byte[] buffer, int length) { + if ((hwaddr == null) || (hwaddr.length != ETHER_ADDR_LEN)) return null; + if (buffer == null) return null; + length = Math.min(length, buffer.length); + return (new ConnectivityPacketSummary(hwaddr, buffer, length)).toString(); + } + + private ConnectivityPacketSummary(byte[] hwaddr, byte[] buffer, int length) { + mHwAddr = hwaddr; + mBytes = buffer; + mLength = Math.min(length, mBytes.length); + mPacket = ByteBuffer.wrap(mBytes, 0, mLength); + mPacket.order(ByteOrder.BIG_ENDIAN); + + final StringJoiner sj = new StringJoiner(" "); + // TODO: support other link-layers, or even no link-layer header. + parseEther(sj); + mSummary = sj.toString(); + } + + public String toString() { + return mSummary; + } + + private void parseEther(StringJoiner sj) { + if (mPacket.remaining() < ETHER_HEADER_LEN) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + mPacket.position(ETHER_SRC_ADDR_OFFSET); + final ByteBuffer srcMac = (ByteBuffer) mPacket.slice().limit(ETHER_ADDR_LEN); + sj.add(ByteBuffer.wrap(mHwAddr).equals(srcMac) ? "TX" : "RX"); + sj.add(getMacAddressString(srcMac)); + + mPacket.position(ETHER_DST_ADDR_OFFSET); + final ByteBuffer dstMac = (ByteBuffer) mPacket.slice().limit(ETHER_ADDR_LEN); + sj.add(">").add(getMacAddressString(dstMac)); + + mPacket.position(ETHER_TYPE_OFFSET); + final int etherType = asUint(mPacket.getShort()); + switch (etherType) { + case ETHER_TYPE_ARP: + sj.add("arp"); + parseARP(sj); + break; + case ETHER_TYPE_IPV4: + sj.add("ipv4"); + parseIPv4(sj); + break; + case ETHER_TYPE_IPV6: + sj.add("ipv6"); + parseIPv6(sj); + break; + default: + // Unknown ether type. + sj.add("ethtype").add(asString(etherType)); + break; + } + } + + private void parseARP(StringJoiner sj) { + if (mPacket.remaining() < ARP_PAYLOAD_LEN) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + if (asUint(mPacket.getShort()) != ARP_HWTYPE_ETHER || + asUint(mPacket.getShort()) != ETHER_TYPE_IPV4 || + asUint(mPacket.get()) != ETHER_ADDR_LEN || + asUint(mPacket.get()) != IPV4_ADDR_LEN) { + sj.add("unexpected header"); + return; + } + + final int opCode = asUint(mPacket.getShort()); + + final String senderHwAddr = getMacAddressString(mPacket); + final String senderIPv4 = getIPv4AddressString(mPacket); + getMacAddressString(mPacket); // target hardware address, unused + final String targetIPv4 = getIPv4AddressString(mPacket); + + if (opCode == ARP_REQUEST) { + sj.add("who-has").add(targetIPv4); + } else if (opCode == ARP_REPLY) { + sj.add("reply").add(senderIPv4).add(senderHwAddr); + } else { + sj.add("unknown opcode").add(asString(opCode)); + } + } + + private void parseIPv4(StringJoiner sj) { + if (!mPacket.hasRemaining()) { + sj.add("runt"); + return; + } + + final int startOfIpLayer = mPacket.position(); + final int ipv4HeaderLength = (mPacket.get(startOfIpLayer) & IPV4_IHL_MASK) * 4; + if (mPacket.remaining() < ipv4HeaderLength || + mPacket.remaining() < IPV4_HEADER_MIN_LEN) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + final int startOfTransportLayer = startOfIpLayer + ipv4HeaderLength; + + mPacket.position(startOfIpLayer + IPV4_FLAGS_OFFSET); + final int flagsAndFragment = asUint(mPacket.getShort()); + final boolean isFragment = (flagsAndFragment & IPV4_FRAGMENT_MASK) != 0; + + mPacket.position(startOfIpLayer + IPV4_PROTOCOL_OFFSET); + final int protocol = asUint(mPacket.get()); + + mPacket.position(startOfIpLayer + IPV4_SRC_ADDR_OFFSET); + final String srcAddr = getIPv4AddressString(mPacket); + + mPacket.position(startOfIpLayer + IPV4_DST_ADDR_OFFSET); + final String dstAddr = getIPv4AddressString(mPacket); + + sj.add(srcAddr).add(">").add(dstAddr); + + mPacket.position(startOfTransportLayer); + if (protocol == IPPROTO_UDP) { + sj.add("udp"); + if (isFragment) sj.add("fragment"); + else parseUDP(sj); + } else { + sj.add("proto").add(asString(protocol)); + if (isFragment) sj.add("fragment"); + } + } + + private void parseIPv6(StringJoiner sj) { + if (mPacket.remaining() < IPV6_HEADER_LEN) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + final int startOfIpLayer = mPacket.position(); + + mPacket.position(startOfIpLayer + IPV6_PROTOCOL_OFFSET); + final int protocol = asUint(mPacket.get()); + + mPacket.position(startOfIpLayer + IPV6_SRC_ADDR_OFFSET); + final String srcAddr = getIPv6AddressString(mPacket); + final String dstAddr = getIPv6AddressString(mPacket); + + sj.add(srcAddr).add(">").add(dstAddr); + + mPacket.position(startOfIpLayer + IPV6_HEADER_LEN); + if (protocol == IPPROTO_ICMPV6) { + sj.add("icmp6"); + parseICMPv6(sj); + } else { + sj.add("proto").add(asString(protocol)); + } + } + + private void parseICMPv6(StringJoiner sj) { + if (mPacket.remaining() < ICMPV6_HEADER_MIN_LEN) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + final int icmp6Type = asUint(mPacket.get()); + final int icmp6Code = asUint(mPacket.get()); + mPacket.getShort(); // checksum, unused + + switch (icmp6Type) { + case ICMPV6_ROUTER_SOLICITATION: + sj.add("rs"); + parseICMPv6RouterSolicitation(sj); + break; + case ICMPV6_ROUTER_ADVERTISEMENT: + sj.add("ra"); + parseICMPv6RouterAdvertisement(sj); + break; + case ICMPV6_NEIGHBOR_SOLICITATION: + sj.add("ns"); + parseICMPv6NeighborMessage(sj); + break; + case ICMPV6_NEIGHBOR_ADVERTISEMENT: + sj.add("na"); + parseICMPv6NeighborMessage(sj); + break; + default: + sj.add("type").add(asString(icmp6Type)); + sj.add("code").add(asString(icmp6Code)); + break; + } + } + + private void parseICMPv6RouterSolicitation(StringJoiner sj) { + final int RESERVED = 4; + if (mPacket.remaining() < RESERVED) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + mPacket.position(mPacket.position() + RESERVED); + parseICMPv6NeighborDiscoveryOptions(sj); + } + + private void parseICMPv6RouterAdvertisement(StringJoiner sj) { + final int FLAGS_AND_TIMERS = 3 * 4; + if (mPacket.remaining() < FLAGS_AND_TIMERS) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + mPacket.position(mPacket.position() + FLAGS_AND_TIMERS); + parseICMPv6NeighborDiscoveryOptions(sj); + } + + private void parseICMPv6NeighborMessage(StringJoiner sj) { + final int RESERVED = 4; + final int minReq = RESERVED + IPV6_ADDR_LEN; + if (mPacket.remaining() < minReq) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + mPacket.position(mPacket.position() + RESERVED); + sj.add(getIPv6AddressString(mPacket)); + parseICMPv6NeighborDiscoveryOptions(sj); + } + + private void parseICMPv6NeighborDiscoveryOptions(StringJoiner sj) { + // All ND options are TLV, where T is one byte and L is one byte equal + // to the length of T + L + V in units of 8 octets. + while (mPacket.remaining() >= ICMPV6_ND_OPTION_MIN_LENGTH) { + final int ndType = asUint(mPacket.get()); + final int ndLength = asUint(mPacket.get()); + final int ndBytes = ndLength * ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR - 2; + if (mPacket.remaining() < ndBytes) break; + final int position = mPacket.position(); + + switch (ndType) { + case ICMPV6_ND_OPTION_SLLA: + sj.add("slla"); + sj.add(getMacAddressString(mPacket)); + break; + case ICMPV6_ND_OPTION_TLLA: + sj.add("tlla"); + sj.add(getMacAddressString(mPacket)); + break; + case ICMPV6_ND_OPTION_MTU: + sj.add("mtu"); + final short reserved = mPacket.getShort(); + sj.add(asString(mPacket.getInt())); + break; + default: + // Skip. + break; + } + + mPacket.position(position + ndBytes); + } + } + + private void parseUDP(StringJoiner sj) { + if (mPacket.remaining() < UDP_HEADER_LEN) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + final int previous = mPacket.position(); + final int srcPort = asUint(mPacket.getShort()); + final int dstPort = asUint(mPacket.getShort()); + sj.add(asString(srcPort)).add(">").add(asString(dstPort)); + + mPacket.position(previous + UDP_HEADER_LEN); + if (srcPort == DHCP4_CLIENT_PORT || dstPort == DHCP4_CLIENT_PORT) { + sj.add("dhcp4"); + parseDHCPv4(sj); + } + } + + private void parseDHCPv4(StringJoiner sj) { + final DhcpPacket dhcpPacket; + try { + dhcpPacket = DhcpPacket.decodeFullPacket(mBytes, mLength, DhcpPacket.ENCAP_L2); + sj.add(dhcpPacket.toString()); + } catch (DhcpPacket.ParseException e) { + sj.add("parse error: " + e); + } + } + + private static String getIPv4AddressString(ByteBuffer ipv4) { + return getIpAddressString(ipv4, IPV4_ADDR_LEN); + } + + private static String getIPv6AddressString(ByteBuffer ipv6) { + return getIpAddressString(ipv6, IPV6_ADDR_LEN); + } + + private static String getIpAddressString(ByteBuffer ip, int byteLength) { + if (ip == null || ip.remaining() < byteLength) return "invalid"; + + byte[] bytes = new byte[byteLength]; + ip.get(bytes, 0, byteLength); + try { + InetAddress addr = InetAddress.getByAddress(bytes); + return addr.getHostAddress(); + } catch (UnknownHostException uhe) { + return "unknown"; + } + } + + private static String getMacAddressString(ByteBuffer mac) { + if (mac == null || mac.remaining() < ETHER_ADDR_LEN) return "invalid"; + + byte[] bytes = new byte[ETHER_ADDR_LEN]; + mac.get(bytes, 0, bytes.length); + Byte[] printableBytes = new Byte[bytes.length]; + int i = 0; + for (byte b : bytes) printableBytes[i++] = b; + + final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x"; + return String.format(MAC48_FORMAT, printableBytes); + } +} diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java new file mode 100644 index 000000000000..362f7570c124 --- /dev/null +++ b/services/net/java/android/net/util/NetworkConstants.java @@ -0,0 +1,146 @@ +/* + * 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.net.util; + +import java.nio.ByteBuffer; + + +/** + * Networking protocol constants. + * + * Includes: + * - constants that describe packet layout + * - various helper functions + * + * @hide + */ +public final class NetworkConstants { + private NetworkConstants() { throw new RuntimeException("no instance permitted"); } + + /** + * Ethernet constants. + * + * See also: + * - https://tools.ietf.org/html/rfc894 + * - https://tools.ietf.org/html/rfc7042 + * - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml + * - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml + */ + public static final int ETHER_DST_ADDR_OFFSET = 0; + public static final int ETHER_SRC_ADDR_OFFSET = 6; + public static final int ETHER_ADDR_LEN = 6; + + public static final int ETHER_TYPE_OFFSET = 12; + public static final int ETHER_TYPE_LENGTH = 2; + public static final int ETHER_TYPE_ARP = 0x0806; + public static final int ETHER_TYPE_IPV4 = 0x0800; + public static final int ETHER_TYPE_IPV6 = 0x86dd; + + public static final int ETHER_HEADER_LEN = 14; + + private static final byte FF = asByte(0xff); + public static final byte[] ETHER_ADDR_BROADCAST = { + FF, FF, FF, FF, FF, FF + }; + + /** + * ARP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc826 + * - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml + */ + public static final int ARP_PAYLOAD_LEN = 28; // For Ethernet+IPv4. + public static final int ARP_REQUEST = 1; + public static final int ARP_REPLY = 2; + public static final int ARP_HWTYPE_RESERVED_LO = 0; + public static final int ARP_HWTYPE_ETHER = 1; + public static final int ARP_HWTYPE_RESERVED_HI = 0xffff; + + /** + * IPv4 constants. + * + * See als: + * - https://tools.ietf.org/html/rfc791 + */ + public static final int IPV4_HEADER_MIN_LEN = 20; + public static final int IPV4_IHL_MASK = 0xf; + public static final int IPV4_FLAGS_OFFSET = 6; + public static final int IPV4_FRAGMENT_MASK = 0x1fff; + public static final int IPV4_PROTOCOL_OFFSET = 9; + public static final int IPV4_SRC_ADDR_OFFSET = 12; + public static final int IPV4_DST_ADDR_OFFSET = 16; + public static final int IPV4_ADDR_LEN = 4; + + /** + * IPv6 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc2460 + */ + public static final int IPV6_HEADER_LEN = 40; + public static final int IPV6_PROTOCOL_OFFSET = 6; + public static final int IPV6_SRC_ADDR_OFFSET = 8; + public static final int IPV6_DST_ADDR_OFFSET = 24; + public static final int IPV6_ADDR_LEN = 16; + + /** + * ICMPv6 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc4443 + * - https://tools.ietf.org/html/rfc4861 + */ + public static final int ICMPV6_HEADER_MIN_LEN = 4; + public static final int ICMPV6_ROUTER_SOLICITATION = 133; + public static final int ICMPV6_ROUTER_ADVERTISEMENT = 134; + public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135; + public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136; + + public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8; + public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8; + public static final int ICMPV6_ND_OPTION_SLLA = 1; + public static final int ICMPV6_ND_OPTION_TLLA = 2; + public static final int ICMPV6_ND_OPTION_MTU = 5; + + /** + * UDP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc768 + */ + public static final int UDP_HEADER_LEN = 8; + + /** + * DHCP(v4) constants. + * + * See also: + * - https://tools.ietf.org/html/rfc2131 + */ + public static final int DHCP4_SERVER_PORT = 67; + public static final int DHCP4_CLIENT_PORT = 68; + + /** + * Utility functions. + */ + public static byte asByte(int i) { return (byte) i; } + + public static String asString(int i) { return Integer.toString(i); } + + public static int asUint(byte b) { return (b & 0xff); } + public static int asUint(short s) { return (s & 0xffff); } +} diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java index fa37576a15f1..fb78457538be 100644 --- a/services/print/java/com/android/server/print/RemotePrintService.java +++ b/services/print/java/com/android/server/print/RemotePrintService.java @@ -132,8 +132,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleDestroy() { - throwIfDestroyed(); - // Stop tracking printers. stopTrackingAllPrinters(); @@ -174,7 +172,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleOnAllPrintJobsHandled() { - throwIfDestroyed(); mHasActivePrintJobs = false; if (!isBound()) { // The service is dead and neither has active jobs nor discovery @@ -208,7 +205,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleRequestCancelPrintJob(final PrintJobInfo printJob) { - throwIfDestroyed(); if (!isBound()) { ensureBound(); mPendingCommands.add(new Runnable() { @@ -235,7 +231,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleOnPrintJobQueued(final PrintJobInfo printJob) { - throwIfDestroyed(); mHasActivePrintJobs = true; if (!isBound()) { ensureBound(); @@ -262,7 +257,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleCreatePrinterDiscoverySession() { - throwIfDestroyed(); mHasPrinterDiscoverySession = true; if (!isBound()) { ensureBound(); @@ -289,7 +283,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleDestroyPrinterDiscoverySession() { - throwIfDestroyed(); mHasPrinterDiscoverySession = false; if (!isBound()) { // The service is dead and neither has active jobs nor discovery @@ -328,7 +321,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleStartPrinterDiscovery(final List<PrinterId> priorityList) { - throwIfDestroyed(); // Take a note that we are doing discovery. mDiscoveryPriorityList = new ArrayList<PrinterId>(); if (priorityList != null) { @@ -359,7 +351,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleStopPrinterDiscovery() { - throwIfDestroyed(); // We are not doing discovery anymore. mDiscoveryPriorityList = null; if (!isBound()) { @@ -392,7 +383,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleValidatePrinters(final List<PrinterId> printerIds) { - throwIfDestroyed(); if (!isBound()) { ensureBound(); mPendingCommands.add(new Runnable() { @@ -419,23 +409,40 @@ final class RemotePrintService implements DeathRecipient { } /** - * Request the custom printer icon for a printer. + * Queue a request for a custom printer icon for a printer. * * @param printerId the id of the printer the icon should be loaded for - * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon() + * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon */ public void requestCustomPrinterIcon(@NonNull PrinterId printerId) { - try { - if (isBound()) { + mHandler.obtainMessage(MyHandler.MSG_REQUEST_CUSTOM_PRINTER_ICON, + printerId).sendToTarget(); + } + + /** + * Request a custom printer icon for a printer. + * + * @param printerId the id of the printer the icon should be loaded for + * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon + */ + private void handleRequestCustomPrinterIcon(@NonNull PrinterId printerId) { + if (!isBound()) { + ensureBound(); + mPendingCommands.add(() -> handleRequestCustomPrinterIcon(printerId)); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] requestCustomPrinterIcon()"); + } + + try { mPrintService.requestCustomPrinterIcon(printerId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error requesting icon for " + printerId, re); } - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error requesting icon for " + printerId, re); } } private void handleStartPrinterStateTracking(final @NonNull PrinterId printerId) { - throwIfDestroyed(); // Take a note we are tracking the printer. if (mTrackedPrinterList == null) { mTrackedPrinterList = new ArrayList<PrinterId>(); @@ -467,7 +474,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleStopPrinterStateTracking(final PrinterId printerId) { - throwIfDestroyed(); // We are no longer tracking the printer. if (mTrackedPrinterList == null || !mTrackedPrinterList.remove(printerId)) { return; @@ -581,12 +587,6 @@ final class RemotePrintService implements DeathRecipient { } } - private void throwIfDestroyed() { - if (mDestroyed) { - throw new IllegalStateException("Cannot interact with a destroyed service"); - } - } - private class RemoteServiceConneciton implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { @@ -657,6 +657,7 @@ final class RemotePrintService implements DeathRecipient { public static final int MSG_ON_PRINT_JOB_QUEUED = 10; public static final int MSG_DESTROY = 11; public static final int MSG_BINDER_DIED = 12; + public static final int MSG_REQUEST_CUSTOM_PRINTER_ICON = 13; public MyHandler(Looper looper) { super(looper, null, false); @@ -665,6 +666,11 @@ final class RemotePrintService implements DeathRecipient { @Override @SuppressWarnings("unchecked") public void handleMessage(Message message) { + if (mDestroyed) { + Slog.w(LOG_TAG, "Not handling " + message + " as service for " + mComponentName + + " is already destroyed"); + return; + } switch (message.what) { case MSG_CREATE_PRINTER_DISCOVERY_SESSION: { handleCreatePrinterDiscoverySession(); @@ -719,6 +725,11 @@ final class RemotePrintService implements DeathRecipient { case MSG_BINDER_DIED: { handleBinderDied(); } break; + + case MSG_REQUEST_CUSTOM_PRINTER_ICON: { + PrinterId printerId = (PrinterId) message.obj; + handleRequestCustomPrinterIcon(printerId); + } break; } } } diff --git a/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java new file mode 100644 index 000000000000..403b65c44a2f --- /dev/null +++ b/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java @@ -0,0 +1,222 @@ +/* + * 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.server.notification; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +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; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.media.session.MediaSession; +import android.os.Build; +import android.os.UserHandle; +import android.provider.Settings; +import android.service.notification.StatusBarNotification; +import android.telecom.TelecomManager; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class NotificationComparatorTest { + @Mock Context mContext; + @Mock TelecomManager mTm; + @Mock RankingHandler handler; + @Mock PackageManager mPm; + + private final String callPkg = "com.android.server.notification"; + private final int callUid = 10; + private String smsPkg; + private final int smsUid = 11; + private final String pkg2 = "pkg2"; + private final int uid2 = 1111111; + + private NotificationRecord mRecordMinCall; + private NotificationRecord mRecordHighCall; + private NotificationRecord mRecordDefaultMedia; + private NotificationRecord mRecordEmail; + private NotificationRecord mRecordInlineReply; + private NotificationRecord mRecordSms; + private NotificationRecord mRecordStarredContact; + private NotificationRecord mRecordContact; + private NotificationRecord mRecordUrgent; + private NotificationRecord mRecordCheater; + + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + int userId = UserHandle.myUserId(); + + when(mContext.getResources()).thenReturn( + InstrumentationRegistry.getTargetContext().getResources()); + when(mContext.getContentResolver()).thenReturn( + InstrumentationRegistry.getTargetContext().getContentResolver()); + when(mContext.getPackageManager()).thenReturn(mPm); + when(mContext.getSystemService(eq(Context.TELECOM_SERVICE))).thenReturn(mTm); + when(mTm.getDefaultDialerPackage()).thenReturn(callPkg); + final ApplicationInfo legacy = new ApplicationInfo(); + legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1; + try { + when(mPm.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())).thenReturn(legacy); + when(mContext.getApplicationInfo()).thenReturn(legacy); + } catch (PackageManager.NameNotFoundException e) { + // let's hope not + } + + smsPkg = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.SMS_DEFAULT_APPLICATION); + + Notification n1 = new Notification.Builder(mContext) + .setCategory(Notification.CATEGORY_CALL) + .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) + .build(); + mRecordMinCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg, + callPkg, getDefaultChannel(), 1, "minCall", callUid, callUid, n1, + new UserHandle(userId), "", 2000)); + mRecordMinCall.setUserImportance(NotificationManager.IMPORTANCE_MIN); + + Notification n2 = new Notification.Builder(mContext) + .setCategory(Notification.CATEGORY_CALL) + .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) + .build(); + mRecordHighCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg, + callPkg, getDefaultChannel(), 1, "highcall", callUid, callUid, n2, + new UserHandle(userId), "", 1999)); + mRecordHighCall.setUserImportance(NotificationManager.IMPORTANCE_HIGH); + + Notification n3 = new Notification.Builder(mContext) + .setStyle(new Notification.MediaStyle() + .setMediaSession(new MediaSession.Token(null))) + .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) + .build(); + mRecordDefaultMedia = new NotificationRecord(mContext, new StatusBarNotification(pkg2, + pkg2, getDefaultChannel(), 1, "media", uid2, uid2, n3, new UserHandle(userId), + "", 1499)); + mRecordDefaultMedia.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT); + + Notification n4 = new Notification.Builder(mContext) + .setStyle(new Notification.MessagingStyle("sender!")).build(); + mRecordInlineReply = new NotificationRecord(mContext, new StatusBarNotification(pkg2, + pkg2, getDefaultChannel(), 1, "inlinereply", uid2, uid2, n4, new UserHandle(userId), + "", 1599)); + mRecordInlineReply.setUserImportance(NotificationManager.IMPORTANCE_HIGH); + mRecordInlineReply.setPackagePriority(Notification.PRIORITY_MAX); + + Notification n5 = new Notification.Builder(mContext) + .setCategory(Notification.CATEGORY_MESSAGE).build(); + mRecordSms = new NotificationRecord(mContext, new StatusBarNotification(smsPkg, + smsPkg, getDefaultChannel(), 1, "sms", smsUid, smsUid, n5, new UserHandle(userId), + "", 1299)); + mRecordSms.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT); + + Notification n6 = new Notification.Builder(mContext).build(); + mRecordStarredContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2, + pkg2, getDefaultChannel(), 1, "starred", uid2, uid2, n6, new UserHandle(userId), + "", 1259)); + mRecordStarredContact.setContactAffinity(ValidateNotificationPeople.STARRED_CONTACT); + mRecordStarredContact.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT); + + Notification n7 = new Notification.Builder(mContext).build(); + mRecordContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2, + pkg2, getDefaultChannel(), 1, "contact", uid2, uid2, n7, new UserHandle(userId), + "", 1259)); + mRecordContact.setContactAffinity(ValidateNotificationPeople.VALID_CONTACT); + mRecordContact.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT); + + Notification n8 = new Notification.Builder(mContext).build(); + mRecordUrgent = new NotificationRecord(mContext, new StatusBarNotification(pkg2, + pkg2, getDefaultChannel(), 1, "urgent", uid2, uid2, n8, new UserHandle(userId), + "", 1258)); + mRecordUrgent.setUserImportance(NotificationManager.IMPORTANCE_HIGH); + + Notification n9 = new Notification.Builder(mContext) + .setCategory(Notification.CATEGORY_MESSAGE) + .setFlag(Notification.FLAG_ONGOING_EVENT + |Notification.FLAG_FOREGROUND_SERVICE, true) + .build(); + mRecordCheater = new NotificationRecord(mContext, new StatusBarNotification(pkg2, + pkg2, getDefaultChannel(), 1, "cheater", uid2, uid2, n9, new UserHandle(userId), + "", 9258)); + mRecordCheater.setUserImportance(NotificationManager.IMPORTANCE_LOW); + + Notification n10 = new Notification.Builder(mContext) + .setStyle(new Notification.InboxStyle().setSummaryText("message!")).build(); + mRecordEmail = new NotificationRecord(mContext, new StatusBarNotification(pkg2, + pkg2, getDefaultChannel(), 1, "email", uid2, uid2, n10, new UserHandle(userId), + "", 1599)); + mRecordEmail.setUserImportance(NotificationManager.IMPORTANCE_HIGH); + } + + @Test + public void testOrdering() throws Exception { + final List<NotificationRecord> expected = new ArrayList<>(); + expected.add(mRecordHighCall); + expected.add(mRecordDefaultMedia); + expected.add(mRecordStarredContact); + expected.add(mRecordContact); + expected.add(mRecordInlineReply); + expected.add(mRecordSms); + expected.add(mRecordEmail); + expected.add(mRecordUrgent); + expected.add(mRecordCheater); + expected.add(mRecordMinCall); + + List<NotificationRecord> actual = new ArrayList<>(); + actual.addAll(expected); + Collections.shuffle(actual); + + Collections.sort(actual, new NotificationComparator(mContext)); + + assertEquals(expected, actual); + } + + @Test + public void testMessaging() throws Exception { + NotificationComparator comp = new NotificationComparator(mContext); + assertTrue(comp.isImportantMessaging(mRecordStarredContact)); + assertTrue(comp.isImportantMessaging(mRecordContact)); + assertTrue(comp.isImportantMessaging(mRecordInlineReply)); + assertTrue(comp.isImportantMessaging(mRecordSms)); + assertFalse(comp.isImportantMessaging(mRecordEmail)); + assertFalse(comp.isImportantMessaging(mRecordCheater)); + } + + private NotificationChannel getDefaultChannel() { + return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name", + NotificationManager.IMPORTANCE_LOW); + } +} diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java index e4a355f632f4..80c4ccee078c 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -57,8 +57,6 @@ public class NotificationManagerServiceTest { when(mockPackageManager.getApplicationInfo(any(), anyInt(), anyInt())) .thenReturn(applicationInfo); mNotificationManagerService.setPackageManager(mockPackageManager); - - mNotificationManagerService.setRankingHelper(mock(RankingHelper.class)); mNotificationManagerService.setHandler(new Handler(context.getMainLooper())); // Tests call directly into the Binder. @@ -69,6 +67,7 @@ public class NotificationManagerServiceTest { public void testCreateNotificationChannel_SuccessCallsListener() throws Exception { final NotificationChannel channel = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT); + mNotificationManagerService.setRankingHelper(mock(RankingHelper.class)); final CountDownLatch latch = new CountDownLatch(1); mBinderService.createNotificationChannel("test_pkg", channel, new IOnNotificationChannelCreatedListener.Stub() { diff --git a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java index a7d2c04ecb61..fc94271f8f86 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java @@ -59,7 +59,7 @@ public class NotificationRecordTest { @Mock PackageManager mPm; private final String pkg = "com.android.server.notification"; - private final int uid = 0; + private final int uid = 9583; private final String pkg2 = "pkg2"; private final int uid2 = 1111111; private final int id1 = 1; 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 16d0a75e2657..e6afe763bebf 100644 --- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java @@ -375,7 +375,7 @@ public class RankingHelperTest { final NotificationChannel channel2 = new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH); - mHelper.updateNotificationChannelFromRanker(pkg, uid, channel2); + mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2); // no fields should be changed assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId())); @@ -396,7 +396,7 @@ public class RankingHelperTest { new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH); channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); - mHelper.updateNotificationChannelFromRanker(pkg, uid, channel2); + mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2); // no fields should be changed assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId())); @@ -418,7 +418,7 @@ public class RankingHelperTest { channel2.enableVibration(true); channel2.setVibrationPattern(new long[] {100}); - mHelper.updateNotificationChannelFromRanker(pkg, uid, channel2); + mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2); // no fields should be changed assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId())); @@ -439,7 +439,7 @@ public class RankingHelperTest { new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH); channel2.setLights(true); - mHelper.updateNotificationChannelFromRanker(pkg, uid, channel2); + mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2); // no fields should be changed assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId())); @@ -460,7 +460,7 @@ public class RankingHelperTest { new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH); channel2.setBypassDnd(false); - mHelper.updateNotificationChannelFromRanker(pkg, uid, channel2); + mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2); // no fields should be changed assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId())); @@ -481,7 +481,7 @@ public class RankingHelperTest { new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH); channel2.setSound(new Uri.Builder().scheme("test2").build()); - mHelper.updateNotificationChannelFromRanker(pkg, uid, channel2); + mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2); // no fields should be changed assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId())); diff --git a/services/tests/servicestests/src/android/net/util/BlockingSocketReaderTest.java b/services/tests/servicestests/src/android/net/util/BlockingSocketReaderTest.java new file mode 100644 index 000000000000..e03350f29f95 --- /dev/null +++ b/services/tests/servicestests/src/android/net/util/BlockingSocketReaderTest.java @@ -0,0 +1,148 @@ +/* + * 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.net.util; + +import static android.system.OsConstants.*; + +import android.system.ErrnoException; +import android.system.Os; +import android.system.StructTimeval; + +import libcore.io.IoBridge; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import junit.framework.TestCase; + + +/** + * Tests for BlockingSocketReader. + * + * @hide + */ +public class BlockingSocketReaderTest extends TestCase { + static final InetAddress LOOPBACK6 = Inet6Address.getLoopbackAddress(); + static final StructTimeval TIMEO = StructTimeval.fromMillis(500); + + protected CountDownLatch mLatch; + protected FileDescriptor mLocalSocket; + protected InetSocketAddress mLocalSockName; + protected byte[] mLastRecvBuf; + protected boolean mExited; + protected BlockingSocketReader mReceiver; + + @Override + public void setUp() { + resetLatch(); + mLocalSocket = null; + mLocalSockName = null; + mLastRecvBuf = null; + mExited = false; + + mReceiver = new BlockingSocketReader() { + @Override + protected FileDescriptor createSocket() { + FileDescriptor s = null; + try { + s = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + Os.bind(s, LOOPBACK6, 0); + mLocalSockName = (InetSocketAddress) Os.getsockname(s); + Os.setsockoptTimeval(s, SOL_SOCKET, SO_SNDTIMEO, TIMEO); + } catch (ErrnoException|SocketException e) { + closeSocket(s); + fail(); + return null; + } + + mLocalSocket = s; + return s; + } + + @Override + protected void handlePacket(byte[] recvbuf, int length) { + mLastRecvBuf = Arrays.copyOf(recvbuf, length); + mLatch.countDown(); + } + + @Override + protected void onExit() { + mExited = true; + mLatch.countDown(); + } + }; + } + + @Override + public void tearDown() { + if (mReceiver != null) mReceiver.stop(); + mReceiver = null; + } + + void resetLatch() { mLatch = new CountDownLatch(1); } + + void waitForActivity() throws Exception { + assertTrue(mLatch.await(500, TimeUnit.MILLISECONDS)); + resetLatch(); + } + + void sendPacket(byte[] contents) throws Exception { + final DatagramSocket sender = new DatagramSocket(); + sender.connect(mLocalSockName); + sender.send(new DatagramPacket(contents, contents.length)); + sender.close(); + } + + public void testBasicWorking() throws Exception { + assertTrue(mReceiver.start()); + assertTrue(mLocalSockName != null); + assertEquals(LOOPBACK6, mLocalSockName.getAddress()); + assertTrue(0 < mLocalSockName.getPort()); + assertTrue(mLocalSocket != null); + assertFalse(mExited); + + final byte[] one = "one 1".getBytes("UTF-8"); + sendPacket(one); + waitForActivity(); + assertEquals(1, mReceiver.numPacketsReceived()); + assertTrue(Arrays.equals(one, mLastRecvBuf)); + assertFalse(mExited); + + final byte[] two = "two 2".getBytes("UTF-8"); + sendPacket(two); + waitForActivity(); + assertEquals(2, mReceiver.numPacketsReceived()); + assertTrue(Arrays.equals(two, mLastRecvBuf)); + assertFalse(mExited); + + mReceiver.stop(); + waitForActivity(); + assertEquals(2, mReceiver.numPacketsReceived()); + assertTrue(Arrays.equals(two, mLastRecvBuf)); + assertTrue(mExited); + } +} diff --git a/services/tests/servicestests/src/android/net/util/ConnectivityPacketSummaryTest.java b/services/tests/servicestests/src/android/net/util/ConnectivityPacketSummaryTest.java new file mode 100644 index 000000000000..766e5c048f1b --- /dev/null +++ b/services/tests/servicestests/src/android/net/util/ConnectivityPacketSummaryTest.java @@ -0,0 +1,377 @@ +/* + * 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.net.util; + +import static android.net.util.NetworkConstants.*; + +import libcore.util.HexEncoding; + +import junit.framework.TestCase; + + +/** + * Tests for ConnectivityPacketSummary. + * + * @hide + */ +public class ConnectivityPacketSummaryTest extends TestCase { + private static final byte[] MYHWADDR = { + asByte(0x80), asByte(0x7a), asByte(0xbf), asByte(0x6f), asByte(0x48), asByte(0xf3) + }; + + private String getSummary(String hexBytes) { + hexBytes = hexBytes.replaceAll("\\s+", ""); + final byte[] bytes = HexEncoding.decode(hexBytes.toCharArray(), false); + return ConnectivityPacketSummary.summarize(MYHWADDR, bytes); + } + + public void testParseICMPv6DADProbe() { + final String packet = + // Ethernet + "3333FF6F48F3 807ABF6F48F3 86DD" + + // IPv6 + "600000000018 3A FF" + + "00000000000000000000000000000000" + + "FF0200000000000000000001FF6F48F3" + + // ICMPv6 + "87 00 A8E7" + + "00000000" + + "FE80000000000000827ABFFFFE6F48F3"; + + final String expected = + "TX 80:7a:bf:6f:48:f3 > 33:33:ff:6f:48:f3 ipv6" + + " :: > ff02::1:ff6f:48f3 icmp6" + + " ns fe80::827a:bfff:fe6f:48f3"; + + assertEquals(expected, getSummary(packet)); + } + + public void testParseICMPv6RS() { + final String packet = + // Ethernet + "333300000002 807ABF6F48F3 86DD" + + // IPv6 + "600000000010 3A FF" + + "FE80000000000000827ABFFFFE6F48F3" + + "FF020000000000000000000000000002" + + // ICMPv6 RS + "85 00 6973" + + "00000000" + + "01 01 807ABF6F48F3"; + + final String expected = + "TX 80:7a:bf:6f:48:f3 > 33:33:00:00:00:02 ipv6" + + " fe80::827a:bfff:fe6f:48f3 > ff02::2 icmp6" + + " rs slla 80:7a:bf:6f:48:f3"; + + assertEquals(expected, getSummary(packet)); + } + + public void testParseICMPv6RA() { + final String packet = + // Ethernet + "807ABF6F48F3 100E7E263FC1 86DD" + + // IPv6 + "600000000068 3A FF" + + "FE80000000000000FA000004FD000001" + + "FE80000000000000827ABFFFFE6F48F3" + + // ICMPv6 RA + "86 00 8141" + + "40 00 0E10" + + "00000000" + + "00000000" + + "01 01 00005E000265" + + "05 01 0000000005DC" + + "19 05 000000000E10" + + " 20014860486000000000000000008844" + + " 20014860486000000000000000008888" + + "03 04 40 C0" + + " 00278D00" + + " 00093A80" + + " 00000000" + + " 2401FA000004FD000000000000000000"; + + final String expected = + "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" + + " fe80::fa00:4:fd00:1 > fe80::827a:bfff:fe6f:48f3 icmp6" + + " ra slla 00:00:5e:00:02:65 mtu 1500"; + + assertEquals(expected, getSummary(packet)); + } + + public void testParseICMPv6NS() { + final String packet = + // Ethernet + "807ABF6F48F3 100E7E263FC1 86DD" + + // IPv6 + "6C0000000020 3A FF" + + "FE80000000000000FA000004FD000001" + + "FF0200000000000000000001FF01C146" + + // ICMPv6 NS + "87 00 8AD4" + + "00000000" + + "2401FA000004FD0015EA6A5C7B01C146" + + "01 01 00005E000265"; + + final String expected = + "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" + + " fe80::fa00:4:fd00:1 > ff02::1:ff01:c146 icmp6" + + " ns 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 slla 00:00:5e:00:02:65"; + + assertEquals(expected, getSummary(packet)); + } + + public void testParseICMPv6NA() { + final String packet = + // Ethernet + "00005E000265 807ABF6F48F3 86DD" + + "600000000020 3A FF" + + "2401FA000004FD0015EA6A5C7B01C146" + + "FE80000000000000FA000004FD000001" + + "88 00 E8126" + + "0000000" + + "2401FA000004FD0015EA6A5C7B01C146" + + "02 01 807ABF6F48F3"; + + final String expected = + "TX 80:7a:bf:6f:48:f3 > 00:00:5e:00:02:65 ipv6" + + " 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 > fe80::fa00:4:fd00:1 icmp6" + + " na 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 tlla 80:7a:bf:6f:48:f3"; + + assertEquals(expected, getSummary(packet)); + } + + public void testParseARPRequest() { + final String packet = + // Ethernet + "FFFFFFFFFFFF 807ABF6F48F3 0806" + + // ARP + "0001 0800 06 04" + + // Request + "0001" + + "807ABF6F48F3 64706ADB" + + "000000000000 64706FFD"; + + final String expected = + "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff arp" + + " who-has 100.112.111.253"; + + assertEquals(expected, getSummary(packet)); + } + + public void testParseARPReply() { + final String packet = + // Ethernet + "807ABF6F48F3 288A1CA8DFC1 0806" + + // ARP + "0001 0800 06 04" + + // Reply + "0002" + + "288A1CA8DFC1 64706FFD"+ + "807ABF6F48F3 64706ADB" + + // Ethernet padding to packet min size. + "0000000000000000000000000000"; + + final String expected = + "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 arp" + + " reply 100.112.111.253 28:8a:1c:a8:df:c1"; + + assertEquals(expected, getSummary(packet)); + } + + public void testParseDHCPv4Discover() { + final String packet = + // Ethernet + "FFFFFFFFFFFF 807ABF6F48F3 0800" + + // IPv4 + "451001580000400040113986" + + "00000000" + + "FFFFFFFF" + + // UDP + "0044 0043" + + "0144 5559" + + // DHCPv4 + "01 01 06 00" + + "79F7ACA4" + + "0000 0000" + + "00000000" + + "00000000" + + "00000000" + + "00000000" + + "807ABF6F48F300000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "63 82 53 63" + + "35 01 01" + + "3D 07 01807ABF6F48F3" + + "39 02 05DC" + + "3C 12 616E64726F69642D646863702D372E312E32" + + "0C 18 616E64726F69642D36623030366333313333393835343139" + + "37 0A 01 03 06 0F 1A 1C 33 3A 3B 2B" + + "FF" + + "00"; + + final String expectedPrefix = + "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff ipv4" + + " 0.0.0.0 > 255.255.255.255 udp" + + " 68 > 67 dhcp4" + + " 80:7a:bf:6f:48:f3 DISCOVER"; + + assertTrue(getSummary(packet).startsWith(expectedPrefix)); + } + + public void testParseDHCPv4Offer() { + final String packet = + // Ethernet + "807ABF6F48F3 288A1CA8DFC1 0800" + + // IPv4 + "4500013D4D2C0000401188CB" + + "64706FFD" + + "64706ADB" + + // UDP + "0043 0044" + + "0129 371D" + + // DHCPv4 + "02 01 06 01" + + "79F7ACA4" + + "0000 0000" + + "00000000" + + "64706ADB" + + "00000000" + + "00000000" + + "807ABF6F48F300000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "63 82 53 63" + + "35 01 02" + + "36 04 AC188A0B" + + "33 04 00000708" + + "01 04 FFFFF000" + + "03 04 64706FFE" + + "06 08 08080808" + + " 08080404" + + "FF0001076165313A363636FF"; + + final String expectedPrefix = + "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 ipv4" + + " 100.112.111.253 > 100.112.106.219 udp" + + " 67 > 68 dhcp4" + + " 80:7a:bf:6f:48:f3 OFFER"; + + assertTrue(getSummary(packet).startsWith(expectedPrefix)); + } + + public void testParseDHCPv4Request() { + final String packet = + // Ethernet + "FFFFFFFFFFFF 807ABF6F48F3 0800" + + // IPv4 + "45100164000040004011397A" + + "00000000" + + "FFFFFFFF" + + // UDP + "0044 0043" + + "0150 E5C7" + + // DHCPv4 + "01 01 06 00" + + "79F7ACA4" + + "0001 0000" + + "00000000" + + "00000000" + + "00000000" + + "00000000" + + "807ABF6F48F300000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "63 82 53 63" + + "35 01 03" + + "3D 07 01807ABF6F48F3" + + "32 04 64706ADB" + + "36 04 AC188A0B" + + "39 02 05DC" + + "3C 12 616E64726F69642D646863702D372E312E32" + + "0C 18 616E64726F69642D36623030366333313333393835343139" + + "37 0A 01 03 06 0F 1A 1C 33 3A 3B 2B" + + "FF" + + "00"; + + final String expectedPrefix = + "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff ipv4" + + " 0.0.0.0 > 255.255.255.255 udp" + + " 68 > 67 dhcp4" + + " 80:7a:bf:6f:48:f3 REQUEST"; + + assertTrue(getSummary(packet).startsWith(expectedPrefix)); + } + + public void testParseDHCPv4Ack() { + final String packet = + // Ethernet + "807ABF6F48F3 288A1CA8DFC1 0800" + + // IPv4 + "4500013D4D3B0000401188BC" + + "64706FFD" + + "64706ADB" + + // UDP + "0043 0044" + + "0129 341C" + + // DHCPv4 + "02 01 06 01" + + "79F7ACA4" + + "0001 0000" + + "00000000" + + "64706ADB" + + "00000000" + + "00000000" + + "807ABF6F48F300000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "63 82 53 63" + + "35 01 05" + + "36 04 AC188A0B" + + "33 04 00000708" + + "01 04 FFFFF000" + + "03 04 64706FFE" + + "06 08 08080808" + + " 08080404" + + "FF0001076165313A363636FF"; + + final String expectedPrefix = + "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 ipv4" + + " 100.112.111.253 > 100.112.106.219 udp" + + " 67 > 68 dhcp4" + + " 80:7a:bf:6f:48:f3 ACK"; + + assertTrue(getSummary(packet).startsWith(expectedPrefix)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index 50911cb32ddf..c653b8eecffa 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -16,10 +16,13 @@ package com.android.server; +import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT; +import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE; import static android.net.NetworkScoreManager.CACHE_FILTER_NONE; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; @@ -28,12 +31,15 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyListOf; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isA; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.never; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.Manifest.permission; @@ -44,37 +50,42 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.net.INetworkRecommendationProvider; import android.net.INetworkScoreCache; import android.net.NetworkKey; -import android.net.NetworkScoreManager; import android.net.NetworkScorerAppManager; import android.net.NetworkScorerAppManager.NetworkScorerAppData; +import android.net.RecommendationRequest; +import android.net.RecommendationResult; import android.net.ScoredNetwork; import android.net.WifiKey; +import android.net.wifi.WifiConfiguration; +import android.os.Bundle; import android.os.IBinder; +import android.os.IRemoteCallback; +import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; -import android.provider.Settings; -import android.provider.Settings.Global; import android.support.test.InstrumentationRegistry; import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; -import com.android.internal.R; import com.android.server.devicepolicy.MockUtils; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; /** * Tests for {@link NetworkScoreService}. @@ -85,12 +96,8 @@ public class NetworkScoreServiceTest { private static final ScoredNetwork SCORED_NETWORK = new ScoredNetwork(new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")), null /* rssiCurve*/); - private static final NetworkScorerAppData PREV_SCORER = new NetworkScorerAppData( - "prevPackageName", 0, "prevScorerName", null /* configurationActivityClassName */, - "prevScoringServiceClass"); - private static final NetworkScorerAppData NEW_SCORER = new NetworkScorerAppData( - "newPackageName", 1, "newScorerName", null /* configurationActivityClassName */, - "newScoringServiceClass"); + private static final NetworkScorerAppData NEW_SCORER = + new NetworkScorerAppData("newPackageName", 1, "newScoringServiceClass"); @Mock private PackageManager mPackageManager; @Mock private NetworkScorerAppManager mNetworkScorerAppManager; @@ -98,10 +105,12 @@ public class NetworkScoreServiceTest { @Mock private Resources mResources; @Mock private INetworkScoreCache.Stub mNetworkScoreCache, mNetworkScoreCache2; @Mock private IBinder mIBinder, mIBinder2; + @Mock private INetworkRecommendationProvider mRecommendationProvider; @Captor private ArgumentCaptor<List<ScoredNetwork>> mScoredNetworkCaptor; private ContentResolver mContentResolver; private NetworkScoreService mNetworkScoreService; + private RecommendationRequest mRecommendationRequest; @Before public void setUp() throws Exception { @@ -112,57 +121,135 @@ public class NetworkScoreServiceTest { when(mContext.getContentResolver()).thenReturn(mContentResolver); when(mContext.getResources()).thenReturn(mResources); mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager); + WifiConfiguration configuration = new WifiConfiguration(); + mRecommendationRequest = new RecommendationRequest.Builder() + .setCurrentRecommendedWifiConfig(configuration).build(); } @Test - public void testSystemReady_networkScorerProvisioned() throws Exception { - Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 1); + public void testSystemRunning() { + when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER); - mNetworkScoreService.systemReady(); + mNetworkScoreService.systemRunning(); - verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString()); + verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent( + new ComponentName(NEW_SCORER.packageName, + NEW_SCORER.recommendationServiceClassName))), + any(ServiceConnection.class), + eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), + eq(UserHandle.SYSTEM)); } @Test - public void testSystemReady_networkScorerNotProvisioned_defaultScorer() throws Exception { - Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0); - - when(mResources.getString(R.string.config_defaultNetworkScorerPackageName)) - .thenReturn(NEW_SCORER.mPackageName); + public void testRequestScores_noPermission() throws Exception { + doThrow(new SecurityException()).when(mContext) + .enforceCallingOrSelfPermission(eq(permission.BROADCAST_NETWORK_PRIVILEGED), + anyString()); + try { + mNetworkScoreService.requestScores(null); + fail("BROADCAST_NETWORK_PRIVILEGED not enforced."); + } catch (SecurityException e) { + // expected + } + } - mNetworkScoreService.systemReady(); + @Test + public void testRequestScores_providerNotConnected() throws Exception { + assertFalse(mNetworkScoreService.requestScores(new NetworkKey[0])); + verifyZeroInteractions(mRecommendationProvider); + } - verify(mNetworkScorerAppManager).setActiveScorer(NEW_SCORER.mPackageName); - assertEquals(1, - Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED)); + @Test + public void testRequestScores_providerThrowsRemoteException() throws Exception { + injectProvider(); + doThrow(new RemoteException()).when(mRecommendationProvider) + .requestScores(any(NetworkKey[].class)); + assertFalse(mNetworkScoreService.requestScores(new NetworkKey[0])); } @Test - public void testSystemReady_networkScorerNotProvisioned_noDefaultScorer() throws Exception { - Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0); + public void testRequestScores_providerAvailable() throws Exception { + injectProvider(); - when(mResources.getString(R.string.config_defaultNetworkScorerPackageName)) - .thenReturn(null); + final NetworkKey[] networks = new NetworkKey[0]; + assertTrue(mNetworkScoreService.requestScores(networks)); + verify(mRecommendationProvider).requestScores(networks); + } - mNetworkScoreService.systemReady(); + @Test + public void testRequestRecommendation_noPermission() throws Exception { + doThrow(new SecurityException()).when(mContext) + .enforceCallingOrSelfPermission(eq(permission.BROADCAST_NETWORK_PRIVILEGED), + anyString()); + try { + mNetworkScoreService.requestRecommendation(mRecommendationRequest); + fail("BROADCAST_NETWORK_PRIVILEGED not enforced."); + } catch (SecurityException e) { + // expected + } + } - verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString()); - assertEquals(1, - Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED)); + @Test + public void testRequestRecommendation_mainThread() throws Exception { + when(mContext.getMainLooper()).thenReturn(Looper.myLooper()); + try { + mNetworkScoreService.requestRecommendation(mRecommendationRequest); + fail("requestRecommendation run on main thread."); + } catch (RuntimeException e) { + // expected + } } @Test - public void testSystemRunning() { - when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER); + public void testRequestRecommendation_providerNotConnected() throws Exception { + when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); + + final RecommendationResult result = + mNetworkScoreService.requestRecommendation(mRecommendationRequest); + assertNotNull(result); + assertEquals(mRecommendationRequest.getCurrentSelectedConfig(), + result.getWifiConfiguration()); + } - mNetworkScoreService.systemRunning(); + @Test + public void testRequestRecommendation_providerThrowsRemoteException() throws Exception { + injectProvider(); + when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); + doThrow(new RemoteException()).when(mRecommendationProvider) + .requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class), + anyInt()); + + final RecommendationResult result = + mNetworkScoreService.requestRecommendation(mRecommendationRequest); + assertNotNull(result); + assertEquals(mRecommendationRequest.getCurrentSelectedConfig(), + result.getWifiConfiguration()); + } - verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent( - new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))), - any(ServiceConnection.class), - eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), - eq(UserHandle.SYSTEM)); + @Test + public void testRequestRecommendation_resultReturned() throws Exception { + injectProvider(); + when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); + final WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiConfiguration.SSID = "testRequestRecommendation_resultReturned"; + final RecommendationResult providerResult = + new RecommendationResult(wifiConfiguration); + final Bundle bundle = new Bundle(); + bundle.putParcelable(EXTRA_RECOMMENDATION_RESULT, providerResult); + doAnswer(invocation -> { + bundle.putInt(EXTRA_SEQUENCE, invocation.getArgumentAt(2, int.class)); + invocation.getArgumentAt(1, IRemoteCallback.class).sendResult(bundle); + return null; + }).when(mRecommendationProvider) + .requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class), + anyInt()); + + final RecommendationResult result = + mNetworkScoreService.requestRecommendation(mRecommendationRequest); + assertNotNull(result); + assertEquals(providerResult.getWifiConfiguration().SSID, + result.getWifiConfiguration().SSID); } @Test @@ -288,45 +375,6 @@ public class NetworkScoreServiceTest { } @Test - public void testSetActiveScorer_failure() throws RemoteException { - when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER); - when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(false); - mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache, - CACHE_FILTER_NONE); - - boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName); - - assertFalse(success); - verify(mNetworkScoreCache).clearScores(); - verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent( - new ComponentName(PREV_SCORER.mPackageName, PREV_SCORER.mScoringServiceClassName))), - any(ServiceConnection.class), - eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), - eq(UserHandle.SYSTEM)); - } - - @Test - public void testSetActiveScorer_success() throws RemoteException { - when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, NEW_SCORER); - when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(true); - mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache, - CACHE_FILTER_NONE); - - boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName); - - assertTrue(success); - verify(mNetworkScoreCache).clearScores(); - verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent( - new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))), - any(ServiceConnection.class), - eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), - eq(UserHandle.SYSTEM)); - verify(mContext, times(2)).sendBroadcastAsUser( - MockUtils.checkIntentAction(NetworkScoreManager.ACTION_SCORER_CHANGED), - eq(UserHandle.SYSTEM)); - } - - @Test public void testDisableScoring_notActiveScorer_noBroadcastNetworkPermission() { when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false); when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED)) @@ -338,48 +386,6 @@ public class NetworkScoreServiceTest { } catch (SecurityException e) { // expected } - - } - - @Test - public void testDisableScoring_activeScorer() throws RemoteException { - when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true); - when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null); - when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true); - mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache, - CACHE_FILTER_NONE); - - mNetworkScoreService.disableScoring(); - - verify(mNetworkScoreCache).clearScores(); - verify(mContext).sendBroadcastAsUser( - MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED) - .setPackage(PREV_SCORER.mPackageName)), - eq(UserHandle.SYSTEM)); - verify(mContext, never()).bindServiceAsUser(any(Intent.class), - any(ServiceConnection.class), anyInt(), any(UserHandle.class)); - } - - @Test - public void testDisableScoring_notActiveScorer_hasBroadcastNetworkPermission() - throws RemoteException { - when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false); - when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED)) - .thenReturn(PackageManager.PERMISSION_GRANTED); - when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null); - when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true); - mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache, - CACHE_FILTER_NONE); - - mNetworkScoreService.disableScoring(); - - verify(mNetworkScoreCache).clearScores(); - verify(mContext).sendBroadcastAsUser( - MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED) - .setPackage(PREV_SCORER.mPackageName)), - eq(UserHandle.SYSTEM)); - verify(mContext, never()).bindServiceAsUser(any(Intent.class), - any(ServiceConnection.class), anyInt(), any(UserHandle.class)); } @Test @@ -434,4 +440,24 @@ public class NetworkScoreServiceTest { assertFalse(stringWriter.toString().isEmpty()); } + + // "injects" the mock INetworkRecommendationProvider into the NetworkScoreService. + private void injectProvider() { + final ComponentName componentName = new ComponentName(NEW_SCORER.packageName, + NEW_SCORER.recommendationServiceClassName); + when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER); + when(mContext.bindServiceAsUser(isA(Intent.class), isA(ServiceConnection.class), anyInt(), + isA(UserHandle.class))).thenAnswer(new Answer<Boolean>() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + IBinder mockBinder = mock(IBinder.class); + when(mockBinder.queryLocalInterface(anyString())) + .thenReturn(mRecommendationProvider); + invocation.getArgumentAt(1, ServiceConnection.class) + .onServiceConnected(componentName, mockBinder); + return true; + } + }); + mNetworkScoreService.systemRunning(); + } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 0c008861b346..c35d11420fbc 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -49,6 +49,7 @@ import android.util.Pair; import com.android.internal.R; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.pm.UserRestrictionsUtils; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -76,7 +77,7 @@ import static org.mockito.Mockito.when; /** * Tests for DevicePolicyManager( and DevicePolicyManagerService). - * + * You can run them via: m FrameworksServicesTests && adb install \ -r ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk && @@ -84,6 +85,9 @@ import static org.mockito.Mockito.when; -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner (mmma frameworks/base/services/tests/servicestests/ for non-ninja build) + * + * , or: + * runtest -c com.android.server.devicepolicy.DevicePolicyManagerTest frameworks-services */ @SmallTest public class DevicePolicyManagerTest extends DpmTestBase { @@ -1190,6 +1194,22 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertTrue(dpm.setDeviceOwner(admin1, "owner-name", UserHandle.USER_SYSTEM)); + // Check that the user restrictions that are enabled by default are set. Then unset them. + String[] defaultRestrictions = UserRestrictionsUtils + .getDefaultEnabledForDeviceOwner().toArray(new String[0]); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions(defaultRestrictions), + dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions() + ); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions(defaultRestrictions), + dpm.getUserRestrictions(admin1) + ); + + for (String restriction : defaultRestrictions) { + dpm.clearUserRestriction(admin1, restriction); + } + DpmTestUtils.assertRestrictions( DpmTestUtils.newRestrictions(), dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions() @@ -1993,7 +2013,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // UnfinishedVerificationException. } - public void setup_DeviceAdminFeatureOff() throws Exception { + private void setup_DeviceAdminFeatureOff() throws Exception { when(mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) .thenReturn(false); when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) @@ -2009,6 +2029,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_DeviceAdminFeatureOff() throws Exception { setup_DeviceAdminFeatureOff(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, @@ -2030,7 +2052,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED); } - public void setup_ManagedProfileFeatureOff() throws Exception { + private void setup_ManagedProfileFeatureOff() throws Exception { when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(false); initializeDpms(); @@ -2044,6 +2066,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_ManagedProfileFeatureOff() throws Exception { setup_ManagedProfileFeatureOff(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, @@ -2085,7 +2109,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED); } - public void setup_nonSplitUser_firstBoot_primaryUser() throws Exception { + private void setup_nonSplitUser_firstBoot_primaryUser() throws Exception { when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(false); @@ -2098,6 +2122,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_nonSplitUser_firstBoot_primaryUser() throws Exception { setup_nonSplitUser_firstBoot_primaryUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, @@ -2121,7 +2147,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_NOT_SYSTEM_USER_SPLIT); } - public void setup_nonSplitUser_afterDeviceSetup_primaryUser() throws Exception { + private void setup_nonSplitUser_afterDeviceSetup_primaryUser() throws Exception { when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(false); @@ -2135,6 +2161,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_nonSplitUser_afterDeviceSetup_primaryUser() throws Exception { setup_nonSplitUser_afterDeviceSetup_primaryUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false/* because of completed device setup */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); @@ -2159,7 +2187,88 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_NOT_SYSTEM_USER_SPLIT); } - public void setup_splitUser_firstBoot_systemUser() throws Exception { + public void testIsProvisioningAllowed_nonSplitUser_withDo_primaryUser() throws Exception { + setDeviceOwner(); + setup_nonSplitUser_afterDeviceSetup_primaryUser(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); + mContext.packageName = admin1.getPackageName(); + + // COMP mode is allowed. + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + + when(mContext.userManager.hasUserRestriction( + eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), + eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) + .thenReturn(true); + + // The DO should be allowed to initiate provisioning if it set the restriction itself. + when(mContext.userManager.getUserRestrictionSource( + eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), + eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) + .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + + // The DO should not be allowed to initiate provisioning if the restriction is set by + // another entity. + when(mContext.userManager.getUserRestrictionSource( + eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), + eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) + .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); + } + + public void + testCheckProvisioningPreCondition_nonSplitUser_withDo_primaryUser() throws Exception { + setDeviceOwner(); + setup_nonSplitUser_afterDeviceSetup_primaryUser(); + mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); + + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, + DevicePolicyManager.CODE_HAS_DEVICE_OWNER); + + // COMP mode is allowed. + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + DevicePolicyManager.CODE_OK); + + // And other DPCs can also provisioning a managed profile (DO + BYOD case). + assertCheckProvisioningPreCondition( + DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + "some.other.dpc.package.name", + DevicePolicyManager.CODE_OK); + + when(mContext.userManager.hasUserRestriction( + eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), + eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) + .thenReturn(true); + + // The DO should be allowed to initiate provisioning if it set the restriction itself, but + // other packages should be forbidden. + when(mContext.userManager.getUserRestrictionSource( + eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), + eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) + .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition( + DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + "some.other.dpc.package.name", + DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + + // The DO should not be allowed to initiate provisioning if the restriction is set by + // another entity. + when(mContext.userManager.getUserRestrictionSource( + eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), + eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) + .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + assertCheckProvisioningPreCondition( + DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + "some.other.dpc.package.name", + DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + } + + private void setup_splitUser_firstBoot_systemUser() throws Exception { when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); @@ -2172,6 +2281,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_splitUser_firstBoot_systemUser() throws Exception { setup_splitUser_firstBoot_systemUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false /* because canAddMoreManagedProfiles returns false */); @@ -2188,7 +2299,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER); assertCheckProvisioningPreCondition( DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, DevicePolicyManager.CODE_OK); @@ -2196,7 +2307,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_SYSTEM_USER); } - public void setup_splitUser_afterDeviceSetup_systemUser() throws Exception { + private void setup_splitUser_afterDeviceSetup_systemUser() throws Exception { when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); @@ -2209,6 +2320,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_splitUser_afterDeviceSetup_systemUser() throws Exception { setup_splitUser_afterDeviceSetup_systemUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true/* it's undefined behavior. Can be changed into false in the future */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, @@ -2226,7 +2339,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, DevicePolicyManager.CODE_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER); assertCheckProvisioningPreCondition( DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, DevicePolicyManager.CODE_OK); @@ -2234,7 +2347,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_SYSTEM_USER); } - public void setup_splitUser_firstBoot_primaryUser() throws Exception { + private void setup_splitUser_firstBoot_primaryUser() throws Exception { when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); @@ -2247,6 +2360,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_splitUser_firstBoot_primaryUser() throws Exception { setup_splitUser_firstBoot_primaryUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, @@ -2269,7 +2384,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_OK); } - public void setup_splitUser_afterDeviceSetup_primaryUser() throws Exception { + private void setup_splitUser_afterDeviceSetup_primaryUser() throws Exception { when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); @@ -2283,6 +2398,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_splitUser_afterDeviceSetup_primaryUser() throws Exception { setup_splitUser_afterDeviceSetup_primaryUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true/* it's undefined behavior. Can be changed into false in the future */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); @@ -2307,7 +2424,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_USER_SETUP_COMPLETED); } - public void setup_provisionManagedProfileWithDeviceOwner_systemUser() throws Exception { + private void setup_provisionManagedProfileWithDeviceOwner_systemUser() throws Exception { setDeviceOwner(); when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) @@ -2323,6 +2440,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_provisionManagedProfileWithDeviceOwner_systemUser() throws Exception { setup_provisionManagedProfileWithDeviceOwner_systemUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false /* can't provision managed profile on system user */); } @@ -2351,6 +2470,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_provisionManagedProfileWithDeviceOwner_primaryUser() throws Exception { setup_provisionManagedProfileWithDeviceOwner_primaryUser(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); + mContext.packageName = admin1.getPackageName(); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); } @@ -2358,6 +2479,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { throws Exception { setup_provisionManagedProfileWithDeviceOwner_primaryUser(); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); + + // COMP mode is allowed. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_OK); } @@ -2368,7 +2491,9 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); - when(mContext.userManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_USER)) + when(mContext.userManager.hasUserRestriction( + eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE), + eq(UserHandle.of(DpmMockContext.CALLER_USER_HANDLE)))) .thenReturn(true); when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE, false /* we can't remove a managed profile */)).thenReturn(false); @@ -2382,6 +2507,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_provisionManagedProfileCantRemoveUser_primaryUser() throws Exception { setup_provisionManagedProfileCantRemoveUser_primaryUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); } @@ -2396,7 +2523,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testCheckProvisioningPreCondition_permission() { // GIVEN the permission MANAGE_PROFILE_AND_DEVICE_OWNERS is not granted try { - dpm.checkProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE); + dpm.checkProvisioningPreCondition( + DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, "some.package"); fail("Didn't throw SecurityException"); } catch (SecurityException expected) { } @@ -2664,8 +2792,26 @@ public class DevicePolicyManagerTest extends DpmTestBase { final int ANOTHER_USER_ID = 36; mContext.addUser(ANOTHER_USER_ID, 0); + // Since the managed profile is not affiliated, they should not be allowed to talk to each + // other. + targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); + MoreAsserts.assertEmpty(targetUsers); + + mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; + targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); + MoreAsserts.assertEmpty(targetUsers); + + // Setting affiliation ids + final List<String> userAffiliationIds = Arrays.asList("some.affiliation-id"); + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + dpm.setAffiliationIds(admin1, userAffiliationIds); + + mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; + dpm.setAffiliationIds(admin1, userAffiliationIds); + // Calling from device owner admin, the result list should just contain the managed // profile user id. + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); MoreAsserts.assertContentsInAnyOrder(targetUsers, UserHandle.of(MANAGED_PROFILE_USER_ID)); @@ -2674,6 +2820,18 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); MoreAsserts.assertContentsInAnyOrder(targetUsers, UserHandle.SYSTEM); + + // Changing affiliation ids in one + dpm.setAffiliationIds(admin1, Arrays.asList("some-different-affiliation-id")); + + // Since the managed profile is not affiliated any more, they should not be allowed to talk + // to each other. + targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); + MoreAsserts.assertEmpty(targetUsers); + + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); + MoreAsserts.assertEmpty(targetUsers); } public void testGetBindDeviceAdminTargetUsers_differentPackage() throws Exception { @@ -2688,8 +2846,16 @@ public class DevicePolicyManagerTest extends DpmTestBase { new ComponentName("another.package", "whatever.class"); addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2); + // Setting affiliation ids + final List<String> userAffiliationIds = Arrays.asList("some-affiliation-id"); + dpm.setAffiliationIds(admin1, userAffiliationIds); + + mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; + dpm.setAffiliationIds(adminDifferentPackage, userAffiliationIds); + // Calling from device owner admin, we should get zero bind device admin target users as // their packages are different. + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1); MoreAsserts.assertEmpty(targetUsers); @@ -2763,8 +2929,14 @@ public class DevicePolicyManagerTest extends DpmTestBase { } private void assertCheckProvisioningPreCondition(String action, int provisioningCondition) { - assertEquals("checkProvisioningPreCondition(" + action + ") returning unexpected result", - provisioningCondition, dpm.checkProvisioningPreCondition(action)); + assertCheckProvisioningPreCondition(action, admin1.getPackageName(), provisioningCondition); + } + + private void assertCheckProvisioningPreCondition( + String action, String packageName, int provisioningCondition) { + assertEquals("checkProvisioningPreCondition(" + + action + ", " + packageName + ") returning unexpected result", + provisioningCondition, dpm.checkProvisioningPreCondition(action, packageName)); } /** diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java index db27f7230db9..8a1197618acd 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java @@ -82,6 +82,10 @@ public abstract class DpmTestBase extends AndroidTestCase { eq(packageName), eq(0), eq(userId)); + + doReturn(ai.uid).when(mMockContext.packageManager).getPackageUidAsUser( + eq(packageName), + eq(userId)); } protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid) diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 99af9e8ef8e2..6bc4c19f6c89 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -46,6 +46,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ILauncherApps; @@ -70,10 +71,12 @@ import android.os.Handler; import android.os.Looper; import android.os.PersistableBundle; import android.os.Process; +import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.test.InstrumentationTestCase; import android.test.mock.MockContext; +import android.util.ArrayMap; import android.util.Log; import android.util.Pair; @@ -123,6 +126,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected static final String[] EMPTY_STRINGS = new String[0]; // Just for readability. protected static final String MAIN_ACTIVITY_CLASS = "MainActivity"; + protected static final String PIN_CONFIRM_ACTIVITY_CLASS = "PinConfirmActivity"; // public for mockito public class BaseContext extends MockContext { @@ -161,6 +165,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { public void unregisterReceiver(BroadcastReceiver receiver) { // ignore. } + + @Override + public void startActivityAsUser(Intent intent, UserHandle user) { + // ignore, use spy to intercept it. + } } /** Context used in the client side */ @@ -201,6 +210,10 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { public XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) { return BaseShortcutManagerTest.this.injectXmlMetaData(activityInfo, key); } + + public void sendIntentSender(IntentSender intent) { + // Placeholder for spying. + } } /** ShortcutService with injection override methods. */ @@ -304,6 +317,15 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } @Override + ComponentName getDefaultLauncher(@UserIdInt int userId) { + final ComponentName activity = mDefaultLauncher.get(userId); + if (activity != null) { + return activity; + } + return super.getDefaultLauncher(userId); + } + + @Override PackageInfo injectPackageInfoWithUninstalled(String packageName, @UserIdInt int userId, boolean getSignatures) { return getInjectedPackageInfo(packageName, userId, getSignatures); @@ -375,6 +397,12 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } @Override + ComponentName injectGetPinConfirmationActivity(@NonNull String launcherPackageName, + int launcherUserId) { + return mPinConfirmActivityFetcher.apply(launcherPackageName, launcherUserId); + } + + @Override boolean injectIsActivityEnabledAndExported(ComponentName activity, @UserIdInt int userId) { assertNotNull(activity); return mEnabledActivityChecker.test(activity, userId); @@ -413,6 +441,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } @Override + void injectSendIntentSender(IntentSender intent) { + mContext.sendIntentSender(intent); + } + + @Override void wtf(String message, Throwable th) { // During tests, WTF is fatal. fail(message + " exception: " + th + "\n" + Log.getStackTraceString(th)); @@ -583,7 +616,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected static final UserInfo USER_INFO_0 = withProfileGroupId( new UserInfo(USER_0, "user0", - UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY | UserInfo.FLAG_INITIALIZED), 10); + UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY | UserInfo.FLAG_INITIALIZED), 0); protected static final UserInfo USER_INFO_10 = new UserInfo(USER_10, "user10", UserInfo.FLAG_INITIALIZED); @@ -593,19 +626,24 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected static final UserInfo USER_INFO_P0 = withProfileGroupId( new UserInfo(USER_P0, "userP0", - UserInfo.FLAG_MANAGED_PROFILE), 10); + UserInfo.FLAG_MANAGED_PROFILE), 0); protected BiPredicate<String, Integer> mDefaultLauncherChecker = (callingPackage, userId) -> LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage) || LAUNCHER_3.equals(callingPackage) || LAUNCHER_4.equals(callingPackage); + private final Map<Integer, ComponentName> mDefaultLauncher = new ArrayMap<>(); + protected BiPredicate<ComponentName, Integer> mMainActivityChecker = (activity, userId) -> true; protected BiFunction<String, Integer, ComponentName> mMainActivityFetcher = (packageName, userId) -> new ComponentName(packageName, MAIN_ACTIVITY_CLASS); + protected BiFunction<String, Integer, ComponentName> mPinConfirmActivityFetcher = + (packageName, userId) -> new ComponentName(packageName, PIN_CONFIRM_ACTIVITY_CLASS); + protected BiPredicate<ComponentName, Integer> mEnabledActivityChecker = (activity, userId) -> true; // all activities are enabled. @@ -722,6 +760,19 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { return b(mRunningUsers.get(userId)) && b(mUnlockedUsers.get(userId)); })); + when(mMockUserManager.getProfileParent(anyInt())) + .thenAnswer(new AnswerWithSystemCheck<>(inv -> { + final int userId = (Integer) inv.getArguments()[0]; + final UserInfo ui = mUserInfos.get(userId); + assertNotNull(ui); + if (ui.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) { + return null; + } + final UserInfo parent = mUserInfos.get(ui.profileGroupId); + assertNotNull(parent); + return parent; + })); + when(mMockActivityManagerInternal.getUidProcessState(anyInt())).thenReturn( ActivityManager.PROCESS_STATE_CACHED_EMPTY); @@ -1098,10 +1149,31 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { return mInjectedClientPackage; } + /** + * This controls {@link ShortcutService#hasShortcutHostPermission(String, int)}, but + * not {@link ShortcutService#getDefaultLauncher(int)}. To control the later, use + * {@link #setDefaultLauncher(int, ComponentName)}. + */ protected void setDefaultLauncherChecker(BiPredicate<String, Integer> p) { mDefaultLauncherChecker = p; } + /** + * Set the default launcher. This will update {@link #mDefaultLauncherChecker} set by + * {@link #setDefaultLauncherChecker} too. + */ + protected void setDefaultLauncher(int userId, ComponentName launcherActivity) { + mDefaultLauncher.put(userId, launcherActivity); + + final BiPredicate<String, Integer> oldChecker = mDefaultLauncherChecker; + mDefaultLauncherChecker = (checkPackageName, checkUserId) -> { + if ((checkUserId == userId) && (launcherActivity != null)) { + return launcherActivity.getPackageName().equals(checkPackageName); + } + return oldChecker.test(checkPackageName, checkUserId); + }; + } + protected void runWithCaller(String packageName, int userId, Runnable r) { final String previousPackage = mInjectedClientPackage; final int previousUserId = UserHandle.getUserId(mInjectedCallingUid); @@ -1218,6 +1290,13 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } /** + * Make a shortcut with an ID only. + */ + protected ShortcutInfo makeShortcutIdOnly(String id) { + return new ShortcutInfo.Builder(mClientContext, id).build(); + } + + /** * Make a shortcut with an ID. */ protected ShortcutInfo makeShortcut(String id) { @@ -1226,12 +1305,19 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); } + @Deprecated // Title was renamed to short label. protected ShortcutInfo makeShortcutWithTitle(String id, String title) { return makeShortcut( id, title, /* activity =*/ null, /* icon =*/ null, makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); } + protected ShortcutInfo makeShortcutWithShortLabel(String id, String shortLabel) { + return makeShortcut( + id, shortLabel, /* activity =*/ null, /* icon =*/ null, + makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); + } + /** * Make a shortcut with an ID and timestamp. */ @@ -1624,6 +1710,13 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { return getLauncherShortcuts(launcher, userId, ShortcutQuery.FLAG_GET_PINNED); } + protected List<ShortcutInfo> getShortcutAsLauncher(int targetUserId) { + final ShortcutQuery q = new ShortcutQuery(); + q.setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC | ShortcutQuery.FLAG_MATCH_DYNAMIC + | ShortcutQuery.FLAG_MATCH_PINNED); + return mLauncherApps.getShortcuts(q, UserHandle.of(targetUserId)); + } + protected ShortcutInfo getShortcutInfoAsLauncher(String packageName, String shortcutId, int userId) { final List<ShortcutInfo> infoList = @@ -1897,7 +1990,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { public static List<ShortcutInfo> assertAllHaveIcon( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { - assertTrue("ID " + s.getId() + " has no icon ", s.hasIconFile() || s.hasIconResource()); + assertTrue("ID " + s.getId() + " has no icon ", + s.hasIconFile() || s.hasIconResource() || s.getIcon() != null); } return actualShortcuts; } @@ -1959,4 +2053,31 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { return ri(PACKAGE_FALLBACK_LAUNCHER, PACKAGE_FALLBACK_LAUNCHER_NAME, true, PACKAGE_FALLBACK_LAUNCHER_PRIORITY); } + + protected void makeCallerForeground() { + try { + mService.mUidObserver.onUidStateChanged( + mInjectedCallingUid, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + protected void makeCallerBackground() { + try { + mService.mUidObserver.onUidStateChanged( + mInjectedCallingUid, ActivityManager.PROCESS_STATE_TOP_SLEEPING); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + protected void publishManifestShortcutsAsCaller(int resId) { + addManifestShortcutResource( + new ComponentName(getCallingPackage(), ShortcutActivity.class.getName()), + resId); + updatePackageVersion(getCallingPackage(), 1); + mService.mPackageMonitor.onReceive(getTestContext(), + genPackageAddIntent(getCallingPackage(), getCallingUserId())); + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java index ba4dbc1d258e..3684ca0228fd 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java @@ -31,7 +31,8 @@ import android.test.suitebuilder.annotation.SmallTest; import java.util.List; /** - * Tests for {@link ShortcutService#hasShortcutHostPermissionInner}. + * Tests for {@link ShortcutService#hasShortcutHostPermissionInner}, which includes + * {@link ShortcutService#getDefaultLauncher}. */ @SmallTest public class ShortcutManagerTest6 extends BaseShortcutManagerTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java new file mode 100644 index 000000000000..fbf0ed2938eb --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java @@ -0,0 +1,1228 @@ +/* + * 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.server.pm; + +import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException; +import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertForLauncherCallbackNoThrow; +import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith; +import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.annotation.Nullable; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.LauncherApps; +import android.content.pm.LauncherApps.PinItemRequest; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.drawable.Icon; +import android.os.UserHandle; +import android.test.MoreAsserts; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Pair; + +import com.android.frameworks.servicestests.R; + +import org.mockito.ArgumentCaptor; + +/** + * Tests for {@link ShortcutManager#requestPinShortcut} and relevant APIs. + * + m FrameworksServicesTests && + adb install \ + -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk && + adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest8 \ + -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner + + * TODO for CTS + * - Foreground check. + * - Reading icons from requested shortcuts. + * - Invalid pre-approved token. + */ +@SmallTest +public class ShortcutManagerTest8 extends BaseShortcutManagerTest { + private ShortcutRequestPinProcessor mProcessor; + + @Override + protected void initService() { + super.initService(); + mProcessor = mService.getShortcutRequestPinProcessorForTest(); + } + + @Override + protected void setCaller(String packageName, int userId) { + super.setCaller(packageName, userId); + + // Note during this test, assume all callers are in the foreground by default. + makeCallerForeground(); + } + + public void testGetParentOrSelfUserId() { + assertEquals(USER_0, mService.getParentOrSelfUserId(USER_0)); + assertEquals(USER_10, mService.getParentOrSelfUserId(USER_10)); + assertEquals(USER_11, mService.getParentOrSelfUserId(USER_11)); + assertEquals(USER_0, mService.getParentOrSelfUserId(USER_P0)); + } + + public void testIsRequestPinShortcutSupported() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + setDefaultLauncher(USER_10, mMainActivityFetcher.apply(LAUNCHER_2, USER_10)); + + Pair<ComponentName, Integer> actual; + // User 0 + actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_0); + + assertEquals(LAUNCHER_1, actual.first.getPackageName()); + assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName()); + assertEquals(USER_0, (int) actual.second); + + // User 10 + actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_10); + + assertEquals(LAUNCHER_2, actual.first.getPackageName()); + assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName()); + assertEquals(USER_10, (int) actual.second); + + // User P0 -> managed profile, return user-0's launcher. + actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_P0); + + assertEquals(LAUNCHER_1, actual.first.getPackageName()); + assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName()); + assertEquals(USER_0, (int) actual.second); + + // Check from the public API. + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertTrue(mManager.isRequestPinShortcutSupported()); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertTrue(mManager.isRequestPinShortcutSupported()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + assertTrue(mManager.isRequestPinShortcutSupported()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertTrue(mManager.isRequestPinShortcutSupported()); + }); + + // Now, USER_0's launcher no longer has a confirm activity. + mPinConfirmActivityFetcher = (packageName, userId) -> + !LAUNCHER_2.equals(packageName) + ? null : new ComponentName(packageName, PIN_CONFIRM_ACTIVITY_CLASS); + + // User 10 -- still has confirm activity. + actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_10); + + assertEquals(LAUNCHER_2, actual.first.getPackageName()); + assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName()); + assertEquals(USER_10, (int) actual.second); + + // But user-0 and user p0 no longer has a confirmation activity. + assertNull(mProcessor.getRequestPinShortcutConfirmationActivity(USER_0)); + assertNull(mProcessor.getRequestPinShortcutConfirmationActivity(USER_P0)); + + // Check from the public API. + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertFalse(mManager.isRequestPinShortcutSupported()); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertFalse(mManager.isRequestPinShortcutSupported()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + assertTrue(mManager.isRequestPinShortcutSupported()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertFalse(mManager.isRequestPinShortcutSupported()); + }); + } + + public void testRequestPinShortcut_notSupported() { + // User-0's launcher has no confirmation activity. + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + mPinConfirmActivityFetcher = (packageName, userId) -> + !LAUNCHER_2.equals(packageName) + ? null : new ComponentName(packageName, PIN_CONFIRM_ACTIVITY_CLASS); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + ShortcutInfo s1 = makeShortcut("s1"); + + assertFalse(mManager.requestPinShortcut(s1, + /*PendingIntent=*/ null)); + + verify(mServiceContext, times(0)) + .startActivityAsUser(any(Intent.class), any(UserHandle.class)); + verify(mServiceContext, times(0)) + .sendIntentSender(any(IntentSender.class)); + }); + + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + ShortcutInfo s1 = makeShortcut("s1"); + + assertFalse(mManager.requestPinShortcut(s1, + /*PendingIntent=*/ null)); + + verify(mServiceContext, times(0)) + .startActivityAsUser(any(Intent.class), any(UserHandle.class)); + verify(mServiceContext, times(0)) + .sendIntentSender(any(IntentSender.class)); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + ShortcutInfo s1 = makeShortcut("s1"); + + assertFalse(mManager.requestPinShortcut(s1, + /*PendingIntent=*/ null)); + + verify(mServiceContext, times(0)) + .startActivityAsUser(any(Intent.class), any(UserHandle.class)); + verify(mServiceContext, times(0)) + .sendIntentSender(any(IntentSender.class)); + }); + } + + private void assertPinItemRequestIntent(Intent actualIntent, String expectedPackage) { + assertEquals(LauncherApps.ACTION_CONFIRM_PIN_ITEM, actualIntent.getAction()); + assertEquals(expectedPackage, actualIntent.getComponent().getPackageName()); + assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, + actualIntent.getComponent().getClassName()); + assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK, + actualIntent.getFlags()); + } + + public void testNotForeground() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + makeCallerBackground(); + + assertExpectException(IllegalStateException.class, "foreground activity", () -> { + assertTrue(mManager.requestPinShortcut(makeShortcut("s1"), + /* resultIntent= */ null)); + }); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + verify(mServiceContext, times(0)).startActivityAsUser( + any(Intent.class), any(UserHandle.class)); + }); + } + + private void assertPinItemRequest(PinItemRequest actualRequest) { + assertNotNull(actualRequest); + + assertEquals(PinItemRequest.REQUEST_TYPE_SHORTCUT, actualRequest.getRequestType()); + } + + /** + * Basic flow: + * - Launcher supports the feature. + * - Shortcut doesn't pre-exist. + */ + private void checkRequestPinShortcut(@Nullable PendingIntent resultIntent) { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + setDefaultLauncher(USER_10, mMainActivityFetcher.apply(LAUNCHER_2, USER_10)); + + final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + ShortcutInfo s1 = makeShortcutWithIcon("s1", res32x32); + + assertTrue(mManager.requestPinShortcut(s1, + resultIntent == null ? null : resultIntent.getIntentSender())); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + // Shortcut shouldn't be registered yet. + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("s1") + .areAllOrphan() + .areAllWithNoIntent(); + + assertAllHaveIcon(list(request.getShortcutInfo())); + + // Accept the request. + assertForLauncherCallbackNoThrow(mLauncherApps, + () -> assertTrue(request.accept())) + .assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_P0) + .haveIds("s1"); + }); + + // This method is always called, even with PI == null. + if (resultIntent == null) { + verify(mServiceContext, times(1)).sendIntentSender(eq(null)); + } else { + verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class)); + } + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllNotDynamic() + .areAllEnabled() + .areAllPinned() + .areAllWithIntent(); + }); + } + + public void testRequestPinShortcut() { + checkRequestPinShortcut(/* resultIntent=*/ null); + } + + public void testRequestPinShortcut_withCallback() { + final PendingIntent resultIntent = + PendingIntent.getActivity(getTestContext(), 0, new Intent(), 0); + + checkRequestPinShortcut(resultIntent); + } + + public void testRequestPinShortcut_dynamicExists() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + // Create dynamic shortcut + ShortcutInfo s1 = makeShortcutWithIcon("s1", res32x32); + assertTrue(mManager.setDynamicShortcuts(list(s1))); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned() + .areAllWithNoIntent(); + + assertAllHaveIcon(list(request.getShortcutInfo())); + + // Accept the request. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDynamic() + .areAllEnabled() + .areAllPinned(); + }); + } + + public void testRequestPinShortcut_manifestExists() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned() + .areAllWithNoIntent(); + + assertAllHaveIcon(list(request.getShortcutInfo())); + + // Accept the request. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllManifest() + .areAllEnabled() + .areAllPinned(); + }); + } + + public void testRequestPinShortcut_dynamicExists_alreadyPinned() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1")))); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDynamic() + .areAllPinned(); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null)); + + // The intent should be sent right away. + verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class)); + }); + } + + public void testRequestPinShortcut_manifestExists_alreadyPinned() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllManifest() + .areAllPinned(); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"), + /* resultIntent=*/ null)); + + // The intent should be sent right away. + verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class)); + }); + } + + public void testRequestPinShortcut_wasDynamic_alreadyPinned() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1")))); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + mManager.removeAllDynamicShortcuts(); + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllNotDynamic() + .areAllEnabled() + .areAllPinned(); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null)); + + // The intent should be sent right away. + verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class)); + }); + } + + public void testRequestPinShortcut_wasDynamic_disabled_alreadyPinned() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1")))); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + mManager.disableShortcuts(list("s1")); + + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllNotDynamic() + .areAllDisabled() + .areAllPinned(); + + assertExpectException(IllegalArgumentException.class, "exists but disabled", () -> { + mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null); + }); + + // Shouldn't be called. + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + }); + } + + public void testRequestPinShortcut_wasManifest_alreadyPinned() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_0); + + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllNotManifest() + .areAllDisabled() + .areAllPinned(); + + assertExpectException(IllegalArgumentException.class, "exists but disabled", () -> { + mManager.requestPinShortcut(makeShortcutIdOnly("ms1"), + /* resultIntent=*/ null); + }); + + // Shouldn't be called. + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + }); + } + + public void testRequestPinShortcut_dynamicExists_alreadyPinnedByAnother() { + // Initially all launchers have the shortcut permission, until we call setDefaultLauncher(). + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1")))); + }); + + runWithCaller(LAUNCHER_2, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0); + }); + + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDynamic() + .areAllPinned(); + + // The shortcut is already pinned, but not by the current launcher, so it'll still + // invoke the whole flow. + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned() // Note it's not pinned by this launcher. + .areAllWithNoIntent(); + + // Accept the request. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDynamic() + .areAllEnabled() + .areAllPinned(); + }); + } + + public void testRequestPinShortcut_manifestExists_alreadyPinnedByAnother() { + // Initially all launchers have the shortcut permission, until we call setDefaultLauncher(). + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + }); + + runWithCaller(LAUNCHER_2, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0); + }); + + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllManifest() + .areAllPinned(); + + // The shortcut is already pinned, but not by the current launcher, so it'll still + // invoke the whole flow. + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned() // Note it's not pinned by this launcher. + .areAllWithNoIntent(); + + // Accept the request. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllManifest() + .areAllEnabled() + .areAllPinned(); + }); + } + + /** + * The launcher already has a pinned shortuct. The new one should be added, not replace + * the existing one. + */ + public void testRequestPinShortcut_launcherAlreadyHasPinned() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), makeShortcut("s2")))); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_P0); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned() + .areAllWithNoIntent(); + + // Accept the request. + assertTrue(request.accept()); + + assertWith(getShortcutAsLauncher(USER_P0)) + .haveIds("s1", "s2") + .areAllDynamic() + .areAllEnabled() + .areAllPinned(); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1", "s2") + .areAllDynamic() + .areAllEnabled() + .areAllPinned(); + }); + } + + /** + * When trying to pin an existing shortcut, the new fields shouldn't override existing fields. + */ + public void testRequestPinShortcut_dynamicExists_titleWontChange() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + // Create dynamic shortcut + ShortcutInfo s1 = makeShortcutWithIcon("s1", res32x32); + assertTrue(mManager.setDynamicShortcuts(list(s1))); + + assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("s1", "xxx"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned() + .areAllWithNoIntent(); + + assertAllHaveIcon(list(request.getShortcutInfo())); + + // Accept the request. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDynamic() + .areAllEnabled() + .areAllPinned() + .forShortcutWithId("s1", (si) -> { + // Still the original title. + assertEquals("Title-s1", si.getShortLabel()); + }); + }); + } + + /** + * When trying to pin an existing shortcut, the new fields shouldn't override existing fields. + */ + public void testRequestPinShortcut_manifestExists_titleWontChange() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + + assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("ms1", "xxx"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned() + .areAllWithNoIntent(); + + assertAllHaveIcon(list(request.getShortcutInfo())); + + // Accept the request. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllManifest() + .areAllEnabled() + .areAllPinned() + .forShortcutWithId("ms1", (si) -> { + // Still the original title. + // Title should be something like: + // "string-com.android.test.1-user:20-res:2131034112/en" + MoreAsserts.assertContainsRegex("^string-", si.getShortLabel().toString()); + }); + }); + } + + /** + * The dynamic shortcut existed, but before accepting(), it's removed. Because the request + * has a partial shortcut, accept() should fail. + */ + public void testRequestPinShortcut_dynamicExists_thenRemoved_error() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + // Create dynamic shortcut + ShortcutInfo s1 = makeShortcut("s1"); + assertTrue(mManager.setDynamicShortcuts(list(s1))); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + mManager.removeAllDynamicShortcuts(); + + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned() + .areAllWithNoIntent(); + + // Accept the request -> should fail. + assertForLauncherCallbackNoThrow(mLauncherApps, + () -> assertFalse(request.accept())) + .assertNoCallbackCalled(); + }); + + // Intent shouldn't be sent. + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + } + + /** + * The dynamic shortcut existed, but before accepting(), it's removed. Because the request + * has all the mandatory fields, we can go ahead and still publish it. + */ + public void testRequestPinShortcut_dynamicExists_thenRemoved_okay() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + // Create dynamic shortcut + ShortcutInfo s1 = makeShortcut("s1"); + assertTrue(mManager.setDynamicShortcuts(list(s1))); + + assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("s1", "new"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + mManager.removeAllDynamicShortcuts(); + + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned() + .areAllWithNoIntent(); + + // Accept the request -> should fail. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllFloating() + .forShortcutWithId("s1", si -> { + assertEquals("new", si.getShortLabel()); + }); + }); + } + + /** + * The manifest shortcut existed, but before accepting(), it's removed. Because the request + * has a partial shortcut, accept() should fail. + */ + public void testRequestPinShortcut_manifestExists_thenRemoved_error() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + publishManifestShortcutsAsCaller(R.xml.shortcut_0); + + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned() + .areAllWithNoIntent(); + + // Accept the request -> should fail. + assertForLauncherCallbackNoThrow(mLauncherApps, + () -> assertFalse(request.accept())) + .assertNoCallbackCalled(); + }); + + // Intent shouldn't be sent. + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + } + + /** + * The manifest shortcut existed, but before accepting(), it's removed. Because the request + * has all the mandatory fields, we can go ahead and still publish it. + */ + public void testRequestPinShortcut_manifestExists_thenRemoved_okay() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + + assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("ms1", "new"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + publishManifestShortcutsAsCaller(R.xml.shortcut_0); + + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned() + .areAllWithNoIntent(); + + + // Accept the request -> should fail. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllMutable() // Note it's no longer immutable. + .areAllFloating() + .forShortcutWithId("ms1", si -> { + assertEquals("new", si.getShortLabel()); + }); + }); + } + + /** + * The dynamic shortcut existed, but before accepting(), it's removed. Because the request + * has a partial shortcut, accept() should fail. + */ + public void testRequestPinShortcut_dynamicExists_thenDisabled_error() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + ShortcutInfo s1 = makeShortcut("s1"); + assertTrue(mManager.setDynamicShortcuts(list(s1))); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + }); + + // Then, pin by another launcher and disable it. + // We have to pin it here so that disable() won't remove it. + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_2, USER_0)); + runWithCaller(LAUNCHER_2, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + mManager.disableShortcuts(list("s1")); + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDisabled(); + }); + + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned() + .areAllWithNoIntent(); + + // Accept the request -> should fail. + assertForLauncherCallbackNoThrow(mLauncherApps, + () -> assertFalse(request.accept())) + .assertNoCallbackCalled(); + + // Note s1 is floating and pinned by another launcher, so it shouldn't be + // visible here. + assertWith(getShortcutAsLauncher(USER_P0)) + .isEmpty(); + }); + + // Intent shouldn't be sent. + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDisabled(); + }); + } + + /** + * The manifest shortcut existed, but before accepting(), it's removed. Because the request + * has a partial shortcut, accept() should fail. + */ + public void testRequestPinShortcut_manifestExists_thenDisabled_error() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + }); + + // Then, pin by another launcher and disable it. + // We have to pin it here so that disable() won't remove it. + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_2, USER_0)); + runWithCaller(LAUNCHER_2, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_0); + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllDisabled(); + }); + + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned() + .areAllWithNoIntent(); + + // Accept the request -> should fail. + assertForLauncherCallbackNoThrow(mLauncherApps, + () -> assertFalse(request.accept())) + .assertNoCallbackCalled(); + + // Note ms1 is floating and pinned by another launcher, so it shouldn't be + // visible here. + assertWith(getShortcutAsLauncher(USER_P0)) + .isEmpty(); + }); + + // Intent shouldn't be sent. + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllDisabled(); + }); + } + + // TODO More tests: + + // Cancel previous pending request and release memory? + + // Check the launcher callback too. + + // Missing fields -- pre and post, both. +} diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 40d8ac0434c8..9b2c94e0d63b 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -241,33 +241,66 @@ public class UserManagerTest extends AndroidTestCase { } } - // Make sure createProfile would fail if we have DISALLOW_ADD_USER. + // Make sure createUser would fail if we have DISALLOW_ADD_USER. @MediumTest - public void testCreateProfileForUser_disallowAddUser() throws Exception { + public void testCreateUser_disallowAddUser() throws Exception { final int primaryUserId = mUserManager.getPrimaryUser().id; final UserHandle primaryUserHandle = new UserHandle(primaryUserId); mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, primaryUserHandle); try { + UserInfo userInfo = createUser("SecondaryUser", /*flags=*/ 0); + assertNull(userInfo); + } finally { + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, + primaryUserHandle); + } + } + + // Make sure createProfile would fail if we have DISALLOW_ADD_MANAGED_PROFILE. + @MediumTest + public void testCreateProfileForUser_disallowAddManagedProfile() throws Exception { + final int primaryUserId = mUserManager.getPrimaryUser().id; + final UserHandle primaryUserHandle = new UserHandle(primaryUserId); + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true, + primaryUserHandle); + try { UserInfo userInfo = createProfileForUser("Managed", UserInfo.FLAG_MANAGED_PROFILE, primaryUserId); assertNull(userInfo); } finally { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false, primaryUserHandle); } } - // Make sure createProfileEvenWhenDisallowedForUser bypass DISALLOW_ADD_USER. + // Make sure createProfileEvenWhenDisallowedForUser bypass DISALLOW_ADD_MANAGED_PROFILE. @MediumTest public void testCreateProfileForUserEvenWhenDisallowed() throws Exception { final int primaryUserId = mUserManager.getPrimaryUser().id; final UserHandle primaryUserHandle = new UserHandle(primaryUserId); - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, primaryUserHandle); + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true, + primaryUserHandle); try { UserInfo userInfo = createProfileEvenWhenDisallowedForUser("Managed", UserInfo.FLAG_MANAGED_PROFILE, primaryUserId); assertNotNull(userInfo); } finally { + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false, + primaryUserHandle); + } + } + + // createProfile succeeds even if DISALLOW_ADD_USER is set + @MediumTest + public void testCreateProfileForUser_disallowAddUser() throws Exception { + final int primaryUserId = mUserManager.getPrimaryUser().id; + final UserHandle primaryUserHandle = new UserHandle(primaryUserId); + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, primaryUserHandle); + try { + UserInfo userInfo = createProfileForUser("Managed", + UserInfo.FLAG_MANAGED_PROFILE, primaryUserId); + assertNotNull(userInfo); + } finally { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, primaryUserHandle); } diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java index 162a1a90875a..01808cb7462d 100644 --- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java @@ -26,6 +26,7 @@ import android.support.test.runner.AndroidJUnit4; import java.util.ArrayList; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; import static org.junit.Assert.assertEquals; /** @@ -77,10 +78,12 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(sNavBarWindow, windows.get(2)); assertEquals(sImeWindow, windows.get(1)); assertEquals(sImeDialogWindow, windows.get(0)); + + exitingAppWindow.removeImmediately(); } @Test - public void testForAllWindows_WithImeTarget() throws Exception { + public void testForAllWindows_WithAppImeTarget() throws Exception { final WindowState imeAppTarget = createWindow(null, TYPE_BASE_APPLICATION, sDisplayContent, "imeAppTarget"); @@ -121,4 +124,83 @@ public class DisplayContentTests extends WindowTestsBase { sWm.mInputMethodTarget = null; imeAppTarget.removeImmediately(); } + + @Test + public void testForAllWindows_WithStatusBarImeTarget() throws Exception { + + sWm.mInputMethodTarget = sStatusBarWindow; + + final ArrayList<WindowState> windows = new ArrayList(); + + // Test forward traversal. + sDisplayContent.forAllWindows(w -> {windows.add(w);}, false /* traverseTopToBottom */); + + assertEquals(sWallpaperWindow, windows.get(0)); + assertEquals(sChildAppWindowBelow, windows.get(1)); + assertEquals(sAppWindow, windows.get(2)); + assertEquals(sChildAppWindowAbove, windows.get(3)); + assertEquals(sDockedDividerWindow, windows.get(4)); + assertEquals(sStatusBarWindow, windows.get(5)); + assertEquals(sImeWindow, windows.get(6)); + assertEquals(sImeDialogWindow, windows.get(7)); + assertEquals(sNavBarWindow, windows.get(8)); + + // Test backward traversal. + windows.clear(); + sDisplayContent.forAllWindows(w -> {windows.add(w);}, true /* traverseTopToBottom */); + + assertEquals(sWallpaperWindow, windows.get(8)); + assertEquals(sChildAppWindowBelow, windows.get(7)); + assertEquals(sAppWindow, windows.get(6)); + assertEquals(sChildAppWindowAbove, windows.get(5)); + assertEquals(sDockedDividerWindow, windows.get(4)); + assertEquals(sStatusBarWindow, windows.get(3)); + assertEquals(sImeWindow, windows.get(2)); + assertEquals(sImeDialogWindow, windows.get(1)); + assertEquals(sNavBarWindow, windows.get(0)); + + // Clean-up + sWm.mInputMethodTarget = null; + } + + @Test + public void testForAllWindows_WithInBetweenWindowToken() throws Exception { + // This window is set-up to be z-ordered between some windows that go in the same token like + // the nav bar and status bar. + final WindowState voiceInteractionWindow = createWindow(null, TYPE_VOICE_INTERACTION, + sDisplayContent, "voiceInteractionWindow"); + + final ArrayList<WindowState> windows = new ArrayList(); + + // Test forward traversal. + sDisplayContent.forAllWindows(w -> {windows.add(w);}, false /* traverseTopToBottom */); + + assertEquals(sWallpaperWindow, windows.get(0)); + assertEquals(sChildAppWindowBelow, windows.get(1)); + assertEquals(sAppWindow, windows.get(2)); + assertEquals(sChildAppWindowAbove, windows.get(3)); + assertEquals(sDockedDividerWindow, windows.get(4)); + assertEquals(voiceInteractionWindow, windows.get(5)); + assertEquals(sStatusBarWindow, windows.get(6)); + assertEquals(sNavBarWindow, windows.get(7)); + assertEquals(sImeWindow, windows.get(8)); + assertEquals(sImeDialogWindow, windows.get(9)); + + // Test backward traversal. + windows.clear(); + sDisplayContent.forAllWindows(w -> {windows.add(w);}, true /* traverseTopToBottom */); + + assertEquals(sWallpaperWindow, windows.get(9)); + assertEquals(sChildAppWindowBelow, windows.get(8)); + assertEquals(sAppWindow, windows.get(7)); + assertEquals(sChildAppWindowAbove, windows.get(6)); + assertEquals(sDockedDividerWindow, windows.get(5)); + assertEquals(voiceInteractionWindow, windows.get(4)); + assertEquals(sStatusBarWindow, windows.get(3)); + assertEquals(sNavBarWindow, windows.get(2)); + assertEquals(sImeWindow, windows.get(1)); + assertEquals(sImeDialogWindow, windows.get(0)); + + voiceInteractionWindow.removeImmediately(); + } } diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java index b10c27342a29..ef6ee32c805b 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java @@ -76,7 +76,7 @@ public class WindowFrameTests { final Rect mInsetBounds = new Rect(); boolean mFullscreenForTest = true; TaskWithBounds(Rect bounds) { - super(0, mStubStack, 0, sWm, null, null, false); + super(0, mStubStack, 0, sWm, null, null, false, 0, false); mBounds = bounds; } @Override diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java index ab6968b6c149..41bf646a6a1d 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java @@ -83,7 +83,7 @@ public class WindowTestsBase { createWindow(null, TYPE_INPUT_METHOD_DIALOG, sDisplayContent, "sImeDialogWindow"); sStatusBarWindow = createWindow(null, TYPE_STATUS_BAR, sDisplayContent, "sStatusBarWindow"); sNavBarWindow = - createWindow(null, TYPE_NAVIGATION_BAR, sStatusBarWindow.mToken, "sNavBarWindow"); + createWindow(null, TYPE_NAVIGATION_BAR, sDisplayContent, "sNavBarWindow"); sDockedDividerWindow = createWindow(null, TYPE_DOCK_DIVIDER, sDisplayContent, "sDockedDividerWindow"); sAppWindow = createWindow(null, TYPE_BASE_APPLICATION, sDisplayContent, "sAppWindow"); @@ -106,10 +106,10 @@ public class WindowTestsBase { final int stackId = sNextStackId++; dc.addStackToDisplay(stackId, true); final TaskStack stack = sWm.mStackIdToStack.get(stackId); - final Task task = new Task(sNextTaskId++, stack, 0, sWm, null, EMPTY, false); + final Task task = new Task(sNextTaskId++, stack, 0, sWm, null, EMPTY, false, 0, false); stack.addTask(task, true); final TestAppWindowToken token = new TestAppWindowToken(dc); - task.addAppToken(0, token, 0, false); + task.addChild(token, 0); return token; } diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java index 6e74deb02e6c..a664f21bc0dc 100644 --- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java +++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java @@ -729,6 +729,10 @@ public class ShortcutManagerTestUtils { return new ShortcutListAsserter(list); } + public static ShortcutListAsserter assertWith(ShortcutInfo... list) { + return assertWith(list(list)); + } + /** * New style assertion that allows chained calls. */ @@ -886,6 +890,30 @@ public class ShortcutManagerTestUtils { return this; } + public ShortcutListAsserter areAllFloating() { + forAllShortcuts(s -> assertTrue("id=" + s.getId(), + s.isPinned() && !s.isDeclaredInManifest() && !s.isDynamic())); + return this; + } + + public ShortcutListAsserter areAllNotFloating() { + forAllShortcuts(s -> assertTrue("id=" + s.getId(), + !(s.isPinned() && !s.isDeclaredInManifest() && !s.isDynamic()))); + return this; + } + + public ShortcutListAsserter areAllOrphan() { + forAllShortcuts(s -> assertTrue("id=" + s.getId(), + !s.isPinned() && !s.isDeclaredInManifest() && !s.isDynamic())); + return this; + } + + public ShortcutListAsserter areAllNotOrphan() { + forAllShortcuts(s -> assertTrue("id=" + s.getId(), + s.isPinned() || s.isDeclaredInManifest() || s.isDynamic())); + return this; + } + public ShortcutListAsserter areAllWithKeyFieldsOnly() { forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.hasKeyFieldsOnly())); return this; @@ -906,6 +934,16 @@ public class ShortcutManagerTestUtils { return this; } + public ShortcutListAsserter areAllWithIntent() { + forAllShortcuts(s -> assertNotNull("id=" + s.getId(), s.getIntent())); + return this; + } + + public ShortcutListAsserter areAllWithNoIntent() { + forAllShortcuts(s -> assertNull("id=" + s.getId(), s.getIntent())); + return this; + } + public ShortcutListAsserter forAllShortcuts(Consumer<ShortcutInfo> sa) { boolean found = false; for (int i = 0; i < mList.size(); i++) { @@ -1064,6 +1102,16 @@ public class ShortcutManagerTestUtils { return asserter; } + public static LauncherCallbackAsserter assertForLauncherCallbackNoThrow( + LauncherApps launcherApps, Runnable body) { + try { + return assertForLauncherCallback(launcherApps, body); + } catch (InterruptedException e) { + fail("Caught InterruptedException"); + return null; // Never happens. + } + } + public static void retryUntil(BooleanSupplier checker, String message) { retryUntil(checker, message, 30); } diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index f541f70d4334..cb32d1fb97c1 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -121,6 +121,23 @@ class IntervalStats { endTime = timeStamp; } + void updateChooserCounts(String packageName, String category, String action) { + UsageStats usageStats = getOrCreateUsageStats(packageName); + if (usageStats.mChooserCounts == null) { + usageStats.mChooserCounts = new ArrayMap<>(); + } + ArrayMap<String, Integer> chooserCounts; + final int idx = usageStats.mChooserCounts.indexOfKey(action); + if (idx < 0) { + chooserCounts = new ArrayMap<>(); + usageStats.mChooserCounts.put(action, chooserCounts); + } else { + chooserCounts = usageStats.mChooserCounts.valueAt(idx); + } + int currentCount = chooserCounts.getOrDefault(category, 0); + chooserCounts.put(category, currentCount + 1); + } + void updateConfigurationStats(Configuration config, long timeStamp) { if (activeConfiguration != null) { ConfigurationStats activeStats = configurations.get(activeConfiguration); diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index 849262e47045..1b28db72b9b3 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -17,6 +17,7 @@ package com.android.server.usage; import android.app.usage.TimeSparseArray; +import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; import android.os.Build; import android.util.AtomicFile; @@ -502,6 +503,12 @@ class UsageStatsDatabase { pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_DAILY], mCal.getTimeInMillis()); + mCal.setTimeInMillis(currentTimeMillis); + mCal.addDays(-14); + for (int i = 0; i < mIntervalDirs.length; ++i) { + pruneChooserCountsOlderThan(mIntervalDirs[i], mCal.getTimeInMillis()); + } + // We must re-index our file list or we will be trying to read // deleted files. indexFilesLocked(); @@ -531,6 +538,43 @@ class UsageStatsDatabase { } } + private static void pruneChooserCountsOlderThan(File dir, long expiryTime) { + File[] files = dir.listFiles(); + if (files != null) { + for (File f : files) { + String path = f.getPath(); + if (path.endsWith(BAK_SUFFIX)) { + f = new File(path.substring(0, path.length() - BAK_SUFFIX.length())); + } + + long beginTime; + try { + beginTime = UsageStatsXml.parseBeginTime(f); + } catch (IOException e) { + beginTime = 0; + } + + if (beginTime < expiryTime) { + try { + final AtomicFile af = new AtomicFile(f); + final IntervalStats stats = new IntervalStats(); + UsageStatsXml.read(af, stats); + final int pkgCount = stats.packageStats.size(); + for (int i = 0; i < pkgCount; i++) { + UsageStats pkgStats = stats.packageStats.valueAt(i); + if (pkgStats.mChooserCounts != null) { + pkgStats.mChooserCounts.clear(); + } + } + UsageStatsXml.write(af, stats); + } catch (IOException e) { + Slog.e(TAG, "Failed to delete chooser counts from usage stats file", e); + } + } + } + } + } + /** * Update the stats in the database. They may not be written to disk immediately. */ diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 515370fa00a0..4bfc3df73568 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -1379,6 +1379,31 @@ public class UsageStatsService extends SystemService implements } UsageStatsService.this.dump(args, pw); } + + @Override + public void reportChooserSelection(String packageName, int userId, String contentType, + String[] annotations, String action) { + if (packageName == null) { + Slog.w(TAG, "Event report user selecting a null package"); + return; + } + + UsageEvents.Event event = new UsageEvents.Event(); + event.mPackage = packageName; + + // This will later be converted to system time. + event.mTimeStamp = SystemClock.elapsedRealtime(); + + event.mEventType = Event.CHOOSER_ACTION; + + event.mAction = action; + + event.mContentType = contentType; + + event.mContentAnnotations = annotations; + + mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); + } } /** diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java index 03cee9c26da9..96f3305e2538 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java @@ -26,6 +26,7 @@ import android.app.usage.TimeSparseArray; import android.app.usage.UsageEvents; import android.app.usage.UsageStats; import android.content.res.Configuration; +import android.util.ArrayMap; import java.io.IOException; import java.net.ProtocolException; @@ -37,6 +38,11 @@ final class UsageStatsXmlV1 { private static final String PACKAGES_TAG = "packages"; private static final String PACKAGE_TAG = "package"; + private static final String CHOOSER_COUNT_TAG = "chosen_action"; + private static final String CATEGORY_TAG = "category"; + private static final String NAME = "name"; + private static final String COUNT = "count"; + private static final String CONFIGURATIONS_TAG = "configurations"; private static final String CONFIG_TAG = "config"; @@ -59,7 +65,7 @@ final class UsageStatsXmlV1 { private static final String TIME_ATTR = "time"; private static void loadUsageStats(XmlPullParser parser, IntervalStats statsOut) - throws IOException { + throws XmlPullParserException, IOException { final String pkg = parser.getAttributeValue(null, PACKAGE_ATTR); if (pkg == null) { throw new ProtocolException("no " + PACKAGE_ATTR + " attribute present"); @@ -72,6 +78,51 @@ final class UsageStatsXmlV1 { parser, LAST_TIME_ACTIVE_ATTR); stats.mTotalTimeInForeground = XmlUtils.readLongAttribute(parser, TOTAL_TIME_ACTIVE_ATTR); stats.mLastEvent = XmlUtils.readIntAttribute(parser, LAST_EVENT_ATTR); + int eventCode; + while ((eventCode = parser.next()) != XmlPullParser.END_DOCUMENT) { + final String tag = parser.getName(); + if (eventCode == XmlPullParser.END_TAG && tag.equals(PACKAGE_TAG)) { + break; + } + if (eventCode != XmlPullParser.START_TAG) { + continue; + } + if (tag.equals(CHOOSER_COUNT_TAG)) { + String action = XmlUtils.readStringAttribute(parser, NAME); + loadChooserCounts(parser, stats, action); + } + } + } + + private static void loadChooserCounts( + XmlPullParser parser, UsageStats usageStats, String action) + throws XmlPullParserException, IOException { + if (action == null) { + return; + } + if (usageStats.mChooserCounts == null) { + usageStats.mChooserCounts = new ArrayMap<>(); + } + if (!usageStats.mChooserCounts.containsKey(action)) { + ArrayMap<String, Integer> counts = new ArrayMap<>(); + usageStats.mChooserCounts.put(action, counts); + } + + int eventCode; + while ((eventCode = parser.next()) != XmlPullParser.END_DOCUMENT) { + final String tag = parser.getName(); + if (eventCode == XmlPullParser.END_TAG && tag.equals(CHOOSER_COUNT_TAG)) { + break; + } + if (eventCode != XmlPullParser.START_TAG) { + continue; + } + if (tag.equals(CATEGORY_TAG)) { + String category = XmlUtils.readStringAttribute(parser, NAME); + int count = XmlUtils.readIntAttribute(parser, COUNT); + usageStats.mChooserCounts.get(action).put(category, count); + } + } } private static void loadConfigStats(XmlPullParser parser, IntervalStats statsOut) @@ -135,10 +186,45 @@ final class UsageStatsXmlV1 { XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName); XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground); XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.mLastEvent); - + writeChooserCounts(xml, usageStats); xml.endTag(null, PACKAGE_TAG); } + private static void writeChooserCounts(XmlSerializer xml, final UsageStats usageStats) + throws IOException { + if (usageStats == null || usageStats.mChooserCounts == null || + usageStats.mChooserCounts.keySet().isEmpty()) { + return; + } + final int chooserCountSize = usageStats.mChooserCounts.size(); + for (int i = 0; i < chooserCountSize; i++) { + final String action = usageStats.mChooserCounts.keyAt(i); + final ArrayMap<String, Integer> counts = usageStats.mChooserCounts.valueAt(i); + if (action == null || counts == null || counts.isEmpty()) { + continue; + } + xml.startTag(null, CHOOSER_COUNT_TAG); + XmlUtils.writeStringAttribute(xml, NAME, action); + writeCountsForAction(xml, counts); + xml.endTag(null, CHOOSER_COUNT_TAG); + } + } + + private static void writeCountsForAction(XmlSerializer xml, ArrayMap<String, Integer> counts) + throws IOException { + final int countsSize = counts.size(); + for (int i = 0; i < countsSize; i++) { + String key = counts.keyAt(i); + int count = counts.valueAt(i); + if (count > 0) { + xml.startTag(null, CATEGORY_TAG); + XmlUtils.writeStringAttribute(xml, NAME, key); + XmlUtils.writeIntAttribute(xml, COUNT, count); + xml.endTag(null, CATEGORY_TAG); + } + } + } + private static void writeConfigStats(XmlSerializer xml, final IntervalStats stats, final ConfigurationStats configStats, boolean isActive) throws IOException { xml.startTag(null, CONFIG_TAG); diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 59e4c8021bdc..ba770ef5053c 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -183,6 +183,15 @@ class UserUsageStatsService { for (IntervalStats stats : mCurrentStats) { if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) { stats.updateConfigurationStats(newFullConfig, event.mTimeStamp); + } else if (event.mEventType == UsageEvents.Event.CHOOSER_ACTION) { + stats.updateChooserCounts(event.mPackage, event.mContentType, event.mAction); + String[] annotations = event.mContentAnnotations; + if (annotations != null) { + for (String annotation : annotations) { + // TODO(kanlig): update with confidences of annotations. + stats.updateChooserCounts(event.mPackage, annotation, event.mAction); + } + } } else { stats.update(event.mPackage, event.mTimeStamp, event.mEventType); } @@ -520,6 +529,32 @@ class UserUsageStatsService { } pw.decreaseIndent(); + pw.println(); + pw.increaseIndent(); + pw.println("ChooserCounts"); + pw.increaseIndent(); + for (UsageStats usageStats : pkgStats.values()) { + pw.printPair("package", usageStats.mPackageName); + if (usageStats.mChooserCounts != null) { + final int chooserCountSize = usageStats.mChooserCounts.size(); + for (int i = 0; i < chooserCountSize; i++) { + final String action = usageStats.mChooserCounts.keyAt(i); + final ArrayMap<String, Integer> counts = usageStats.mChooserCounts.valueAt(i); + final int annotationSize = counts.size(); + for (int j = 0; j < annotationSize; j++) { + final String key = counts.keyAt(j); + final int count = counts.valueAt(j); + if (count != 0) { + pw.printPair("ChooserCounts", action + ":" + key + " is " + + Integer.toString(count)); + pw.println(); + } + } + } + } + pw.println(); + } + pw.println("configurations"); pw.increaseIndent(); final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations; @@ -593,6 +628,8 @@ class UserUsageStatsService { return "USER_INTERACTION"; case UsageEvents.Event.SHORTCUT_INVOCATION: return "SHORTCUT_INVOCATION"; + case UsageEvents.Event.CHOOSER_ACTION: + return "CHOOSER_ACTION"; default: return "UNKNOWN"; } diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index b86a85be9f33..db7a31ac33af 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -361,8 +361,6 @@ public class UsbDeviceManager { UsbManager.removeFunction(persisted, UsbManager.USB_FUNCTION_MTP)); } - setEnabledFunctions(null, false, false); - String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim(); updateState(state); @@ -456,13 +454,12 @@ public class UsbDeviceManager { return false; } - private boolean setUsbConfig(String config) { + private void setUsbConfig(String config) { if (DEBUG) Slog.d(TAG, "setUsbConfig(" + config + ")"); // set the new configuration // we always set it due to b/23631400, where adbd was getting killed // and not restarted due to property timeouts on some devices SystemProperties.set(USB_CONFIG_PROPERTY, config); - return waitForState(config); } private void setAdbEnabled(boolean enable) { @@ -557,8 +554,18 @@ public class UsbDeviceManager { // Kick the USB stack to close existing connections. setUsbConfig(UsbManager.USB_FUNCTION_NONE); + if (!waitForState(UsbManager.USB_FUNCTION_NONE)) { + Slog.e(TAG, "Failed to kick USB config"); + return false; + } + // Set the new USB configuration. - if (!setUsbConfig(functions)) { + setUsbConfig(functions); + + // Start up dependent services. + updateUsbStateBroadcastIfNeeded(true); + + if (!waitForState(functions)) { Slog.e(TAG, "Failed to switch USB config to " + functions); return false; } @@ -599,8 +606,12 @@ public class UsbDeviceManager { } else { Slog.e(TAG, "nativeGetAccessoryStrings failed"); } - } else if (!mConnected && !enteringAccessoryMode) { - notifyAccessoryModeExit(); + } else { + if (!enteringAccessoryMode) { + notifyAccessoryModeExit(); + } else if (DEBUG) { + Slog.v(TAG, "Debouncing accessory mode exit"); + } } } @@ -641,7 +652,7 @@ public class UsbDeviceManager { return false; } - private void updateUsbStateBroadcastIfNeeded() { + private void updateUsbStateBroadcastIfNeeded(boolean configChanged) { // send a sticky broadcast containing current USB state Intent intent = new Intent(UsbManager.ACTION_USB_STATE); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING @@ -650,6 +661,7 @@ public class UsbDeviceManager { intent.putExtra(UsbManager.USB_HOST_CONNECTED, mHostConnected); intent.putExtra(UsbManager.USB_CONFIGURED, mConfigured); intent.putExtra(UsbManager.USB_DATA_UNLOCKED, isUsbTransferAllowed() && mUsbDataUnlocked); + intent.putExtra(UsbManager.USB_CONFIG_CHANGED, configChanged); if (mCurrentFunctions != null) { String[] functions = mCurrentFunctions.split(","); @@ -747,7 +759,7 @@ public class UsbDeviceManager { setEnabledFunctions(null, false, false); } if (mBootCompleted) { - updateUsbStateBroadcastIfNeeded(); + updateUsbStateBroadcastIfNeeded(false); updateUsbFunctions(); } break; @@ -759,7 +771,7 @@ public class UsbDeviceManager { args.recycle(); updateUsbNotification(); if (mBootCompleted) { - updateUsbStateBroadcastIfNeeded(); + updateUsbStateBroadcastIfNeeded(false); } break; case MSG_ENABLE_ADB: @@ -775,11 +787,11 @@ public class UsbDeviceManager { case MSG_SYSTEM_READY: updateUsbNotification(); updateAdbNotification(); - updateUsbStateBroadcastIfNeeded(); updateUsbFunctions(); break; case MSG_BOOT_COMPLETED: mBootCompleted = true; + setEnabledFunctions(null, false, false); if (mCurrentAccessory != null) { getCurrentSettings().accessoryAttached(mCurrentAccessory); } @@ -805,6 +817,9 @@ public class UsbDeviceManager { break; } case MSG_ACCESSORY_MODE_ENTER_TIMEOUT: { + if (DEBUG) { + Slog.v(TAG, "Accessory mode enter timeout: " + mConnected); + } if (!mConnected) { notifyAccessoryModeExit(); } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index e6354b7eefa6..8f2a44ccd401 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -165,6 +165,14 @@ public class CarrierConfigManager { public static final String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool"; + /** + * Control whether users receive a simplified network settings UI and improved network + * selection. + * @hide + */ + public static final String + KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool"; + /** Control whether users can reach the SIM lock settings. */ public static final String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool"; @@ -1138,6 +1146,25 @@ public class CarrierConfigManager { public static final String KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT = "network_notification_delay_int"; + /** + * Indicates whether the carrier supports 3gpp call forwarding MMI codes while roaming. If + * false, the user will be notified that call forwarding is not available when the MMI code + * fails. + */ + public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = + "support_3gpp_call_forwarding_while_roaming_bool"; + + /** + * Determine whether user edited tether APN (type dun) has effect + * {@code false} - Default. APN with dun type in telephony database has no effect. + * + * {@code true} - DUN APN added/edited in ApnEditor will be used for tethering data call. + * + * @hide + */ + public static final String KEY_EDITABLE_TETHER_APN_BOOL = + "editable_tether_apn_bool"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -1179,6 +1206,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL, true); sDefaults.putBoolean(KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL, false); sDefaults.putBoolean(KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false); + sDefaults.putBoolean(KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL, false); sDefaults.putBoolean(KEY_HIDE_SIM_LOCK_SETTINGS_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONED_BOOL, false); @@ -1340,6 +1368,8 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_PERSIST_LPP_MODE_BOOL, false); sDefaults.putStringArray(KEY_CARRIER_WIFI_STRING_ARRAY, null); sDefaults.putInt(KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT, -1); + sDefaults.putBoolean(KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true); + sDefaults.putBoolean(KEY_EDITABLE_TETHER_APN_BOOL, false); } /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 918ef5eccbde..77972715e9c8 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -1852,21 +1852,14 @@ public class TelephonyManager { public static final int SIM_STATE_NETWORK_LOCKED = 4; /** SIM card state: Ready */ public static final int SIM_STATE_READY = 5; - /** SIM card state: SIM Card is NOT READY - *@hide - */ + /** SIM card state: SIM Card is NOT READY */ public static final int SIM_STATE_NOT_READY = 6; - /** SIM card state: SIM Card Error, permanently disabled - *@hide - */ + /** SIM card state: SIM Card Error, permanently disabled */ public static final int SIM_STATE_PERM_DISABLED = 7; - /** SIM card state: SIM Card Error, present but faulty - *@hide - */ + /** SIM card state: SIM Card Error, present but faulty */ public static final int SIM_STATE_CARD_IO_ERROR = 8; /** SIM card state: SIM Card restricted, present but not usable due to * carrier restrictions. - *@hide */ public static final int SIM_STATE_CARD_RESTRICTED = 9; @@ -1912,6 +1905,7 @@ public class TelephonyManager { * @see #SIM_STATE_NOT_READY * @see #SIM_STATE_PERM_DISABLED * @see #SIM_STATE_CARD_IO_ERROR + * @see #SIM_STATE_CARD_RESTRICTED */ public int getSimState() { int slotIdx = getDefaultSim(); @@ -1949,8 +1943,8 @@ public class TelephonyManager { * @see #SIM_STATE_NOT_READY * @see #SIM_STATE_PERM_DISABLED * @see #SIM_STATE_CARD_IO_ERROR + * @see #SIM_STATE_CARD_RESTRICTED */ - /** {@hide} */ public int getSimState(int slotIdx) { int simState = SubscriptionManager.getSimStateForSlotIdx(slotIdx); return simState; diff --git a/test-runner/Android.mk b/test-runner/Android.mk index a317994c41ba..0e9a4854e2c9 100644 --- a/test-runner/Android.mk +++ b/test-runner/Android.mk @@ -21,7 +21,6 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_JAVA_LIBRARIES := core-oj core-libart junit framework -LOCAL_STATIC_JAVA_LIBRARIES := junit-runner LOCAL_MODULE:= android.test.runner diff --git a/test-runner/src/junit/MODULE_LICENSE_CPL b/test-runner/src/junit/MODULE_LICENSE_CPL new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/test-runner/src/junit/MODULE_LICENSE_CPL diff --git a/test-runner/src/junit/README.android b/test-runner/src/junit/README.android new file mode 100644 index 000000000000..1384a1fedda2 --- /dev/null +++ b/test-runner/src/junit/README.android @@ -0,0 +1,11 @@ +URL: https://github.com/junit-team/junit4 +License: Common Public License Version 1.0 +License File: cpl-v10.html + +This is JUnit 4.10 source that was previously part of the Android Public API. +Where necessary it has been patched to be compatible (according to Android API +requirements) with JUnit 3.8. + +These are copied here to ensure that the android.test.runner target remains +compatible with the last version of the Android API (25) that contained these +classes even when external/junit is upgraded to a later version. diff --git a/test-runner/src/junit/cpl-v10.html b/test-runner/src/junit/cpl-v10.html new file mode 100644 index 000000000000..36aa208d4a29 --- /dev/null +++ b/test-runner/src/junit/cpl-v10.html @@ -0,0 +1,125 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"> +<HTML> +<HEAD> +<TITLE>Common Public License - v 1.0</TITLE> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +</HEAD> + +<BODY BGCOLOR="#FFFFFF" VLINK="#800000"> + + +<P ALIGN="CENTER"><B>Common Public License - v 1.0</B> +<P><B></B><FONT SIZE="3"></FONT> +<P><FONT SIZE="3"></FONT><FONT SIZE="2">THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.</FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"><B>1. DEFINITIONS</B></FONT> +<P><FONT SIZE="2">"Contribution" means:</FONT> + +<UL><FONT SIZE="2">a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and<BR CLEAR="LEFT"> +b) in the case of each subsequent Contributor:</FONT></UL> + + +<UL><FONT SIZE="2">i) changes to the Program, and</FONT></UL> + + +<UL><FONT SIZE="2">ii) additions to the Program;</FONT></UL> + + +<UL><FONT SIZE="2">where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. </FONT><FONT SIZE="2">A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. </FONT><FONT SIZE="2">Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. </FONT></UL> + +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">"Contributor" means any person or entity that distributes the Program.</FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">"Licensed Patents " mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. </FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2">"Program" means the Contributions distributed in accordance with this Agreement.</FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.</FONT> +<P><FONT SIZE="2"><B></B></FONT> +<P><FONT SIZE="2"><B>2. GRANT OF RIGHTS</B></FONT> + +<UL><FONT SIZE="2"></FONT><FONT SIZE="2">a) </FONT><FONT SIZE="2">Subject to the terms of this Agreement, each Contributor hereby grants</FONT><FONT SIZE="2"> Recipient a non-exclusive, worldwide, royalty-free copyright license to</FONT><FONT SIZE="2" COLOR="#FF0000"> </FONT><FONT SIZE="2">reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.</FONT></UL> + + +<UL><FONT SIZE="2"></FONT></UL> + + +<UL><FONT SIZE="2"></FONT><FONT SIZE="2">b) Subject to the terms of this Agreement, each Contributor hereby grants </FONT><FONT SIZE="2">Recipient a non-exclusive, worldwide,</FONT><FONT SIZE="2" COLOR="#008000"> </FONT><FONT SIZE="2">royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. </FONT></UL> + + +<UL><FONT SIZE="2"></FONT></UL> + + +<UL><FONT SIZE="2">c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program.</FONT></UL> + + +<UL><FONT SIZE="2"></FONT></UL> + + +<UL><FONT SIZE="2">d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. </FONT></UL> + + +<UL><FONT SIZE="2"></FONT></UL> + +<P><FONT SIZE="2"><B>3. REQUIREMENTS</B></FONT> +<P><FONT SIZE="2"><B></B>A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:</FONT> + +<UL><FONT SIZE="2">a) it complies with the terms and conditions of this Agreement; and</FONT></UL> + + +<UL><FONT SIZE="2">b) its license agreement:</FONT></UL> + + +<UL><FONT SIZE="2">i) effectively disclaims</FONT><FONT SIZE="2"> on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; </FONT></UL> + + +<UL><FONT SIZE="2">ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; </FONT></UL> + + +<UL><FONT SIZE="2">iii)</FONT><FONT SIZE="2"> states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and</FONT></UL> + + +<UL><FONT SIZE="2">iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.</FONT><FONT SIZE="2" COLOR="#0000FF"> </FONT><FONT SIZE="2" COLOR="#FF0000"></FONT></UL> + + +<UL><FONT SIZE="2" COLOR="#FF0000"></FONT><FONT SIZE="2"></FONT></UL> + +<P><FONT SIZE="2">When the Program is made available in source code form:</FONT> + +<UL><FONT SIZE="2">a) it must be made available under this Agreement; and </FONT></UL> + + +<UL><FONT SIZE="2">b) a copy of this Agreement must be included with each copy of the Program. </FONT></UL> + +<P><FONT SIZE="2"></FONT><FONT SIZE="2" COLOR="#0000FF"><STRIKE></STRIKE></FONT> +<P><FONT SIZE="2" COLOR="#0000FF"><STRIKE></STRIKE></FONT><FONT SIZE="2">Contributors may not remove or alter any copyright notices contained within the Program. </FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. </FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"><B>4. COMMERCIAL DISTRIBUTION</B></FONT> +<P><FONT SIZE="2">Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense.</FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages.</FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2" COLOR="#0000FF"></FONT> +<P><FONT SIZE="2" COLOR="#0000FF"></FONT><FONT SIZE="2"><B>5. NO WARRANTY</B></FONT> +<P><FONT SIZE="2">EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is</FONT><FONT SIZE="2"> solely responsible for determining the appropriateness of using and distributing </FONT><FONT SIZE="2">the Program</FONT><FONT SIZE="2"> and assumes all risks associated with its exercise of rights under this Agreement</FONT><FONT SIZE="2">, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, </FONT><FONT SIZE="2">programs or equipment, and unavailability or interruption of operations</FONT><FONT SIZE="2">. </FONT><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2"><B>6. DISCLAIMER OF LIABILITY</B></FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2">EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES </FONT><FONT SIZE="2">(INCLUDING WITHOUT LIMITATION LOST PROFITS),</FONT><FONT SIZE="2"> HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.</FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"><B>7. GENERAL</B></FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2">If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.</FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">If Recipient institutes patent litigation against a Contributor with respect to a patent applicable to software (including a cross-claim or counterclaim in a lawsuit), then any patent licenses granted by that Contributor to such Recipient under this Agreement shall terminate as of the date such litigation is filed. In addition, if Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. </FONT><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. </FONT><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2">Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to </FONT><FONT SIZE="2">publish new versions (including revisions) of this Agreement from time to </FONT><FONT SIZE="2">time. No one other than the Agreement Steward has the right to modify this Agreement. IBM is the initial Agreement Steward. IBM may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. </FONT><FONT SIZE="2">Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new </FONT><FONT SIZE="2">version. </FONT><FONT SIZE="2">Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, </FONT><FONT SIZE="2">by implication, estoppel or otherwise</FONT><FONT SIZE="2">.</FONT><FONT SIZE="2"> All rights in the Program not expressly granted under this Agreement are reserved.</FONT> +<P><FONT SIZE="2"></FONT> +<P><FONT SIZE="2">This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.</FONT> +<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT> +<P><FONT SIZE="2"></FONT> + +</BODY> + +</HTML>
\ No newline at end of file diff --git a/test-runner/src/junit/runner/BaseTestRunner.java b/test-runner/src/junit/runner/BaseTestRunner.java new file mode 100644 index 000000000000..e7e043133c95 --- /dev/null +++ b/test-runner/src/junit/runner/BaseTestRunner.java @@ -0,0 +1,340 @@ +package junit.runner; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringReader; +import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.text.NumberFormat; +import java.util.Properties; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestListener; +import junit.framework.TestSuite; + +/** + * Base class for all test runners. + * This class was born live on stage in Sardinia during XP2000. + */ +public abstract class BaseTestRunner implements TestListener { + public static final String SUITE_METHODNAME= "suite"; + + private static Properties fPreferences; + static int fgMaxMessageLength= 500; + static boolean fgFilterStack= true; + boolean fLoading= true; + + /* + * Implementation of TestListener + */ + public synchronized void startTest(Test test) { + testStarted(test.toString()); + } + + protected static void setPreferences(Properties preferences) { + fPreferences= preferences; + } + + protected static Properties getPreferences() { + if (fPreferences == null) { + fPreferences= new Properties(); + fPreferences.put("loading", "true"); + fPreferences.put("filterstack", "true"); + readPreferences(); + } + return fPreferences; + } + + public static void savePreferences() throws IOException { + FileOutputStream fos= new FileOutputStream(getPreferencesFile()); + try { + getPreferences().store(fos, ""); + } finally { + fos.close(); + } + } + + // android-changed remove 'static' qualifier for API compatibility + public void setPreference(String key, String value) { + getPreferences().put(key, value); + } + + public synchronized void endTest(Test test) { + testEnded(test.toString()); + } + + public synchronized void addError(final Test test, final Throwable t) { + testFailed(TestRunListener.STATUS_ERROR, test, t); + } + + public synchronized void addFailure(final Test test, final AssertionFailedError t) { + testFailed(TestRunListener.STATUS_FAILURE, test, t); + } + + // TestRunListener implementation + + public abstract void testStarted(String testName); + + public abstract void testEnded(String testName); + + public abstract void testFailed(int status, Test test, Throwable t); + + /** + * Returns the Test corresponding to the given suite. This is + * a template method, subclasses override runFailed(), clearStatus(). + */ + public Test getTest(String suiteClassName) { + if (suiteClassName.length() <= 0) { + clearStatus(); + return null; + } + Class<?> testClass= null; + try { + testClass= loadSuiteClass(suiteClassName); + } catch (ClassNotFoundException e) { + String clazz= e.getMessage(); + if (clazz == null) + clazz= suiteClassName; + runFailed("Class not found \""+clazz+"\""); + return null; + } catch(Exception e) { + runFailed("Error: "+e.toString()); + return null; + } + Method suiteMethod= null; + try { + suiteMethod= testClass.getMethod(SUITE_METHODNAME, new Class[0]); + } catch(Exception e) { + // try to extract a test suite automatically + clearStatus(); + return new TestSuite(testClass); + } + if (! Modifier.isStatic(suiteMethod.getModifiers())) { + runFailed("Suite() method must be static"); + return null; + } + Test test= null; + try { + test= (Test)suiteMethod.invoke(null, (Object[])new Class[0]); // static method + if (test == null) + return test; + } + catch (InvocationTargetException e) { + runFailed("Failed to invoke suite():" + e.getTargetException().toString()); + return null; + } + catch (IllegalAccessException e) { + runFailed("Failed to invoke suite():" + e.toString()); + return null; + } + + clearStatus(); + return test; + } + + /** + * Returns the formatted string of the elapsed time. + */ + public String elapsedTimeAsString(long runTime) { + return NumberFormat.getInstance().format((double)runTime/1000); + } + + /** + * Processes the command line arguments and + * returns the name of the suite class to run or null + */ + protected String processArguments(String[] args) { + String suiteName= null; + for (int i= 0; i < args.length; i++) { + if (args[i].equals("-noloading")) { + setLoading(false); + } else if (args[i].equals("-nofilterstack")) { + fgFilterStack= false; + } else if (args[i].equals("-c")) { + if (args.length > i+1) + suiteName= extractClassName(args[i+1]); + else + System.out.println("Missing Test class name"); + i++; + } else { + suiteName= args[i]; + } + } + return suiteName; + } + + /** + * Sets the loading behaviour of the test runner + */ + public void setLoading(boolean enable) { + fLoading= enable; + } + /** + * Extract the class name from a String in VA/Java style + */ + public String extractClassName(String className) { + if(className.startsWith("Default package for")) + return className.substring(className.lastIndexOf(".")+1); + return className; + } + + /** + * Truncates a String to the maximum length. + */ + public static String truncate(String s) { + if (fgMaxMessageLength != -1 && s.length() > fgMaxMessageLength) + s= s.substring(0, fgMaxMessageLength)+"..."; + return s; + } + + /** + * Override to define how to handle a failed loading of + * a test suite. + */ + protected abstract void runFailed(String message); + + // BEGIN android-changed - add back getLoader() for API compatibility + /** + * Returns the loader to be used. + * + * @deprecated not present in JUnit4.10 + */ + public TestSuiteLoader getLoader() { + return new StandardTestSuiteLoader(); + } + // END android-changed + + /** + * Returns the loaded Class for a suite name. + */ + protected Class<?> loadSuiteClass(String suiteClassName) throws ClassNotFoundException { + return Class.forName(suiteClassName); + } + + /** + * Clears the status message. + */ + protected void clearStatus() { // Belongs in the GUI TestRunner class + } + + protected boolean useReloadingTestSuiteLoader() { + return getPreference("loading").equals("true") && fLoading; + } + + private static File getPreferencesFile() { + String home= System.getProperty("user.home"); + return new File(home, "junit.properties"); + } + + private static void readPreferences() { + InputStream is= null; + try { + is= new FileInputStream(getPreferencesFile()); + setPreferences(new Properties(getPreferences())); + getPreferences().load(is); + } catch (IOException e) { + try { + if (is != null) + is.close(); + } catch (IOException e1) { + } + } + } + + public static String getPreference(String key) { + return getPreferences().getProperty(key); + } + + public static int getPreference(String key, int dflt) { + String value= getPreference(key); + int intValue= dflt; + if (value == null) + return intValue; + try { + intValue= Integer.parseInt(value); + } catch (NumberFormatException ne) { + } + return intValue; + } + + /** + * Returns a filtered stack trace + */ + public static String getFilteredTrace(Throwable t) { + StringWriter stringWriter= new StringWriter(); + PrintWriter writer= new PrintWriter(stringWriter); + t.printStackTrace(writer); + StringBuffer buffer= stringWriter.getBuffer(); + String trace= buffer.toString(); + return BaseTestRunner.getFilteredTrace(trace); + } + + // BEGIN android-changed - add back this method for API compatibility + /** @deprecated not present in JUnit4.10 */ + public static boolean inVAJava() { + return false; + } + // END android-changed + + /** + * Filters stack frames from internal JUnit classes + */ + public static String getFilteredTrace(String stack) { + if (showStackRaw()) + return stack; + + StringWriter sw= new StringWriter(); + PrintWriter pw= new PrintWriter(sw); + StringReader sr= new StringReader(stack); + // BEGIN android-changed + // Use a sensible default buffer size + BufferedReader br= new BufferedReader(sr, 1000); + // END android-changed + + String line; + try { + while ((line= br.readLine()) != null) { + if (!filterLine(line)) + pw.println(line); + } + } catch (Exception IOException) { + return stack; // return the stack unfiltered + } + return sw.toString(); + } + + protected static boolean showStackRaw() { + return !getPreference("filterstack").equals("true") || fgFilterStack == false; + } + + static boolean filterLine(String line) { + String[] patterns= new String[] { + "junit.framework.TestCase", + "junit.framework.TestResult", + "junit.framework.TestSuite", + "junit.framework.Assert.", // don't filter AssertionFailure + "junit.swingui.TestRunner", + "junit.awtui.TestRunner", + "junit.textui.TestRunner", + "java.lang.reflect.Method.invoke(" + }; + for (int i= 0; i < patterns.length; i++) { + if (line.indexOf(patterns[i]) > 0) + return true; + } + return false; + } + + static { + fgMaxMessageLength= getPreference("maxmessage", fgMaxMessageLength); + } + +} diff --git a/test-runner/src/junit/runner/StandardTestSuiteLoader.java b/test-runner/src/junit/runner/StandardTestSuiteLoader.java new file mode 100644 index 000000000000..808963a5aea0 --- /dev/null +++ b/test-runner/src/junit/runner/StandardTestSuiteLoader.java @@ -0,0 +1,23 @@ +package junit.runner; + +// android-changed - class not present in upstream JUnit 4.10 +// added here to retain BaseTestRunner.getLoader API + +/** + * The standard test suite loader. It can only load the same class once. + * {@hide} + */ +public class StandardTestSuiteLoader implements TestSuiteLoader { + /** + * Uses the system class loader to load the test class + */ + public Class load(String suiteClassName) throws ClassNotFoundException { + return Class.forName(suiteClassName); + } + /** + * Uses the system class loader to load the test class + */ + public Class reload(Class aClass) throws ClassNotFoundException { + return aClass; + } +} diff --git a/test-runner/src/junit/runner/TestRunListener.java b/test-runner/src/junit/runner/TestRunListener.java new file mode 100644 index 000000000000..0e9581989eee --- /dev/null +++ b/test-runner/src/junit/runner/TestRunListener.java @@ -0,0 +1,20 @@ +package junit.runner; +/** + * A listener interface for observing the + * execution of a test run. Unlike TestListener, + * this interface using only primitive objects, + * making it suitable for remote test execution. + * {@hide} - Not needed for 1.0 SDK + */ + public interface TestRunListener { + /* test status constants*/ + public static final int STATUS_ERROR= 1; + public static final int STATUS_FAILURE= 2; + + public void testRunStarted(String testSuiteName, int testCount); + public void testRunEnded(long elapsedTime); + public void testRunStopped(long elapsedTime); + public void testStarted(String testName); + public void testEnded(String testName); + public void testFailed(int status, String testName, String trace); +} diff --git a/test-runner/src/junit/runner/TestSuiteLoader.java b/test-runner/src/junit/runner/TestSuiteLoader.java new file mode 100644 index 000000000000..9cc6d81e125e --- /dev/null +++ b/test-runner/src/junit/runner/TestSuiteLoader.java @@ -0,0 +1,11 @@ +package junit.runner; + +/** + * An interface to define how a test suite should be loaded. + * + */ +// TODO: deprecate +public interface TestSuiteLoader { + abstract public Class load(String suiteClassName) throws ClassNotFoundException; + abstract public Class reload(Class aClass) throws ClassNotFoundException; +} diff --git a/test-runner/src/junit/runner/Version.java b/test-runner/src/junit/runner/Version.java new file mode 100644 index 000000000000..dd88c03372c8 --- /dev/null +++ b/test-runner/src/junit/runner/Version.java @@ -0,0 +1,20 @@ +package junit.runner; + +/** + * This class defines the current version of JUnit + */ +public class Version { + private Version() { + // don't instantiate + } + + public static String id() { + return "4.10"; + } + + // android-changed + /** @hide - not needed for public API */ + public static void main(String[] args) { + System.out.println(id()); + } +} diff --git a/test-runner/src/junit/runner/package-info.java b/test-runner/src/junit/runner/package-info.java new file mode 100644 index 000000000000..b746185f333e --- /dev/null +++ b/test-runner/src/junit/runner/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides JUnit v3.x test runners. + */ +package junit.runner;
\ No newline at end of file diff --git a/test-runner/src/junit/runner/package.html b/test-runner/src/junit/runner/package.html new file mode 100644 index 000000000000..f08fa7013299 --- /dev/null +++ b/test-runner/src/junit/runner/package.html @@ -0,0 +1,5 @@ +<HTML> +<BODY> +Utility classes supporting the junit test framework. +</BODY> +</HTML> diff --git a/test-runner/src/junit/textui/ResultPrinter.java b/test-runner/src/junit/textui/ResultPrinter.java new file mode 100644 index 000000000000..b4914529bf4f --- /dev/null +++ b/test-runner/src/junit/textui/ResultPrinter.java @@ -0,0 +1,144 @@ + +package junit.textui; + +import java.io.PrintStream; +// android-changed +// The following line was removed for compatibility with Android libraries. +// import java.text.NumberFormat; +import java.util.Enumeration; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestFailure; +import junit.framework.TestListener; +import junit.framework.TestResult; +import junit.runner.BaseTestRunner; + +public class ResultPrinter implements TestListener { + PrintStream fWriter; + int fColumn= 0; + + public ResultPrinter(PrintStream writer) { + fWriter= writer; + } + + /* API for use by textui.TestRunner + */ + + synchronized void print(TestResult result, long runTime) { + printHeader(runTime); + printErrors(result); + printFailures(result); + printFooter(result); + } + + void printWaitPrompt() { + getWriter().println(); + getWriter().println("<RETURN> to continue"); + } + + /* Internal methods + */ + + protected void printHeader(long runTime) { + getWriter().println(); + getWriter().println("Time: "+elapsedTimeAsString(runTime)); + } + + protected void printErrors(TestResult result) { + printDefects(result.errors(), result.errorCount(), "error"); + } + + protected void printFailures(TestResult result) { + printDefects(result.failures(), result.failureCount(), "failure"); + } + + protected void printDefects(Enumeration<TestFailure> booBoos, int count, String type) { + if (count == 0) return; + if (count == 1) + getWriter().println("There was " + count + " " + type + ":"); + else + getWriter().println("There were " + count + " " + type + "s:"); + for (int i= 1; booBoos.hasMoreElements(); i++) { + printDefect(booBoos.nextElement(), i); + } + } + + public void printDefect(TestFailure booBoo, int count) { // only public for testing purposes + printDefectHeader(booBoo, count); + printDefectTrace(booBoo); + } + + protected void printDefectHeader(TestFailure booBoo, int count) { + // I feel like making this a println, then adding a line giving the throwable a chance to print something + // before we get to the stack trace. + getWriter().print(count + ") " + booBoo.failedTest()); + } + + protected void printDefectTrace(TestFailure booBoo) { + getWriter().print(BaseTestRunner.getFilteredTrace(booBoo.trace())); + } + + protected void printFooter(TestResult result) { + if (result.wasSuccessful()) { + getWriter().println(); + getWriter().print("OK"); + getWriter().println (" (" + result.runCount() + " test" + (result.runCount() == 1 ? "": "s") + ")"); + + } else { + getWriter().println(); + getWriter().println("FAILURES!!!"); + getWriter().println("Tests run: "+result.runCount()+ + ", Failures: "+result.failureCount()+ + ", Errors: "+result.errorCount()); + } + getWriter().println(); + } + + + /** + * Returns the formatted string of the elapsed time. + * Duplicated from BaseTestRunner. Fix it. + */ + protected String elapsedTimeAsString(long runTime) { + // android-changed + // The following line was altered for compatibility with + // Android libraries. + return Double.toString((double)runTime/1000); + } + + public PrintStream getWriter() { + return fWriter; + } + /** + * @see junit.framework.TestListener#addError(Test, Throwable) + */ + public void addError(Test test, Throwable t) { + getWriter().print("E"); + } + + /** + * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError) + */ + public void addFailure(Test test, AssertionFailedError t) { + getWriter().print("F"); + } + + /** + * @see junit.framework.TestListener#endTest(Test) + */ + public void endTest(Test test) { + } + + /** + * @see junit.framework.TestListener#startTest(Test) + */ + public void startTest(Test test) { + getWriter().print("."); + if (fColumn++ >= 40) { + getWriter().println(); + fColumn= 0; + } + } + +} diff --git a/test-runner/src/junit/textui/TestRunner.java b/test-runner/src/junit/textui/TestRunner.java new file mode 100644 index 000000000000..046448e5e76a --- /dev/null +++ b/test-runner/src/junit/textui/TestRunner.java @@ -0,0 +1,203 @@ +package junit.textui; + + +import java.io.PrintStream; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestResult; +import junit.framework.TestSuite; +import junit.runner.BaseTestRunner; +import junit.runner.Version; + +/** + * A command line based tool to run tests. + * <pre> + * java junit.textui.TestRunner [-wait] TestCaseClass + * </pre> + * + * <p>TestRunner expects the name of a TestCase class as argument. + * If this class defines a static <code>suite</code> method it + * will be invoked and the returned test is run. Otherwise all + * the methods starting with "test" having no arguments are run.</p> + * + * <p> When the wait command line argument is given TestRunner + * waits until the users types RETURN.</p> + * + * <p>TestRunner prints a trace as the tests are executed followed by a + * summary at the end.</p> + */ +public class TestRunner extends BaseTestRunner { + private ResultPrinter fPrinter; + + public static final int SUCCESS_EXIT= 0; + public static final int FAILURE_EXIT= 1; + public static final int EXCEPTION_EXIT= 2; + + /** + * Constructs a TestRunner. + */ + public TestRunner() { + this(System.out); + } + + /** + * Constructs a TestRunner using the given stream for all the output + */ + public TestRunner(PrintStream writer) { + this(new ResultPrinter(writer)); + } + + /** + * Constructs a TestRunner using the given ResultPrinter all the output + */ + public TestRunner(ResultPrinter printer) { + fPrinter= printer; + } + + /** + * Runs a suite extracted from a TestCase subclass. + */ + static public void run(Class<? extends TestCase> testClass) { + run(new TestSuite(testClass)); + } + + /** + * Runs a single test and collects its results. + * This method can be used to start a test run + * from your program. + * <pre> + * public static void main (String[] args) { + * test.textui.TestRunner.run(suite()); + * } + * </pre> + */ + static public TestResult run(Test test) { + TestRunner runner= new TestRunner(); + return runner.doRun(test); + } + + /** + * Runs a single test and waits until the user + * types RETURN. + */ + static public void runAndWait(Test suite) { + TestRunner aTestRunner= new TestRunner(); + aTestRunner.doRun(suite, true); + } + + @Override + public void testFailed(int status, Test test, Throwable t) { + } + + @Override + public void testStarted(String testName) { + } + + @Override + public void testEnded(String testName) { + } + + /** + * Creates the TestResult to be used for the test run. + */ + protected TestResult createTestResult() { + return new TestResult(); + } + + public TestResult doRun(Test test) { + return doRun(test, false); + } + + public TestResult doRun(Test suite, boolean wait) { + TestResult result= createTestResult(); + result.addListener(fPrinter); + long startTime= System.currentTimeMillis(); + suite.run(result); + long endTime= System.currentTimeMillis(); + long runTime= endTime-startTime; + fPrinter.print(result, runTime); + + pause(wait); + return result; + } + + protected void pause(boolean wait) { + if (!wait) return; + fPrinter.printWaitPrompt(); + try { + System.in.read(); + } + catch(Exception e) { + } + } + + public static void main(String args[]) { + TestRunner aTestRunner= new TestRunner(); + try { + TestResult r= aTestRunner.start(args); + if (!r.wasSuccessful()) + System.exit(FAILURE_EXIT); + System.exit(SUCCESS_EXIT); + } catch(Exception e) { + System.err.println(e.getMessage()); + System.exit(EXCEPTION_EXIT); + } + } + + /** + * Starts a test run. Analyzes the command line arguments and runs the given + * test suite. + */ + public TestResult start(String args[]) throws Exception { + String testCase= ""; + String method= ""; + boolean wait= false; + + for (int i= 0; i < args.length; i++) { + if (args[i].equals("-wait")) + wait= true; + else if (args[i].equals("-c")) + testCase= extractClassName(args[++i]); + else if (args[i].equals("-m")) { + String arg= args[++i]; + int lastIndex= arg.lastIndexOf('.'); + testCase= arg.substring(0, lastIndex); + method= arg.substring(lastIndex + 1); + } else if (args[i].equals("-v")) + System.err.println("JUnit " + Version.id() + " by Kent Beck and Erich Gamma"); + else + testCase= args[i]; + } + + if (testCase.equals("")) + throw new Exception("Usage: TestRunner [-wait] testCaseName, where name is the name of the TestCase class"); + + try { + if (!method.equals("")) + return runSingleMethod(testCase, method, wait); + Test suite= getTest(testCase); + return doRun(suite, wait); + } catch (Exception e) { + throw new Exception("Could not create and run test suite: " + e); + } + } + + protected TestResult runSingleMethod(String testCase, String method, boolean wait) throws Exception { + Class<? extends TestCase> testClass= loadSuiteClass(testCase).asSubclass(TestCase.class); + Test test= TestSuite.createTest(testClass, method); + return doRun(test, wait); + } + + @Override + protected void runFailed(String message) { + System.err.println(message); + System.exit(FAILURE_EXIT); + } + + public void setPrinter(ResultPrinter printer) { + fPrinter= printer; + } + + +}
\ No newline at end of file diff --git a/test-runner/src/junit/textui/package-info.java b/test-runner/src/junit/textui/package-info.java new file mode 100644 index 000000000000..2dcc10cf7388 --- /dev/null +++ b/test-runner/src/junit/textui/package-info.java @@ -0,0 +1,5 @@ +/** + * Provides JUnit v3.x command line based tool to run tests. + * {@hide} + */ +package junit.textui;
\ No newline at end of file diff --git a/test-runner/src/junit/textui/package.html b/test-runner/src/junit/textui/package.html new file mode 100644 index 000000000000..723f2ae5151c --- /dev/null +++ b/test-runner/src/junit/textui/package.html @@ -0,0 +1,6 @@ +<HTML> +<BODY> +Utility classes supporting the junit test framework. +{@hide} - Not needed for 1.0 SDK +</BODY> +</HTML> diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java index 6a064d271bcc..f9cac43fff55 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java @@ -39,13 +39,13 @@ import android.net.metrics.IpReachabilityEvent; import android.net.metrics.NetworkEvent; import android.net.metrics.RaEvent; import android.net.metrics.ValidationProbeEvent; - -import junit.framework.TestCase; - +import android.test.suitebuilder.annotation.SmallTest; import java.util.Arrays; +import junit.framework.TestCase; public class IpConnectivityEventBuilderTest extends TestCase { + @SmallTest public void testDefaultNetworkEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(DefaultNetworkEvent.class), @@ -58,6 +58,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " default_network_event <", " network_id <", " network_id: 102", @@ -70,13 +72,13 @@ public class IpConnectivityEventBuilderTest extends TestCase { " transport_types: 2", " transport_types: 3", " >", - " time_ms: 1", ">", "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testDhcpClientEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(DhcpClientEvent.class), @@ -87,19 +89,20 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " dhcp_event <", " duration_ms: 192", - " error_code: 0", " if_name: \"wlan0\"", " state_transition: \"SomeState\"", " >", - " time_ms: 1", ">", "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testDhcpErrorEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(DhcpErrorEvent.class), @@ -109,19 +112,20 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " dhcp_event <", " duration_ms: 0", - " error_code: 50397184", " if_name: \"wlan0\"", - " state_transition: \"\"", + " error_code: 50397184", " >", - " time_ms: 1", ">", "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testDnsEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(DnsEvent.class), @@ -133,6 +137,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " dns_lookup_batch <", " event_types: 1", " event_types: 1", @@ -162,13 +168,13 @@ public class IpConnectivityEventBuilderTest extends TestCase { " return_codes: 200", " return_codes: 178", " >", - " time_ms: 1", ">", "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testIpManagerEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(IpManagerEvent.class), @@ -179,18 +185,20 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " ip_provisioning_event <", " event_type: 1", " if_name: \"wlan0\"", " latency_ms: 5678", " >", - " time_ms: 1", ">", "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testIpReachabilityEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(IpReachabilityEvent.class), @@ -200,17 +208,19 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " ip_reachability_event <", " event_type: 512", " if_name: \"wlan0\"", " >", - " time_ms: 1", ">", "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testNetworkEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(NetworkEvent.class), @@ -221,6 +231,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " network_event <", " event_type: 5", " latency_ms: 20410", @@ -228,13 +240,13 @@ public class IpConnectivityEventBuilderTest extends TestCase { " network_id: 100", " >", " >", - " time_ms: 1", ">", "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testValidationProbeEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(ValidationProbeEvent.class), @@ -247,6 +259,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { "dropped_events: 0", "events <", " time_ms: 1", + " transport: 0", " validation_probe_event <", " latency_ms: 40730", " network_id <", @@ -261,6 +274,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } + @SmallTest public void testApfProgramEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(ApfProgramEvent.class), @@ -273,6 +287,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " apf_program_event <", " current_ras: 9", " drop_multicast: true", @@ -281,13 +297,13 @@ public class IpConnectivityEventBuilderTest extends TestCase { " lifetime: 200", " program_length: 2048", " >", - " time_ms: 1", ">", "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testApfStatsSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(ApfStats.class), @@ -303,6 +319,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " apf_statistics <", " dropped_ras: 2", " duration_ms: 45000", @@ -313,13 +331,13 @@ public class IpConnectivityEventBuilderTest extends TestCase { " received_ras: 10", " zero_lifetime_ras: 1", " >", - " time_ms: 1", ">", "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testRaEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(RaEvent.class), @@ -333,6 +351,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " ra_event <", " dnssl_lifetime: -1", " prefix_preferred_lifetime: 300", @@ -341,7 +361,6 @@ public class IpConnectivityEventBuilderTest extends TestCase { " route_info_lifetime: -1", " router_lifetime: 2000", " >", - " time_ms: 1", ">", "version: 2"); @@ -350,7 +369,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { static void verifySerialization(String want, ConnectivityMetricsEvent... input) { try { - byte[] got = IpConnectivityEventBuilder.serialize(0, Arrays.asList(input)); + byte[] got = IpConnectivityEventBuilder.serialize(0, + IpConnectivityEventBuilder.toProto(Arrays.asList(input))); IpConnectivityLog log = IpConnectivityLog.parseFrom(got); assertEquals(want, log.toString()); } catch (Exception e) { diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java index 78d16d93287e..e9257fa619ba 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java @@ -32,22 +32,19 @@ import android.net.metrics.IpReachabilityEvent; import android.net.metrics.RaEvent; import android.net.metrics.ValidationProbeEvent; import android.os.Parcelable; +import android.test.suitebuilder.annotation.SmallTest; import android.util.Base64; - import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass; - -import junit.framework.TestCase; - -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - import java.io.PrintWriter; import java.io.StringWriter; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; +import junit.framework.TestCase; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; public class IpConnectivityMetricsTest extends TestCase { static final IpReachabilityEvent FAKE_EV = @@ -63,6 +60,7 @@ public class IpConnectivityMetricsTest extends TestCase { mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000); } + @SmallTest public void testLoggingEvents() throws Exception { IpConnectivityLog logger = new IpConnectivityLog(mMockService); @@ -76,6 +74,7 @@ public class IpConnectivityMetricsTest extends TestCase { assertEventsEqual(expectedEvent(3), got.get(2)); } + @SmallTest public void testLoggingEventsWithMultipleCallers() throws Exception { IpConnectivityLog logger = new IpConnectivityLog(mMockService); @@ -103,6 +102,7 @@ public class IpConnectivityMetricsTest extends TestCase { } } + @SmallTest public void testBufferFlushing() { String output1 = getdump("flush"); assertEquals("", output1); @@ -115,6 +115,7 @@ public class IpConnectivityMetricsTest extends TestCase { assertEquals("", output3); } + @SmallTest public void testRateLimiting() { final IpConnectivityLog logger = new IpConnectivityLog(mService.impl); final ApfProgramEvent ev = new ApfProgramEvent(0, 0, 0, 0, 0); @@ -136,6 +137,7 @@ public class IpConnectivityMetricsTest extends TestCase { assertEquals("", output2); } + @SmallTest public void testEndToEndLogging() { IpConnectivityLog logger = new IpConnectivityLog(mService.impl); @@ -156,22 +158,25 @@ public class IpConnectivityMetricsTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 100", + " transport: 0", " ip_reachability_event <", " event_type: 512", " if_name: \"wlan0\"", " >", - " time_ms: 100", ">", "events <", + " time_ms: 200", + " transport: 0", " dhcp_event <", " duration_ms: 192", - " error_code: 0", " if_name: \"wlan0\"", " state_transition: \"SomeState\"", " >", - " time_ms: 200", ">", "events <", + " time_ms: 300", + " transport: 0", " default_network_event <", " network_id <", " network_id: 102", @@ -184,18 +189,19 @@ public class IpConnectivityMetricsTest extends TestCase { " transport_types: 2", " transport_types: 3", " >", - " time_ms: 300", ">", "events <", + " time_ms: 400", + " transport: 0", " ip_provisioning_event <", " event_type: 1", " if_name: \"wlan0\"", " latency_ms: 5678", " >", - " time_ms: 400", ">", "events <", " time_ms: 500", + " transport: 0", " validation_probe_event <", " latency_ms: 40730", " network_id <", @@ -206,6 +212,8 @@ public class IpConnectivityMetricsTest extends TestCase { " >", ">", "events <", + " time_ms: 600", + " transport: 0", " apf_statistics <", " dropped_ras: 2", " duration_ms: 45000", @@ -216,9 +224,10 @@ public class IpConnectivityMetricsTest extends TestCase { " received_ras: 10", " zero_lifetime_ras: 1", " >", - " time_ms: 600", ">", "events <", + " time_ms: 700", + " transport: 0", " ra_event <", " dnssl_lifetime: -1", " prefix_preferred_lifetime: 300", @@ -227,7 +236,6 @@ public class IpConnectivityMetricsTest extends TestCase { " route_info_lifetime: -1", " router_lifetime: 2000", " >", - " time_ms: 700", ">", "version: 2"); @@ -284,10 +292,5 @@ public class IpConnectivityMetricsTest extends TestCase { } static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR = - new Comparator<ConnectivityMetricsEvent>() { - @Override - public int compare(ConnectivityMetricsEvent ev1, ConnectivityMetricsEvent ev2) { - return (int) (ev1.timestamp - ev2.timestamp); - } - }; + Comparator.comparingLong((ev) -> ev.timestamp); } diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java index 2bb62bbdcf59..75d2f1a9edfd 100644 --- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java +++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java @@ -16,25 +16,35 @@ package com.android.server.connectivity; -import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; +import android.net.metrics.ConnectStats; import android.net.metrics.DnsEvent; import android.net.metrics.INetdEventListener; import android.net.metrics.IpConnectivityLog; import android.os.RemoteException; +import android.system.OsConstants; import android.test.suitebuilder.annotation.SmallTest; - +import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.OptionalInt; +import java.util.stream.IntStream; import junit.framework.TestCase; import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertTrue; - import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; @@ -42,13 +52,6 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import java.io.FileOutputStream; -import java.io.PrintWriter; -import java.util.Arrays; -import java.util.List; -import java.util.OptionalInt; -import java.util.stream.IntStream; - public class NetdEventListenerServiceTest extends TestCase { // TODO: read from NetdEventListenerService after this constant is read from system property @@ -68,45 +71,48 @@ public class NetdEventListenerServiceTest extends TestCase { } } + private static final String EXAMPLE_IPV4 = "192.0.2.1"; + private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1"; + NetdEventListenerService mNetdEventListenerService; @Mock ConnectivityManager mCm; @Mock IpConnectivityLog mLog; ArgumentCaptor<NetworkCallback> mCallbackCaptor; - ArgumentCaptor<DnsEvent> mEvCaptor; + ArgumentCaptor<DnsEvent> mDnsEvCaptor; public void setUp() { MockitoAnnotations.initMocks(this); mCallbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class); - mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); + mDnsEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); mNetdEventListenerService = new NetdEventListenerService(mCm, mLog); verify(mCm, times(1)).registerNetworkCallback(any(), mCallbackCaptor.capture()); } @SmallTest - public void testOneBatch() throws Exception { + public void testOneDnsBatch() throws Exception { log(105, LATENCIES); log(106, Arrays.copyOf(LATENCIES, BATCH_SIZE - 1)); // one lookup short of a batch event - verifyLoggedEvents(new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES)); + verifyLoggedDnsEvents(new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES)); log(106, Arrays.copyOfRange(LATENCIES, BATCH_SIZE - 1, BATCH_SIZE)); - mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); // reset argument captor - verifyLoggedEvents( + mDnsEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); // reset argument captor + verifyLoggedDnsEvents( new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES)); } @SmallTest - public void testSeveralBatches() throws Exception { + public void testSeveralDmsBatches() throws Exception { log(105, LATENCIES); log(106, LATENCIES); log(105, LATENCIES); log(107, LATENCIES); - verifyLoggedEvents( + verifyLoggedDnsEvents( new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES), new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), @@ -114,7 +120,7 @@ public class NetdEventListenerServiceTest extends TestCase { } @SmallTest - public void testBatchAndNetworkLost() throws Exception { + public void testDnsBatchAndNetworkLost() throws Exception { byte[] eventTypes = Arrays.copyOf(EVENT_TYPES, 20); byte[] returnCodes = Arrays.copyOf(RETURN_CODES, 20); int[] latencies = Arrays.copyOf(LATENCIES, 20); @@ -124,14 +130,14 @@ public class NetdEventListenerServiceTest extends TestCase { mCallbackCaptor.getValue().onLost(new Network(105)); log(105, LATENCIES); - verifyLoggedEvents( + verifyLoggedDnsEvents( new DnsEvent(105, eventTypes, returnCodes, latencies), new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES)); } @SmallTest - public void testConcurrentBatchesAndDumps() throws Exception { + public void testConcurrentDnsBatchesAndDumps() throws Exception { final long stop = System.currentTimeMillis() + 100; final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null")); new Thread() { @@ -142,27 +148,121 @@ public class NetdEventListenerServiceTest extends TestCase { } }.start(); - logAsync(105, LATENCIES); - logAsync(106, LATENCIES); - logAsync(107, LATENCIES); + logDnsAsync(105, LATENCIES); + logDnsAsync(106, LATENCIES); + logDnsAsync(107, LATENCIES); - verifyLoggedEvents(500, + verifyLoggedDnsEvents(500, new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES), new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES)); } @SmallTest - public void testConcurrentBatchesAndNetworkLoss() throws Exception { - logAsync(105, LATENCIES); + public void testConcurrentDnsBatchesAndNetworkLoss() throws Exception { + logDnsAsync(105, LATENCIES); Thread.sleep(10L); - // call onLost() asynchronously to logAsync's onDnsEvent() calls. + // call onLost() asynchronously to logDnsAsync's onDnsEvent() calls. mCallbackCaptor.getValue().onLost(new Network(105)); // do not verify unpredictable batch verify(mLog, timeout(500).times(1)).log(any()); } + @SmallTest + public void testConnectLogging() throws Exception { + final int OK = 0; + Thread[] logActions = { + // ignored + connectEventAction(OsConstants.EALREADY, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.EALREADY, 0, EXAMPLE_IPV6), + connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6), + connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6), + // valid latencies + connectEventAction(OK, 110, EXAMPLE_IPV4), + connectEventAction(OK, 23, EXAMPLE_IPV4), + connectEventAction(OK, 45, EXAMPLE_IPV4), + connectEventAction(OK, 56, EXAMPLE_IPV4), + connectEventAction(OK, 523, EXAMPLE_IPV6), + connectEventAction(OK, 214, EXAMPLE_IPV6), + connectEventAction(OK, 67, EXAMPLE_IPV6), + // errors + connectEventAction(OsConstants.EPERM, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.EPERM, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.EAGAIN, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV6), + connectEventAction(OsConstants.EADDRINUSE, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6), + connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6), + connectEventAction(OsConstants.ECONNREFUSED, 0, EXAMPLE_IPV4), + }; + + for (Thread t : logActions) { + t.start(); + } + for (Thread t : logActions) { + t.join(); + } + + List<IpConnectivityEvent> events = new ArrayList<>(); + mNetdEventListenerService.flushStatistics(events); + + IpConnectivityEvent got = events.get(0); + String want = String.join("\n", + "time_ms: 0", + "transport: 0", + "connect_statistics <", + " connect_count: 12", + " errnos_counters <", + " key: 1", + " value: 2", + " >", + " errnos_counters <", + " key: 11", + " value: 1", + " >", + " errnos_counters <", + " key: 13", + " value: 3", + " >", + " errnos_counters <", + " key: 98", + " value: 1", + " >", + " errnos_counters <", + " key: 110", + " value: 3", + " >", + " errnos_counters <", + " key: 111", + " value: 1", + " >", + " ipv6_addr_count: 6", + " latencies_ms: 23", + " latencies_ms: 45", + " latencies_ms: 56", + " latencies_ms: 67", + " latencies_ms: 110", + " latencies_ms: 214", + " latencies_ms: 523", + ">\n"); + verifyConnectEvent(want, got); + } + + Thread connectEventAction(int error, int latencyMs, String ipAddr) { + return new Thread(() -> { + try { + mNetdEventListenerService.onConnectEvent(100, error, latencyMs, ipAddr, 80, 1); + } catch (Exception e) { + fail(e.toString()); + } + }); + } + void log(int netId, int[] latencies) { try { for (int l : latencies) { @@ -174,7 +274,7 @@ public class NetdEventListenerServiceTest extends TestCase { } } - void logAsync(int netId, int[] latencies) { + void logDnsAsync(int netId, int[] latencies) { new Thread() { public void run() { log(netId, latencies); @@ -182,15 +282,15 @@ public class NetdEventListenerServiceTest extends TestCase { }.start(); } - void verifyLoggedEvents(DnsEvent... expected) { - verifyLoggedEvents(0, expected); + void verifyLoggedDnsEvents(DnsEvent... expected) { + verifyLoggedDnsEvents(0, expected); } - void verifyLoggedEvents(int wait, DnsEvent... expectedEvents) { - verify(mLog, timeout(wait).times(expectedEvents.length)).log(mEvCaptor.capture()); - for (DnsEvent got : mEvCaptor.getAllValues()) { + void verifyLoggedDnsEvents(int wait, DnsEvent... expectedEvents) { + verify(mLog, timeout(wait).times(expectedEvents.length)).log(mDnsEvCaptor.capture()); + for (DnsEvent got : mDnsEvCaptor.getAllValues()) { OptionalInt index = IntStream.range(0, expectedEvents.length) - .filter(i -> eventsEqual(expectedEvents[i], got)) + .filter(i -> dnsEventsEqual(expectedEvents[i], got)) .findFirst(); // Don't match same expected event more than once. index.ifPresent(i -> expectedEvents[i] = null); @@ -199,11 +299,22 @@ public class NetdEventListenerServiceTest extends TestCase { } /** equality function for DnsEvent to avoid overriding equals() and hashCode(). */ - static boolean eventsEqual(DnsEvent expected, DnsEvent got) { + static boolean dnsEventsEqual(DnsEvent expected, DnsEvent got) { return (expected == got) || ((expected != null) && (got != null) && (expected.netId == got.netId) && Arrays.equals(expected.eventTypes, got.eventTypes) && Arrays.equals(expected.returnCodes, got.returnCodes) && Arrays.equals(expected.latenciesMs, got.latenciesMs)); } + + static void verifyConnectEvent(String expected, IpConnectivityEvent got) { + try { + Arrays.sort(got.getConnectStatistics().latenciesMs); + Arrays.sort(got.getConnectStatistics().errnosCounters, + Comparator.comparingInt((p) -> p.key)); + assertEquals(expected, got.toString()); + } catch (Exception e) { + fail(e.toString()); + } + } } diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java new file mode 100644 index 000000000000..98073ce1e59b --- /dev/null +++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java @@ -0,0 +1,141 @@ +/* + * 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.server.connectivity; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.telephony.TelephonyManager; +import android.test.suitebuilder.annotation.SmallTest; +import com.android.server.connectivity.NetworkNotificationManager.NotificationType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import junit.framework.TestCase; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class NetworkNotificationManagerTest extends TestCase { + + static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities(); + static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities(); + static { + CELL_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + CELL_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + + WIFI_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + WIFI_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + } + + @Mock Context mCtx; + @Mock Resources mResources; + @Mock PackageManager mPm; + @Mock TelephonyManager mTelephonyManager; + @Mock NotificationManager mNotificationManager; + @Mock NetworkAgentInfo mWifiNai; + @Mock NetworkAgentInfo mCellNai; + @Mock NetworkInfo mNetworkInfo; + ArgumentCaptor<Notification> mCaptor; + + NetworkNotificationManager mManager; + + public void setUp() { + MockitoAnnotations.initMocks(this); + mCaptor = ArgumentCaptor.forClass(Notification.class); + mWifiNai.networkCapabilities = WIFI_CAPABILITIES; + mWifiNai.networkInfo = mNetworkInfo; + mCellNai.networkCapabilities = CELL_CAPABILITIES; + mCellNai.networkInfo = mNetworkInfo; + when(mCtx.getResources()).thenReturn(mResources); + when(mCtx.getPackageManager()).thenReturn(mPm); + when(mCtx.getApplicationInfo()).thenReturn(new ApplicationInfo()); + when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B); + + mManager = new NetworkNotificationManager(mCtx, mTelephonyManager, mNotificationManager); + } + + @SmallTest + public void testNotificationsShownAndCleared() { + final int NETWORK_ID_BASE = 100; + List<NotificationType> types = Arrays.asList(NotificationType.values()); + List<Integer> ids = new ArrayList<>(types.size()); + for (int i = 0; i < ids.size(); i++) { + ids.add(NETWORK_ID_BASE + i); + } + Collections.shuffle(ids); + Collections.shuffle(types); + + for (int i = 0; i < ids.size(); i++) { + mManager.showNotification(ids.get(i), types.get(i), mWifiNai, mCellNai, null, false); + } + + Collections.shuffle(ids); + for (int i = 0; i < ids.size(); i++) { + mManager.clearNotification(ids.get(i)); + } + + for (int i = 0; i < ids.size(); i++) { + final int id = ids.get(i); + final int eventId = types.get(i).eventId; + final String tag = NetworkNotificationManager.tagFor(id); + verify(mNotificationManager, times(1)).notifyAsUser(eq(tag), eq(eventId), any(), any()); + verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(eventId), any()); + } + } + + @SmallTest + public void testNoInternetNotificationsNotShownForCellular() { + mManager.showNotification(100, NO_INTERNET, mCellNai, mWifiNai, null, false); + mManager.showNotification(101, LOST_INTERNET, mCellNai, mWifiNai, null, false); + + verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any()); + + mManager.showNotification(102, NO_INTERNET, mWifiNai, mCellNai, null, false); + + final int eventId = NO_INTERNET.eventId; + final String tag = NetworkNotificationManager.tagFor(102); + verify(mNotificationManager, times(1)).notifyAsUser(eq(tag), eq(eventId), any(), any()); + } + + @SmallTest + public void testNotificationsNotShownIfNoInternetCapability() { + mWifiNai.networkCapabilities = new NetworkCapabilities(); + mWifiNai.networkCapabilities .addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + mManager.showNotification(102, NO_INTERNET, mWifiNai, mCellNai, null, false); + mManager.showNotification(103, LOST_INTERNET, mWifiNai, mCellNai, null, false); + mManager.showNotification(104, NETWORK_SWITCH, mWifiNai, mCellNai, null, false); + + verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any()); + } +} diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java index 7d6f32bd8f88..28a2cb399cd0 100644 --- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java +++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java @@ -93,8 +93,7 @@ public class WindowManagerPermissionTests extends TestCase { } try { - mWm.addAppToken(0, null, 0, 0, 0, false, false, 0, 0, false, false, null, - Configuration.EMPTY, 0, false, false, 0, -1, false); + mWm.addAppToken(0, null, 0, 0, false, false, 0, false, false, false, 0, -1); fail("IWindowManager.addAppToken did not throw SecurityException as" + " expected"); } catch (SecurityException e) { @@ -104,7 +103,7 @@ public class WindowManagerPermissionTests extends TestCase { } try { - mWm.setAppTask(null, 0, INVALID_STACK_ID, null, null, 0, false, false); + mWm.addAppToTask(null, 0); fail("IWindowManager.setAppGroupId did not throw SecurityException as" + " expected"); } catch (SecurityException e) { diff --git a/tools/incident_report/Android.mk b/tools/incident_report/Android.mk new file mode 100644 index 000000000000..ed89bd6251e3 --- /dev/null +++ b/tools/incident_report/Android.mk @@ -0,0 +1,39 @@ +# +# Copyright (C) 2015 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. +# +LOCAL_PATH:= $(call my-dir) + +# ========================================================== +# Build the host executable: protoc-gen-javastream +# ========================================================== +include $(CLEAR_VARS) + +LOCAL_MODULE := incident_report + +LOCAL_C_INCLUDES := \ + external/protobuf/src + +LOCAL_SRC_FILES := \ + generic_message.cpp \ + main.cpp \ + printer.cpp + +LOCAL_SHARED_LIBRARIES := \ + libplatformprotos \ + libprotobuf-cpp-full + +include $(BUILD_HOST_EXECUTABLE) + + diff --git a/tools/incident_report/formatter.cpp b/tools/incident_report/formatter.cpp new file mode 100644 index 000000000000..944348f30022 --- /dev/null +++ b/tools/incident_report/formatter.cpp @@ -0,0 +1,89 @@ +/* + * 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 "proto_format.h" + +#include <string.h> + +extern int const PROTO_FORMAT_STRING_POOL_SIZE; +extern int const PROTO_FORMAT_ENUM_LABELS_LENGTH; +extern int const PROTO_FORMAT_MESSAGES_LENGTH; +extern int const PROTO_FORMAT_FIELDS_LENGTH; + +extern char const PROTO_FORMAT_STRING_POOL[]; +extern ProtoFieldFormat const PROTO_FORMAT_FIELDS[]; +extern ProtoEnumLabel const PROTO_FORMAT_ENUM_LABELS[]; +extern ProtoMessageFormat const PROTO_FORMAT_MESSAGES[]; + +static const char* +get_string(int index) +{ + if (index >= 0 && index < PROTO_FORMAT_STRING_POOL_SIZE) { + return PROTO_FORMAT_STRING_POOL + index; + } else { + // These indices all come from within the generated table, so just crash now. + *(int*)NULL = 42; + return NULL; + } +} + +static ProtoMessageFormat const* +get_message(int index) +{ + if (index >= 0 && index < PROTO_FORMAT_MESSAGES_LENGTH) { + return PROTO_FORMAT_MESSAGES + index; + } else { + // These indices all come from within the generated table, so just crash now. + *(int*)NULL = 42; + return NULL; + } +} + +static int +compare_name(const char* full, const char* package, const char* clazz) +{ + int const packageLen = strlen(package); + int cmp = strncmp(full, package, packageLen); + if (cmp == 0) { + cmp = full[packageLen] - '.'; + if (cmp == 0) { + return strcmp(full + packageLen, clazz); + } + } + return cmp; +} + +int +find_message_index(const char* name) +{ + size_t low = 0; + size_t high = PROTO_FORMAT_FIELDS_LENGTH - 1; + + while (low <= high) { + size_t mid = (low + high) >> 1; + ProtoMessageFormat const* msg = get_message(mid); + + int cmp = compare_name(name, get_string(msg->package_name), get_string(msg->package_name)); + if (cmp < 0) { + low = mid + 1; + } else if (cmp > 0) { + high = mid - 1; + } else { + return mid; + } + } + return -1; +} diff --git a/tools/incident_report/generic_message.cpp b/tools/incident_report/generic_message.cpp new file mode 100644 index 000000000000..84d9d7cfe7c2 --- /dev/null +++ b/tools/incident_report/generic_message.cpp @@ -0,0 +1,70 @@ +/* + * 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 "generic_message.h" + +GenericMessage::GenericMessage() +{ +} + +GenericMessage::~GenericMessage() +{ +} + +void +GenericMessage::addInt32(int32_t fieldId, uint32_t value) +{ + Node node; + node.type = TYPE_VALUE32; + node.value32 = value; + mNodes.insert(pair<int32_t,Node>(fieldId, node)); +} + +void +GenericMessage::addInt64(int32_t fieldId, uint64_t value) +{ + Node node; + node.type = TYPE_VALUE64; + node.value64 = value; + mNodes.insert(pair<int32_t,Node>(fieldId, node)); +} + +GenericMessage* +GenericMessage::addMessage(int32_t fieldId) +{ + GenericMessage* result = new GenericMessage(); + Node node; + node.type = TYPE_MESSAGE; + node.message = result; + mNodes.insert(pair<int32_t,Node>(fieldId, node)); + return result; +} + +void +GenericMessage::addString(int32_t fieldId, const string& value) +{ + Node node; + node.type = TYPE_STRING; + node.str = new string(value); + mNodes.insert(pair<int32_t,Node>(fieldId, node)); +} + +GenericMessage::const_iterator_pair +GenericMessage::find(int fieldId) const +{ + return mNodes.equal_range(fieldId); +} + diff --git a/tools/incident_report/generic_message.h b/tools/incident_report/generic_message.h new file mode 100644 index 000000000000..df3f7b22dfc7 --- /dev/null +++ b/tools/incident_report/generic_message.h @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#ifndef GENERIC_MESSAGE_H +#define GENERIC_MESSAGE_H + +#include <map> +#include <string> + +using namespace std; + +/** + * Class to represent a protobuf Message, where we don't actually + * know what any of the fields are, just their type codes. In other + * words, this loslessly stores a parsed protobuf object without + * having the .proto file that generated it. + */ +class GenericMessage +{ +public: + GenericMessage(); + ~GenericMessage(); + + enum { + TYPE_VALUE32, + TYPE_VALUE64, + TYPE_MESSAGE, + TYPE_STRING, + TYPE_DATA + }; + + struct Node { + uint32_t type; + union { + uint32_t value32; + uint64_t value64; + GenericMessage* message; + string* str; + string* data; + }; + }; + + void addInt32(int32_t fieldId, uint32_t value); + void addInt64(int32_t fieldId, uint64_t value); + GenericMessage* addMessage(int32_t fieldId); + void addString(int32_t fieldId, const string& value); + + typedef multimap<int32_t,Node>::const_iterator const_iterator; + typedef pair<const_iterator,const_iterator> const_iterator_pair; + + const_iterator_pair find(int fieldId) const; + +private: + multimap<int,Node> mNodes; +}; + +#endif // GENERIC_MESSAGE_H + diff --git a/tools/incident_report/main.cpp b/tools/incident_report/main.cpp new file mode 100644 index 000000000000..a814847e41f7 --- /dev/null +++ b/tools/incident_report/main.cpp @@ -0,0 +1,576 @@ +/* + * 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 "generic_message.h" +#include "printer.h" + +#include <frameworks/base/core/proto/android/os/incident_proto.pb.h> +#include <google/protobuf/wire_format.h> +#include <google/protobuf/io/coded_stream.h> +#include <google/protobuf/io/zero_copy_stream_impl.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +using namespace android::os; +using namespace google::protobuf; +using namespace google::protobuf::io; +using namespace google::protobuf::internal; + +static bool read_message(CodedInputStream* in, Descriptor const* descriptor, + GenericMessage* message); +static void print_message(Out* out, Descriptor const* descriptor, GenericMessage const* message); + +// ================================================================================ +static bool +read_length_delimited(CodedInputStream* in, uint32 fieldId, Descriptor const* descriptor, + GenericMessage* message) +{ + uint32 size; + if (!in->ReadVarint32(&size)) { + return false; + } + + FieldDescriptor const* field = descriptor->FindFieldByNumber(fieldId); + if (field != NULL) { + int type = field->type(); + if (type == FieldDescriptor::TYPE_MESSAGE) { + GenericMessage* child = message->addMessage(fieldId); + + CodedInputStream::Limit limit = in->PushLimit(size); + bool rv = read_message(in, field->message_type(), child); + in->PopLimit(limit); + return rv; + } else if (type == FieldDescriptor::TYPE_STRING) { + // TODO: do a version of readstring that just pumps the data + // rather than allocating a string which we don't care about. + string str; + if (in->ReadString(&str, size)) { + message->addString(fieldId, str); + return true; + } else { + return false; + } + } else if (type == FieldDescriptor::TYPE_BYTES) { + // TODO: Save bytes field. + return in->Skip(size); + } + } + return in->Skip(size); +} + +// ================================================================================ +static bool +read_message(CodedInputStream* in, Descriptor const* descriptor, GenericMessage* message) +{ + uint32 value32; + uint64 value64; + + while (true) { + uint32 tag = in->ReadTag(); + if (tag == 0) { + return true; + } + int fieldId = WireFormatLite::GetTagFieldNumber(tag); + switch (WireFormatLite::GetTagWireType(tag)) { + case WireFormatLite::WIRETYPE_VARINT: + if (in->ReadVarint64(&value64)) { + message->addInt64(fieldId, value64); + break; + } else { + return false; + } + case WireFormatLite::WIRETYPE_FIXED64: + if (in->ReadLittleEndian64(&value64)) { + message->addInt64(fieldId, value64); + break; + } else { + return false; + } + case WireFormatLite::WIRETYPE_LENGTH_DELIMITED: + if (!read_length_delimited(in, fieldId, descriptor, message)) { + return false; + } + break; + case WireFormatLite::WIRETYPE_FIXED32: + if (in->ReadLittleEndian32(&value32)) { + message->addInt32(fieldId, value32); + break; + } else { + return false; + } + default: + fprintf(stderr, "bad tag: 0x%x (%d) at index %d\n", tag, tag, + in->CurrentPosition()); + return false; + } + } +} + +// ================================================================================ +static void +print_value(Out* out, FieldDescriptor const* field, GenericMessage::Node const& node) +{ + uint32_t val32; + FieldDescriptor::Type type = field->type(); + + switch (node.type) { + case GenericMessage::TYPE_VALUE32: + switch (type) { + case FieldDescriptor::TYPE_FIXED32: + out->printf("%u", node.value32); + break; + case FieldDescriptor::TYPE_SFIXED32: + out->printf("%d", node.value32); + break; + case FieldDescriptor::TYPE_FLOAT: + out->printf("%f", *(float*)&node.value32); + break; + default: + out->printf("(unexpected value %d (0x%x)", node.value32, node.value32); + break; + } + break; + case GenericMessage::TYPE_VALUE64: + switch (type) { + case FieldDescriptor::TYPE_FIXED64: + case FieldDescriptor::TYPE_SFIXED64: + case FieldDescriptor::TYPE_DOUBLE: + out->printf("%f", *(double*)&node.value64); + break; + case FieldDescriptor::TYPE_SINT32: + case FieldDescriptor::TYPE_INT32: + val32 = (uint32_t)node.value32; + out->printf("%d", val32); + break; + case FieldDescriptor::TYPE_INT64: + case FieldDescriptor::TYPE_UINT32: + val32 = (uint32_t)node.value32; + out->printf("%u", val32); + break; + case FieldDescriptor::TYPE_UINT64: + case FieldDescriptor::TYPE_SINT64: + case FieldDescriptor::TYPE_BOOL: + if (node.value64) { + out->printf("true"); + } else { + out->printf("false"); + } + break; + case FieldDescriptor::TYPE_ENUM: + default: + out->printf("(unexpected value %ld (0x%x))", node.value64, node.value64); + break; + } + break; + case GenericMessage::TYPE_MESSAGE: + print_message(out, field->message_type(), node.message); + break; + case GenericMessage::TYPE_STRING: + // TODO: custom format for multi-line strings. + out->printf("%s", node.str->c_str()); + break; + case GenericMessage::TYPE_DATA: + out->printf("<bytes>"); + break; + } +} + +static void +print_message(Out* out, Descriptor const* descriptor, GenericMessage const* message) +{ + out->printf("%s {\n", descriptor->name().c_str()); + out->indent(); + + int const N = descriptor->field_count(); + for (int i=0; i<N; i++) { + FieldDescriptor const* field = descriptor->field(i); + + int fieldId = field->number(); + bool repeated = field->label() == FieldDescriptor::LABEL_REPEATED; + FieldDescriptor::Type type = field->type(); + GenericMessage::const_iterator_pair it = message->find(fieldId); + + out->printf("%s=", field->name().c_str()); + if (repeated) { + if (it.first != it.second) { + out->printf("["); + if (type == FieldDescriptor::TYPE_MESSAGE + || type == FieldDescriptor::TYPE_STRING + || type == FieldDescriptor::TYPE_BYTES) { + out->printf("\n"); + } + out->indent(); + + for (GenericMessage::const_iterator_pair it = message->find(fieldId); + it.first != it.second; it.first++) { + print_value(out, field, it.first->second); + if (type == FieldDescriptor::TYPE_MESSAGE + || type == FieldDescriptor::TYPE_STRING + || type == FieldDescriptor::TYPE_BYTES) { + out->printf("\n"); + } + } + + out->dedent(); + out->printf("]"); + } else { + out->printf("[]"); + } + } else { + if (it.first != it.second) { + print_value(out, field, it.first->second); + } else { + switch (type) { + case FieldDescriptor::TYPE_BOOL: + out->printf("false"); + break; + case FieldDescriptor::TYPE_STRING: + case FieldDescriptor::TYPE_MESSAGE: + out->printf(""); + break; + case FieldDescriptor::TYPE_ENUM: + out->printf("%s", field->default_value_enum()->name().c_str()); + break; + default: + out->printf("0"); + break; + } + } + } + out->printf("\n"); + } + out->dedent(); + out->printf("}"); +} + +// ================================================================================ +static uint8_t* +write_raw_varint(uint8_t* buf, uint32_t val) +{ + uint8_t* p = buf; + while (true) { + if ((val & ~0x7F) == 0) { + *p++ = (uint8_t)val; + return p; + } else { + *p++ = (uint8_t)((val & 0x7F) | 0x80); + val >>= 7; + } + } +} + +static int +write_all(int fd, uint8_t const* buf, size_t size) +{ + while (size > 0) { + ssize_t amt = ::write(fd, buf, size); + if (amt < 0) { + return errno; + } + size -= amt; + buf += amt; + } + return 0; +} + +static int +adb_incident_workaround(const char* adbSerial, const vector<string>& sections) +{ + const int maxAllowedSize = 20 * 1024 * 1024; // 20MB + uint8_t* buffer = (uint8_t*)malloc(maxAllowedSize); + + for (vector<string>::const_iterator it=sections.begin(); it!=sections.end(); it++) { + Descriptor const* descriptor = IncidentProto::descriptor(); + FieldDescriptor const* field; + + // Get the name and field id. + string name = *it; + char* end; + int id = strtol(name.c_str(), &end, 0); + if (*end == '\0') { + // If it's an id, find out the string. + field = descriptor->FindFieldByNumber(id); + if (field == NULL) { + fprintf(stderr, "Unable to find field number: %d\n", id); + return 1; + } + name = field->name(); + } else { + // If it's a string, find out the id. + field = descriptor->FindFieldByName(name); + if (field == NULL) { + fprintf(stderr, "Unable to find field: %s\n", name.c_str()); + return 1; + } + id = field->number(); + } + + int pfd[2]; + if (pipe(pfd) != 0) { + fprintf(stderr, "pipe failed: %s\n", strerror(errno)); + return 1; + } + + pid_t pid = fork(); + if (pid == -1) { + fprintf(stderr, "fork failed: %s\n", strerror(errno)); + return 1; + } else if (pid == 0) { + // child + dup2(pfd[1], STDOUT_FILENO); + close(pfd[0]); + close(pfd[1]); + + char const** args = (char const**)malloc(sizeof(char*) * 8); + int argpos = 0; + args[argpos++] = "adb"; + if (adbSerial != NULL) { + args[argpos++] = "-s"; + args[argpos++] = adbSerial; + } + args[argpos++] = "shell"; + args[argpos++] = "dumpsys"; + args[argpos++] = name.c_str(); + args[argpos++] = "--proto"; + args[argpos++] = NULL; + execvp(args[0], (char*const*)args); + fprintf(stderr, "execvp failed: %s\n", strerror(errno)); + return 1; + } else { + // parent + close(pfd[1]); + + size_t size = 0; + while (size < maxAllowedSize) { + ssize_t amt = read(pfd[0], buffer + size, maxAllowedSize - size); + if (amt == 0) { + break; + } else if (amt == -1) { + fprintf(stderr, "read error: %s\n", strerror(errno)); + return 1; + } + size += amt; + } + + int status; + do { + waitpid(pid, &status, 0); + } while (!WIFEXITED(status)); + if (WEXITSTATUS(status) != 0) { + return WEXITSTATUS(status); + } + + if (size > 0) { + uint8_t header[20]; + uint8_t* p = write_raw_varint(header, (id << 3) | 2); + p = write_raw_varint(p, size); + int err = write_all(STDOUT_FILENO, header, p-header); + if (err != 0) { + fprintf(stderr, "write error: %s\n", strerror(err)); + return 1; + } + err = write_all(STDOUT_FILENO, buffer, size); + if (err != 0) { + fprintf(stderr, "write error: %s\n", strerror(err)); + return 1; + } + } + + close(pfd[0]); + } + } + + return 0; +} + +// ================================================================================ +static void +usage(FILE* out) +{ + fprintf(out, "usage: incident_report -i INPUT [-o OUTPUT]\n"); + fprintf(out, "\n"); + fprintf(out, "Pretty-prints an incident report protobuf file.\n"); + fprintf(out, " -i INPUT the input file. INPUT may be '-' to use stdin\n"); + fprintf(out, " -o OUTPUT the output file. OUTPUT may be '-' or omitted to use stdout\n"); + fprintf(out, "\n"); + fprintf(out, "\n"); + fprintf(out, "usage: incident_report [-o OUTPUT] [-t|b] [-s SERIAL] [SECTION...]\n"); + fprintf(out, "\n"); + fprintf(out, "Take an incident report over adb (which must be in the PATH).\n"); + fprintf(out, " -b output the incident report raw protobuf format\n"); + fprintf(out, " -o OUTPUT the output file. OUTPUT may be '-' or omitted to use stdout\n"); + fprintf(out, " -s SERIAL sent to adb to choose which device, instead of $ANDROID_SERIAL\n"); + fprintf(out, " -t output the incident report in pretty-printed text format\n"); + fprintf(out, "\n"); + fprintf(out, " SECTION which bugreport sections to print, either the int code of the\n"); + fprintf(out, " section in the Incident proto or the field name. If ommited,\n"); + fprintf(out, " the report will contain all fields\n"); + fprintf(out, "\n"); +} + +int +main(int argc, char** argv) +{ + enum { OUTPUT_TEXT, OUTPUT_PROTO } outputFormat = OUTPUT_TEXT; + const char* inFilename = NULL; + const char* outFilename = NULL; + const char* adbSerial = NULL; + bool adbIncidentWorkaround = true; + pid_t childPid = -1; + vector<string> sections; + + int opt; + while ((opt = getopt(argc, argv, "bhi:o:s:tw")) != -1) { + switch (opt) { + case 'b': + outputFormat = OUTPUT_PROTO; + break; + case 'i': + inFilename = optarg; + break; + case 'o': + outFilename = optarg; + break; + case 's': + adbSerial = optarg; + break; + case 't': + outputFormat = OUTPUT_TEXT; + break; + case 'h': + usage(stdout); + return 0; + case 'w': + adbIncidentWorkaround = false; + break; + default: + usage(stderr); + return 1; + } + } + + while (optind < argc) { + sections.push_back(argv[optind++]); + } + + int inFd; + if (inFilename != NULL) { + // translate-only mode - oepn the file or use stdin. + if (strcmp("-", inFilename) == 0) { + inFd = STDIN_FILENO; + } else { + inFd = open(inFilename, O_RDONLY | O_CLOEXEC); + if (inFd < 0) { + fprintf(stderr, "unable to open file for read (%s): %s\n", strerror(errno), + inFilename); + return 1; + } + } + } else { + // pipe mode - run adb shell incident ... + int pfd[2]; + if (pipe(pfd) != 0) { + fprintf(stderr, "pipe failed: %s\n", strerror(errno)); + return 1; + } + + childPid = fork(); + if (childPid == -1) { + fprintf(stderr, "fork failed: %s\n", strerror(errno)); + return 1; + } else if (childPid == 0) { + dup2(pfd[1], STDOUT_FILENO); + close(pfd[0]); + close(pfd[1]); + // child + if (adbIncidentWorkaround) { + // TODO: Until the device side incident command is checked in, + // the incident_report builds the outer Incident proto by hand + // from individual adb shell dumpsys <service> --proto calls, + // with a maximum allowed output size. + return adb_incident_workaround(adbSerial, sections); + } + + // TODO: This is what the real implementation will be... + char const** args = (char const**)malloc(sizeof(char*) * (6 + sections.size())); + int argpos = 0; + args[argpos++] = "adb"; + if (adbSerial != NULL) { + args[argpos++] = "-s"; + args[argpos++] = adbSerial; + } + args[argpos++] = "shell"; + args[argpos++] = "incident"; + for (vector<string>::const_iterator it=sections.begin(); it!=sections.end(); it++) { + args[argpos++] = it->c_str(); + } + args[argpos++] = NULL; + execvp(args[0], (char*const*)args); + fprintf(stderr, "execvp failed: %s\n", strerror(errno)); + return 0; + } else { + // parent + inFd = pfd[0]; + close(pfd[1]); + } + } + + int outFd; + if (outFilename == NULL || strcmp("-", outFilename) == 0) { + outFd = STDOUT_FILENO; + } else { + outFd = open(outFilename, O_CREAT | O_RDWR, 0666); + if (outFd < 0) { + fprintf(stderr, "unable to open file for write: %s\n", outFilename); + return 1; + } + } + + GenericMessage message; + + Descriptor const* descriptor = IncidentProto::descriptor(); + FileInputStream infile(inFd); + CodedInputStream in(&infile); + + if (!read_message(&in, descriptor, &message)) { + fprintf(stderr, "unable to read incident\n"); + return 1; + } + + Out out(outFd); + + print_message(&out, descriptor, &message); + out.printf("\n"); + + if (childPid != -1) { + int status; + do { + waitpid(childPid, &status, 0); + } while (!WIFEXITED(status)); + if (WEXITSTATUS(status) != 0) { + return WEXITSTATUS(status); + } + } + + return 0; +} diff --git a/tools/incident_report/printer.cpp b/tools/incident_report/printer.cpp new file mode 100644 index 000000000000..8111b2786007 --- /dev/null +++ b/tools/incident_report/printer.cpp @@ -0,0 +1,128 @@ +/* + * 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 "printer.h" + +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + +#define INITIAL_BUF_SIZE (16*1024) + +char const* SPACES = " "; +const int SPACE_COUNT = strlen(SPACES); + +Out::Out(int fd) + :mOut(fd == STDOUT_FILENO ? stdout : fdopen(fd, "w")), + mBufSize(INITIAL_BUF_SIZE), + mBuf((char*)malloc(INITIAL_BUF_SIZE)), + mIndent(0), + mPendingIndent(false) +{ +} + +Out::~Out() +{ + fclose(mOut); +} + +int +Out::reallocate(int size) +{ + if (size > mBufSize) { + char* p = (char*)malloc(size); + if (p != NULL) { + free(mBuf); + mBufSize = size; + mBuf = p; + return size; + } + } + return mBufSize; +} + +void +Out::printf(const char* format, ...) +{ + if (mPendingIndent) { + print_indent(); + mPendingIndent = false; + } + + int len; + + va_list args; + va_start(args, format); + + len = vsnprintf(mBuf, mBufSize, format, args); + bool truncated = (len >= mBufSize) && (reallocate(len) < len); + + len = vsnprintf(mBuf, mBufSize, format, args); + va_end(args); + + if (len > 0) { + if (mIndent == 0) { + fwrite(mBuf, len, 1, mOut); + } else { + char* last = mBuf; + char* p; + do { + p = strchr(last, '\n'); + int size = p != NULL ? p - last + 1 : strlen(last); + fwrite(last, size, 1, mOut); + if (p != NULL) { + if (p[1] == '\0') { + mPendingIndent = true; + } else { + print_indent(); + } + } + last = p+1; + } while (p != NULL); + } + } +} + +void +Out::indent() +{ + mPendingIndent = true; + mIndent += 2; +} + +void +Out::dedent() +{ + if (mIndent > 0) { + mIndent -= 2; + } +} + +void +Out::print_indent() +{ +#if 0 + fprintf(mOut, "[%d]", mIndent); +#else + int indent = mIndent; + while (indent > SPACE_COUNT) { + fwrite(SPACES, SPACE_COUNT, 1, mOut); + indent -= SPACE_COUNT; + } + fwrite(SPACES + SPACE_COUNT - indent, indent, 1, mOut); +#endif +} diff --git a/tools/incident_report/printer.h b/tools/incident_report/printer.h new file mode 100644 index 000000000000..ed93fa19542c --- /dev/null +++ b/tools/incident_report/printer.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#ifndef PRINTER_H +#define PRINTER_H + +#include <stdio.h> + +class Out +{ +public: + Out(int fd); + ~Out(); + + void printf(const char* format, ...); + + void indent(); + void dedent(); + +private: + FILE* mOut; + int mBufSize; + char* mBuf; + int mIndent; + bool mPendingIndent; + + int reallocate(int size); + void print_indent(); +}; + +#endif // PRINTER_H diff --git a/tools/incident_section_gen/Android.mk b/tools/incident_section_gen/Android.mk new file mode 100644 index 000000000000..acf3f8327b5c --- /dev/null +++ b/tools/incident_section_gen/Android.mk @@ -0,0 +1,35 @@ +# +# Copyright (C) 2015 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. +# +LOCAL_PATH:= $(call my-dir) + +# ========================================================== +# Build the host executable: protoc-gen-javastream +# ========================================================== +include $(CLEAR_VARS) + +LOCAL_MODULE := incident-section-gen +LOCAL_CFLAGS += -g -O0 +LOCAL_C_INCLUDES := \ + external/protobuf/src +LOCAL_SRC_FILES := \ + main.cpp +LOCAL_LDFLAGS := -ldl +LOCAL_SHARED_LIBRARIES := \ + libplatformprotos \ + libprotobuf-cpp-full + +include $(BUILD_HOST_EXECUTABLE) + diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp new file mode 100644 index 000000000000..d0048105d9fe --- /dev/null +++ b/tools/incident_section_gen/main.cpp @@ -0,0 +1,67 @@ +/* + * 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 <frameworks/base/core/proto/android/os/incident_proto.pb.h> + + +#include <map> + +using namespace android::os; +using namespace google::protobuf; +using namespace google::protobuf::io; +using namespace google::protobuf::internal; +using namespace std; + +int +main(int, const char**) +{ + map<string,FieldDescriptor const*> sections; + int N; + + printf("// Auto generated file. Do not modify\n"); + printf("\n"); + printf("#include \"incident_sections.h\"\n"); + printf("\n"); + + Descriptor const* descriptor = IncidentProto::descriptor(); + N = descriptor->field_count(); + for (int i=0; i<N; i++) { + const FieldDescriptor* field = descriptor->field(i); + if (field->type() == FieldDescriptor::TYPE_MESSAGE) { + sections[field->name()] = field; + } + } + + printf("IncidentSection const INCIDENT_SECTIONS[] = {\n"); + N = sections.size(); + int i = 0; + for (map<string,FieldDescriptor const*>::const_iterator it = sections.begin(); + it != sections.end(); it++, i++) { + const FieldDescriptor* field = it->second; + printf(" { %d, \"%s\" }", field->number(), field->name().c_str()); + if (i != N-1) { + printf(",\n"); + } else { + printf("\n"); + } + } + printf("};\n"); + + printf("const int INCIDENT_SECTION_COUNT = %d;\n", N); + + return 0; +} diff --git a/tools/layoutlib/.idea/modules.xml b/tools/layoutlib/.idea/modules.xml index 9bdc3815da29..6ffc1cc12b88 100644 --- a/tools/layoutlib/.idea/modules.xml +++ b/tools/layoutlib/.idea/modules.xml @@ -4,6 +4,7 @@ <modules> <module fileurl="file://$PROJECT_DIR$/bridge/bridge.iml" filepath="$PROJECT_DIR$/bridge/bridge.iml" /> <module fileurl="file://$PROJECT_DIR$/create/create.iml" filepath="$PROJECT_DIR$/create/create.iml" /> + <module fileurl="file://$PROJECT_DIR$/legacy/legacy.iml" filepath="$PROJECT_DIR$/legacy/legacy.iml" /> <module fileurl="file://$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" filepath="$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" /> </modules> </component> diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index a0ded8745508..4b9815d93ac7 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -34,7 +34,6 @@ import android.os.IRemoteCallback; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.DisplayMetrics; -import android.view.AppTransitionAnimationSpec; import java.lang.Override; @@ -78,11 +77,10 @@ public class IWindowManagerImpl implements IWindowManager { // ---- unused implementation of IWindowManager ---- @Override - public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4, - boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9, boolean arg10, - Rect arg11, Configuration arg12, int arg13, boolean arg14, boolean arg15, int arg16, - int arg17, boolean arg18) - throws RemoteException { + public void addAppToken(int addPos, IApplicationToken token, int taskId, + int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int configChanges, + boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable, + int targetSdkVersion, int rotationAnimationHint) throws RemoteException { // TODO Auto-generated method stub } @@ -327,9 +325,7 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public void setAppTask(IBinder arg0, int arg1, int arg2, Rect arg3, Configuration arg4, - int arg5, boolean arg6, boolean arg7) - throws RemoteException { + public void addAppToTask(IBinder arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub } diff --git a/tools/layoutlib/legacy/Android.mk b/tools/layoutlib/legacy/Android.mk new file mode 100644 index 000000000000..5855f894c111 --- /dev/null +++ b/tools/layoutlib/legacy/Android.mk @@ -0,0 +1,30 @@ +# +# Copyright (C) 2008 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. +# +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under,src) + +LOCAL_JAVA_LIBRARIES := \ + layoutlib_api-prebuilt + +LOCAL_MODULE := layoutlib-legacy + +include $(BUILD_HOST_JAVA_LIBRARY) + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) + diff --git a/tools/layoutlib/legacy/legacy.iml b/tools/layoutlib/legacy/legacy.iml new file mode 100644 index 000000000000..a167a7585eda --- /dev/null +++ b/tools/layoutlib/legacy/legacy.iml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="library" name="layoutlib_api-prebuilt" level="project" /> + </component> +</module>
\ No newline at end of file diff --git a/tools/layoutlib/legacy/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/legacy/src/com/android/layoutlib/bridge/Bridge.java new file mode 100644 index 000000000000..0cfc181116c7 --- /dev/null +++ b/tools/layoutlib/legacy/src/com/android/layoutlib/bridge/Bridge.java @@ -0,0 +1,75 @@ +/* + * 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.layoutlib.bridge;import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.Result.Status; +import com.android.ide.common.rendering.api.SessionParams; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; + +/** + * Legacy Bridge used in the SDK version of layoutlib + */ +public final class Bridge extends com.android.ide.common.rendering.api.Bridge { + private static final String SDK_NOT_SUPPORTED = "The SDK layoutlib version is not supported"; + private static final Result NOT_SUPPORTED_RESULT = + Status.NOT_IMPLEMENTED.createResult(SDK_NOT_SUPPORTED); + private static BufferedImage sImage; + + private static class BridgeRenderSession extends RenderSession { + + @Override + public synchronized BufferedImage getImage() { + if (sImage == null) { + sImage = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = sImage.createGraphics(); + g.clearRect(0, 0, 500, 500); + g.drawString(SDK_NOT_SUPPORTED, 20, 20); + g.dispose(); + } + + return sImage; + } + + @Override + public Result render(long timeout, boolean forceMeasure) { + return NOT_SUPPORTED_RESULT; + } + + @Override + public Result measure(long timeout) { + return NOT_SUPPORTED_RESULT; + } + + @Override + public Result getResult() { + return NOT_SUPPORTED_RESULT; + } + } + + + @Override + public RenderSession createSession(SessionParams params) { + return new BridgeRenderSession(); + } + + @Override + public int getApiLevel() { + return 0; + } +} diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java index 21b7a04e07dc..84ec8b7d0fdd 100644 --- a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java +++ b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java @@ -171,6 +171,9 @@ public class Hprof implements ClassDataRetriever { arg1.getDevice().getSyncService().pullFile(arg0, target.getAbsoluteFile().toString(), new NullProgressMonitor()); } catch (Exception e) { + if (target != null) { + target.delete(); + } e.printStackTrace(); target = null; } @@ -189,6 +192,9 @@ public class Hprof implements ClassDataRetriever { out.write(arg0); out.close(); } catch (Exception e) { + if (target != null) { + target.delete(); + } e.printStackTrace(); target = null; } @@ -215,6 +221,8 @@ public class Hprof implements ClassDataRetriever { return analyzeHprof(hprofLocalFile); } catch (Exception e) { throw new RuntimeException(e); + } finally { + hprofLocalFile.delete(); } } } diff --git a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java new file mode 100755 index 000000000000..c3287481aec9 --- /dev/null +++ b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java @@ -0,0 +1,196 @@ +/* + * 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. + */ + +package android.net.wifi; + +import android.Manifest.permission; +import android.annotation.SystemApi; +import android.content.Context; +import android.net.INetworkScoreCache; +import android.net.NetworkKey; +import android.net.ScoredNetwork; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * {@link INetworkScoreCache} implementation for Wifi Networks. + * + * @hide + */ +public class WifiNetworkScoreCache extends INetworkScoreCache.Stub { + private static final String TAG = "WifiNetworkScoreCache"; + private static final boolean DBG = false; + + // A Network scorer returns a score in the range [-128, +127] + // We treat the lowest possible score as though there were no score, effectively allowing the + // scorer to provide an RSSI threshold below which a network should not be used. + public static final int INVALID_NETWORK_SCORE = Byte.MIN_VALUE; + private final Context mContext; + + // The key is of the form "<ssid>"<bssid> + // TODO: What about SSIDs that can't be encoded as UTF-8? + private final Map<String, ScoredNetwork> mNetworkCache; + + public WifiNetworkScoreCache(Context context) { + mContext = context; + mNetworkCache = new HashMap<String, ScoredNetwork>(); + } + + @Override public final void updateScores(List<ScoredNetwork> networks) { + if (networks == null) { + return; + } + Log.e(TAG, "updateScores list size=" + networks.size()); + + synchronized(mNetworkCache) { + for (ScoredNetwork network : networks) { + String networkKey = buildNetworkKey(network); + if (networkKey == null) continue; + mNetworkCache.put(networkKey, network); + } + } + } + + @Override public final void clearScores() { + synchronized (mNetworkCache) { + mNetworkCache.clear(); + } + } + + /** + * Returns whether there is any score info for the given ScanResult. + * + * This includes null-score info, so it should only be used when determining whether to request + * scores from the network scorer. + */ + public boolean isScoredNetwork(ScanResult result) { + return getScoredNetwork(result) != null; + } + + /** + * Returns whether there is a non-null score curve for the given ScanResult. + * + * A null score curve has special meaning - we should never connect to an ephemeral network if + * the score curve is null. + */ + public boolean hasScoreCurve(ScanResult result) { + ScoredNetwork network = getScoredNetwork(result); + return network != null && network.rssiCurve != null; + } + + public int getNetworkScore(ScanResult result) { + + int score = INVALID_NETWORK_SCORE; + + ScoredNetwork network = getScoredNetwork(result); + if (network != null && network.rssiCurve != null) { + score = network.rssiCurve.lookupScore(result.level); + if (DBG) { + Log.e(TAG, "getNetworkScore found scored network " + network.networkKey + + " score " + Integer.toString(score) + + " RSSI " + result.level); + } + } + return score; + } + + /** + * Returns the ScoredNetwork metered hint for a given ScanResult. + * + * If there is no ScoredNetwork associated with the ScanResult then false will be returned. + */ + public boolean getMeteredHint(ScanResult result) { + ScoredNetwork network = getScoredNetwork(result); + return network != null && network.meteredHint; + } + + public int getNetworkScore(ScanResult result, boolean isActiveNetwork) { + + int score = INVALID_NETWORK_SCORE; + + ScoredNetwork network = getScoredNetwork(result); + if (network != null && network.rssiCurve != null) { + score = network.rssiCurve.lookupScore(result.level, isActiveNetwork); + if (DBG) { + Log.e(TAG, "getNetworkScore found scored network " + network.networkKey + + " score " + Integer.toString(score) + + " RSSI " + result.level + + " isActiveNetwork " + isActiveNetwork); + } + } + return score; + } + + private ScoredNetwork getScoredNetwork(ScanResult result) { + String key = buildNetworkKey(result); + if (key == null) return null; + + //find it + synchronized(mNetworkCache) { + ScoredNetwork network = mNetworkCache.get(key); + return network; + } + } + + private String buildNetworkKey(ScoredNetwork network) { + if (network == null || network.networkKey == null) return null; + if (network.networkKey.wifiKey == null) return null; + if (network.networkKey.type == NetworkKey.TYPE_WIFI) { + String key = network.networkKey.wifiKey.ssid; + if (key == null) return null; + if (network.networkKey.wifiKey.bssid != null) { + key = key + network.networkKey.wifiKey.bssid; + } + return key; + } + return null; + } + + private String buildNetworkKey(ScanResult result) { + if (result == null || result.SSID == null) { + return null; + } + StringBuilder key = new StringBuilder("\""); + key.append(result.SSID); + key.append("\""); + if (result.BSSID != null) { + key.append(result.BSSID); + } + return key.toString(); + } + + @Override protected final void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG); + writer.println("WifiNetworkScoreCache"); + writer.println(" All score curves:"); + for (Map.Entry<String, ScoredNetwork> entry : mNetworkCache.entrySet()) { + ScoredNetwork scoredNetwork = entry.getValue(); + writer.println(" " + entry.getKey() + ": " + scoredNetwork.rssiCurve + + ", meteredHint=" + scoredNetwork.meteredHint); + } + writer.println(" Current network scores:"); + WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + for (ScanResult scanResult : wifiManager.getScanResults()) { + writer.println(" " + buildNetworkKey(scanResult) + ": " + getNetworkScore(scanResult)); + } + } + +} diff --git a/wifi/tests/Android.mk b/wifi/tests/Android.mk index 5850feee004a..eac49d20b88d 100644 --- a/wifi/tests/Android.mk +++ b/wifi/tests/Android.mk @@ -50,6 +50,7 @@ LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude) LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-test \ + guava \ mockito-target-minus-junit4 \ frameworks-base-testutils \ diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java new file mode 100644 index 000000000000..f8549b9cb715 --- /dev/null +++ b/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java @@ -0,0 +1,156 @@ +/* + * 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.net.wifi; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.NetworkKey; +import android.net.RssiCurve; +import android.net.ScoredNetwork; +import android.net.WifiKey; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link WifiNetworkScoreCache}. */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class WifiNetworkScoreCacheTest { + + @Mock public Context mockContext; // isn't used, can be null + @Mock private RssiCurve mockRssiCurve; + + public static final String SSID = "ssid"; + public static final String FORMATTED_SSID = "\"" + SSID + "\""; + public static final String BSSID = "AA:AA:AA:AA:AA:AA"; + + public static final WifiKey VALID_KEY = new WifiKey(FORMATTED_SSID, BSSID); + + public static final ScanResult VALID_SCAN_RESULT = buildScanResult(SSID, BSSID); + + private ScoredNetwork mValidScoredNetwork; + private WifiNetworkScoreCache mScoreCache = + new WifiNetworkScoreCache(mockContext); + + private static ScanResult buildScanResult(String ssid, String bssid) { + return new ScanResult( + WifiSsid.createFromAsciiEncoded(ssid), + bssid, + "" /* caps */, + 0 /* level */, + 0 /* frequency */, + 0 /* tsf */, + 0 /* distCm */, + 0 /* distSdCm*/); + } + + private static ScoredNetwork buildScoredNetwork(WifiKey key, RssiCurve curve) { + return new ScoredNetwork(new NetworkKey(key), curve); + } + + // Called from setup + private void initializeCacheWithValidScoredNetwork() { + mScoreCache.updateScores(ImmutableList.of(mValidScoredNetwork)); + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mValidScoredNetwork = buildScoredNetwork(VALID_KEY, mockRssiCurve); + mScoreCache = new WifiNetworkScoreCache(mockContext); + initializeCacheWithValidScoredNetwork(); + } + + + @Test + public void isScoredNetworkShouldReturnTrueAfterUpdateScoresIsCalled() { + assertTrue(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT)); + } + + @Test + public void isScoredNetworkShouldReturnFalseAfterClearScoresIsCalled() { + mScoreCache.clearScores(); + assertFalse(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT)); + } + + @Test + public void updateScoresShouldAddNewNetwork() { + WifiKey key2 = new WifiKey("\"ssid2\"", BSSID); + ScoredNetwork network2 = buildScoredNetwork(key2, mockRssiCurve); + ScanResult result2 = buildScanResult("ssid2", BSSID); + + mScoreCache.updateScores(ImmutableList.of(network2)); + + assertTrue(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT)); + assertTrue(mScoreCache.isScoredNetwork(result2)); + } + + @Test + public void hasScoreCurveShouldReturnTrue() { + assertTrue(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT)); + } + + @Test + public void hasScoreCurveShouldReturnFalseWhenNoCachedNetwork() { + ScanResult unscored = buildScanResult("fake", BSSID); + assertFalse(mScoreCache.hasScoreCurve(unscored)); + } + + @Test + public void hasScoreCurveShouldReturnFalseWhenScoredNetworkHasNoCurve() { + ScoredNetwork noCurve = buildScoredNetwork(VALID_KEY, null /* rssiCurve */); + mScoreCache.updateScores(ImmutableList.of(noCurve)); + + assertFalse(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT)); + } + + @Test + public void getNetworkScoreShouldReturnScore() { + final byte score = 50; + final int rssi = -70; + ScanResult result = new ScanResult(VALID_SCAN_RESULT); + result.level = rssi; + + when(mockRssiCurve.lookupScore(rssi)).thenReturn(score); + + assertEquals(score, mScoreCache.getNetworkScore(result)); + } + + @Test + public void getMeteredHintShouldReturnFalse() { + assertFalse(mScoreCache.getMeteredHint(VALID_SCAN_RESULT)); + } + + @Test + public void getMeteredHintShouldReturnTrue() { + ScoredNetwork network = + new ScoredNetwork(new NetworkKey(VALID_KEY), mockRssiCurve, true /* metered Hint */); + mScoreCache.updateScores(ImmutableList.of(network)); + + assertTrue(mScoreCache.getMeteredHint(VALID_SCAN_RESULT)); + } +} |