diff options
100 files changed, 3352 insertions, 1558 deletions
diff --git a/Android.mk b/Android.mk index 9676958605dd..d7c16d14656f 100644 --- a/Android.mk +++ b/Android.mk @@ -307,7 +307,6 @@ files_to_check_apis_generated := \ files_to_document := \ $(files_to_check_apis) \ $(call find-other-java-files,\ - $(addprefix ../../, $(FRAMEWORKS_DATA_BINDING_JAVA_SRC_DIRS)) \ test-runner/src) # These are relative to frameworks/base diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java index 92ee7ccfc294..5653a039a9ed 100644 --- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java @@ -16,6 +16,8 @@ package android.text; +import static android.text.TextDirectionHeuristics.LTR; + import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; @@ -182,4 +184,84 @@ public class StaticLayoutPerfTest { .build(); } } + + @Test + public void testCreate_MeasuredText_NoStyled_Greedy_NoHyphenation() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final PremeasuredText text = PremeasuredText.build( + generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); + state.resumeTiming(); + + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .build(); + } + } + + @Test + public void testCreate_MeasuredText_NoStyled_Greedy_Hyphenation() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final PremeasuredText text = PremeasuredText.build( + generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); + state.resumeTiming(); + + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .build(); + } + } + + @Test + public void testCreate_MeasuredText_NoStyled_Balanced_NoHyphenation() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final PremeasuredText text = PremeasuredText.build( + generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); + state.resumeTiming(); + + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) + .build(); + } + } + + @Test + public void testCreate_MeasuredText_NoStyled_Balanced_Hyphenation() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final PremeasuredText text = PremeasuredText.build( + generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); + state.resumeTiming(); + + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) + .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) + .build(); + } + } + + @Test + public void testCreate_MeasuredText_Styled_Greedy_NoHyphenation() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final PremeasuredText text = PremeasuredText.build( + generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT, LTR); + state.resumeTiming(); + + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .build(); + } + } } diff --git a/api/current.txt b/api/current.txt index 51bf811a413f..843d9bd384f6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6395,6 +6395,7 @@ package android.app.admin { method public boolean isBackupServiceEnabled(android.content.ComponentName); method public deprecated boolean isCallerApplicationRestrictionsManagingPackage(); method public boolean isDeviceOwnerApp(java.lang.String); + method public boolean isEphemeralUser(android.content.ComponentName); method public boolean isLockTaskPermitted(java.lang.String); method public boolean isLogoutEnabled(); method public boolean isManagedProfile(android.content.ComponentName); @@ -6561,6 +6562,7 @@ package android.app.admin { field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10 field public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 8; // 0x8 field public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1; // 0x1 + field public static final int LEAVE_ALL_SYSTEM_APPS_ENABLED = 16; // 0x10 field public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 16; // 0x10 field public static final int LOCK_TASK_FEATURE_HOME = 4; // 0x4 field public static final int LOCK_TASK_FEATURE_KEYGUARD = 32; // 0x20 @@ -7027,6 +7029,8 @@ package android.app.slice { ctor public Slice.Builder(android.app.slice.Slice.Builder); method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice); method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice, java.lang.String); + method public android.app.slice.Slice.Builder addBundle(android.os.Bundle, java.lang.String, java.lang.String...); + method public android.app.slice.Slice.Builder addBundle(android.os.Bundle, java.lang.String, java.util.List<java.lang.String>); method public deprecated android.app.slice.Slice.Builder addColor(int, java.lang.String, java.lang.String...); method public deprecated android.app.slice.Slice.Builder addColor(int, java.lang.String, java.util.List<java.lang.String>); method public android.app.slice.Slice.Builder addHints(java.lang.String...); @@ -7050,6 +7054,7 @@ package android.app.slice { public final class SliceItem implements android.os.Parcelable { method public int describeContents(); method public android.app.PendingIntent getAction(); + method public android.os.Bundle getBundle(); method public deprecated int getColor(); method public java.lang.String getFormat(); method public java.util.List<java.lang.String> getHints(); @@ -7064,6 +7069,7 @@ package android.app.slice { method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR; field public static final java.lang.String FORMAT_ACTION = "action"; + field public static final java.lang.String FORMAT_BUNDLE = "bundle"; field public static final deprecated java.lang.String FORMAT_COLOR = "color"; field public static final java.lang.String FORMAT_IMAGE = "image"; field public static final java.lang.String FORMAT_INT = "int"; @@ -42277,6 +42283,27 @@ package android.text { method public abstract int getSpanTypeId(); } + public class PremeasuredText implements android.text.Spanned { + method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic); + method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int); + method public char charAt(int); + method public int getEnd(); + method public android.text.TextPaint getPaint(); + method public int getParagraphCount(); + method public int getParagraphEnd(int); + method public int getParagraphStart(int); + method public int getSpanEnd(java.lang.Object); + method public int getSpanFlags(java.lang.Object); + method public int getSpanStart(java.lang.Object); + method public <T> T[] getSpans(int, int, java.lang.Class<T>); + method public int getStart(); + method public java.lang.CharSequence getText(); + method public android.text.TextDirectionHeuristic getTextDir(); + method public int length(); + method public int nextSpanTransition(int, int, java.lang.Class); + method public java.lang.CharSequence subSequence(int, int); + } + public class Selection { method public static boolean extendDown(android.text.Spannable, android.text.Layout); method public static boolean extendLeft(android.text.Spannable, android.text.Layout); diff --git a/api/system-current.txt b/api/system-current.txt index 15f070a5d5dd..d87fcbca9f2e 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -613,7 +613,9 @@ package android.app.usage { public final class UsageStatsManager { method public int getAppStandbyBucket(java.lang.String); + method public java.util.Map<java.lang.String, java.lang.Integer> getAppStandbyBuckets(); method public void setAppStandbyBucket(java.lang.String, int); + method public void setAppStandbyBuckets(java.util.Map<java.lang.String, java.lang.Integer>); method public void whitelistAppTemporarily(java.lang.String, long, android.os.UserHandle); field public static final int STANDBY_BUCKET_EXEMPTED = 5; // 0x5 field public static final int STANDBY_BUCKET_NEVER = 50; // 0x32 diff --git a/cmds/ime/Android.mk b/cmds/ime/Android.mk index 6803fc01154c..ca608e8c2c34 100644 --- a/cmds/ime/Android.mk +++ b/cmds/ime/Android.mk @@ -3,14 +3,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_MODULE := imelib -LOCAL_MODULE_STEM := ime -include $(BUILD_JAVA_LIBRARY) - -include $(CLEAR_VARS) LOCAL_MODULE := ime LOCAL_MODULE_CLASS := EXECUTABLES LOCAL_SRC_FILES := ime -LOCAL_REQUIRED_MODULES := imelib include $(BUILD_PREBUILT) diff --git a/cmds/ime/ime b/cmds/ime/ime index 1a1fdd96da6e..180dc761055b 100755 --- a/cmds/ime/ime +++ b/cmds/ime/ime @@ -1,8 +1,2 @@ #!/system/bin/sh -# Script to start "pm" on the device, which has a very rudimentary -# shell. -# -base=/system -export CLASSPATH=$base/framework/ime.jar -exec app_process $base/bin com.android.commands.ime.Ime "$@" - +exec cmd input_method "$@" diff --git a/cmds/ime/src/com/android/commands/ime/Ime.java b/cmds/ime/src/com/android/commands/ime/Ime.java deleted file mode 100644 index 72a0af6c2d99..000000000000 --- a/cmds/ime/src/com/android/commands/ime/Ime.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2007 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.commands.ime; - -import com.android.internal.view.IInputMethodManager; - -import android.os.RemoteException; -import android.os.ServiceManager; -import android.util.PrintStreamPrinter; -import android.util.Printer; -import android.view.inputmethod.InputMethodInfo; - -import java.util.List; - -public final class Ime { - IInputMethodManager mImm; - - private String[] mArgs; - private int mNextArg; - private String mCurArgData; - - private static final String IMM_NOT_RUNNING_ERR = - "Error: Could not access the Input Method Manager. Is the system running?"; - - public static void main(String[] args) { - new Ime().run(args); - } - - public void run(String[] args) { - if (args.length < 1) { - showUsage(); - return; - } - - mImm = IInputMethodManager.Stub.asInterface(ServiceManager.getService("input_method")); - if (mImm == null) { - System.err.println(IMM_NOT_RUNNING_ERR); - return; - } - - mArgs = args; - String op = args[0]; - mNextArg = 1; - - if ("list".equals(op)) { - runList(); - return; - } - - if ("enable".equals(op)) { - runSetEnabled(true); - return; - } - - if ("disable".equals(op)) { - runSetEnabled(false); - return; - } - - if ("set".equals(op)) { - runSet(); - return; - } - - if (op != null) { - System.err.println("Error: unknown command '" + op + "'"); - } - showUsage(); - } - - /** - * Execute the list sub-command. - */ - private void runList() { - String opt; - boolean all = false; - boolean brief = false; - while ((opt=nextOption()) != null) { - if (opt.equals("-a")) { - all = true; - } else if (opt.equals("-s")) { - brief = true; - } else { - System.err.println("Error: Unknown option: " + opt); - showUsage(); - return; - } - } - - - List<InputMethodInfo> methods; - if (!all) { - try { - methods = mImm.getEnabledInputMethodList(); - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(IMM_NOT_RUNNING_ERR); - return; - } - } else { - try { - methods = mImm.getInputMethodList(); - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(IMM_NOT_RUNNING_ERR); - return; - } - } - - if (methods != null) { - Printer pr = new PrintStreamPrinter(System.out); - for (int i=0; i<methods.size(); i++) { - InputMethodInfo imi = methods.get(i); - if (brief) { - System.out.println(imi.getId()); - } else { - System.out.println(imi.getId() + ":"); - imi.dump(pr, " "); - } - } - } - } - - private void runSetEnabled(boolean state) { - String id = nextArg(); - if (id == null) { - System.err.println("Error: no input method ID specified"); - showUsage(); - return; - } - - try { - boolean res = mImm.setInputMethodEnabled(id, state); - if (state) { - System.out.println("Input method " + id + ": " - + (res ? "already enabled" : "now enabled")); - } else { - System.out.println("Input method " + id + ": " - + (res ? "now disabled" : "already disabled")); - } - } catch (IllegalArgumentException e) { - System.err.println("Error: " + e.getMessage()); - return; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(IMM_NOT_RUNNING_ERR); - return; - } - } - - private void runSet() { - String id = nextArg(); - if (id == null) { - System.err.println("Error: no input method ID specified"); - showUsage(); - return; - } - - try { - mImm.setInputMethod(null, id); - System.out.println("Input method " + id + " selected"); - } catch (IllegalArgumentException e) { - System.err.println("Error: " + e.getMessage()); - return; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(IMM_NOT_RUNNING_ERR); - return; - } - } - - private String nextOption() { - if (mNextArg >= mArgs.length) { - return null; - } - String arg = mArgs[mNextArg]; - if (!arg.startsWith("-")) { - return null; - } - mNextArg++; - if (arg.equals("--")) { - return null; - } - if (arg.length() > 1 && arg.charAt(1) != '-') { - if (arg.length() > 2) { - mCurArgData = arg.substring(2); - return arg.substring(0, 2); - } else { - mCurArgData = null; - return arg; - } - } - mCurArgData = null; - return arg; - } - - private String nextOptionData() { - if (mCurArgData != null) { - return mCurArgData; - } - if (mNextArg >= mArgs.length) { - return null; - } - String data = mArgs[mNextArg]; - mNextArg++; - return data; - } - - private String nextArg() { - if (mNextArg >= mArgs.length) { - return null; - } - String arg = mArgs[mNextArg]; - mNextArg++; - return arg; - } - - private static void showUsage() { - System.err.println("usage: ime list [-a] [-s]"); - System.err.println(" ime enable ID"); - System.err.println(" ime disable ID"); - System.err.println(" ime set ID"); - System.err.println(""); - System.err.println("The list command prints all enabled input methods. Use"); - System.err.println("the -a option to see all input methods. Use"); - System.err.println("the -s option to see only a single summary line of each."); - System.err.println(""); - System.err.println("The enable command allows the given input method ID to be used."); - System.err.println(""); - System.err.println("The disable command disallows the given input method ID from use."); - System.err.println(""); - System.err.println("The set command switches to the given input method ID."); - } -} diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp index 2ef037143f07..fc0bdccb268b 100644 --- a/cmds/incident_helper/Android.bp +++ b/cmds/incident_helper/Android.bp @@ -38,6 +38,7 @@ cc_binary { cc_test { name: "incident_helper_test", + test_suites: ["device-tests"], defaults: ["incident_helper_defaults"], local_include_dirs: ["src/"], diff --git a/cmds/incident_helper/AndroidTest.xml b/cmds/incident_helper/AndroidTest.xml new file mode 100644 index 000000000000..6d242bcf435a --- /dev/null +++ b/cmds/incident_helper/AndroidTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> +<configuration description="Config for incident_helper_test"> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="incident_helper_test->/data/nativetest/incident_helper_test" /> + <option name="push" value="testdata->/data/nativetest/testdata" /> + </target_preparer> + <option name="test-suite-tag" value="apct" /> + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/nativetest" /> + <option name="module-name" value="incident_helper_test" /> + </test> +</configuration> diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk index cb5fd02ef30b..fb8ef6338d90 100644 --- a/cmds/incidentd/Android.mk +++ b/cmds/incidentd/Android.mk @@ -120,14 +120,6 @@ LOCAL_SHARED_LIBRARIES := \ libservices \ libutils \ -relative_path_prefix := nativetest64/incidentd_test -testdata_files := $(call find-subdir-files, testdata/*) - -GEN := $(addprefix $(TARGET_OUT_DATA)/$(relative_path_prefix)/, $(testdata_files)) -$(GEN): PRIVATE_PATH := $(LOCAL_PATH) -$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@ -$(GEN): $(TARGET_OUT_DATA)/$(relative_path_prefix)/testdata/% : $(LOCAL_PATH)/testdata/% - $(transform-generated-source) -LOCAL_GENERATED_SOURCES += $(GEN) +LOCAL_TEST_DATA := $(call find-test-data-in-subdirs, $(LOCAL_PATH), *, testdata) include $(BUILD_NATIVE_TEST) diff --git a/cmds/incidentd/AndroidTest.xml b/cmds/incidentd/AndroidTest.xml new file mode 100644 index 000000000000..7f0e4ee305e3 --- /dev/null +++ b/cmds/incidentd/AndroidTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> +<configuration description="Config for incidentd_test"> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="incidentd_test->/data/nativetest/incidentd_test" /> + <option name="push" value="testdata->/data/nativetest/testdata" /> + </target_preparer> + <option name="test-suite-tag" value="apct" /> + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/nativetest" /> + <option name="module-name" value="incidentd_test" /> + </test> +</configuration> diff --git a/cmds/incidentd/README.md b/cmds/incidentd/README.md index daa39248e3f8..ad0fa08c7326 100644 --- a/cmds/incidentd/README.md +++ b/cmds/incidentd/README.md @@ -5,13 +5,19 @@ For the first time, build the test and create an empty directly on device: ``` -root$ make -j incidentd_test && adb shell mkdir /data/nativetest64/incidentd_test +root$ make -j incidentd_test && adb shell mkdir /data/nativetest/incidentd_test ``` -Run the test on a device +Run the test on a device manually ``` root$ mmm -j frameworks/base/cmds/incidentd && \ -adb push $OUT/data/nativetest64/incidentd_test/* /data/nativetest64/incidentd_test/ && \ -adb shell /data/nativetest64/incidentd_test/incidentd_test 2>/dev/null +adb push $OUT/data/nativetest/incidentd_test/* /data/nativetest/incidentd_test/ && \ +adb shell /data/nativetest/incidentd_test/incidentd_test 2>/dev/null ``` + +Run the test via AndroidTest.xml + +``` +root$ atest incidentd_test +```
\ No newline at end of file diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index 2df0c90e9baf..7589b9387924 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -58,7 +58,7 @@ const int FIELD_ID_NAME = 2; const int FIELD_ID_METRICS = 1; const int FIELD_ID_UID_MAP = 2; -#define STATS_DATA_DIR "/data/system/stats-data" +#define STATS_DATA_DIR "/data/misc/stats-data" StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AnomalyMonitor>& anomalyMonitor, diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index dc12efb2666e..d8f0facbaa9b 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -46,7 +46,7 @@ namespace os { namespace statsd { constexpr const char* kPermissionDump = "android.permission.DUMP"; -#define STATS_SERVICE_DIR "/data/system/stats-service" +#define STATS_SERVICE_DIR "/data/misc/stats-service" // ====================================================================== /** diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index 540199d59910..e52b2739c991 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -29,7 +29,7 @@ namespace android { namespace os { namespace statsd { -#define STATS_SERVICE_DIR "/data/system/stats-service" +#define STATS_SERVICE_DIR "/data/misc/stats-service" using android::base::StringPrintf; using std::unique_ptr; diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index 62f06a760656..3a4dfdaaf54d 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -30,7 +30,7 @@ namespace android { namespace os { namespace statsd { -#define STATS_SERVICE_DIR "/data/system/stats-service" +#define STATS_SERVICE_DIR "/data/misc/stats-service" // for ConfigMetricsReportList const int FIELD_ID_REPORTS = 2; diff --git a/cmds/uiautomator/library/Android.mk b/cmds/uiautomator/library/Android.mk index 22cffe60b2a8..62a2865d0bcc 100644 --- a/cmds/uiautomator/library/Android.mk +++ b/cmds/uiautomator/library/Android.mk @@ -36,7 +36,7 @@ include $(BUILD_STATIC_JAVA_LIBRARY) # Generate the stub source files include $(CLEAR_VARS) LOCAL_SRC_FILES := $(uiautomator.core_src_files) -LOCAL_JAVA_LIBRARIES := $(uiautomator.core_java_libraries) legacy-android-test +LOCAL_JAVA_LIBRARIES := $(uiautomator.core_java_libraries) android.test.base LOCAL_MODULE_CLASS := JAVA_LIBRARIES LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/core-src \ $(LOCAL_PATH)/testrunner-src diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index b6fb12018201..c1a51044e349 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -256,8 +256,10 @@ public class AppOpsManager { public static final int OP_RUN_ANY_IN_BACKGROUND = 70; /** @hide Change Wi-Fi connectivity state */ public static final int OP_CHANGE_WIFI_STATE = 71; + /** @hide Request package deletion through package installer */ + public static final int OP_REQUEST_DELETE_PACKAGES = 72; /** @hide */ - public static final int _NUM_OP = 72; + public static final int _NUM_OP = 73; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -410,6 +412,7 @@ public class AppOpsManager { OP_CAMERA, // Body sensors OP_BODY_SENSORS, + OP_REQUEST_DELETE_PACKAGES, // APPOP PERMISSIONS OP_ACCESS_NOTIFICATIONS, @@ -499,6 +502,7 @@ public class AppOpsManager { OP_ANSWER_PHONE_CALLS, OP_RUN_ANY_IN_BACKGROUND, OP_CHANGE_WIFI_STATE, + OP_REQUEST_DELETE_PACKAGES, }; /** @@ -578,6 +582,7 @@ public class AppOpsManager { OPSTR_ANSWER_PHONE_CALLS, null, // OP_RUN_ANY_IN_BACKGROUND null, // OP_CHANGE_WIFI_STATE + null, // OP_REQUEST_DELETE_PACKAGES }; /** @@ -657,6 +662,7 @@ public class AppOpsManager { "ANSWER_PHONE_CALLS", "RUN_ANY_IN_BACKGROUND", "CHANGE_WIFI_STATE", + "REQUEST_DELETE_PACKAGES", }; /** @@ -736,6 +742,7 @@ public class AppOpsManager { Manifest.permission.ANSWER_PHONE_CALLS, null, // no permission for OP_RUN_ANY_IN_BACKGROUND Manifest.permission.CHANGE_WIFI_STATE, + Manifest.permission.REQUEST_DELETE_PACKAGES, }; /** @@ -816,6 +823,7 @@ public class AppOpsManager { null, // ANSWER_PHONE_CALLS null, // OP_RUN_ANY_IN_BACKGROUND null, // OP_CHANGE_WIFI_STATE + null, // REQUEST_DELETE_PACKAGES }; /** @@ -895,6 +903,7 @@ public class AppOpsManager { false, // ANSWER_PHONE_CALLS false, // OP_RUN_ANY_IN_BACKGROUND false, // OP_CHANGE_WIFI_STATE + false, // OP_REQUEST_DELETE_PACKAGES }; /** @@ -973,6 +982,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS AppOpsManager.MODE_ALLOWED, // OP_RUN_ANY_IN_BACKGROUND AppOpsManager.MODE_ALLOWED, // OP_CHANGE_WIFI_STATE + AppOpsManager.MODE_ALLOWED, // REQUEST_DELETE_PACKAGES }; /** @@ -1055,6 +1065,7 @@ public class AppOpsManager { false, // ANSWER_PHONE_CALLS false, // OP_RUN_ANY_IN_BACKGROUND false, // OP_CHANGE_WIFI_STATE + false, // OP_REQUEST_DELETE_PACKAGES }; /** diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 34cd67e455ce..cfff3614a8e4 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3480,6 +3480,16 @@ public class DevicePolicyManager { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION"; + + /** + * Broadcast action: notify managed provisioning that new managed user is created. + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MANAGED_USER_CREATED = + "android.app.action.MANAGED_USER_CREATED"; + /** * Widgets are enabled in keyguard */ @@ -6205,20 +6215,25 @@ public class DevicePolicyManager { public static final int MAKE_USER_DEMO = 0x0004; /** - * Flag used by {@link #createAndManageUser} to specificy that the newly created user should be + * Flag used by {@link #createAndManageUser} to specify that the newly created user should be * started in the background as part of the user creation. */ - // TODO: Investigate solutions for the case where reboot happens before setup is completed. public static final int START_USER_IN_BACKGROUND = 0x0008; /** + * Flag used by {@link #createAndManageUser} to specify that the newly created user should skip + * the disabling of system apps during provisioning. + */ + public static final int LEAVE_ALL_SYSTEM_APPS_ENABLED = 0x0010; + + /** * @hide */ @IntDef( flag = true, - prefix = {"SKIP_", "MAKE_USER_", "START_"}, + prefix = {"SKIP_", "MAKE_USER_", "START_", "LEAVE_"}, value = {SKIP_SETUP_WIZARD, MAKE_USER_EPHEMERAL, MAKE_USER_DEMO, - START_USER_IN_BACKGROUND} + START_USER_IN_BACKGROUND, LEAVE_ALL_SYSTEM_APPS_ENABLED} ) @Retention(RetentionPolicy.SOURCE) public @interface CreateAndManageUserFlags {} @@ -6361,6 +6376,21 @@ public class DevicePolicyManager { } /** + * Checks if the profile owner is running in an ephemeral user. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @return whether the profile owner is running in an ephemeral user. + */ + public boolean isEphemeralUser(@NonNull ComponentName admin) { + throwIfParentInstance("isEphemeralUser"); + try { + return mService.isEphemeralUser(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Retrieves the application restrictions for a given target application running in the calling * user. * <p> @@ -8664,4 +8694,25 @@ public class DevicePolicyManager { */ void onApplicationUserDataCleared(String packageName, boolean succeeded); } + + /** + * Returns set of system apps that should be removed during provisioning. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param userId ID of the user to be provisioned. + * @param provisioningAction action indicating type of provisioning, should be one of + * {@link #ACTION_PROVISION_MANAGED_DEVICE}, {@link #ACTION_PROVISION_MANAGED_PROFILE} or + * {@link #ACTION_PROVISION_MANAGED_USER}. + * + * @hide + */ + public Set<String> getDisallowedSystemApps(ComponentName admin, int userId, + String provisioningAction) { + try { + return new ArraySet<>( + mService.getDisallowedSystemApps(admin, userId, provisioningAction)); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 81da197f2129..b76618b25e7d 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -358,6 +358,7 @@ interface IDevicePolicyManager { IApplicationThread caller, IBinder token, in Intent service, IServiceConnection connection, int flags, int targetUserId); List<UserHandle> getBindDeviceAdminTargetUsers(in ComponentName admin); + boolean isEphemeralUser(in ComponentName admin); long getLastSecurityLogRetrievalTime(); long getLastBugReportRequestTime(); @@ -375,4 +376,6 @@ interface IDevicePolicyManager { void setLogoutEnabled(in ComponentName admin, boolean enabled); boolean isLogoutEnabled(); + + List<String> getDisallowedSystemApps(in ComponentName admin, int userId, String provisioningAction); } diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java index 9b3b4624f42e..b13067ee97e3 100644 --- a/core/java/android/app/slice/Slice.java +++ b/core/java/android/app/slice/Slice.java @@ -463,6 +463,32 @@ public final class Slice implements Parcelable { } /** + * Add a bundle to the slice being constructed. + * <p>Expected to be used for support library extension, should not be used for general + * development + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} + */ + public Slice.Builder addBundle(Bundle bundle, @Nullable String subType, + @SliceHint String... hints) { + mItems.add(new SliceItem(bundle, SliceItem.FORMAT_BUNDLE, subType, + hints)); + return this; + } + + /** + * Add a bundle to the slice being constructed. + * <p>Expected to be used for support library extension, should not be used for general + * development + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} + */ + public Slice.Builder addBundle(Bundle bundle, @Nullable String subType, + @SliceHint List<String> hints) { + return addBundle(bundle, subType, hints.toArray(new String[hints.size()])); + } + + /** * Construct the slice. */ public Slice build() { diff --git a/core/java/android/app/slice/SliceItem.java b/core/java/android/app/slice/SliceItem.java index 5d04c3e813b1..bcfd413fb823 100644 --- a/core/java/android/app/slice/SliceItem.java +++ b/core/java/android/app/slice/SliceItem.java @@ -21,6 +21,7 @@ import android.annotation.StringDef; import android.app.PendingIntent; import android.app.RemoteInput; import android.graphics.drawable.Icon; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -47,6 +48,7 @@ import java.util.List; * <li>{@link #FORMAT_INT}</li> * <li>{@link #FORMAT_TIMESTAMP}</li> * <li>{@link #FORMAT_REMOTE_INPUT}</li> + * <li>{@link #FORMAT_BUNDLE}</li> * * The hints that a {@link SliceItem} are a set of strings which annotate * the content. The hints that are guaranteed to be understood by the system @@ -54,6 +56,8 @@ import java.util.List; */ public final class SliceItem implements Parcelable { + private static final String TAG = "SliceItem"; + /** * @hide */ @@ -65,6 +69,7 @@ public final class SliceItem implements Parcelable { FORMAT_INT, FORMAT_TIMESTAMP, FORMAT_REMOTE_INPUT, + FORMAT_BUNDLE, }) @Retention(RetentionPolicy.SOURCE) public @interface SliceType {} @@ -105,6 +110,10 @@ public final class SliceItem implements Parcelable { * A {@link SliceItem} that contains a {@link RemoteInput}. */ public static final String FORMAT_REMOTE_INPUT = "input"; + /** + * A {@link SliceItem} that contains a {@link Bundle}. + */ + public static final String FORMAT_BUNDLE = "bundle"; /** * @hide @@ -143,20 +152,6 @@ public final class SliceItem implements Parcelable { } /** - * @hide - */ - public void addHint(@Slice.SliceHint String hint) { - mHints = ArrayUtils.appendElement(String.class, mHints, hint); - } - - /** - * @hide - */ - public void removeHint(String hint) { - ArrayUtils.removeElement(String.class, mHints, hint); - } - - /** * Get the format of this SliceItem. * <p> * The format will be one of the following types supported by the platform: @@ -167,6 +162,7 @@ public final class SliceItem implements Parcelable { * <li>{@link #FORMAT_INT}</li> * <li>{@link #FORMAT_TIMESTAMP}</li> * <li>{@link #FORMAT_REMOTE_INPUT}</li> + * <li>{@link #FORMAT_BUNDLE}</li> * @see #getSubType() () */ public String getFormat() { @@ -193,6 +189,13 @@ public final class SliceItem implements Parcelable { } /** + * @return The parcelable held by this {@link #FORMAT_BUNDLE} SliceItem + */ + public Bundle getBundle() { + return (Bundle) mObj; + } + + /** * @return The icon held by this {@link #FORMAT_IMAGE} SliceItem */ public Icon getIcon() { @@ -321,6 +324,7 @@ public final class SliceItem implements Parcelable { case FORMAT_SLICE: case FORMAT_IMAGE: case FORMAT_REMOTE_INPUT: + case FORMAT_BUNDLE: ((Parcelable) obj).writeToParcel(dest, flags); break; case FORMAT_ACTION: @@ -357,6 +361,8 @@ public final class SliceItem implements Parcelable { return in.readLong(); case FORMAT_REMOTE_INPUT: return RemoteInput.CREATOR.createFromParcel(in); + case FORMAT_BUNDLE: + return Bundle.CREATOR.createFromParcel(in); } throw new RuntimeException("Unsupported type " + type); } diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl index 4fbbdf2a9281..f089c127d03f 100644 --- a/core/java/android/app/usage/IUsageStatsManager.aidl +++ b/core/java/android/app/usage/IUsageStatsManager.aidl @@ -19,6 +19,8 @@ package android.app.usage; import android.app.usage.UsageEvents; import android.content.pm.ParceledListSlice; +import java.util.Map; + /** * System private API for talking with the UsageStatsManagerService. * @@ -38,4 +40,6 @@ interface IUsageStatsManager { in String[] annotations, String action); int getAppStandbyBucket(String packageName, String callingPackage, int userId); void setAppStandbyBucket(String packageName, int bucket, int userId); + Map getAppStandbyBuckets(String callingPackage, int userId); + void setAppStandbyBuckets(in Map appBuckets, int userId); } diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index d614b20a0788..1fc45c9fdb94 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -324,11 +324,11 @@ public final class UsageStatsManager { * state of the app based on app usage patterns. Standby buckets determine how much an app will * be restricted from running background tasks such as jobs, alarms and certain PendingIntent * callbacks. - * Restrictions increase progressively from {@link #STANDBY_BUCKET_ACTIVE} to + * <p>Restrictions increase progressively from {@link #STANDBY_BUCKET_ACTIVE} to * {@link #STANDBY_BUCKET_RARE}, with {@link #STANDBY_BUCKET_ACTIVE} being the least * restrictive. The battery level of the device might also affect the restrictions. * - * @return the current standby bucket of the calling app. + * @return the current standby bucket of the calling app. One of STANDBY_BUCKET_* constants. */ public @StandbyBuckets int getAppStandbyBucket() { try { @@ -359,7 +359,13 @@ public final class UsageStatsManager { /** * {@hide} - * Changes the app standby state to the provided bucket. + * Changes an app's standby bucket to the provided value. The caller can only set the standby + * bucket for a different app than itself. + * @param packageName the package name of the app to set the bucket for. A SecurityException + * will be thrown if the package name is that of the caller. + * @param bucket the standby bucket to set it to, which should be one of STANDBY_BUCKET_*. + * Setting a standby bucket outside of the range of STANDBY_BUCKET_ACTIVE to + * STANDBY_BUCKET_NEVER will result in a SecurityException. */ @SystemApi @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) @@ -373,6 +379,39 @@ public final class UsageStatsManager { /** * {@hide} + * Returns the current standby bucket of every app that has a bucket assigned to it. + * The caller must hold the permission android.permission.PACKAGE_USAGE_STATS. The key of the + * returned Map is the package name and the value is the bucket assigned to the package. + * @see #getAppStandbyBucket() + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) + public Map<String, Integer> getAppStandbyBuckets() { + try { + return (Map<String, Integer>) mService.getAppStandbyBuckets( + mContext.getOpPackageName(), mContext.getUserId()); + } catch (RemoteException e) { + } + return Collections.EMPTY_MAP; + } + + /** + * {@hide} + * Changes the app standby bucket for multiple apps at once. The Map is keyed by the package + * name and the value is one of STANDBY_BUCKET_*. + * @param appBuckets a map of package name to bucket value. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) + public void setAppStandbyBuckets(Map<String, Integer> appBuckets) { + try { + mService.setAppStandbyBuckets(appBuckets, mContext.getUserId()); + } catch (RemoteException e) { + } + } + + /** + * {@hide} * Temporarily whitelist the specified app for a short duration. This is to allow an app * receiving a high priority message to be able to access the network and acquire wakelocks * even if the device is in power-save mode or the app is currently considered inactive. diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java index a1a9347df690..794435457f25 100644 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ b/core/java/android/bluetooth/BluetoothPbap.java @@ -25,6 +25,10 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Log; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** * The Android Bluetooth API is not finalized, and *will* change. Use at your * own risk. @@ -48,11 +52,10 @@ import android.util.Log; * * @hide */ -public class BluetoothPbap { +public class BluetoothPbap implements BluetoothProfile { private static final String TAG = "BluetoothPbap"; - private static final boolean DBG = true; - private static final boolean VDBG = false; + private static final boolean DBG = false; /** * Intent used to broadcast the change in connection state of the PBAP @@ -111,9 +114,9 @@ public class BluetoothPbap { private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = new IBluetoothStateChangeCallback.Stub() { public void onBluetoothStateChange(boolean up) { - if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); + log("onBluetoothStateChange: up=" + up); if (!up) { - if (VDBG) Log.d(TAG, "Unbinding service..."); + log("Unbinding service..."); synchronized (mConnection) { try { mService = null; @@ -126,7 +129,7 @@ public class BluetoothPbap { synchronized (mConnection) { try { if (mService == null) { - if (VDBG) Log.d(TAG, "Binding service..."); + log("Binding service..."); doBind(); } } catch (Exception re) { @@ -205,47 +208,60 @@ public class BluetoothPbap { } /** - * Get the current state of the BluetoothPbap service. - * - * @return One of the STATE_ return codes, or {@link BluetoothProfile#STATE_DISCONNECTED} - * if this proxy object is currently not connected to the Pbap service. + * {@inheritDoc} */ - public int getState() { - if (VDBG) log("getState()"); + @Override + public List<BluetoothDevice> getConnectedDevices() { + log("getConnectedDevices()"); final IBluetoothPbap service = mService; - if (service != null) { - try { - return service.getState(); - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } - } else { + if (service == null) { Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); + return new ArrayList<BluetoothDevice>(); + } + try { + return service.getConnectedDevices(); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } + return new ArrayList<BluetoothDevice>(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getConnectionState(BluetoothDevice device) { + log("getConnectionState: device=" + device); + final IBluetoothPbap service = mService; + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.STATE_DISCONNECTED; + } + try { + return service.getConnectionState(device); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); } return BluetoothProfile.STATE_DISCONNECTED; } /** - * Get the currently connected remote Bluetooth device (PCE). - * - * @return The remote Bluetooth device, or null if not in connected or connecting state, or if - * this proxy object is not connected to the Pbap service. + * {@inheritDoc} */ - public BluetoothDevice getClient() { - if (VDBG) log("getClient()"); + @Override + public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + log("getDevicesMatchingConnectionStates: states=" + Arrays.toString(states)); final IBluetoothPbap service = mService; - if (service != null) { - try { - return service.getClient(); - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } - } else { + if (service == null) { Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); + return new ArrayList<BluetoothDevice>(); + } + try { + return service.getDevicesMatchingConnectionStates(states); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); } - return null; + return new ArrayList<BluetoothDevice>(); } /** @@ -253,20 +269,9 @@ public class BluetoothPbap { * include connecting). Returns false if not connected, or if this proxy * object is not currently connected to the Pbap service. */ + // TODO: This is currently being used by SettingsLib and internal app. public boolean isConnected(BluetoothDevice device) { - if (VDBG) log("isConnected(" + device + ")"); - final IBluetoothPbap service = mService; - if (service != null) { - try { - return service.isConnected(device); - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } - } else { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } - return false; + return getConnectionState(device) == BluetoothAdapter.STATE_CONNECTED; } /** @@ -274,47 +279,27 @@ public class BluetoothPbap { * it may soon be made asynchronous. Returns false if this proxy object is * not currently connected to the Pbap service. */ - public boolean disconnect() { - if (DBG) log("disconnect()"); + // TODO: This is currently being used by SettingsLib and will be used in the future. + // TODO: Must specify target device. Implement this in the service. + public boolean disconnect(BluetoothDevice device) { + log("disconnect()"); final IBluetoothPbap service = mService; - if (service != null) { - try { - service.disconnect(); - return true; - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } - } else { + if (service == null) { Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); + return false; } - return false; - } - - /** - * Check class bits for possible PBAP support. - * This is a simple heuristic that tries to guess if a device with the - * given class bits might support PBAP. It is not accurate for all - * devices. It tries to err on the side of false positives. - * - * @return True if this device might support PBAP. - */ - public static boolean doesClassMatchSink(BluetoothClass btClass) { - // TODO optimize the rule - switch (btClass.getDeviceClass()) { - case BluetoothClass.Device.COMPUTER_DESKTOP: - case BluetoothClass.Device.COMPUTER_LAPTOP: - case BluetoothClass.Device.COMPUTER_SERVER: - case BluetoothClass.Device.COMPUTER_UNCATEGORIZED: - return true; - default: - return false; + try { + service.disconnect(device); + return true; + } catch (RemoteException e) { + Log.e(TAG, e.toString()); } + return false; } private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { - if (DBG) log("Proxy object connected"); + log("Proxy object connected"); mService = IBluetoothPbap.Stub.asInterface(service); if (mServiceListener != null) { mServiceListener.onServiceConnected(BluetoothPbap.this); @@ -322,7 +307,7 @@ public class BluetoothPbap { } public void onServiceDisconnected(ComponentName className) { - if (DBG) log("Proxy object disconnected"); + log("Proxy object disconnected"); mService = null; if (mServiceListener != null) { mServiceListener.onServiceDisconnected(); @@ -331,6 +316,8 @@ public class BluetoothPbap { }; private static void log(String msg) { - Log.d(TAG, msg); + if (DBG) { + Log.d(TAG, msg); + } } } diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index 46a230b50605..ebbc710922c2 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -254,4 +254,28 @@ public interface BluetoothProfile { */ public void onServiceDisconnected(int profile); } + + /** + * Convert an integer value of connection state into human readable string + * + * @param connectionState - One of {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, + * {@link #STATE_CONNECTED}, or {@link #STATE_DISCONNECTED} + * @return a string representation of the connection state, STATE_UNKNOWN if the state + * is not defined + * @hide + */ + static String getConnectionStateName(int connectionState) { + switch (connectionState) { + case STATE_DISCONNECTED: + return "STATE_DISCONNECTED"; + case STATE_CONNECTING: + return "STATE_CONNECTING"; + case STATE_CONNECTED: + return "STATE_CONNECTED"; + case STATE_DISCONNECTING: + return "STATE_DISCONNECTING"; + default: + return "STATE_UNKNOWN"; + } + } } diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java index 9369eebfd984..6c9f1b25bf66 100644 --- a/core/java/android/os/UserManagerInternal.java +++ b/core/java/android/os/UserManagerInternal.java @@ -130,7 +130,8 @@ public abstract class UserManagerInternal { * <p>Called by the {@link com.android.server.devicepolicy.DevicePolicyManagerService} when * createAndManageUser is called by the device owner. */ - public abstract UserInfo createUserEvenWhenDisallowed(String name, int flags); + public abstract UserInfo createUserEvenWhenDisallowed(String name, int flags, + String[] disallowedPackages); /** * Same as {@link UserManager#removeUser(int userHandle)}, but bypasses the check for diff --git a/core/java/android/text/PremeasuredText.java b/core/java/android/text/PremeasuredText.java new file mode 100644 index 000000000000..465314dd21ac --- /dev/null +++ b/core/java/android/text/PremeasuredText.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2017 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.IntRange; +import android.annotation.NonNull; +import android.util.IntArray; + +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; + +/** + * A text which has already been measured. + * + * TODO: Rename to better name? e.g. MeasuredText, FrozenText etc. + */ +public class PremeasuredText implements Spanned { + private static final char LINE_FEED = '\n'; + + // The original text. + private final @NonNull CharSequence mText; + + // The inclusive start offset of the measuring target. + private final @IntRange(from = 0) int mStart; + + // The exclusive end offset of the measuring target. + private final @IntRange(from = 0) int mEnd; + + // The TextPaint used for measurement. + private final @NonNull TextPaint mPaint; + + // The requested text direction. + private final @NonNull TextDirectionHeuristic mTextDir; + + // The measured paragraph texts. + private final @NonNull MeasuredText[] mMeasuredTexts; + + // The sorted paragraph end offsets. + private final @NonNull int[] mParagraphBreakPoints; + + /** + * Build PremeasuredText from the text. + * + * @param text The text to be measured. + * @param paint The paint to be used for drawing. + * @param textDir The text direction. + * @return The measured text. + */ + public static @NonNull PremeasuredText build(@NonNull CharSequence text, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir) { + return PremeasuredText.build(text, paint, textDir, 0, text.length()); + } + + /** + * Build PremeasuredText from the specific range of the text.. + * + * @param text The text to be measured. + * @param paint The paint to be used for drawing. + * @param textDir The text direction. + * @param start The inclusive start offset of the text. + * @param end The exclusive start offset of the text. + * @return The measured text. + */ + public static @NonNull PremeasuredText build(@NonNull CharSequence text, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end) { + Preconditions.checkNotNull(text); + Preconditions.checkNotNull(paint); + Preconditions.checkNotNull(textDir); + Preconditions.checkArgumentInRange(start, 0, text.length(), "start"); + Preconditions.checkArgumentInRange(end, 0, text.length(), "end"); + + final IntArray paragraphEnds = new IntArray(); + final ArrayList<MeasuredText> measuredTexts = new ArrayList<>(); + + int paraEnd = 0; + for (int paraStart = start; paraStart < end; paraStart = paraEnd) { + paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end); + if (paraEnd < 0) { + // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end. + paraEnd = end; + } else { + paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph. + } + + paragraphEnds.add(paraEnd); + measuredTexts.add(MeasuredText.buildForStaticLayout( + paint, text, paraStart, paraEnd, textDir, null /* no recycle */)); + } + + return new PremeasuredText(text, start, end, paint, textDir, + measuredTexts.toArray(new MeasuredText[measuredTexts.size()]), + paragraphEnds.toArray()); + } + + // Use PremeasuredText.build instead. + private PremeasuredText(@NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir, + @NonNull MeasuredText[] measuredTexts, + @NonNull int[] paragraphBreakPoints) { + mText = text; + mStart = start; + mEnd = end; + mPaint = paint; + mMeasuredTexts = measuredTexts; + mParagraphBreakPoints = paragraphBreakPoints; + mTextDir = textDir; + } + + /** + * Return the underlying text. + */ + public @NonNull CharSequence getText() { + return mText; + } + + /** + * Returns the inclusive start offset of measured region. + */ + public @IntRange(from = 0) int getStart() { + return mStart; + } + + /** + * Returns the exclusive end offset of measured region. + */ + public @IntRange(from = 0) int getEnd() { + return mEnd; + } + + /** + * Returns the text direction associated with char sequence. + */ + public @NonNull TextDirectionHeuristic getTextDir() { + return mTextDir; + } + + /** + * Returns the paint used to measure this text. + */ + public @NonNull TextPaint getPaint() { + return mPaint; + } + + /** + * Returns the length of the paragraph of this text. + */ + public @IntRange(from = 0) int getParagraphCount() { + return mParagraphBreakPoints.length; + } + + /** + * Returns the paragraph start offset of the text. + */ + public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) { + Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); + return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1]; + } + + /** + * Returns the paragraph end offset of the text. + */ + public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) { + Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); + return mParagraphBreakPoints[paraIndex]; + } + + /** @hide */ + public @NonNull MeasuredText getMeasuredText(@IntRange(from = 0) int paraIndex) { + return mMeasuredTexts[paraIndex]; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Spanned overrides + // + // Just proxy for underlying mText if appropriate. + + @Override + public <T> T[] getSpans(int start, int end, Class<T> type) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpans(start, end, type); + } else { + return ArrayUtils.emptyArray(type); + } + } + + @Override + public int getSpanStart(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanStart(tag); + } else { + return -1; + } + } + + @Override + public int getSpanEnd(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanEnd(tag); + } else { + return -1; + } + } + + @Override + public int getSpanFlags(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanFlags(tag); + } else { + return 0; + } + } + + @Override + public int nextSpanTransition(int start, int limit, Class type) { + if (mText instanceof Spanned) { + return ((Spanned) mText).nextSpanTransition(start, limit, type); + } else { + return mText.length(); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // CharSequence overrides. + // + // Just proxy for underlying mText. + + @Override + public int length() { + return mText.length(); + } + + @Override + public char charAt(int index) { + // TODO: Should this be index + mStart ? + return mText.charAt(index); + } + + @Override + public CharSequence subSequence(int start, int end) { + // TODO: return PremeasuredText. + // TODO: Should this be index + mStart, end + mStart ? + return mText.subSequence(start, end); + } + + @Override + public String toString() { + return mText.toString(); + } +} diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 53ddd1699d68..2e10fe8d4267 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -610,8 +610,8 @@ public class StaticLayout extends Layout { /* package */ void generate(Builder b, boolean includepad, boolean trackpad) { final CharSequence source = b.mText; - int bufStart = b.mStart; - int bufEnd = b.mEnd; + final int bufStart = b.mStart; + final int bufEnd = b.mEnd; TextPaint paint = b.mPaint; int outerWidth = b.mWidth; TextDirectionHeuristic textDir = b.mTextDir; @@ -634,10 +634,6 @@ public class StaticLayout extends Layout { Paint.FontMetricsInt fm = b.mFontMetricsInt; int[] chooseHtv = null; - Spanned spanned = null; - if (source instanceof Spanned) - spanned = (Spanned) source; - final int[] indents; if (mLeftIndents != null || mRightIndents != null) { final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length; @@ -660,16 +656,34 @@ public class StaticLayout extends Layout { b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, indents, mLeftPaddings, mRightPaddings); - MeasuredText measured = null; + PremeasuredText premeasured = null; + final Spanned spanned; + if (source instanceof PremeasuredText) { + premeasured = (PremeasuredText) source; + + final CharSequence original = premeasured.getText(); + spanned = (original instanceof Spanned) ? (Spanned) original : null; + + if (bufStart != premeasured.getStart() || bufEnd != premeasured.getEnd()) { + // The buffer position has changed. Re-measure here. + premeasured = PremeasuredText.build(original, paint, textDir, bufStart, bufEnd); + } else { + // We can use premeasured information. + + // Overwrite with the one when premeasured. + // TODO: Give an option for developer not to overwrite and measure again here? + textDir = premeasured.getTextDir(); + paint = premeasured.getPaint(); + } + } else { + premeasured = PremeasuredText.build(source, paint, textDir, bufStart, bufEnd); + spanned = (source instanceof Spanned) ? (Spanned) source : null; + } + try { - int paraEnd; - for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) { - paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd); - if (paraEnd < 0) { - paraEnd = bufEnd; - } else { - paraEnd++; - } + for (int paraIndex = 0; paraIndex < premeasured.getParagraphCount(); paraIndex++) { + final int paraStart = premeasured.getParagraphStart(paraIndex); + final int paraEnd = premeasured.getParagraphEnd(paraIndex); int firstWidthLineCount = 1; int firstWidth = outerWidth; @@ -735,8 +749,7 @@ public class StaticLayout extends Layout { } } - measured = MeasuredText.buildForStaticLayout( - paint, source, paraStart, paraEnd, textDir, measured); + final MeasuredText measured = premeasured.getMeasuredText(paraIndex); final char[] chs = measured.getChars(); final int[] spanEndCache = measured.getSpanEndCache().getRawArray(); final int[] fmCache = measured.getFontMetrics().getRawArray(); @@ -887,7 +900,8 @@ public class StaticLayout extends Layout { if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && mLineCount < mMaximumVisibleLineCount) { - measured = MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, measured); + final MeasuredText measured = + MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, null); paint.getFontMetricsInt(fm); v = out(source, bufEnd, bufEnd, fm.ascent, fm.descent, @@ -901,9 +915,6 @@ public class StaticLayout extends Layout { ellipsizedWidth, 0, paint, false); } } finally { - if (measured != null) { - measured.recycle(); - } nFinish(nativePtr); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 129a255c1989..c6c42faad6b0 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -20,6 +20,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER; import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM; +import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; @@ -1586,7 +1587,15 @@ public final class ViewRootImpl implements ViewParent, } void dispatchApplyInsets(View host) { - host.dispatchApplyWindowInsets(getWindowInsets(true /* forceConstruct */)); + WindowInsets insets = getWindowInsets(true /* forceConstruct */); + final boolean layoutInCutout = + (mWindowAttributes.flags2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0; + if (!layoutInCutout) { + // Window is either not laid out in cutout or the status bar inset takes care of + // clearing the cutout, so we don't need to dispatch the cutout to the hierarchy. + insets = insets.consumeCutout(); + } + host.dispatchApplyWindowInsets(insets); } private static boolean shouldUseDisplaySize(final WindowManager.LayoutParams lp) { diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 6cf6b69f4aa6..d7aaee73f976 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -382,7 +382,7 @@ final class TextClassifierImpl implements TextClassifier { } final String type = getHighestScoringType(classifications); - addActions(builder, IntentFactory.create(mContext, type, text)); + addActions(builder, IntentFactory.create(mContext, type, classifiedText)); return builder.setSignature(getSignature(text, start, end)).build(); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index d9bc51fffd6a..f8083babef29 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -77,6 +77,7 @@ import android.text.InputFilter; import android.text.InputType; import android.text.Layout; import android.text.ParcelableSpan; +import android.text.PremeasuredText; import android.text.Selection; import android.text.SpanWatcher; import android.text.Spannable; @@ -5326,7 +5327,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (imm != null) imm.restartInput(this); } else if (type == BufferType.SPANNABLE || mMovement != null) { text = mSpannableFactory.newSpannable(text); - } else if (!(text instanceof CharWrapper)) { + } else if (!(text instanceof PremeasuredText || text instanceof CharWrapper)) { text = TextUtils.stringOrSpannedString(text); } @@ -5610,10 +5611,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener spannable = (Spannable) text; } else { spannable = mSpannableFactory.newSpannable(text); - text = spannable; } SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); + if (spans.length == 0) { + return text; + } else { + text = spannable; + } + for (int i = 0; i < spans.length; i++) { spannable.removeSpan(spans[i]); } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index ca8624d9c01e..1fd5564773b1 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -80,7 +80,6 @@ interface IInputMethodManager { boolean switchToLastInputMethod(in IBinder token); boolean switchToNextInputMethod(in IBinder token, boolean onlyCurrentIme); boolean shouldOfferSwitchingToNextInputMethod(in IBinder token); - boolean setInputMethodEnabled(String id, boolean enabled); void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); int getInputMethodWindowVisibleHeight(); void clearLastInputMethodWindowForTransition(in IBinder token); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 09204261811d..ab7b07ec96ba 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -407,6 +407,7 @@ <protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" /> <protected-broadcast android:name="android.app.action.SYSTEM_UPDATE_POLICY_CHANGED" /> <protected-broadcast android:name="android.app.action.DEVICE_OWNER_CHANGED" /> + <protected-broadcast android:name="android.app.action.MANAGED_USER_CREATED" /> <!-- Added in N --> <protected-broadcast android:name="android.intent.action.ANR" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 0b38d1b1fca1..9a26229d5946 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -963,12 +963,6 @@ --> <integer name="config_longPressOnBackBehavior">0</integer> - <!-- Control the behavior when the user panic presses the back button. - 0 - Nothing - 1 - Go to home - --> - <integer name="config_backPanicBehavior">0</integer> - <!-- Control the behavior when the user short presses the power button. 0 - Nothing 1 - Go to sleep (doze) @@ -1387,6 +1381,9 @@ <!-- True if WallpaperService is enabled --> <bool name="config_enableWallpaperService">true</bool> + <!-- Class name of WallpaperManagerService. --> + <string name="config_wallpaperManagerServiceName">com.android.server.wallpaper.WallpaperManagerService</string> + <!-- Enables the TimeZoneRuleManager service. This is the master switch for the updateable time zone update mechanism. --> <bool name="config_enableUpdateableTimeZoneRules">false</bool> diff --git a/core/res/res/values/disallowed_apps_managed_device.xml b/core/res/res/values/disallowed_apps_managed_device.xml new file mode 100644 index 000000000000..8940b15ccabb --- /dev/null +++ b/core/res/res/values/disallowed_apps_managed_device.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (C) 2017 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> + <!-- A list of apps to be removed from the managed device. --> + <string-array name="disallowed_apps_managed_device"> + </string-array> +</resources> diff --git a/core/res/res/values/disallowed_apps_managed_profile.xml b/core/res/res/values/disallowed_apps_managed_profile.xml new file mode 100644 index 000000000000..e3a513f4f096 --- /dev/null +++ b/core/res/res/values/disallowed_apps_managed_profile.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (C) 2017 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> + <!-- A list of apps to be removed from the managed profile. --> + <string-array name="disallowed_apps_managed_profile"> + </string-array> +</resources> diff --git a/core/res/res/values/disallowed_apps_managed_user.xml b/core/res/res/values/disallowed_apps_managed_user.xml new file mode 100644 index 000000000000..b7b645dc0780 --- /dev/null +++ b/core/res/res/values/disallowed_apps_managed_user.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (C) 2017 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> + <!-- A list of apps to be removed from the managed user. --> + <string-array name="disallowed_apps_managed_user"> + </string-array> +</resources> diff --git a/core/res/res/values/required_apps_managed_device.xml b/core/res/res/values/required_apps_managed_device.xml new file mode 100644 index 000000000000..0ac706f51a70 --- /dev/null +++ b/core/res/res/values/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (C) 2017 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> + <!-- A list of apps to be retained on the managed device. + Takes precedence over the disallowed apps lists. --> + <string-array name="required_apps_managed_device"> + <item>com.android.settings</item> + <item>com.android.contacts</item> + <item>com.android.dialer</item> + <item>com.android.stk</item> <!-- Required by com.android.phone by certain carriers --> + <item>com.android.providers.downloads</item> + <item>com.android.providers.downloads.ui</item> + <item>com.android.documentsui</item> + </string-array> +</resources> diff --git a/core/res/res/values/required_apps_managed_profile.xml b/core/res/res/values/required_apps_managed_profile.xml new file mode 100644 index 000000000000..a0b8492644ee --- /dev/null +++ b/core/res/res/values/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (C) 2017 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> + <!-- A list of apps to be retained in the managed profile. + Takes precedence over the disallowed apps lists. --> + <string-array name="required_apps_managed_profile"> + <item>com.android.contacts</item> + <item>com.android.settings</item> + <item>com.android.providers.downloads</item> + <item>com.android.providers.downloads.ui</item> + <item>com.android.documentsui</item> + </string-array> +</resources> diff --git a/core/res/res/values/required_apps_managed_user.xml b/core/res/res/values/required_apps_managed_user.xml new file mode 100644 index 000000000000..e8fdb210805c --- /dev/null +++ b/core/res/res/values/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (C) 2017 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> + <!-- A list of apps to be retained on the managed user. + Takes precedence over the disallowed apps lists. --> + <string-array name="required_apps_managed_user"> + <item>com.android.settings</item> + <item>com.android.contacts</item> + <item>com.android.dialer</item> + <item>com.android.stk</item> <!-- Required by com.android.phone by certain carriers --> + <item>com.android.providers.downloads</item> + <item>com.android.providers.downloads.ui</item> + <item>com.android.documentsui</item> + </string-array> +</resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index f659360199ef..69d27fc7059f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -287,6 +287,7 @@ <java-symbol type="bool" name="split_action_bar_is_narrow" /> <java-symbol type="bool" name="config_useVolumeKeySounds" /> <java-symbol type="bool" name="config_enableWallpaperService" /> + <java-symbol type="string" name="config_wallpaperManagerServiceName" /> <java-symbol type="bool" name="config_enableUpdateableTimeZoneRules" /> <java-symbol type="bool" name="config_timeZoneRulesUpdateTrackingEnabled" /> <java-symbol type="string" name="config_timeZoneRulesUpdaterPackage" /> @@ -424,7 +425,6 @@ <java-symbol type="integer" name="config_veryLongPressOnPowerBehavior" /> <java-symbol type="integer" name="config_veryLongPressTimeout" /> <java-symbol type="integer" name="config_longPressOnBackBehavior" /> - <java-symbol type="integer" name="config_backPanicBehavior" /> <java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAdjust" /> <java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAbsolute" /> <java-symbol type="integer" name="config_max_pan_devices" /> @@ -1213,6 +1213,18 @@ <java-symbol type="array" name="config_telephonyHardware" /> <java-symbol type="array" name="config_keySystemUuidMapping" /> <java-symbol type="array" name="config_gpsParameters" /> + <java-symbol type="array" name="required_apps_managed_user" /> + <java-symbol type="array" name="required_apps_managed_profile" /> + <java-symbol type="array" name="required_apps_managed_device" /> + <java-symbol type="array" name="disallowed_apps_managed_user" /> + <java-symbol type="array" name="disallowed_apps_managed_profile" /> + <java-symbol type="array" name="disallowed_apps_managed_device" /> + <java-symbol type="array" name="vendor_required_apps_managed_user" /> + <java-symbol type="array" name="vendor_required_apps_managed_profile" /> + <java-symbol type="array" name="vendor_required_apps_managed_device" /> + <java-symbol type="array" name="vendor_disallowed_apps_managed_user" /> + <java-symbol type="array" name="vendor_disallowed_apps_managed_profile" /> + <java-symbol type="array" name="vendor_disallowed_apps_managed_device" /> <java-symbol type="drawable" name="default_wallpaper" /> <java-symbol type="drawable" name="default_lock_wallpaper" /> diff --git a/core/res/res/values/vendor_disallowed_apps_managed_device.xml b/core/res/res/values/vendor_disallowed_apps_managed_device.xml new file mode 100644 index 000000000000..c826d27d371e --- /dev/null +++ b/core/res/res/values/vendor_disallowed_apps_managed_device.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (C) 2017 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> + <!-- A list of apps to be removed from the managed device by a particular vendor. --> + <string-array name="vendor_disallowed_apps_managed_device"> + </string-array> +</resources> diff --git a/core/res/res/values/vendor_disallowed_apps_managed_profile.xml b/core/res/res/values/vendor_disallowed_apps_managed_profile.xml new file mode 100644 index 000000000000..5fcb2778b6bc --- /dev/null +++ b/core/res/res/values/vendor_disallowed_apps_managed_profile.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (C) 2017 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> + <!-- A list of apps to be removed from the managed profile by a particular vendor. --> + <string-array name="vendor_disallowed_apps_managed_profile"> + </string-array> +</resources> diff --git a/core/res/res/values/vendor_disallowed_apps_managed_user.xml b/core/res/res/values/vendor_disallowed_apps_managed_user.xml new file mode 100644 index 000000000000..3355d77aac6a --- /dev/null +++ b/core/res/res/values/vendor_disallowed_apps_managed_user.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (C) 2017 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> + <!-- A list of apps to be removed from the managed user by a particular vendor. --> + <string-array name="vendor_disallowed_apps_managed_user"> + </string-array> +</resources> diff --git a/core/res/res/values/vendor_required_apps_managed_device.xml b/core/res/res/values/vendor_required_apps_managed_device.xml new file mode 100644 index 000000000000..e684e22d8599 --- /dev/null +++ b/core/res/res/values/vendor_required_apps_managed_device.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (C) 2017 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> + <!-- A list of apps to be retained on the managed device by a particular vendor. + Takes precedence over the disallowed apps lists. --> + <string-array name="vendor_required_apps_managed_device"> + </string-array> +</resources> diff --git a/core/res/res/values/vendor_required_apps_managed_profile.xml b/core/res/res/values/vendor_required_apps_managed_profile.xml new file mode 100644 index 000000000000..4a3edf80c6e7 --- /dev/null +++ b/core/res/res/values/vendor_required_apps_managed_profile.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (C) 2017 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> + <!-- A list of apps to be retained in the managed profile by a particular vendor. + Takes precedence over the disallowed apps lists. --> + <string-array name="vendor_required_apps_managed_profile"> + </string-array> +</resources> diff --git a/core/res/res/values/vendor_required_apps_managed_user.xml b/core/res/res/values/vendor_required_apps_managed_user.xml new file mode 100644 index 000000000000..71dbd62f963c --- /dev/null +++ b/core/res/res/values/vendor_required_apps_managed_user.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (C) 2017 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> + <!-- A list of apps to be retained on the managed user by a particular vendor. + Takes precedence over the disallowed apps lists. --> + <string-array name="vendor_required_apps_managed_user"> + </string-array> +</resources> diff --git a/core/tests/coretests/assets/fonts/LineBreakingOverhangsTestFont.ttf b/core/tests/coretests/assets/fonts/LineBreakingOverhangsTestFont.ttf Binary files differdeleted file mode 100644 index cf769ed3e8a7..000000000000 --- a/core/tests/coretests/assets/fonts/LineBreakingOverhangsTestFont.ttf +++ /dev/null diff --git a/core/tests/coretests/assets/fonts/LineBreakingOverhangsTestFont.ttx b/core/tests/coretests/assets/fonts/LineBreakingOverhangsTestFont.ttx deleted file mode 100644 index 04d85922a977..000000000000 --- a/core/tests/coretests/assets/fonts/LineBreakingOverhangsTestFont.ttx +++ /dev/null @@ -1,234 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Copyright (C) 2017 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. ---> -<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.9"> - - <GlyphOrder> - <!-- The 'id' attribute is only for humans; it is ignored when parsed. --> - <GlyphID id="0" name=".notdef"/> - <GlyphID id="1" name="space"/> - <GlyphID id="2" name="R"/> - <GlyphID id="3" name="a"/> - <GlyphID id="4" name="y"/> - </GlyphOrder> - - <head> - <!-- Most of this table will be recalculated by the compiler --> - <tableVersion value="1.0"/> - <fontRevision value="1.0"/> - <checkSumAdjustment value="0x26d0d624"/> - <magicNumber value="0x5f0f3cf5"/> - <flags value="00000000 00000011"/> - <unitsPerEm value="1000"/> - <created value="Tue Oct 3 23:00:00 2017"/> - <modified value="Tue Oct 3 23:33:15 2017"/> - <xMin value="-1500"/> - <yMin value="0"/> - <xMax value="5000"/> - <yMax value="1000"/> - <macStyle value="00000000 00000000"/> - <lowestRecPPEM value="7"/> - <fontDirectionHint value="2"/> - <indexToLocFormat value="0"/> - <glyphDataFormat value="0"/> - </head> - - <hhea> - <tableVersion value="0x00010000"/> - <ascent value="1000"/> - <descent value="-200"/> - <lineGap value="0"/> - <advanceWidthMax value="1000"/> - <minLeftSideBearing value="-1500"/> - <minRightSideBearing value="-4000"/> - <xMaxExtent value="5000"/> - <caretSlopeRise value="1"/> - <caretSlopeRun value="0"/> - <caretOffset value="0"/> - <reserved0 value="0"/> - <reserved1 value="0"/> - <reserved2 value="0"/> - <reserved3 value="0"/> - <metricDataFormat value="0"/> - <numberOfHMetrics value="1"/> - </hhea> - - <maxp> - <!-- Most of this table will be recalculated by the compiler --> - <tableVersion value="0x10000"/> - <numGlyphs value="5"/> - <maxPoints value="4"/> - <maxContours value="1"/> - <maxCompositePoints value="0"/> - <maxCompositeContours value="0"/> - <maxZones value="0"/> - <maxTwilightPoints value="0"/> - <maxStorage value="0"/> - <maxFunctionDefs value="0"/> - <maxInstructionDefs value="0"/> - <maxStackElements value="0"/> - <maxSizeOfInstructions value="0"/> - <maxComponentElements value="0"/> - <maxComponentDepth value="0"/> - </maxp> - - <OS_2> - <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex' - will be recalculated by the compiler --> - <version value="3"/> - <xAvgCharWidth value="594"/> - <usWeightClass value="400"/> - <usWidthClass value="5"/> - <fsType value="00000000 00001000"/> - <ySubscriptXSize value="650"/> - <ySubscriptYSize value="600"/> - <ySubscriptXOffset value="0"/> - <ySubscriptYOffset value="75"/> - <ySuperscriptXSize value="650"/> - <ySuperscriptYSize value="600"/> - <ySuperscriptXOffset value="0"/> - <ySuperscriptYOffset value="350"/> - <yStrikeoutSize value="50"/> - <yStrikeoutPosition value="300"/> - <sFamilyClass value="0"/> - <panose> - <bFamilyType value="0"/> - <bSerifStyle value="0"/> - <bWeight value="5"/> - <bProportion value="0"/> - <bContrast value="0"/> - <bStrokeVariation value="0"/> - <bArmStyle value="0"/> - <bLetterForm value="0"/> - <bMidline value="0"/> - <bXHeight value="0"/> - </panose> - <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/> - <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/> - <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/> - <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/> - <achVendID value="UKWN"/> - <fsSelection value="00000000 01000000"/> - <usFirstCharIndex value="32"/> - <usLastCharIndex value="121"/> - <sTypoAscender value="800"/> - <sTypoDescender value="-200"/> - <sTypoLineGap value="200"/> - <usWinAscent value="1000"/> - <usWinDescent value="200"/> - <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/> - <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/> - <sxHeight value="500"/> - <sCapHeight value="700"/> - <usDefaultChar value="0"/> - <usBreakChar value="32"/> - <usMaxContext value="0"/> - </OS_2> - - <hmtx> - <mtx name=".notdef" width="1000" lsb="0"/> - <mtx name="R" width="1000" lsb="0"/> - <mtx name="a" width="1000" lsb="0"/> - <mtx name="space" width="1000" lsb="0"/> - <mtx name="y" width="1000" lsb="-1500"/> - </hmtx> - - <cmap> - <tableVersion version="0"/> - <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="64" language="0" nGroups="4"> - <map code="0x20" name="space"/><!-- SPACE --> - <map code="0x52" name="R"/><!-- LATIN CAPITAL LETTER R --> - <map code="0x61" name="a"/><!-- LATIN SMALL LETTER A --> - <map code="0x79" name="y"/><!-- LATIN SMALL LETTER Y --> - </cmap_format_12> - </cmap> - - <loca> - <!-- The 'loca' table will be calculated by the compiler --> - </loca> - - <glyf> - - <!-- The xMin, yMin, xMax and yMax values - will be recalculated by the compiler. --> - - <TTGlyph name=".notdef"/><!-- contains no outline data --> - - <TTGlyph name="R" xMin="0" yMin="0" xMax="5000" yMax="1000"> - <contour> - <pt x="0" y="0" on="1"/> - <pt x="0" y="1000" on="1"/> - <pt x="5000" y="1000" on="1"/> - <pt x="5000" y="0" on="1"/> - </contour> - <instructions/> - </TTGlyph> - - <TTGlyph name="a"/><!-- contains no outline data --> - - <TTGlyph name="space"/><!-- contains no outline data --> - - <TTGlyph name="y" xMin="-1500" yMin="0" xMax="1000" yMax="1000"> - <contour> - <pt x="-1500" y="0" on="1"/> - <pt x="-1500" y="1000" on="1"/> - <pt x="1000" y="1000" on="1"/> - <pt x="1000" y="0" on="1"/> - </contour> - <instructions/> - </TTGlyph> - - </glyf> - - <name> - <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True"> - Font for LineBreakingOverhangsTest - </namerecord> - <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True"> - Regular - </namerecord> - <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True"> - Font for LineBreakingOverhangsTest - </namerecord> - <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True"> - SampleFont-Regular - </namerecord> - <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409"> - Sample Font - </namerecord> - <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409"> - Regular - </namerecord> - <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409"> - Sample Font - </namerecord> - <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409"> - SampleFont-Regular - </namerecord> - </name> - - <post> - <formatType value="3.0"/> - <italicAngle value="0.0"/> - <underlinePosition value="-75"/> - <underlineThickness value="50"/> - <isFixedPitch value="0"/> - <minMemType42 value="0"/> - <maxMemType42 value="0"/> - <minMemType1 value="0"/> - <maxMemType1 value="0"/> - </post> - -</ttFont> diff --git a/core/tests/coretests/src/android/text/LineBreakingOverhangsTest.java b/core/tests/coretests/src/android/text/LineBreakingOverhangsTest.java deleted file mode 100644 index 4f18b0b4c7a4..000000000000 --- a/core/tests/coretests/src/android/text/LineBreakingOverhangsTest.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2012 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 static org.junit.Assert.assertEquals; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.Typeface; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class LineBreakingOverhangsTest { - private static final int EM = 100; // Make 1em == 100px. - private static final TextPaint sTextPaint = new TextPaint(); - - static { - // The test font has following coverage and overhangs. - // All the characters have a width of 1em. - // space: no overhangs - // R: 4em overhang on the right - // a: no overhang - // y: 1.5em overhang on the left - sTextPaint.setTypeface(Typeface.createFromAsset( - InstrumentationRegistry.getTargetContext().getAssets(), - "fonts/LineBreakingOverhangsTestFont.ttf")); - sTextPaint.setTextSize(EM); - } - - private static void layout(@NonNull CharSequence source, @NonNull int[] breaks, double width, - @Nullable int[] leftPadding, @Nullable int[] rightPadding) { - layout(source, breaks, width, leftPadding, rightPadding, null /* indents */); - } - - private static void layout(@NonNull CharSequence source, @NonNull int[] breaks, double width, - @Nullable int[] leftPadding, @Nullable int[] rightPadding, @Nullable int[] indents) { - final StaticLayout staticLayout = StaticLayout.Builder - .obtain(source, 0, source.length(), sTextPaint, (int) width) - .setAvailablePaddings(leftPadding, rightPadding) - .setIndents(indents, indents) - .build(); - - final int lineCount = breaks.length + 1; - assertEquals("Number of lines", lineCount, staticLayout.getLineCount()); - - for (int line = 0; line < lineCount; line++) { - final int lineStart = staticLayout.getLineStart(line); - final int lineEnd = staticLayout.getLineEnd(line); - - if (line == 0) { - assertEquals("Line start for first line", 0, lineStart); - } else { - assertEquals("Line start for line " + line, breaks[line - 1], lineStart); - } - - if (line == lineCount - 1) { - assertEquals("Line end for last line", source.length(), lineEnd); - } else { - assertEquals("Line end for line " + line, breaks[line], lineEnd); - } - } - } - - private static final int[] NO_BREAK = new int[] {}; - - private static final int[] NO_PADDING = null; - // Maximum needed for left side of 'y'. - private static final int[] FULL_LEFT_PADDING = new int[] {(int) (1.5 * EM)}; - // Maximum padding needed for right side of 'R'. - private static final int[] FULL_RIGHT_PADDING = new int[] {4 * EM}; - - private static final int[] ONE_EM_PADDING = new int[] {1 * EM}; - private static final int[] HALF_EM_PADDING = new int[] {(int) (0.5 * EM)}; - private static final int[] QUARTER_EM_PADDING = new int[] {(int) (0.25 * EM)}; - - @Test - public void testRightOverhang() { - // The advance of "aaa R" is 5em, but the right-side overhang of 'R' would need 4em more, so - // we break the line if there's not enough overhang. - - // Enough right padding, so the whole line fits in 5em. - layout("aaa R", NO_BREAK, 5 * EM, NO_PADDING, FULL_RIGHT_PADDING); - - // No right padding, so we'd need 9em to fit the advance and the right padding of 'R'. - layout("aaa R", new int[] {4}, 8.9 * EM, NO_PADDING, NO_PADDING); - layout("aaa R", NO_BREAK, 9 * EM, NO_PADDING, NO_PADDING); - - // 1em of right padding means we can fit the string in 8em. - layout("aaa R", new int[] {4}, 7.9 * EM, NO_PADDING, ONE_EM_PADDING); - layout("aaa R", NO_BREAK, 8 * EM, NO_PADDING, ONE_EM_PADDING); - } - - @Test - public void testLeftOverhang() { - // The advance of "y a" is 3em, but the left-side overhang of 'y' would need 1.5em more, so - // we break the line if there's not enough overhang. - - // Enough left padding, so the whole line fits in 3em. - layout("y a", NO_BREAK, 3 * EM, FULL_LEFT_PADDING, NO_PADDING); - - // No right padding, so we'd need 4.5em to fit the advance and the left padding of 'y'. - layout("y a", new int[] {2}, 4.4 * EM, NO_PADDING, NO_PADDING); - layout("y a", NO_BREAK, 4.5 * EM, NO_PADDING, NO_PADDING); - - // 1em of left padding means we can fit the string in 3.5em. - layout("y a", new int[] {2}, 3.4 * EM, ONE_EM_PADDING, NO_PADDING); - layout("y a", NO_BREAK, 3.5 * EM, ONE_EM_PADDING, NO_PADDING); - } - - @Test - public void testBothSidesOverhang() { - // The advance of "y a R" is 5em, but the left-side overhang of 'y' would need 1.5em more, - // and the right side overhang or 'R' would need 4em more, so we break the line if there's - // not enough overhang. - - // Enough padding, so the whole line fits in 5em. - layout("y a R", NO_BREAK, 5 * EM, FULL_LEFT_PADDING, FULL_RIGHT_PADDING); - - // No padding, so we'd need 10.5em to fit the advance and the paddings. - layout("y a R", new int[] {4}, 10.4 * EM, NO_PADDING, NO_PADDING); - layout("y a R", NO_BREAK, 10.5 * EM, NO_PADDING, NO_PADDING); - - // 1em of padding on each side means we can fit the string in 8.5em. - layout("y a R", new int[] {4}, 8.4 * EM, ONE_EM_PADDING, ONE_EM_PADDING); - layout("y a R", NO_BREAK, 8.5 * EM, ONE_EM_PADDING, ONE_EM_PADDING); - } - - @Test - public void testIndentsDontAffectPaddings() { - // This is identical to the previous test, except that it applies wide indents of 4em on - // each side and thus needs an extra 8em of width. This test makes sure that indents and - // paddings are independent. - final int[] indents = new int[] {4 * EM}; - final int indentAdj = 8 * EM; - - // Enough padding, so the whole line fits in 5em. - layout("y a R", NO_BREAK, 5 * EM + indentAdj, FULL_LEFT_PADDING, FULL_RIGHT_PADDING, - indents); - - // No padding, so we'd need 10.5em to fit the advance and the paddings. - layout("y a R", new int[] {4}, 10.4 * EM + indentAdj, NO_PADDING, NO_PADDING, indents); - layout("y a R", NO_BREAK, 10.5 * EM + indentAdj, NO_PADDING, NO_PADDING, indents); - - // 1em of padding on each side means we can fit the string in 8.5em. - layout("y a R", new int[] {4}, 8.4 * EM + indentAdj, ONE_EM_PADDING, ONE_EM_PADDING, - indents); - layout("y a R", NO_BREAK, 8.5 * EM + indentAdj, ONE_EM_PADDING, ONE_EM_PADDING, indents); - } -} diff --git a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java index 04a486eb7cda..f1a730a7a420 100644 --- a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java +++ b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java @@ -20,13 +20,18 @@ package android.text; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import android.text.style.BulletSpan; import android.text.style.QuoteSpan; import android.text.style.SubscriptSpan; import android.text.style.UnderlineSpan; import org.junit.Test; +import org.junit.runner.RunWith; +@SmallTest +@RunWith(AndroidJUnit4.class) public class SpannableStringBuilderTest extends SpannableTest { protected Spannable newSpannableWithText(String text) { diff --git a/core/tests/coretests/src/android/text/format/FormatterTest.java b/core/tests/coretests/src/android/text/format/FormatterTest.java index dee51dcbc7ff..2d9401686e81 100644 --- a/core/tests/coretests/src/android/text/format/FormatterTest.java +++ b/core/tests/coretests/src/android/text/format/FormatterTest.java @@ -189,7 +189,7 @@ public class FormatterTest { // Make sure it works on different locales. setLocale(new Locale("ru", "RU")); - assertEquals("1 мин", Formatter.formatShortElapsedTimeRoundingUpToMinutes( + assertEquals("1 мин.", Formatter.formatShortElapsedTimeRoundingUpToMinutes( mContext, 1 * SECOND)); } diff --git a/graphics/java/android/graphics/drawable/RippleComponent.java b/graphics/java/android/graphics/drawable/RippleComponent.java index 0e38826eb34b..626bcee9454b 100644 --- a/graphics/java/android/graphics/drawable/RippleComponent.java +++ b/graphics/java/android/graphics/drawable/RippleComponent.java @@ -93,12 +93,8 @@ abstract class RippleComponent { protected final void onHotspotBoundsChanged() { if (!mHasMaxRadius) { - final float halfWidth = mBounds.width() / 2.0f; - final float halfHeight = mBounds.height() / 2.0f; - final float targetRadius = (float) Math.sqrt(halfWidth * halfWidth - + halfHeight * halfHeight); - - onTargetRadiusChanged(targetRadius); + mTargetRadius = getTargetRadius(mBounds); + onTargetRadiusChanged(mTargetRadius); } } diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 8b185f2b8903..734cff542c51 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -299,6 +299,12 @@ public class RippleDrawable extends LayerDrawable { onHotspotBoundsChanged(); } + final int count = mExitingRipplesCount; + final RippleForeground[] ripples = mExitingRipples; + for (int i = 0; i < count; i++) { + ripples[i].onBoundsChange(); + } + if (mBackground != null) { mBackground.onBoundsChange(); } @@ -560,8 +566,7 @@ public class RippleDrawable extends LayerDrawable { y = mHotspotBounds.exactCenterY(); } - final boolean isBounded = isBounded(); - mRipple = new RippleForeground(this, mHotspotBounds, x, y, isBounded, mForceSoftware); + mRipple = new RippleForeground(this, mHotspotBounds, x, y, mForceSoftware); } mRipple.setup(mState.mMaxRadius, mDensity); diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java index 0b5020cbe55c..ecbf5780d67f 100644 --- a/graphics/java/android/graphics/drawable/RippleForeground.java +++ b/graphics/java/android/graphics/drawable/RippleForeground.java @@ -30,6 +30,7 @@ import android.view.DisplayListCanvas; import android.view.RenderNodeAnimator; import android.view.animation.AnimationUtils; import android.view.animation.LinearInterpolator; +import android.view.animation.PathInterpolator; import java.util.ArrayList; @@ -38,18 +39,14 @@ import java.util.ArrayList; */ class RippleForeground extends RippleComponent { private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); - private static final TimeInterpolator DECELERATE_INTERPOLATOR = new LogDecelerateInterpolator( - 400f, 1.4f, 0); + // Matches R.interpolator.fast_out_slow_in but as we have no context we can't just import that + private static final TimeInterpolator DECELERATE_INTERPOLATOR = + new PathInterpolator(0.4f, 0f, 0.2f, 1f); - // Pixel-based accelerations and velocities. - private static final float WAVE_TOUCH_DOWN_ACCELERATION = 2048; - private static final float WAVE_OPACITY_DECAY_VELOCITY = 3; - - // Bounded ripple animation properties. - private static final int BOUNDED_ORIGIN_EXIT_DURATION = 300; - private static final int BOUNDED_RADIUS_EXIT_DURATION = 800; - private static final int BOUNDED_OPACITY_EXIT_DURATION = 400; - private static final float MAX_BOUNDED_RADIUS = 350; + // Time it takes for the ripple to expand + private static final int RIPPLE_ENTER_DURATION = 225; + // Time it takes for the ripple to slide from the touch to the center point + private static final int RIPPLE_ORIGIN_DURATION = 225; private static final int OPACITY_ENTER_DURATION = 75; private static final int OPACITY_EXIT_DURATION = 150; @@ -71,9 +68,6 @@ class RippleForeground extends RippleComponent { private float mTargetX = 0; private float mTargetY = 0; - /** Ripple target radius used when bounded. Not used for clamping. */ - private float mBoundedRadius = 0; - // Software rendering properties. private float mOpacity = 0; @@ -107,19 +101,13 @@ class RippleForeground extends RippleComponent { private float mStartRadius = 0; public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY, - boolean isBounded, boolean forceSoftware) { + boolean forceSoftware) { super(owner, bounds); mForceSoftware = forceSoftware; mStartingX = startingX; mStartingY = startingY; - if (isBounded) { - mBoundedRadius = MAX_BOUNDED_RADIUS * 0.9f - + (float) (MAX_BOUNDED_RADIUS * Math.random() * 0.1); - } else { - mBoundedRadius = 0; - } // Take 60% of the maximum of the width and height, then divided half to get the radius. mStartRadius = Math.max(bounds.width(), bounds.height()) * 0.3f; } @@ -127,6 +115,7 @@ class RippleForeground extends RippleComponent { @Override protected void onTargetRadiusChanged(float targetRadius) { clampStartingPosition(); + switchToUiThreadAnimation(); } private void drawSoftware(Canvas c, Paint p) { @@ -228,16 +217,14 @@ class RippleForeground extends RippleComponent { } mRunningSwAnimators.clear(); - final int duration = getRadiusDuration(); - final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1); - tweenRadius.setDuration(duration); + tweenRadius.setDuration(RIPPLE_ENTER_DURATION); tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR); tweenRadius.start(); mRunningSwAnimators.add(tweenRadius); final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1); - tweenOrigin.setDuration(duration); + tweenOrigin.setDuration(RIPPLE_ORIGIN_DURATION); tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR); tweenOrigin.start(); mRunningSwAnimators.add(tweenOrigin); @@ -267,20 +254,18 @@ class RippleForeground extends RippleComponent { final Paint paint = mOwner.getRipplePaint(); mPropPaint = CanvasProperty.createPaint(paint); - final int radiusDuration = getRadiusDuration(); - final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mTargetRadius); - radius.setDuration(radiusDuration); + radius.setDuration(RIPPLE_ORIGIN_DURATION); radius.setInterpolator(DECELERATE_INTERPOLATOR); mPendingHwAnimators.add(radius); final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mTargetX); - x.setDuration(radiusDuration); + x.setDuration(RIPPLE_ORIGIN_DURATION); x.setInterpolator(DECELERATE_INTERPOLATOR); mPendingHwAnimators.add(x); final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mTargetY); - y.setDuration(radiusDuration); + y.setDuration(RIPPLE_ORIGIN_DURATION); y.setInterpolator(DECELERATE_INTERPOLATOR); mPendingHwAnimators.add(y); @@ -333,12 +318,6 @@ class RippleForeground extends RippleComponent { return MathUtils.lerp(mClampedStartingY - mBounds.exactCenterY(), mTargetY, mTweenY); } - private int getRadiusDuration() { - final float remainingRadius = mTargetRadius - getCurrentRadius(); - return (int) (1000 * Math.sqrt(remainingRadius / WAVE_TOUCH_DOWN_ACCELERATION * - mDensityScale) + 0.5); - } - private float getCurrentRadius() { return MathUtils.lerp(mStartRadius, mTargetRadius, mTweenRadius); } @@ -402,6 +381,14 @@ class RippleForeground extends RippleComponent { } } + private void clearHwProps() { + mPropPaint = null; + mPropRadius = null; + mPropX = null; + mPropY = null; + mUsingProperties = false; + } + private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { @@ -410,39 +397,20 @@ class RippleForeground extends RippleComponent { pruneSwFinished(); if (mRunningHwAnimators.isEmpty()) { - mPropPaint = null; - mPropRadius = null; - mPropX = null; - mPropY = null; + clearHwProps(); } } }; - /** - * Interpolator with a smooth log deceleration. - */ - private static final class LogDecelerateInterpolator implements TimeInterpolator { - private final float mBase; - private final float mDrift; - private final float mTimeScale; - private final float mOutputScale; - - public LogDecelerateInterpolator(float base, float timeScale, float drift) { - mBase = base; - mDrift = drift; - mTimeScale = 1f / timeScale; - - mOutputScale = 1f / computeLog(1f); - } - - private float computeLog(float t) { - return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t); - } - - @Override - public float getInterpolation(float t) { - return computeLog(t) * mOutputScale; + private void switchToUiThreadAnimation() { + for (int i = 0; i < mRunningHwAnimators.size(); i++) { + Animator animator = mRunningHwAnimators.get(i); + animator.removeListener(mAnimationListener); + animator.end(); } + mRunningHwAnimators.clear(); + clearHwProps(); + invalidateSelf(); } /** diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 848c6a81d2b7..5b87e1013baf 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -239,9 +239,9 @@ void EglManager::loadConfigs() { if (!eglChooseConfig(mEglDisplay, attribs16F, &mEglConfigWideGamut, numConfigs, &numConfigs) || numConfigs != 1) { - LOG_ALWAYS_FATAL( - "Device claims wide gamut support, cannot find matching config, error = %s", + ALOGE("Device claims wide gamut support, cannot find matching config, error = %s", eglErrorString()); + EglExtensions.pixelFormatFloat = false; } } } diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index 1b89c96602d3..19d467a69c29 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -127,6 +127,14 @@ public final class AudioDeviceInfo { } /** + * @hide + * @return The underlying {@link AudioDevicePort} instance. + */ + public AudioDevicePort getPort() { + return mPort; + } + + /** * @return The internal device ID. */ public int getId() { diff --git a/packages/SettingsLib/Android.mk b/packages/SettingsLib/Android.mk index 894a1ab416da..69287e858532 100644 --- a/packages/SettingsLib/Android.mk +++ b/packages/SettingsLib/Android.mk @@ -19,8 +19,6 @@ LOCAL_SHARED_ANDROID_LIBRARIES := \ LOCAL_SHARED_JAVA_LIBRARIES := \ apptoolkit-lifecycle-common -LOCAL_STATIC_JAVA_LIBRARY := legacy-android-test - LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res LOCAL_JAR_EXCLUDE_FILES := none diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 2873fb68ce5b..9caff100cb9d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -135,7 +135,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> if (newProfileState == BluetoothProfile.STATE_CONNECTED) { if (profile instanceof MapProfile) { profile.setPreferred(mDevice, true); - } else if (!mProfiles.contains(profile)) { + } + if (!mProfiles.contains(profile)) { mRemovedProfiles.remove(profile); mProfiles.add(profile); if (profile instanceof PanProfile && diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java index 9cda669379dd..991d9221c796 100755..100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -25,6 +25,7 @@ import android.bluetooth.BluetoothHidHost; import android.bluetooth.BluetoothMap; import android.bluetooth.BluetoothMapClient; import android.bluetooth.BluetoothPan; +import android.bluetooth.BluetoothPbap; import android.bluetooth.BluetoothPbapClient; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; @@ -140,9 +141,11 @@ public class LocalBluetoothProfileManager { BluetoothMap.ACTION_CONNECTION_STATE_CHANGED); } - //Create PBAP server profile, but do not add it to list of profiles - // as we do not need to monitor the profile as part of profile list + //Create PBAP server profile + if(DEBUG) Log.d(TAG, "Adding local PBAP profile"); mPbapProfile = new PbapServerProfile(context); + addProfile(mPbapProfile, PbapServerProfile.NAME, + BluetoothPbap.ACTION_CONNECTION_STATE_CHANGED); if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete"); } @@ -495,6 +498,13 @@ public class LocalBluetoothProfileManager { mMapProfile.setPreferred(device, true); } + if ((mPbapProfile != null) && + (mPbapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { + profiles.add(mPbapProfile); + removedProfiles.remove(mPbapProfile); + mPbapProfile.setPreferred(device, true); + } + if (mMapClientProfile != null) { profiles.add(mMapClientProfile); removedProfiles.remove(mMapClientProfile); @@ -503,8 +513,6 @@ public class LocalBluetoothProfileManager { if (mUsePbapPce) { profiles.add(mPbapClientProfile); removedProfiles.remove(mPbapClientProfile); - profiles.remove(mPbapProfile); - removedProfiles.add(mPbapProfile); } if (DEBUG) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java index f3b69125cddc..58465f299578 100755 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java @@ -91,7 +91,7 @@ public class PbapServerProfile implements LocalBluetoothProfile { public boolean disconnect(BluetoothDevice device) { if (mService == null) return false; - return mService.disconnect(); + return mService.disconnect(device); } public int getConnectionStatus(BluetoothDevice device) { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 45b11aac5f35..ebeb35189008 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -26,9 +26,11 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.Dependency.DependencyProvider; import com.android.systemui.keyguard.DismissCallbackRegistry; +import com.android.systemui.qs.QSTileHost; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationGutsManager; import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBouncer; @@ -36,9 +38,8 @@ import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LockIcon; import com.android.systemui.statusbar.phone.LockscreenWallpaper; import com.android.systemui.statusbar.phone.NotificationIconAreaController; -import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.qs.QSTileHost; import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -115,5 +116,8 @@ public class SystemUIFactory { () -> new NotificationLockscreenUserManager(context)); providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager( Dependency.get(NotificationLockscreenUserManager.class), context)); + providers.put(NotificationRemoteInputManager.class, + () -> new NotificationRemoteInputManager( + Dependency.get(NotificationLockscreenUserManager.class), context)); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 83163316bac0..6db46b5917b6 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -38,7 +38,6 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Canvas; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Matrix; @@ -58,10 +57,13 @@ import android.provider.MediaStore; import android.util.DisplayMetrics; import android.util.Slog; import android.view.Display; +import android.view.DisplayListCanvas; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.RenderNode; import android.view.Surface; import android.view.SurfaceControl; +import android.view.ThreadedRenderer; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -153,7 +155,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { int previewWidth = data.previewWidth; int previewHeight = data.previewheight; - Canvas c = new Canvas(); Paint paint = new Paint(); ColorMatrix desat = new ColorMatrix(); desat.setSaturation(0.25f); @@ -161,27 +162,17 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Matrix matrix = new Matrix(); int overlayColor = 0x40FFFFFF; - // Bitmaps created for screenshots are hardware bitmaps. Copy to a software bitmap in order - // to update size for previews. - Bitmap swBitmap = data.image.copy(Bitmap.Config.ARGB_8888, true); - - Bitmap picture = Bitmap.createBitmap(previewWidth, previewHeight, Bitmap.Config.ARGB_8888); matrix.setTranslate((previewWidth - mImageWidth) / 2, (previewHeight - mImageHeight) / 2); - c.setBitmap(picture); - c.drawBitmap(swBitmap, matrix, paint); - c.drawColor(overlayColor); - c.setBitmap(null); + Bitmap picture = generateAdjustedHwBitmap(data.image, previewWidth, previewHeight, matrix, + paint, overlayColor); // Note, we can't use the preview for the small icon, since it is non-square float scale = (float) iconSize / Math.min(mImageWidth, mImageHeight); - Bitmap icon = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888); matrix.setScale(scale, scale); matrix.postTranslate((iconSize - (scale * mImageWidth)) / 2, (iconSize - (scale * mImageHeight)) / 2); - c.setBitmap(icon); - c.drawBitmap(swBitmap, matrix, paint); - c.drawColor(overlayColor); - c.setBitmap(null); + Bitmap icon = generateAdjustedHwBitmap(data.image, iconSize, iconSize, matrix, paint, + overlayColor); // Show the intermediate notification mTickerAddSpace = !mTickerAddSpace; @@ -235,6 +226,22 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mNotificationStyle.bigLargeIcon((Bitmap) null); } + /** + * Generates a new hardware bitmap with specified values, copying the content from the passed + * in bitmap. + */ + private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix, + Paint paint, int color) { + RenderNode node = RenderNode.create("ScreenshotCanvas", null); + node.setLeftTopRightBottom(0, 0, width, height); + node.setClipToBounds(false); + DisplayListCanvas canvas = node.start(width, height); + canvas.drawColor(color); + canvas.drawBitmap(bitmap, matrix, paint); + node.end(canvas); + return ThreadedRenderer.createHardwareBitmap(node, width, height); + } + @Override protected Void doInBackground(Void... params) { if (isCancelled()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index e04bd0e72929..f53eb4897ad5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -206,7 +206,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private OnClickListener mExpandClickListener = new OnClickListener() { @Override public void onClick(View v) { - if (!mShowingPublic && (!mIsLowPriority || isExpanded()) + if (!shouldShowPublic() && (!mIsLowPriority || isExpanded()) && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) { mGroupExpansionChanging = true; final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification); @@ -790,7 +790,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * {@link #getNotificationHeader()} in case it is a low-priority group. */ public NotificationHeaderView getVisibleNotificationHeader() { - if (mIsSummaryWithChildren && !mShowingPublic) { + if (mIsSummaryWithChildren && !shouldShowPublic()) { return mChildrenContainer.getVisibleHeader(); } return getShowingLayout().getVisibleNotificationHeader(); @@ -1512,10 +1512,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } private void updateChildrenVisibility() { - mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren ? VISIBLE + mPrivateLayout.setVisibility(!shouldShowPublic() && !mIsSummaryWithChildren ? VISIBLE : INVISIBLE); if (mChildrenContainer != null) { - mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE + mChildrenContainer.setVisibility(!shouldShowPublic() && mIsSummaryWithChildren ? VISIBLE : INVISIBLE); } // The limits might have changed if the view suddenly became a group or vice versa @@ -1566,7 +1566,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public boolean isExpandable() { - if (mIsSummaryWithChildren && !mShowingPublic) { + if (mIsSummaryWithChildren && !shouldShowPublic()) { return !mChildrenExpanded; } return mEnableNonGroupedNotificationExpand && mExpandable; @@ -1611,7 +1611,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { mFalsingManager.setNotificationExpanded(); - if (mIsSummaryWithChildren && !mShowingPublic && allowChildExpansion + if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion && !mChildrenContainer.showingAsLowPriority()) { final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification); mGroupManager.setGroupExpanded(mStatusBarNotification, userExpanded); @@ -1906,7 +1906,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE); updateChildrenVisibility(); } else { - animateShowingPublic(delay, duration); + animateShowingPublic(delay, duration, mShowingPublic); } NotificationContentView showingLayout = getShowingLayout(); showingLayout.updateBackgroundColor(animated); @@ -1916,13 +1916,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mShowingPublicInitialized = true; } - private void animateShowingPublic(long delay, long duration) { + private void animateShowingPublic(long delay, long duration, boolean showingPublic) { View[] privateViews = mIsSummaryWithChildren ? new View[] {mChildrenContainer} : new View[] {mPrivateLayout}; View[] publicViews = new View[] {mPublicLayout}; - View[] hiddenChildren = mShowingPublic ? privateViews : publicViews; - View[] shownChildren = mShowingPublic ? publicViews : privateViews; + View[] hiddenChildren = showingPublic ? privateViews : publicViews; + View[] shownChildren = showingPublic ? publicViews : privateViews; for (final View hiddenView : hiddenChildren) { hiddenView.setVisibility(View.VISIBLE); hiddenView.animate().cancel(); @@ -1959,7 +1959,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * see {@link #isClearable()}. */ public boolean canViewBeDismissed() { - return isClearable() && (!mShowingPublic || !mSensitiveHiddenInGeneral); + return isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); + } + + private boolean shouldShowPublic() { + return mSensitive && mHideSensitiveForIntrinsicHeight; } public void makeActionsVisibile() { @@ -2005,7 +2009,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public boolean isContentExpandable() { - if (mIsSummaryWithChildren && !mShowingPublic) { + if (mIsSummaryWithChildren && !shouldShowPublic()) { return true; } NotificationContentView showingLayout = getShowingLayout(); @@ -2014,7 +2018,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override protected View getContentView() { - if (mIsSummaryWithChildren && !mShowingPublic) { + if (mIsSummaryWithChildren && !shouldShowPublic()) { return mChildrenContainer; } return getShowingLayout(); @@ -2079,7 +2083,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public int getMaxContentHeight() { - if (mIsSummaryWithChildren && !mShowingPublic) { + if (mIsSummaryWithChildren && !shouldShowPublic()) { return mChildrenContainer.getMaxContentHeight(); } NotificationContentView showingLayout = getShowingLayout(); @@ -2093,7 +2097,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) { return getPinnedHeadsUpHeight(false /* atLeastMinHeight */); - } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) { + } else if (mIsSummaryWithChildren && !isGroupExpanded() && !shouldShowPublic()) { return mChildrenContainer.getMinHeight(); } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp) { return mHeadsUpHeight; @@ -2104,7 +2108,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public int getCollapsedHeight() { - if (mIsSummaryWithChildren && !mShowingPublic) { + if (mIsSummaryWithChildren && !shouldShowPublic()) { return mChildrenContainer.getCollapsedHeight(); } return getMinHeight(); @@ -2144,7 +2148,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public NotificationContentView getShowingLayout() { - return mShowingPublic ? mPublicLayout : mPrivateLayout; + return shouldShowPublic() ? mPublicLayout : mPrivateLayout; } public void setLegacy(boolean legacy) { @@ -2250,7 +2254,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (header != null && header.isInTouchRect(x - getTranslation(), y)) { return true; } - if ((!mIsSummaryWithChildren || mShowingPublic) + if ((!mIsSummaryWithChildren || shouldShowPublic()) && getShowingLayout().disallowSingleClick(x, y)) { return true; } @@ -2280,7 +2284,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (canViewBeDismissed()) { info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); } - boolean expandable = mShowingPublic; + boolean expandable = shouldShowPublic(); boolean isExpanded = false; if (!expandable) { if (mIsSummaryWithChildren) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index 6bcd174adeae..4952da4b1fec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -37,10 +37,13 @@ public class NotificationListener extends NotificationListenerWithPlugins { private static final String TAG = "NotificationListener"; private final NotificationPresenter mPresenter; + private final NotificationRemoteInputManager mRemoteInputManager; private final Context mContext; - public NotificationListener(NotificationPresenter presenter, Context context) { + public NotificationListener(NotificationPresenter presenter, + NotificationRemoteInputManager remoteInputManager, Context context) { mPresenter = presenter; + mRemoteInputManager = remoteInputManager; mContext = context; } @@ -69,7 +72,7 @@ public class NotificationListener extends NotificationListenerWithPlugins { mPresenter.getHandler().post(() -> { processForRemoteInput(sbn.getNotification(), mContext); String key = sbn.getKey(); - mPresenter.getKeysKeptForRemoteInput().remove(key); + mRemoteInputManager.getKeysKeptForRemoteInput().remove(key); boolean isUpdate = mPresenter.getNotificationData().get(key) != null; // In case we don't allow child notifications, we ignore children of // notifications that have a summary, since` we're not going to show them diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java index 4eca2415d5c3..33c72534b1b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar; import android.content.Intent; import android.os.Handler; import android.service.notification.NotificationListenerService; +import android.view.View; import java.util.Set; @@ -29,7 +30,7 @@ import java.util.Set; * want to perform some action before doing so). */ public interface NotificationPresenter extends NotificationUpdateHandler, - NotificationData.Environment { + NotificationData.Environment, NotificationRemoteInputManager.Callback { /** * Returns true if the presenter is not visible. For example, it may not be necessary to do @@ -81,14 +82,6 @@ public interface NotificationPresenter extends NotificationUpdateHandler, void onWorkChallengeChanged(); /** - * Notifications in this set are kept around when they were canceled in response to a remote - * input interaction. This allows us to show what you replied and allows you to continue typing - * into it. - */ - // TODO: Create NotificationEntryManager and move this method to there. - Set<String> getKeysKeptForRemoteInput(); - - /** * Called when the current user changes. * @param newUserId new user id */ @@ -98,4 +91,20 @@ public interface NotificationPresenter extends NotificationUpdateHandler, * Gets the NotificationLockscreenUserManager for this Presenter. */ NotificationLockscreenUserManager getNotificationLockscreenUserManager(); + + /** + * Wakes the device up if dozing. + * + * @param time the time when the request to wake up was issued + * @param where which view caused this wake up request + */ + void wakeUpIfDozing(long time, View where); + + /** + * True if the device currently requires a PIN, pattern, or password to unlock. + * + * @param userId user id to query about + * @return true iff the device is locked + */ + boolean isDeviceLocked(int userId); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java new file mode 100644 index 000000000000..7827f62970e7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -0,0 +1,441 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.systemui.statusbar; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; + +import android.app.ActivityManager; +import android.app.PendingIntent; +import android.app.RemoteInput; +import android.content.Context; +import android.content.Intent; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserManager; +import android.service.notification.StatusBarNotification; +import android.util.ArraySet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.RemoteViews; +import android.widget.TextView; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.Dumpable; +import com.android.systemui.statusbar.policy.RemoteInputView; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Set; + +/** + * Class for handling remote input state over a set of notifications. This class handles things + * like keeping notifications temporarily that were cancelled as a response to a remote input + * interaction, keeping track of notifications to remove when NotificationPresenter is collapsed, + * and handling clicks on remote views. + */ +public class NotificationRemoteInputManager implements Dumpable { + public static final boolean ENABLE_REMOTE_INPUT = + SystemProperties.getBoolean("debug.enable_remote_input", true); + public static final boolean FORCE_REMOTE_INPUT_HISTORY = + SystemProperties.getBoolean("debug.force_remoteinput_history", true); + private static final boolean DEBUG = false; + private static final String TAG = "NotificationRemoteInputManager"; + + /** + * How long to wait before auto-dismissing a notification that was kept for remote input, and + * has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel + * these given that they technically don't exist anymore. We wait a bit in case the app issues + * an update. + */ + private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200; + + protected final ArraySet<NotificationData.Entry> mRemoteInputEntriesToRemoveOnCollapse = + new ArraySet<>(); + protected final NotificationLockscreenUserManager mLockscreenUserManager; + + /** + * Notifications with keys in this set are not actually around anymore. We kept them around + * when they were canceled in response to a remote input interaction. This allows us to show + * what you replied and allows you to continue typing into it. + */ + protected final ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>(); + protected final Context mContext; + private final UserManager mUserManager; + + protected RemoteInputController mRemoteInputController; + protected NotificationPresenter mPresenter; + protected IStatusBarService mBarService; + protected Callback mCallback; + + private final RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { + + @Override + public boolean onClickHandler( + final View view, final PendingIntent pendingIntent, final Intent fillInIntent) { + mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), view); + + if (handleRemoteInput(view, pendingIntent)) { + return true; + } + + if (DEBUG) { + Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); + } + logActionClick(view); + // The intent we are sending is for the application, which + // won't have permission to immediately start an activity after + // the user switches to home. We know it is safe to do at this + // point, so make sure new activity switches are now allowed. + try { + ActivityManager.getService().resumeAppSwitches(); + } catch (RemoteException e) { + } + return mCallback.handleRemoteViewClick(view, pendingIntent, fillInIntent, + () -> superOnClickHandler(view, pendingIntent, fillInIntent)); + } + + private void logActionClick(View view) { + ViewParent parent = view.getParent(); + String key = getNotificationKeyForParent(parent); + if (key == null) { + Log.w(TAG, "Couldn't determine notification for click."); + return; + } + int index = -1; + // If this is a default template, determine the index of the button. + if (view.getId() == com.android.internal.R.id.action0 && + parent != null && parent instanceof ViewGroup) { + ViewGroup actionGroup = (ViewGroup) parent; + index = actionGroup.indexOfChild(view); + } + try { + mBarService.onNotificationActionClick(key, index); + } catch (RemoteException e) { + // Ignore + } + } + + private String getNotificationKeyForParent(ViewParent parent) { + while (parent != null) { + if (parent instanceof ExpandableNotificationRow) { + return ((ExpandableNotificationRow) parent) + .getStatusBarNotification().getKey(); + } + parent = parent.getParent(); + } + return null; + } + + private boolean superOnClickHandler(View view, PendingIntent pendingIntent, + Intent fillInIntent) { + return super.onClickHandler(view, pendingIntent, fillInIntent, + WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); + } + + private boolean handleRemoteInput(View view, PendingIntent pendingIntent) { + if (mCallback.shouldHandleRemoteInput(view, pendingIntent)) { + return true; + } + + Object tag = view.getTag(com.android.internal.R.id.remote_input_tag); + RemoteInput[] inputs = null; + if (tag instanceof RemoteInput[]) { + inputs = (RemoteInput[]) tag; + } + + if (inputs == null) { + return false; + } + + RemoteInput input = null; + + for (RemoteInput i : inputs) { + if (i.getAllowFreeFormInput()) { + input = i; + } + } + + if (input == null) { + return false; + } + + ViewParent p = view.getParent(); + RemoteInputView riv = null; + while (p != null) { + if (p instanceof View) { + View pv = (View) p; + if (pv.isRootNamespace()) { + riv = findRemoteInputView(pv); + break; + } + } + p = p.getParent(); + } + ExpandableNotificationRow row = null; + while (p != null) { + if (p instanceof ExpandableNotificationRow) { + row = (ExpandableNotificationRow) p; + break; + } + p = p.getParent(); + } + + if (row == null) { + return false; + } + + row.setUserExpanded(true); + + if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) { + final int userId = pendingIntent.getCreatorUserHandle().getIdentifier(); + if (mLockscreenUserManager.isLockscreenPublicMode(userId)) { + mCallback.onLockedRemoteInput(row, view); + return true; + } + if (mUserManager.getUserInfo(userId).isManagedProfile() + && mPresenter.isDeviceLocked(userId)) { + mCallback.onLockedWorkRemoteInput(userId, row, view); + return true; + } + } + + if (riv == null) { + riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild()); + if (riv == null) { + return false; + } + if (!row.getPrivateLayout().getExpandedChild().isShown()) { + mCallback.onMakeExpandedVisibleForRemoteInput(row, view); + return true; + } + } + + int width = view.getWidth(); + if (view instanceof TextView) { + // Center the reveal on the text which might be off-center from the TextView + TextView tv = (TextView) view; + if (tv.getLayout() != null) { + int innerWidth = (int) tv.getLayout().getLineWidth(0); + innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight(); + width = Math.min(width, innerWidth); + } + } + int cx = view.getLeft() + width / 2; + int cy = view.getTop() + view.getHeight() / 2; + int w = riv.getWidth(); + int h = riv.getHeight(); + int r = Math.max( + Math.max(cx + cy, cx + (h - cy)), + Math.max((w - cx) + cy, (w - cx) + (h - cy))); + + riv.setRevealParameters(cx, cy, r); + riv.setPendingIntent(pendingIntent); + riv.setRemoteInput(inputs, input); + riv.focusAnimated(); + + return true; + } + + private RemoteInputView findRemoteInputView(View v) { + if (v == null) { + return null; + } + return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG); + } + }; + + public NotificationRemoteInputManager(NotificationLockscreenUserManager lockscreenUserManager, + Context context) { + mLockscreenUserManager = lockscreenUserManager; + mContext = context; + mBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + } + + public void setUpWithPresenter(NotificationPresenter presenter, + Callback callback, + RemoteInputController.Delegate delegate) { + mPresenter = presenter; + mCallback = callback; + mRemoteInputController = new RemoteInputController(delegate); + mRemoteInputController.addCallback(new RemoteInputController.Callback() { + @Override + public void onRemoteInputSent(NotificationData.Entry entry) { + if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) { + mPresenter.removeNotification(entry.key, null); + } else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) { + // We're currently holding onto this notification, but from the apps point of + // view it is already canceled, so we'll need to cancel it on the apps behalf + // after sending - unless the app posts an update in the mean time, so wait a + // bit. + mPresenter.getHandler().postDelayed(() -> { + if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) { + mPresenter.removeNotification(entry.key, null); + } + }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY); + } + } + }); + + } + + public RemoteInputController getController() { + return mRemoteInputController; + } + + public void onUpdateNotification(NotificationData.Entry entry) { + mRemoteInputEntriesToRemoveOnCollapse.remove(entry); + } + + /** + * Returns true if NotificationRemoteInputManager wants to keep this notification around. + * + * @param entry notification being removed + */ + public boolean onRemoveNotification(NotificationData.Entry entry) { + if (entry != null && mRemoteInputController.isRemoteInputActive(entry) + && (entry.row != null && !entry.row.isDismissed())) { + mRemoteInputEntriesToRemoveOnCollapse.add(entry); + return true; + } + return false; + } + + public void onPerformRemoveNotification(StatusBarNotification n, + NotificationData.Entry entry) { + if (mRemoteInputController.isRemoteInputActive(entry)) { + mRemoteInputController.removeRemoteInput(entry, null); + } + if (FORCE_REMOTE_INPUT_HISTORY + && mKeysKeptForRemoteInput.contains(n.getKey())) { + mKeysKeptForRemoteInput.remove(n.getKey()); + } + } + + public void removeRemoteInputEntriesKeptUntilCollapsed() { + for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) { + NotificationData.Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i); + mRemoteInputController.removeRemoteInput(entry, null); + mPresenter.removeNotification(entry.key, mPresenter.getLatestRankingMap()); + } + mRemoteInputEntriesToRemoveOnCollapse.clear(); + } + + public void checkRemoteInputOutside(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar + && event.getX() == 0 && event.getY() == 0 // a touch outside both bars + && mRemoteInputController.isRemoteInputActive()) { + mRemoteInputController.closeRemoteInputs(); + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("NotificationRemoteInputManager state:"); + pw.print(" mRemoteInputEntriesToRemoveOnCollapse: "); + pw.println(mRemoteInputEntriesToRemoveOnCollapse); + pw.print(" mKeysKeptForRemoteInput: "); + pw.println(mKeysKeptForRemoteInput); + } + + public void bindRow(ExpandableNotificationRow row) { + row.setRemoteInputController(mRemoteInputController); + row.setRemoteViewClickHandler(mOnClickHandler); + } + + public Set<String> getKeysKeptForRemoteInput() { + return mKeysKeptForRemoteInput; + } + + @VisibleForTesting + public Set<NotificationData.Entry> getRemoteInputEntriesToRemoveOnCollapse() { + return mRemoteInputEntriesToRemoveOnCollapse; + } + + /** + * Callback for various remote input related events, or for providing information that + * NotificationRemoteInputManager needs to know to decide what to do. + */ + public interface Callback { + + /** + * Called when remote input was activated but the device is locked. + * + * @param row + * @param clicked + */ + void onLockedRemoteInput(ExpandableNotificationRow row, View clicked); + + /** + * Called when remote input was activated but the device is locked and in a managed profile. + * + * @param userId + * @param row + * @param clicked + */ + void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row, View clicked); + + /** + * Called when a row should be made expanded for the purposes of remote input. + * + * @param row + * @param clickedView + */ + void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row, View clickedView); + + /** + * Return whether or not remote input should be handled for this view. + * + * @param view + * @param pendingIntent + * @return true iff the remote input should be handled + */ + boolean shouldHandleRemoteInput(View view, PendingIntent pendingIntent); + + /** + * Performs any special handling for a remote view click. The default behaviour can be + * called through the defaultHandler parameter. + * + * @param view + * @param pendingIntent + * @param fillInIntent + * @param defaultHandler + * @return true iff the click was handled + */ + boolean handleRemoteViewClick(View view, PendingIntent pendingIntent, Intent fillInIntent, + ClickHandler defaultHandler); + } + + /** + * Helper interface meant for passing the default on click behaviour to NotificationPresenter, + * so it may do its own handling before invoking the default behaviour. + */ + public interface ClickHandler { + /** + * Tries to handle a click on a remote view. + * + * @return true iff the click was handled + */ + boolean handleClick(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 5c1c6addb5a6..d162448e09b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -28,6 +28,9 @@ import static com.android.systemui.statusbar.NotificationLockscreenUserManager .NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA; +import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; +import static com.android.systemui.statusbar.NotificationRemoteInputManager + .FORCE_REMOTE_INPUT_HISTORY; import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; @@ -47,7 +50,6 @@ import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.RemoteInput; import android.app.StatusBarManager; import android.app.TaskStackBuilder; import android.app.WallpaperColors; @@ -126,7 +128,6 @@ import android.view.accessibility.AccessibilityManager; import android.view.animation.AccelerateInterpolator; import android.widget.DateTimeView; import android.widget.ImageView; -import android.widget.RemoteViews; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; @@ -204,6 +205,7 @@ import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; +import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.ScrimView; @@ -230,7 +232,6 @@ import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.PreviewInflater; -import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; @@ -250,8 +251,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.Stack; +import java.util.function.Function; public class StatusBar extends SystemUI implements DemoMode, DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener, @@ -262,12 +263,8 @@ public class StatusBar extends SystemUI implements DemoMode, ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter { public static final boolean MULTIUSER_DEBUG = false; - public static final boolean ENABLE_REMOTE_INPUT = - SystemProperties.getBoolean("debug.enable_remote_input", true); public static final boolean ENABLE_CHILD_NOTIFICATIONS = SystemProperties.getBoolean("debug.child_notifs", true); - public static final boolean FORCE_REMOTE_INPUT_HISTORY = - SystemProperties.getBoolean("debug.force_remoteinput_history", true); protected static final int MSG_HIDE_RECENT_APPS = 1020; protected static final int MSG_PRELOAD_RECENT_APPS = 1022; @@ -345,14 +342,6 @@ public class StatusBar extends SystemUI implements DemoMode, private static final boolean ENABLE_LOCKSCREEN_WALLPAPER = true; /** - * How long to wait before auto-dismissing a notification that was kept for remote input, and - * has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel - * these given that they technically don't exist anymore. We wait a bit in case the app issues - * an update. - */ - private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200; - - /** * Never let the alpha become zero for surfaces that draw with SRC - otherwise the RenderNode * won't draw anything and uninitialized memory will show through * if mScrimSrcModeEnabled. Note that 0.001 is rounded down to 0 in @@ -540,6 +529,7 @@ public class StatusBar extends SystemUI implements DemoMode, private NotificationMediaManager mMediaManager; protected NotificationLockscreenUserManager mLockscreenUserManager; + protected NotificationRemoteInputManager mRemoteInputManager; /** Keys of notifications currently visible to the user. */ private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications = @@ -725,6 +715,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void start() { + mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class); mNetworkController = Dependency.get(NetworkController.class); mUserSwitcherController = Dependency.get(UserSwitcherController.class); mScreenLifecycle = Dependency.get(ScreenLifecycle.class); @@ -779,7 +770,6 @@ public class StatusBar extends SystemUI implements DemoMode, mRecents = getComponent(Recents.class); - mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mLockPatternUtils = new LockPatternUtils(mContext); @@ -819,7 +809,7 @@ public class StatusBar extends SystemUI implements DemoMode, } // Set up the initial notification state. - mNotificationListener = new NotificationListener(this, mContext); + mNotificationListener = new NotificationListener(this, mRemoteInputManager, mContext); mNotificationListener.register(); if (DEBUG) { @@ -1161,7 +1151,7 @@ public class StatusBar extends SystemUI implements DemoMode, protected View.OnTouchListener getStatusBarWindowTouchListener() { return (v, event) -> { checkUserAutohide(event); - checkRemoteInputOutside(event); + mRemoteInputManager.checkRemoteInputOutside(event); if (event.getAction() == MotionEvent.ACTION_DOWN) { if (mExpandedVisible) { animateCollapsePanels(); @@ -1434,31 +1424,7 @@ public class StatusBar extends SystemUI implements DemoMode, mKeyguardIndicationController .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); - mRemoteInputController.addCallback(mStatusBarKeyguardViewManager); - - mRemoteInputController.addCallback(new RemoteInputController.Callback() { - @Override - public void onRemoteInputSent(Entry entry) { - if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) { - removeNotification(entry.key, null); - } else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) { - // We're currently holding onto this notification, but from the apps point of - // view it is already canceled, so we'll need to cancel it on the apps behalf - // after sending - unless the app posts an update in the mean time, so wait a - // bit. - mHandler.postDelayed(() -> { - if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) { - removeNotification(entry.key, null); - } - }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY); - } - try { - mBarService.onNotificationDirectReplied(entry.key); - } catch (RemoteException e) { - // system process is dead if we're here. - } - } - }); + mRemoteInputManager.getController().addCallback(mStatusBarKeyguardViewManager); mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback(); mLightBarController.setFingerprintUnlockController(mFingerprintUnlockController); @@ -1635,7 +1601,7 @@ public class StatusBar extends SystemUI implements DemoMode, // sending look longer than it takes. // Also we should not defer the removal if reordering isn't allowed since otherwise // some notifications can't disappear before the panel is closed. - boolean ignoreEarliestRemovalTime = mRemoteInputController.isSpinning(key) + boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key) && !FORCE_REMOTE_INPUT_HISTORY || !mVisualStabilityManager.isReorderingAllowed(); deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime); @@ -1643,7 +1609,7 @@ public class StatusBar extends SystemUI implements DemoMode, mMediaManager.onNotificationRemoved(key); Entry entry = mNotificationData.get(key); - if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key) + if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputManager.getController().isSpinning(key) && entry.row != null && !entry.row.isDismissed()) { StatusBarNotification sbn = entry.notification; @@ -1681,7 +1647,7 @@ public class StatusBar extends SystemUI implements DemoMode, } if (updated) { Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key); - mKeysKeptForRemoteInput.add(entry.key); + mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key); return; } } @@ -1691,12 +1657,11 @@ public class StatusBar extends SystemUI implements DemoMode, return; } - if (entry != null && mRemoteInputController.isRemoteInputActive(entry) - && (entry.row != null && !entry.row.isDismissed())) { + if (mRemoteInputManager.onRemoveNotification(entry)) { mLatestRankingMap = ranking; - mRemoteInputEntriesToRemoveOnCollapse.add(entry); return; } + if (entry != null && mGutsManager.getExposedGuts() != null && mGutsManager.getExposedGuts() == entry.row.getGuts() && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) { @@ -1770,9 +1735,7 @@ public class StatusBar extends SystemUI implements DemoMode, protected void performRemoveNotification(StatusBarNotification n) { Entry entry = mNotificationData.get(n.getKey()); - if (mRemoteInputController.isRemoteInputActive(entry)) { - mRemoteInputController.removeRemoteInput(entry, null); - } + mRemoteInputManager.onPerformRemoveNotification(n, entry); // start old BaseStatusBar.performRemoveNotification. final String pkg = n.getPackageName(); final String tag = n.getTag(); @@ -1785,12 +1748,7 @@ public class StatusBar extends SystemUI implements DemoMode, } else if (mStackScroller.hasPulsingNotifications()) { dismissalSurface = NotificationStats.DISMISSAL_AOD; } - mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), - dismissalSurface); - if (FORCE_REMOTE_INPUT_HISTORY - && mKeysKeptForRemoteInput.contains(n.getKey())) { - mKeysKeptForRemoteInput.remove(n.getKey()); - } + mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface); removeNotification(n.getKey(), null); } catch (RemoteException ex) { @@ -2509,7 +2467,7 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarWindowManager.setHeadsUpShowing(false); mHeadsUpManager.setHeadsUpGoingAway(false); } - removeRemoteInputEntriesKeptUntilCollapsed(); + mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed(); }); } } @@ -2588,17 +2546,8 @@ public class StatusBar extends SystemUI implements DemoMode, } if (!isExpanded) { - removeRemoteInputEntriesKeptUntilCollapsed(); - } - } - - private void removeRemoteInputEntriesKeptUntilCollapsed() { - for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) { - Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i); - mRemoteInputController.removeRemoteInput(entry, null); - removeNotification(entry.key, mLatestRankingMap); + mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed(); } - mRemoteInputEntriesToRemoveOnCollapse.clear(); } public NotificationStackScrollLayout getNotificationScrollLayout() { @@ -3184,19 +3133,12 @@ public class StatusBar extends SystemUI implements DemoMode, if ((mSystemUiVisibility & STATUS_OR_NAV_TRANSIENT) != 0 // a transient bar is revealed && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar && event.getX() == 0 && event.getY() == 0 // a touch outside both bars - && !mRemoteInputController.isRemoteInputActive()) { // not due to typing in IME + && !mRemoteInputManager.getController() + .isRemoteInputActive()) { // not due to typing in IME userAutohide(); } } - private void checkRemoteInputOutside(MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar - && event.getX() == 0 && event.getY() == 0 // a touch outside both bars - && mRemoteInputController.isRemoteInputActive()) { - mRemoteInputController.closeRemoteInputs(); - } - } - private void userAutohide() { cancelAutohide(); mHandler.postDelayed(mAutohide, 350); // longer than app gesture -> flag clear @@ -3377,18 +3319,18 @@ public class StatusBar extends SystemUI implements DemoMode, private void addStatusBarWindow() { makeStatusBarView(); mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class); - mRemoteInputController = new RemoteInputController(new RemoteInputController.Delegate() { - public void setRemoteInputActive(NotificationData.Entry entry, - boolean remoteInputActive) { - mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); - } - public void lockScrollTo(NotificationData.Entry entry) { - mStackScroller.lockScrollTo(entry.row); - } - public void requestDisallowLongPressAndDismiss() { - mStackScroller.requestDisallowLongPress(); - mStackScroller.requestDisallowDismiss(); - } + mRemoteInputManager.setUpWithPresenter(this, this, new RemoteInputController.Delegate() { + public void setRemoteInputActive(NotificationData.Entry entry, + boolean remoteInputActive) { + mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); + } + public void lockScrollTo(NotificationData.Entry entry) { + mStackScroller.lockScrollTo(entry.row); + } + public void requestDisallowLongPressAndDismiss() { + mStackScroller.requestDisallowLongPress(); + mStackScroller.requestDisallowDismiss(); + } }); mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight()); } @@ -3508,8 +3450,8 @@ public class StatusBar extends SystemUI implements DemoMode, String action = intent.getAction(); if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { KeyboardShortcuts.dismiss(); - if (mRemoteInputController != null) { - mRemoteInputController.closeRemoteInputs(); + if (mRemoteInputManager.getController() != null) { + mRemoteInputManager.getController().closeRemoteInputs(); } if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) { int flags = CommandQueue.FLAG_EXCLUDE_NONE; @@ -4547,7 +4489,7 @@ public class StatusBar extends SystemUI implements DemoMode, clearNotificationEffects(); } if (state == StatusBarState.KEYGUARD) { - removeRemoteInputEntriesKeptUntilCollapsed(); + mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed(); maybeEscalateHeadsUp(); } mState = state; @@ -4751,13 +4693,15 @@ public class StatusBar extends SystemUI implements DemoMode, dismissKeyguardThenExecute(dismissAction, true /* afterKeyguardGone */); } - protected void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) { + @Override + public void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) { mLeaveOpenOnKeyguardHide = true; showBouncer(); mPendingRemoteInputView = clicked; } - protected void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row, + @Override + public void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row, View clickedView) { if (isKeyguardShowing()) { onLockedRemoteInput(row, clickedView); @@ -4767,6 +4711,47 @@ public class StatusBar extends SystemUI implements DemoMode, } } + @Override + public boolean shouldHandleRemoteInput(View view, PendingIntent pendingIntent) { + // Skip remote input as doing so will expand the notification shade. + return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0; + } + + @Override + public boolean handleRemoteViewClick(View view, PendingIntent pendingIntent, + Intent fillInIntent, NotificationRemoteInputManager.ClickHandler defaultHandler) { + final boolean isActivity = pendingIntent.isActivity(); + if (isActivity) { + final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity( + mContext, pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId()); + dismissKeyguardThenExecute(() -> { + try { + ActivityManager.getService().resumeAppSwitches(); + } catch (RemoteException e) { + } + + boolean handled = defaultHandler.handleClick(); + + // close the shade if it was open + if (handled && !mNotificationPanel.isFullyCollapsed()) { + animateCollapsePanels( + CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); + visibilityChanged(false); + mAssistManager.hideAssist(); + + // Wait for activity start. + return true; + } else { + return false; + } + + }, afterKeyguardGone); + return true; + } else { + return defaultHandler.handleClick(); + } + } + protected boolean startWorkChallengeIfNecessary(int userId, IntentSender intendSender, String notificationKey) { // Clear pending remote view, as we do not want to trigger pending remote input view when @@ -4803,7 +4788,8 @@ public class StatusBar extends SystemUI implements DemoMode, // End old BaseStatusBar.startWorkChallengeIfNecessary. } - protected void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row, + @Override + public void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row, View clicked) { // Collapse notification and show work challenge animateCollapsePanels(); @@ -5029,6 +5015,7 @@ public class StatusBar extends SystemUI implements DemoMode, return !mNotificationData.getActiveNotifications().isEmpty(); } + @Override public void wakeUpIfDozing(long time, View where) { if (mDozing) { PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -5043,6 +5030,11 @@ public class StatusBar extends SystemUI implements DemoMode, } @Override + public boolean isDeviceLocked(int userId) { + return mKeyguardManager.isDeviceLocked(userId); + } + + @Override public void appTransitionCancelled() { EventBus.getDefault().send(new AppTransitionFinishedEvent()); } @@ -5149,7 +5141,15 @@ public class StatusBar extends SystemUI implements DemoMode, Trace.endSection(); } - public void updateScrimController() { + @VisibleForTesting + void updateScrimController() { + Trace.beginSection("StatusBar#updateScrimController"); + + // We don't want to end up in KEYGUARD state when we're unlocking with + // fingerprint from doze. We should cross fade directly from black. + final boolean wakeAndUnlocking = mFingerprintUnlockController.getMode() + == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK; + if (mBouncerShowing) { mScrimController.transitionTo(ScrimState.BOUNCER); } else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) { @@ -5160,11 +5160,12 @@ public class StatusBar extends SystemUI implements DemoMode, // Handled in DozeScrimController#setPulsing } else if (mDozing) { mScrimController.transitionTo(ScrimState.AOD); - } else if (mIsKeyguard) { + } else if (mIsKeyguard && !wakeAndUnlocking) { mScrimController.transitionTo(ScrimState.KEYGUARD); } else { mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); } + Trace.endSection(); } public boolean isKeyguardShowing() { @@ -5395,7 +5396,6 @@ public class StatusBar extends SystemUI implements DemoMode, protected final NotificationGroupManager mGroupManager = new NotificationGroupManager(); - protected RemoteInputController mRemoteInputController; // for heads up notifications protected HeadsUpManager mHeadsUpManager; @@ -5412,14 +5412,6 @@ public class StatusBar extends SystemUI implements DemoMode, protected boolean mVisible; protected final ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>(); - protected final ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>(); - - /** - * Notifications with keys in this set are not actually around anymore. We kept them around - * when they were canceled in response to a remote input interaction. This allows us to show - * what you replied and allows you to continue typing into it. - */ - protected final ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>(); // mScreenOnFromKeyguard && mVisible. private boolean mVisibleToUser; @@ -5431,8 +5423,6 @@ public class StatusBar extends SystemUI implements DemoMode, protected PowerManager mPowerManager; protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - private UserManager mUserManager; - protected KeyguardManager mKeyguardManager; private LockPatternUtils mLockPatternUtils; private DeviceProvisionedController mDeviceProvisionedController @@ -5486,212 +5476,6 @@ public class StatusBar extends SystemUI implements DemoMode, } }; - private final RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { - - @Override - public boolean onClickHandler( - final View view, final PendingIntent pendingIntent, final Intent fillInIntent) { - wakeUpIfDozing(SystemClock.uptimeMillis(), view); - - if (handleRemoteInput(view, pendingIntent)) { - return true; - } - - if (DEBUG) { - Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); - } - logActionClick(view); - // The intent we are sending is for the application, which - // won't have permission to immediately start an activity after - // the user switches to home. We know it is safe to do at this - // point, so make sure new activity switches are now allowed. - try { - ActivityManager.getService().resumeAppSwitches(); - } catch (RemoteException e) { - } - final boolean isActivity = pendingIntent.isActivity(); - if (isActivity) { - final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity( - mContext, pendingIntent.getIntent(), - mLockscreenUserManager.getCurrentUserId()); - dismissKeyguardThenExecute(() -> { - try { - ActivityManager.getService().resumeAppSwitches(); - } catch (RemoteException e) { - } - - boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent); - - // close the shade if it was open - if (handled && !mNotificationPanel.isFullyCollapsed()) { - animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); - visibilityChanged(false); - mAssistManager.hideAssist(); - - // Wait for activity start. - return true; - } else { - return false; - } - - }, afterKeyguardGone); - return true; - } else { - return superOnClickHandler(view, pendingIntent, fillInIntent); - } - } - - private void logActionClick(View view) { - ViewParent parent = view.getParent(); - String key = getNotificationKeyForParent(parent); - if (key == null) { - Log.w(TAG, "Couldn't determine notification for click."); - return; - } - int index = -1; - // If this is a default template, determine the index of the button. - if (view.getId() == com.android.internal.R.id.action0 && - parent != null && parent instanceof ViewGroup) { - ViewGroup actionGroup = (ViewGroup) parent; - index = actionGroup.indexOfChild(view); - } - try { - mBarService.onNotificationActionClick(key, index); - } catch (RemoteException e) { - // Ignore - } - } - - private String getNotificationKeyForParent(ViewParent parent) { - while (parent != null) { - if (parent instanceof ExpandableNotificationRow) { - return ((ExpandableNotificationRow) parent).getStatusBarNotification().getKey(); - } - parent = parent.getParent(); - } - return null; - } - - private boolean superOnClickHandler(View view, PendingIntent pendingIntent, - Intent fillInIntent) { - return super.onClickHandler(view, pendingIntent, fillInIntent, - WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); - } - - private boolean handleRemoteInput(View view, PendingIntent pendingIntent) { - if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) { - // Skip remote input as doing so will expand the notification shade. - return true; - } - - Object tag = view.getTag(com.android.internal.R.id.remote_input_tag); - RemoteInput[] inputs = null; - if (tag instanceof RemoteInput[]) { - inputs = (RemoteInput[]) tag; - } - - if (inputs == null) { - return false; - } - - RemoteInput input = null; - - for (RemoteInput i : inputs) { - if (i.getAllowFreeFormInput()) { - input = i; - } - } - - if (input == null) { - return false; - } - - ViewParent p = view.getParent(); - RemoteInputView riv = null; - while (p != null) { - if (p instanceof View) { - View pv = (View) p; - if (pv.isRootNamespace()) { - riv = findRemoteInputView(pv); - break; - } - } - p = p.getParent(); - } - ExpandableNotificationRow row = null; - while (p != null) { - if (p instanceof ExpandableNotificationRow) { - row = (ExpandableNotificationRow) p; - break; - } - p = p.getParent(); - } - - if (row == null) { - return false; - } - - row.setUserExpanded(true); - - if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) { - final int userId = pendingIntent.getCreatorUserHandle().getIdentifier(); - if (mLockscreenUserManager.isLockscreenPublicMode(userId)) { - onLockedRemoteInput(row, view); - return true; - } - if (mUserManager.getUserInfo(userId).isManagedProfile() - && mKeyguardManager.isDeviceLocked(userId)) { - onLockedWorkRemoteInput(userId, row, view); - return true; - } - } - - if (riv == null) { - riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild()); - if (riv == null) { - return false; - } - if (!row.getPrivateLayout().getExpandedChild().isShown()) { - onMakeExpandedVisibleForRemoteInput(row, view); - return true; - } - } - - int width = view.getWidth(); - if (view instanceof TextView) { - // Center the reveal on the text which might be off-center from the TextView - TextView tv = (TextView) view; - if (tv.getLayout() != null) { - int innerWidth = (int) tv.getLayout().getLineWidth(0); - innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight(); - width = Math.min(width, innerWidth); - } - } - int cx = view.getLeft() + width / 2; - int cy = view.getTop() + view.getHeight() / 2; - int w = riv.getWidth(); - int h = riv.getHeight(); - int r = Math.max( - Math.max(cx + cy, cx + (h - cy)), - Math.max((w - cx) + cy, (w - cx) + (h - cy))); - - riv.setRevealParameters(cx, cy, r); - riv.setPendingIntent(pendingIntent); - riv.setRemoteInput(inputs, input); - riv.focusAnimated(); - - return true; - } - - private RemoteInputView findRemoteInputView(View v) { - if (v == null) { - return null; - } - return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG); - } - }; - private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -5936,12 +5720,11 @@ public class StatusBar extends SystemUI implements DemoMode, row.setGroupManager(mGroupManager); row.setHeadsUpManager(mHeadsUpManager); row.setAboveShelfChangedListener(mAboveShelfObserver); - row.setRemoteInputController(mRemoteInputController); row.setOnExpandClickListener(this); - row.setRemoteViewClickHandler(mOnClickHandler); row.setInflationCallback(this); row.setSecureStateProvider(this::isKeyguardCurrentlySecure); row.setLongPressListener(getNotificationLongClicker()); + mRemoteInputManager.bindRow(row); // Get the app name. // Note that Notification.Builder#bindHeaderAppName has similar logic @@ -6403,7 +6186,8 @@ public class StatusBar extends SystemUI implements DemoMode, return; } mHeadsUpEntriesToRemoveOnSwitch.remove(entry); - mRemoteInputEntriesToRemoveOnCollapse.remove(entry); + mRemoteInputManager.onUpdateNotification(entry); + if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) { mGutsManager.setKeyToRemoveOnGutsClosed(null); Log.w(TAG, "Notification that was kept for guts was updated. " + key); @@ -6648,11 +6432,6 @@ public class StatusBar extends SystemUI implements DemoMode, return mLatestRankingMap; } - @Override - public Set<String> getKeysKeptForRemoteInput() { - return mKeysKeptForRemoteInput; - } - private final NotificationInfo.CheckSaveListener mCheckSaveListener = (Runnable saveImportance, StatusBarNotification sbn) -> { // If the user has security enabled, show challenge if the setting is changed. 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 ed96b4115888..b0b5b8ec1e09 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; + import android.app.ActivityManager; import android.app.IActivityManager; import android.content.Context; @@ -156,7 +158,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D private void applyFocusableFlag(State state) { boolean panelFocusable = state.statusBarFocusable && state.panelExpanded; if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput) - || StatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) { + || ENABLE_REMOTE_INPUT && state.remoteInputActive) { mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 0f2a2c8b2bc2..fe39a894a094 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -3681,6 +3681,7 @@ public class NotificationStackScrollLayout extends ViewGroup mHideSensitiveNeedsAnimation = true; mNeedsAnimation = true; } + updateContentHeight(); requestChildrenUpdate(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java index 58ff46ad54ee..544585a4a917 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java @@ -50,7 +50,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test public void testGroupSummaryNotShowingIconWhenPublic() { mGroup.setSensitive(true, true); - mGroup.setHideSensitive(true, false, 0, 0); + mGroup.setHideSensitiveForIntrinsicHeight(true); Assert.assertTrue(mGroup.isSummaryWithChildren()); Assert.assertFalse(mGroup.isShowingIcon()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java index 6ecfe3e6be47..f562340664c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java @@ -56,6 +56,7 @@ public class NotificationListenerTest extends SysuiTestCase { private NotificationListenerService.RankingMap mRanking; private Set<String> mKeysKeptForRemoteInput; private NotificationData mNotificationData; + private NotificationRemoteInputManager mRemoteInputManager; @Before public void setUp() { @@ -63,13 +64,14 @@ public class NotificationListenerTest extends SysuiTestCase { mPresenter = mock(NotificationPresenter.class); mNotificationData = mock(NotificationData.class); mRanking = mock(NotificationListenerService.RankingMap.class); + mRemoteInputManager = mock(NotificationRemoteInputManager.class); mKeysKeptForRemoteInput = new HashSet<>(); when(mPresenter.getHandler()).thenReturn(mHandler); when(mPresenter.getNotificationData()).thenReturn(mNotificationData); - when(mPresenter.getKeysKeptForRemoteInput()).thenReturn(mKeysKeptForRemoteInput); + when(mRemoteInputManager.getKeysKeptForRemoteInput()).thenReturn(mKeysKeptForRemoteInput); - mListener = new NotificationListener(mPresenter, mContext); + mListener = new NotificationListener(mPresenter, mRemoteInputManager, mContext); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, new Notification(), UserHandle.CURRENT, null, 0); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java new file mode 100644 index 000000000000..b881c098a1a3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java @@ -0,0 +1,118 @@ +package com.android.systemui.statusbar; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.systemui.SysuiTestCase; + +import com.google.android.collect.Sets; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NotificationRemoteInputManagerTest extends SysuiTestCase { + private static final String TEST_PACKAGE_NAME = "test"; + private static final int TEST_UID = 0; + + private Handler mHandler; + private TestableNotificationRemoteInputManager mRemoteInputManager; + private StatusBarNotification mSbn; + private NotificationData.Entry mEntry; + + @Mock private NotificationPresenter mPresenter; + @Mock private RemoteInputController.Delegate mDelegate; + @Mock private NotificationLockscreenUserManager mLockscreenUserManager; + @Mock private NotificationRemoteInputManager.Callback mCallback; + @Mock private RemoteInputController mController; + @Mock private NotificationListenerService.RankingMap mRanking; + @Mock private ExpandableNotificationRow mRow; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mHandler = new Handler(Looper.getMainLooper()); + + when(mPresenter.getHandler()).thenReturn(mHandler); + when(mPresenter.getLatestRankingMap()).thenReturn(mRanking); + + mRemoteInputManager = new TestableNotificationRemoteInputManager(mLockscreenUserManager, + mContext); + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, + 0, new Notification(), UserHandle.CURRENT, null, 0); + mEntry = new NotificationData.Entry(mSbn); + mEntry.row = mRow; + + mRemoteInputManager.setUpWithPresenterForTest(mPresenter, mCallback, mDelegate, + mController); + } + + @Test + public void testOnRemoveNotificationNotKept() { + assertFalse(mRemoteInputManager.onRemoveNotification(mEntry)); + assertTrue(mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().isEmpty()); + } + + @Test + public void testOnRemoveNotificationKept() { + when(mController.isRemoteInputActive(mEntry)).thenReturn(true); + assertTrue(mRemoteInputManager.onRemoveNotification(mEntry)); + assertTrue(mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().equals( + Sets.newArraySet(mEntry))); + } + + @Test + public void testPerformOnRemoveNotification() { + when(mController.isRemoteInputActive(mEntry)).thenReturn(true); + mRemoteInputManager.getKeysKeptForRemoteInput().add(mEntry.key); + mRemoteInputManager.onPerformRemoveNotification(mSbn, mEntry); + + verify(mController).removeRemoteInput(mEntry, null); + assertTrue(mRemoteInputManager.getKeysKeptForRemoteInput().isEmpty()); + } + + @Test + public void testRemoveRemoteInputEntriesKeptUntilCollapsed() { + mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().add(mEntry); + mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed(); + + assertTrue(mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().isEmpty()); + verify(mController).removeRemoteInput(mEntry, null); + verify(mPresenter).removeNotification(mEntry.key, mRanking); + } + + private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager { + + public TestableNotificationRemoteInputManager( + NotificationLockscreenUserManager lockscreenUserManager, Context context) { + super(lockscreenUserManager, context); + } + + public void setUpWithPresenterForTest(NotificationPresenter presenter, + Callback callback, + RemoteInputController.Delegate delegate, + RemoteInputController controller) { + super.setUpWithPresenter(presenter, callback, delegate); + mRemoteInputController = controller; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 5ff90d91b333..e4c33f11221b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -26,6 +26,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -107,6 +108,7 @@ public class StatusBarTest extends SysuiTestCase { ScrimController mScrimController; IStatusBarService mBarService; ArrayList<Entry> mNotificationList; + FingerprintUnlockController mFingerprintUnlockController; private DisplayMetrics mDisplayMetrics = new DisplayMetrics(); @Before @@ -133,6 +135,7 @@ public class StatusBarTest extends SysuiTestCase { when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0)); mNotificationList = mock(ArrayList.class); mScrimController = mock(ScrimController.class); + mFingerprintUnlockController = mock(FingerprintUnlockController.class); IPowerManager powerManagerService = mock(IPowerManager.class); HandlerThread handlerThread = new HandlerThread("TestThread"); handlerThread.start(); @@ -145,7 +148,7 @@ public class StatusBarTest extends SysuiTestCase { mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache, mKeyguardIndicationController, mStackScroller, mHeadsUpManager, mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView, - mBarService, mScrimController); + mBarService, mScrimController, mFingerprintUnlockController); mStatusBar.mContext = mContext; mStatusBar.mComponents = mContext.getComponents(); doAnswer(invocation -> { @@ -538,18 +541,27 @@ public class StatusBarTest extends SysuiTestCase { @Test public void testFingerprintNotification_UpdatesScrims() { mStatusBar.mStatusBarWindowManager = mock(StatusBarWindowManager.class); - mStatusBar.mFingerprintUnlockController = mock(FingerprintUnlockController.class); mStatusBar.mDozeScrimController = mock(DozeScrimController.class); mStatusBar.notifyFpAuthModeChanged(); verify(mScrimController).transitionTo(any(), any()); } + @Test + public void testFingerprintUnlock_UpdatesScrims() { + // Simulate unlocking from AoD with fingerprint. + when(mFingerprintUnlockController.getMode()) + .thenReturn(FingerprintUnlockController.MODE_WAKE_AND_UNLOCK); + mStatusBar.updateScrimController(); + verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any()); + } + static class TestableStatusBar extends StatusBar { public TestableStatusBar(StatusBarKeyguardViewManager man, UnlockMethodCache unlock, KeyguardIndicationController key, NotificationStackScrollLayout stack, HeadsUpManager hum, NotificationData nd, PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView, - IStatusBarService barService, ScrimController scrimController) { + IStatusBarService barService, ScrimController scrimController, + FingerprintUnlockController fingerprintUnlockController) { mStatusBarKeyguardViewManager = man; mUnlockMethodCache = unlock; mKeyguardIndicationController = key; @@ -563,6 +575,7 @@ public class StatusBarTest extends SysuiTestCase { mBarService = barService; mWakefulnessLifecycle = createAwakeWakefulnessLifecycle(); mScrimController = scrimController; + mFingerprintUnlockController = fingerprintUnlockController; } private WakefulnessLifecycle createAwakeWakefulnessLifecycle() { diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 37f1dc490fe3..5fed93b1b0b1 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -49,12 +49,14 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; +import android.Manifest; import android.annotation.BinderThread; import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -104,6 +106,8 @@ import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; +import android.os.ShellCallback; +import android.os.ShellCommand; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; @@ -178,6 +182,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final boolean DEBUG_RESTORE = DEBUG || false; static final String TAG = "InputMethodManagerService"; + @Retention(SOURCE) + @IntDef({ShellCommandResult.SUCCESS, ShellCommandResult.FAILURE}) + private @interface ShellCommandResult { + int SUCCESS = 0; + int FAILURE = -1; + } + static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1; static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 2; static final int MSG_SHOW_IM_CONFIG = 3; @@ -3885,30 +3896,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // ---------------------------------------------------------------------- - @Override - public boolean setInputMethodEnabled(String id, boolean enabled) { - // TODO: Make this work even for non-current users? - if (!calledFromValidUser()) { - return false; - } - synchronized (mMethodMap) { - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.WRITE_SECURE_SETTINGS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException( - "Requires permission " - + android.Manifest.permission.WRITE_SECURE_SETTINGS); - } - - long ident = Binder.clearCallingIdentity(); - try { - return setInputMethodEnabledLocked(id, enabled); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - boolean setInputMethodEnabledLocked(String id, boolean enabled) { // Make sure this is a valid input method. InputMethodInfo imm = mMethodMap.get(id); @@ -4633,4 +4620,257 @@ public class InputMethodManagerService extends IInputMethodManager.Stub p.println("No input method service."); } } + + @BinderThread + @Override + public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, + @Nullable FileDescriptor err, + @NonNull String[] args, @Nullable ShellCallback callback, + @NonNull ResultReceiver resultReceiver) throws RemoteException { + new ShellCommandImpl(this).exec( + this, in, out, err, args, callback, resultReceiver); + } + + private static final class ShellCommandImpl extends ShellCommand { + @NonNull + final InputMethodManagerService mService; + + ShellCommandImpl(InputMethodManagerService service) { + mService = service; + } + + @BinderThread + @ShellCommandResult + @Override + public int onCommand(@Nullable String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + switch (cmd) { + case "list": + return mService.handleShellCommandListInputMethods(this); + case "enable": + return mService.handleShellCommandEnableDisableInputMethod(this, true); + case "disable": + return mService.handleShellCommandEnableDisableInputMethod(this, false); + case "set": + return mService.handleShellCommandSetInputMethod(this); + case "reset-ime": + return mService.handleShellCommandResetInputMethod(this); + default: + return handleDefaultCommands(cmd); + } + } + + @BinderThread + @Override + public void onHelp() { + try (PrintWriter pw = getOutPrintWriter()) { + pw.println("InputMethodManagerService commands:"); + pw.println(" help"); + pw.println(" Prints this help text."); + pw.println(" dump [options]"); + pw.println(" Synonym of dumpsys."); + pw.println(" list [-a] [-s]"); + pw.println(" prints all enabled input methods."); + pw.println(" -a: see all input methods"); + pw.println(" -s: only a single summary line of each"); + pw.println(" enable <ID>"); + pw.println(" allows the given input method ID to be used."); + pw.println(" disable <ID>"); + pw.println(" disallows the given input method ID to be used."); + pw.println(" set <ID>"); + pw.println(" switches to the given input method ID."); + pw.println(" reset-ime"); + pw.println(" reset currently selected/enabled IMEs to the default ones as if"); + pw.println(" the device is initially booted with the current locale."); + } + } + } + + // ---------------------------------------------------------------------- + // Shell command handlers: + + /** + * Handles {@code adb shell ime list}. + * @param shellCommand {@link ShellCommand} object that is handling this command. + * @return Exit code of the command. + */ + @BinderThread + @ShellCommandResult + private int handleShellCommandListInputMethods(@NonNull ShellCommand shellCommand) { + boolean all = false; + boolean brief = false; + while (true) { + final String nextOption = shellCommand.getNextOption(); + if (nextOption == null) { + break; + } + switch (nextOption) { + case "-a": + all = true; + break; + case "-s": + brief = true; + break; + } + } + final List<InputMethodInfo> methods = all ? + getInputMethodList() : getEnabledInputMethodList(); + final PrintWriter pr = shellCommand.getOutPrintWriter(); + final Printer printer = x -> pr.println(x); + final int N = methods.size(); + for (int i = 0; i < N; ++i) { + if (brief) { + pr.println(methods.get(i).getId()); + } else { + pr.print(methods.get(i).getId()); pr.println(":"); + methods.get(i).dump(printer, " "); + } + } + return ShellCommandResult.SUCCESS; + } + + /** + * Handles {@code adb shell ime enable} and {@code adb shell ime disable}. + * @param shellCommand {@link ShellCommand} object that is handling this command. + * @param enabled {@code true} if the command was {@code adb shell ime enable}. + * @return Exit code of the command. + */ + @BinderThread + @ShellCommandResult + @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + private int handleShellCommandEnableDisableInputMethod( + @NonNull ShellCommand shellCommand, boolean enabled) { + if (!calledFromValidUser()) { + shellCommand.getErrPrintWriter().print( + "Must be called from the foreground user or with INTERACT_ACROSS_USERS_FULL"); + return ShellCommandResult.FAILURE; + } + final String id = shellCommand.getNextArgRequired(); + + final boolean previouslyEnabled; + synchronized (mMethodMap) { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.WRITE_SECURE_SETTINGS) + != PackageManager.PERMISSION_GRANTED) { + shellCommand.getErrPrintWriter().print( + "Caller must have WRITE_SECURE_SETTINGS permission"); + throw new SecurityException( + "Requires permission " + + android.Manifest.permission.WRITE_SECURE_SETTINGS); + } + + final long ident = Binder.clearCallingIdentity(); + try { + previouslyEnabled = setInputMethodEnabledLocked(id, enabled); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + final PrintWriter pr = shellCommand.getOutPrintWriter(); + pr.print("Input method "); + pr.print(id); + pr.print(": "); + pr.print((enabled == previouslyEnabled) ? "already " : "now "); + pr.println(enabled ? "enabled" : "disabled"); + return ShellCommandResult.SUCCESS; + } + + /** + * Handles {@code adb shell ime set}. + * @param shellCommand {@link ShellCommand} object that is handling this command. + * @return Exit code of the command. + */ + @BinderThread + @ShellCommandResult + private int handleShellCommandSetInputMethod(@NonNull ShellCommand shellCommand) { + final String id = shellCommand.getNextArgRequired(); + setInputMethod(null, id); + final PrintWriter pr = shellCommand.getOutPrintWriter(); + pr.print("Input method "); + pr.print(id); + pr.println(" selected"); + return ShellCommandResult.SUCCESS; + } + + /** + * Handles {@code adb shell ime reset-ime}. + * @param shellCommand {@link ShellCommand} object that is handling this command. + * @return Exit code of the command. + */ + @BinderThread + @ShellCommandResult + @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + private int handleShellCommandResetInputMethod(@NonNull ShellCommand shellCommand) { + if (!calledFromValidUser()) { + shellCommand.getErrPrintWriter().print( + "Must be called from the foreground user or with INTERACT_ACROSS_USERS_FULL"); + return ShellCommandResult.FAILURE; + } + synchronized (mMethodMap) { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.WRITE_SECURE_SETTINGS) + != PackageManager.PERMISSION_GRANTED) { + shellCommand.getErrPrintWriter().print( + "Caller must have WRITE_SECURE_SETTINGS permission"); + throw new SecurityException( + "Requires permission " + + android.Manifest.permission.WRITE_SECURE_SETTINGS); + } + final String nextIme; + final List<InputMethodInfo> nextEnabledImes; + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mMethodMap) { + hideCurrentInputLocked(0, null); + unbindCurrentMethodLocked(false); + // Reset the current IME + resetSelectedInputMethodAndSubtypeLocked(null); + // Also reset the settings of the current IME + mSettings.putSelectedInputMethod(null); + // Disable all enabled IMEs. + { + final ArrayList<InputMethodInfo> enabledImes = + mSettings.getEnabledInputMethodListLocked(); + final int N = enabledImes.size(); + for (int i = 0; i < N; ++i) { + setInputMethodEnabledLocked(enabledImes.get(i).getId(), false); + } + } + // Re-enable with default enabled IMEs. + { + final ArrayList<InputMethodInfo> defaultEnabledIme = + InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList); + final int N = defaultEnabledIme.size(); + for (int i = 0; i < N; ++i) { + setInputMethodEnabledLocked(defaultEnabledIme.get(i).getId(), true); + } + } + updateInputMethodsFromSettingsLocked(true /* enabledMayChange */); + InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager, + mSettings.getEnabledInputMethodListLocked(), + mSettings.getCurrentUserId(), + mContext.getBasePackageName()); + nextIme = mSettings.getSelectedInputMethod(); + nextEnabledImes = getEnabledInputMethodList(); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + final PrintWriter pr = shellCommand.getOutPrintWriter(); + pr.println("Reset current and enabled IMEs"); + pr.println("Newly selected IME:"); + pr.print(" "); pr.println(nextIme); + pr.println("Newly enabled IMEs:"); + { + final int N = nextEnabledImes.size(); + for (int i = 0; i < N; ++i) { + pr.print(" "); + pr.println(nextEnabledImes.get(i).getId()); + } + } + return ShellCommandResult.SUCCESS; + } + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 979323fc8408..54938eb05cf5 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -69,7 +69,9 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static android.app.ActivityManager.RESIZE_MODE_SYSTEM; import static android.app.ActivityManager.RESIZE_MODE_USER; @@ -1867,10 +1869,24 @@ final class ActivityManagerShellCommand extends ShellCommand { String value = getNextArgRequired(); int bucket = bucketNameToBucketValue(value); if (bucket < 0) return -1; + boolean multiple = peekNextArg() != null; + IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( Context.USAGE_STATS_SERVICE)); - usm.setAppStandbyBucket(packageName, bucketNameToBucketValue(value), userId); + if (!multiple) { + usm.setAppStandbyBucket(packageName, bucketNameToBucketValue(value), userId); + } else { + HashMap<String, Integer> buckets = new HashMap<>(); + buckets.put(packageName, bucket); + while ((packageName = getNextArg()) != null) { + value = getNextArgRequired(); + bucket = bucketNameToBucketValue(value); + if (bucket < 0) continue; + buckets.put(packageName, bucket); + } + usm.setAppStandbyBuckets(buckets, userId); + } return 0; } @@ -1886,12 +1902,21 @@ final class ActivityManagerShellCommand extends ShellCommand { return -1; } } - String packageName = getNextArgRequired(); + String packageName = getNextArg(); IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( Context.USAGE_STATS_SERVICE)); - int bucket = usm.getAppStandbyBucket(packageName, null, userId); - pw.println(bucket); + if (packageName != null) { + int bucket = usm.getAppStandbyBucket(packageName, null, userId); + pw.println(bucket); + } else { + Map<String, Integer> buckets = (Map<String, Integer>) usm.getAppStandbyBuckets( + SHELL_PACKAGE_NAME, userId); + for (Map.Entry<String, Integer> entry: buckets.entrySet()) { + pw.print(entry.getKey()); pw.print(": "); + pw.println(entry.getValue()); + } + } return 0; } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 48737a54cadc..dabcbcdcbb7c 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -2376,8 +2376,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi activities.add(activity); } } else { - Slog.e(TAG, "restoreTask: Unexpected name=" + name); - XmlUtils.skipCurrentTag(in); + handleUnknownTag(name, in); } } } @@ -2441,5 +2440,11 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Restored task=" + task); return task; } + + void handleUnknownTag(String name, XmlPullParser in) + throws IOException, XmlPullParserException { + Slog.e(TAG, "restoreTask: Unexpected name=" + name); + XmlUtils.skipCurrentTag(in); + } } } diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index c7a2f0b9264e..965159bdfd76 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -565,7 +565,7 @@ public class SyncManager { mLogger = SyncLogger.getInstance(); - SyncStorageEngine.init(context); + SyncStorageEngine.init(context, BackgroundThread.get().getLooper()); mSyncStorageEngine = SyncStorageEngine.getSingleton(); mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() { @Override diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java index 3591871f5386..e498666073ed 100644 --- a/services/core/java/com/android/server/content/SyncStorageEngine.java +++ b/services/core/java/com/android/server/content/SyncStorageEngine.java @@ -36,6 +36,7 @@ import android.database.sqlite.SQLiteQueryBuilder; import android.os.Bundle; import android.os.Environment; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.os.Parcel; import android.os.RemoteCallbackList; @@ -69,7 +70,7 @@ import java.util.TimeZone; * * @hide */ -public class SyncStorageEngine extends Handler { +public class SyncStorageEngine { private static final String TAG = "SyncManager"; private static final String TAG_FILE = "SyncManagerFile"; @@ -462,7 +463,10 @@ public class SyncStorageEngine extends Handler { private boolean mGrantSyncAdaptersAccountAccess; - private SyncStorageEngine(Context context, File dataDir) { + private final MyHandler mHandler; + + private SyncStorageEngine(Context context, File dataDir, Looper looper) { + mHandler = new MyHandler(looper); mContext = context; sSyncStorageEngine = this; @@ -491,15 +495,15 @@ public class SyncStorageEngine extends Handler { } public static SyncStorageEngine newTestInstance(Context context) { - return new SyncStorageEngine(context, context.getFilesDir()); + return new SyncStorageEngine(context, context.getFilesDir(), Looper.getMainLooper()); } - public static void init(Context context) { + public static void init(Context context, Looper looper) { if (sSyncStorageEngine != null) { return; } File dataDir = Environment.getDataDirectory(); - sSyncStorageEngine = new SyncStorageEngine(context, dataDir); + sSyncStorageEngine = new SyncStorageEngine(context, dataDir, looper); } public static SyncStorageEngine getSingleton() { @@ -527,14 +531,21 @@ public class SyncStorageEngine extends Handler { } } - @Override public void handleMessage(Message msg) { - if (msg.what == MSG_WRITE_STATUS) { - synchronized (mAuthorities) { - writeStatusLocked(); - } - } else if (msg.what == MSG_WRITE_STATISTICS) { - synchronized (mAuthorities) { - writeStatisticsLocked(); + private class MyHandler extends Handler { + public MyHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_WRITE_STATUS) { + synchronized (mAuthorities) { + writeStatusLocked(); + } + } else if (msg.what == MSG_WRITE_STATISTICS) { + synchronized (mAuthorities) { + writeStatisticsLocked(); + } } } } @@ -1202,14 +1213,14 @@ public class SyncStorageEngine extends Handler { if (writeStatusNow) { writeStatusLocked(); - } else if (!hasMessages(MSG_WRITE_STATUS)) { - sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS), + } else if (!mHandler.hasMessages(MSG_WRITE_STATUS)) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_WRITE_STATUS), WRITE_STATUS_DELAY); } if (writeStatisticsNow) { writeStatisticsLocked(); - } else if (!hasMessages(MSG_WRITE_STATISTICS)) { - sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS), + } else if (!mHandler.hasMessages(MSG_WRITE_STATISTICS)) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_WRITE_STATISTICS), WRITE_STATISTICS_DELAY); } } @@ -2102,7 +2113,7 @@ public class SyncStorageEngine extends Handler { // The file is being written, so we don't need to have a scheduled // write until the next change. - removeMessages(MSG_WRITE_STATUS); + mHandler.removeMessages(MSG_WRITE_STATUS); FileOutputStream fos = null; try { @@ -2210,7 +2221,7 @@ public class SyncStorageEngine extends Handler { // The file is being written, so we don't need to have a scheduled // write until the next change. - removeMessages(MSG_WRITE_STATISTICS); + mHandler.removeMessages(MSG_WRITE_STATISTICS); FileOutputStream fos = null; try { diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index 61b6fa073be9..42247f94e69f 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -500,13 +500,30 @@ public class BrightnessTracker { } public void dump(PrintWriter pw) { - synchronized (mEventsLock) { - pw.println("BrightnessTracker state:"); - pw.println(" mEvents.size=" + mEvents.size()); - pw.println(" mEventsDirty=" + mEventsDirty); - } + pw.println("BrightnessTracker state:"); synchronized (mDataCollectionLock) { pw.println(" mLastSensorReadings.size=" + mLastSensorReadings.size()); + if (!mLastSensorReadings.isEmpty()) { + pw.println(" mLastSensorReadings time span " + + mLastSensorReadings.peekFirst().timestamp + "->" + + mLastSensorReadings.peekLast().timestamp); + } + } + synchronized (mEventsLock) { + pw.println(" mEventsDirty=" + mEventsDirty); + pw.println(" mEvents.size=" + mEvents.size()); + BrightnessChangeEvent[] events = mEvents.toArray(); + for (int i = 0; i < events.length; ++i) { + pw.print(" " + events[i].timeStamp + ", " + events[i].userId); + pw.print(", " + events[i].lastBrightness + "->" + events[i].brightness + ", {"); + for (int j = 0; j < events[i].luxValues.length; ++j){ + if (j != 0) { + pw.print(", "); + } + pw.print("(" + events[i].luxValues[j] + "," + events[i].luxTimestamps[j] + ")"); + } + pw.println("}"); + } } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 5dfb48a70472..159419e957a8 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -16420,6 +16420,13 @@ public class PackageManagerService extends IPackageManager.Stub + " target SDK " + oldTargetSdk + " does."); return; } + // Prevent persistent apps from being updated + if ((oldPackage.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0) { + res.setError(PackageManager.INSTALL_FAILED_INVALID_APK, + "Package " + oldPackage.packageName + " is a persistent app. " + + "Persistent apps are not updateable."); + return; + } // Prevent apps from downgrading their targetSandbox. final int oldTargetSandbox = oldPackage.applicationInfo.targetSandboxVersion; final int newTargetSandbox = pkg.applicationInfo.targetSandboxVersion; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index dc481ca11c7b..03cd4f1d3269 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -3702,8 +3702,10 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public UserInfo createUserEvenWhenDisallowed(String name, int flags) { - UserInfo user = createUserInternalUnchecked(name, flags, UserHandle.USER_NULL, null); + public UserInfo createUserEvenWhenDisallowed(String name, int flags, + String[] disallowedPackages) { + UserInfo user = createUserInternalUnchecked(name, flags, UserHandle.USER_NULL, + disallowedPackages); // Keep this in sync with UserManager.createUser if (user != null && !user.isAdmin() && !user.isDemo()) { setUserRestriction(UserManager.DISALLOW_SMS, true, user.id); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 61591bbef08d..5f03dd262fa4 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -321,11 +321,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final int LONG_PRESS_BACK_NOTHING = 0; static final int LONG_PRESS_BACK_GO_TO_VOICE_ASSIST = 1; - // Number of presses needed before we induce panic press behavior on the back button - static final int PANIC_PRESS_BACK_COUNT = 4; - static final int PANIC_PRESS_BACK_NOTHING = 0; - static final int PANIC_PRESS_BACK_HOME = 1; - // These need to match the documentation/constant in // core/res/res/values/config.xml static final int LONG_PRESS_HOME_NOTHING = 0; @@ -520,7 +515,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { volatile boolean mBackKeyHandled; volatile boolean mBeganFromNonInteractive; volatile int mPowerKeyPressCounter; - volatile int mBackKeyPressCounter; volatile boolean mEndCallKeyHandled; volatile boolean mCameraGestureTriggeredDuringGoingToSleep; volatile boolean mGoingToSleep; @@ -582,7 +576,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { int mDoublePressOnPowerBehavior; int mTriplePressOnPowerBehavior; int mLongPressOnBackBehavior; - int mPanicPressOnBackBehavior; int mShortPressOnSleepBehavior; int mShortPressOnWindowBehavior; volatile boolean mAwake; @@ -800,16 +793,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_SHOW_PICTURE_IN_PICTURE_MENU = 17; private static final int MSG_BACK_LONG_PRESS = 18; private static final int MSG_DISPOSE_INPUT_CONSUMER = 19; - private static final int MSG_BACK_DELAYED_PRESS = 20; - private static final int MSG_ACCESSIBILITY_SHORTCUT = 21; - private static final int MSG_BUGREPORT_TV = 22; - private static final int MSG_ACCESSIBILITY_TV = 23; - private static final int MSG_DISPATCH_BACK_KEY_TO_AUTOFILL = 24; - private static final int MSG_SYSTEM_KEY_PRESS = 25; - private static final int MSG_HANDLE_ALL_APPS = 26; - private static final int MSG_LAUNCH_ASSIST = 27; - private static final int MSG_LAUNCH_ASSIST_LONG_PRESS = 28; - private static final int MSG_POWER_VERY_LONG_PRESS = 29; + private static final int MSG_ACCESSIBILITY_SHORTCUT = 20; + private static final int MSG_BUGREPORT_TV = 21; + private static final int MSG_ACCESSIBILITY_TV = 22; + private static final int MSG_DISPATCH_BACK_KEY_TO_AUTOFILL = 23; + private static final int MSG_SYSTEM_KEY_PRESS = 24; + private static final int MSG_HANDLE_ALL_APPS = 25; + private static final int MSG_LAUNCH_ASSIST = 26; + private static final int MSG_LAUNCH_ASSIST_LONG_PRESS = 27; + private static final int MSG_POWER_VERY_LONG_PRESS = 28; private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0; private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1; @@ -887,15 +879,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; case MSG_BACK_LONG_PRESS: backLongPress(); - finishBackKeyPress(); break; case MSG_DISPOSE_INPUT_CONSUMER: disposeInputConsumer((InputConsumer) msg.obj); break; - case MSG_BACK_DELAYED_PRESS: - backMultiPressAction(msg.arg1); - finishBackKeyPress(); - break; case MSG_ACCESSIBILITY_SHORTCUT: accessibilityShortcutActivated(); break; @@ -1181,14 +1168,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Reset back key state for long press mBackKeyHandled = false; - // Cancel multi-press detection timeout. - if (hasPanicPressOnBackBehavior()) { - if (mBackKeyPressCounter != 0 - && mBackKeyPressCounter < PANIC_PRESS_BACK_COUNT) { - mHandler.removeMessages(MSG_BACK_DELAYED_PRESS); - } - } - if (hasLongPressOnBackBehavior()) { Message msg = mHandler.obtainMessage(MSG_BACK_LONG_PRESS); msg.setAsynchronous(true); @@ -1202,21 +1181,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Cache handled state boolean handled = mBackKeyHandled; - if (hasPanicPressOnBackBehavior()) { - // Check for back key panic press - ++mBackKeyPressCounter; - - final long eventTime = event.getDownTime(); - - if (mBackKeyPressCounter <= PANIC_PRESS_BACK_COUNT) { - // This could be a multi-press. Wait a little bit longer to confirm. - Message msg = mHandler.obtainMessage(MSG_BACK_DELAYED_PRESS, - mBackKeyPressCounter, 0, eventTime); - msg.setAsynchronous(true); - mHandler.sendMessageDelayed(msg, ViewConfiguration.getMultiPressTimeout()); - } - } - // Reset back long press state cancelPendingBackKeyAction(); @@ -1394,10 +1358,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void finishBackKeyPress() { - mBackKeyPressCounter = 0; - } - private void cancelPendingPowerKeyAction() { if (!mPowerKeyHandled) { mPowerKeyHandled = true; @@ -1415,18 +1375,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void backMultiPressAction(int count) { - if (count >= PANIC_PRESS_BACK_COUNT) { - switch (mPanicPressOnBackBehavior) { - case PANIC_PRESS_BACK_NOTHING: - break; - case PANIC_PRESS_BACK_HOME: - launchHomeFromHotKey(); - break; - } - } - } - private void powerPress(long eventTime, boolean interactive, int count) { if (mScreenOnEarly && !mScreenOnFully) { Slog.i(TAG, "Suppressed redundant power key press while " @@ -1642,10 +1590,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { return mLongPressOnBackBehavior != LONG_PRESS_BACK_NOTHING; } - private boolean hasPanicPressOnBackBehavior() { - return mPanicPressOnBackBehavior != PANIC_PRESS_BACK_NOTHING; - } - private void interceptScreenshotChord() { if (mScreenshotChordEnabled && mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered @@ -2036,8 +1980,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { mLongPressOnBackBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_longPressOnBackBehavior); - mPanicPressOnBackBehavior = mContext.getResources().getInteger( - com.android.internal.R.integer.config_backPanicBehavior); mShortPressOnPowerBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_shortPressOnPowerBehavior); @@ -8296,9 +8238,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { pw.print("mLongPressOnBackBehavior="); pw.println(longPressOnBackBehaviorToString(mLongPressOnBackBehavior)); pw.print(prefix); - pw.print("mPanicPressOnBackBehavior="); - pw.println(panicPressOnBackBehaviorToString(mPanicPressOnBackBehavior)); - pw.print(prefix); pw.print("mLongPressOnHomeBehavior="); pw.println(longPressOnHomeBehaviorToString(mLongPressOnHomeBehavior)); pw.print(prefix); @@ -8498,17 +8437,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private static String panicPressOnBackBehaviorToString(int behavior) { - switch (behavior) { - case PANIC_PRESS_BACK_NOTHING: - return "PANIC_PRESS_BACK_NOTHING"; - case PANIC_PRESS_BACK_HOME: - return "PANIC_PRESS_BACK_HOME"; - default: - return Integer.toString(behavior); - } - } - private static String longPressOnHomeBehaviorToString(int behavior) { switch (behavior) { case LONG_PRESS_HOME_NOTHING: diff --git a/services/core/java/com/android/server/wallpaper/IWallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/IWallpaperManagerService.java new file mode 100644 index 000000000000..60b08dd3093c --- /dev/null +++ b/services/core/java/com/android/server/wallpaper/IWallpaperManagerService.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 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.wallpaper; + +import android.app.IWallpaperManager; +import android.os.IBinder; + +/** + * Extended IWallpaperManager which can receive SystemService's lifetime events. + */ +interface IWallpaperManagerService extends IWallpaperManager, IBinder { + /** + * @see com.android.server.SystemService#onBootPhase(int) + */ + void onBootPhase(int phase); + + /** + * @see com.android.server.SystemService#onUnlockUser(int) + */ + void onUnlockUser(final int userId); +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index b888ec21e708..7b0ed0d06739 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -99,6 +99,7 @@ import com.android.server.EventLogTags; import com.android.server.FgThread; import com.android.server.SystemService; +import java.lang.reflect.InvocationTargetException; import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; @@ -119,14 +120,16 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import com.android.internal.R; -public class WallpaperManagerService extends IWallpaperManager.Stub { +public class WallpaperManagerService extends IWallpaperManager.Stub + implements IWallpaperManagerService { static final String TAG = "WallpaperManagerService"; static final boolean DEBUG = false; static final boolean DEBUG_LIVE = DEBUG || true; public static class Lifecycle extends SystemService { - private WallpaperManagerService mService; + private IWallpaperManagerService mService; public Lifecycle(Context context) { super(context); @@ -134,22 +137,30 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { @Override public void onStart() { - mService = new WallpaperManagerService(getContext()); - publishBinderService(Context.WALLPAPER_SERVICE, mService); + try { + final Class<? extends IWallpaperManagerService> klass = + (Class<? extends IWallpaperManagerService>)Class.forName( + getContext().getResources().getString( + R.string.config_wallpaperManagerServiceName)); + mService = klass.getConstructor(Context.class).newInstance(getContext()); + publishBinderService(Context.WALLPAPER_SERVICE, mService); + } catch (Exception exp) { + Slog.wtf(TAG, "Failed to instantiate WallpaperManagerService", exp); + } } @Override public void onBootPhase(int phase) { - if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { - mService.systemReady(); - } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { - mService.switchUser(UserHandle.USER_SYSTEM, null); + if (mService != null) { + mService.onBootPhase(phase); } } @Override public void onUnlockUser(int userHandle) { - mService.onUnlockUser(userHandle); + if (mService != null) { + mService.onUnlockUser(userHandle); + } } } @@ -1255,7 +1266,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { mLockWallpaperMap.remove(userId); } - void onUnlockUser(final int userId) { + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { + systemReady(); + } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { + switchUser(UserHandle.USER_SYSTEM, null); + } + } + + @Override + public void onUnlockUser(final int userId) { synchronized (mLock) { if (mCurrentUserId == userId) { if (mWaitingForUnlock) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index a3bcdcb9442e..795c97fcf02f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.BIND_DEVICE_ADMIN; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.ActivityManager.USER_OP_SUCCESS; +import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; 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; @@ -44,6 +45,7 @@ import static android.app.admin.DevicePolicyManager.DELEGATION_INSTALL_EXISTING_ import static android.app.admin.DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES; import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS; import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT; +import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER; import static android.app.admin.DevicePolicyManager.START_USER_IN_BACKGROUND; @@ -289,6 +291,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final String ATTR_APPLICATION_RESTRICTIONS_MANAGER = "application-restrictions-manager"; + private static final String MANAGED_PROVISIONING_PKG = "com.android.managedprovisioning"; + // Comprehensive list of delegations. private static final String DELEGATIONS[] = { DELEGATION_CERT_INSTALL, @@ -390,6 +394,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private final LockPatternUtils mLockPatternUtils; private final DevicePolicyConstants mConstants; private final DeviceAdminServiceController mDeviceAdminServiceController; + private final OverlayPackagesProvider mOverlayPackagesProvider; /** * Contains (package-user) pairs to remove. An entry (p, u) implies that removal of package p @@ -1903,6 +1908,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mDeviceAdminServiceController = new DeviceAdminServiceController(this, mConstants); + mOverlayPackagesProvider = new OverlayPackagesProvider(mContext); + if (!mHasFeature) { // Skip the rest of the initialization return; @@ -8321,6 +8328,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0; final boolean demo = (flags & DevicePolicyManager.MAKE_USER_DEMO) != 0 && UserManager.isDeviceInDemoMode(mContext); + final boolean leaveAllSystemAppsEnabled = (flags & LEAVE_ALL_SYSTEM_APPS_ENABLED) != 0; // Create user. UserHandle user = null; synchronized (this) { @@ -8335,8 +8343,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (demo) { userInfoFlags |= UserInfo.FLAG_DEMO; } + String[] disallowedPackages = null; + if (!leaveAllSystemAppsEnabled) { + disallowedPackages = mOverlayPackagesProvider.getNonRequiredApps(admin, + UserHandle.myUserId(), ACTION_PROVISION_MANAGED_USER).toArray( + new String[0]); + } UserInfo userInfo = mUserManagerInternal.createUserEvenWhenDisallowed(name, - userInfoFlags); + userInfoFlags, disallowedPackages); if (userInfo != null) { user = userInfo.getUserHandle(); } @@ -8347,11 +8361,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (user == null) { return null; } + + final int userHandle = user.getIdentifier(); + final Intent intent = new Intent(DevicePolicyManager.ACTION_MANAGED_USER_CREATED) + .putExtra(Intent.EXTRA_USER_HANDLE, userHandle) + .putExtra( + DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED, + leaveAllSystemAppsEnabled) + .setPackage(MANAGED_PROVISIONING_PKG) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM); + final long id = mInjector.binderClearCallingIdentity(); try { final String adminPkg = admin.getPackageName(); - - final int userHandle = user.getIdentifier(); try { // Install the profile owner if not present. if (!mIPackageManager.isPackageAvailable(adminPkg, userHandle)) { @@ -8381,7 +8404,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if ((flags & START_USER_IN_BACKGROUND) != 0) { try { - mInjector.getIActivityManager().startUserInBackground(user.getIdentifier()); + mInjector.getIActivityManager().startUserInBackground(userHandle); } catch (RemoteException re) { // Does not happen, same process } @@ -8389,7 +8412,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return user; } catch (Throwable re) { - mUserManager.removeUser(user.getIdentifier()); + mUserManager.removeUser(userHandle); return null; } finally { mInjector.binderRestoreCallingIdentity(id); @@ -8541,6 +8564,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public boolean isEphemeralUser(ComponentName who) { + Preconditions.checkNotNull(who, "ComponentName is null"); + synchronized (this) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + } + + final int callingUserId = mInjector.userHandleGetCallingUserId(); + final long id = mInjector.binderClearCallingIdentity(); + try { + return mInjector.getUserManager().isUserEphemeral(callingUserId); + } finally { + mInjector.binderRestoreCallingIdentity(id); + } + } + + @Override public Bundle getApplicationRestrictions(ComponentName who, String callerPackage, String packageName) { enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, @@ -11647,4 +11686,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return (deviceOwner != null) && deviceOwner.isLogoutEnabled; } + @Override + public List<String> getDisallowedSystemApps(ComponentName admin, int userId, + String provisioningAction) throws RemoteException { + enforceCanManageProfileAndDeviceOwners(); + return new ArrayList<>( + mOverlayPackagesProvider.getNonRequiredApps(admin, userId, provisioningAction)); + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java new file mode 100644 index 000000000000..d0ec0eeebd5c --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java @@ -0,0 +1,232 @@ +/* + * Copyright 2017, 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.devicepolicy; + +import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; +import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; +import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.NonNull; +import android.app.admin.DeviceAdminReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.ArraySet; +import android.view.inputmethod.InputMethodInfo; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.view.IInputMethodManager; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +/** + * Class that provides the apps that are not required on a managed device / profile according to the + * overlays provided via (vendor_|)required_apps_managed_(profile|device).xml. + */ +public class OverlayPackagesProvider { + + protected static final String TAG = "OverlayPackagesProvider"; + + private final PackageManager mPm; + private final IInputMethodManager mIInputMethodManager; + private final Context mContext; + + public OverlayPackagesProvider(Context context) { + this(context, getIInputMethodManager()); + } + + @VisibleForTesting + OverlayPackagesProvider(Context context, IInputMethodManager iInputMethodManager) { + mContext = context; + mPm = checkNotNull(context.getPackageManager()); + mIInputMethodManager = checkNotNull(iInputMethodManager); + } + + /** + * Computes non-required apps. All the system apps with a launcher that are not in + * the required set of packages will be considered as non-required apps. + * + * Note: If an app is mistakenly listed as both required and disallowed, it will be treated as + * disallowed. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param userId The userId for which the non-required apps needs to be computed. + * @param provisioningAction action indicating type of provisioning, should be one of + * {@link ACTION_PROVISION_MANAGED_DEVICE}, {@link + * ACTION_PROVISION_MANAGED_PROFILE} or + * {@link ACTION_PROVISION_MANAGED_USER}. + * @return the set of non-required apps. + */ + @NonNull + public Set<String> getNonRequiredApps(@NonNull ComponentName admin, int userId, + @NonNull String provisioningAction) { + final Set<String> nonRequiredApps = getLaunchableApps(userId); + // Newly installed system apps are uninstalled when they are not required and are either + // disallowed or have a launcher icon. + nonRequiredApps.removeAll(getRequiredApps(provisioningAction, admin.getPackageName())); + // Don't delete the system input method packages in case of Device owner provisioning. + if (ACTION_PROVISION_MANAGED_DEVICE.equals(provisioningAction) + || ACTION_PROVISION_MANAGED_USER.equals(provisioningAction)) { + nonRequiredApps.removeAll(getSystemInputMethods()); + } + nonRequiredApps.addAll(getDisallowedApps(provisioningAction)); + return nonRequiredApps; + } + + private Set<String> getLaunchableApps(int userId) { + final Intent launcherIntent = new Intent(Intent.ACTION_MAIN); + launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); + final List<ResolveInfo> resolveInfos = mPm.queryIntentActivitiesAsUser(launcherIntent, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + userId); + final Set<String> apps = new ArraySet<>(); + for (ResolveInfo resolveInfo : resolveInfos) { + apps.add(resolveInfo.activityInfo.packageName); + } + return apps; + } + + private Set<String> getSystemInputMethods() { + // InputMethodManager is final so it cannot be mocked. + // So, we're using IInputMethodManager directly because it can be mocked. + final List<InputMethodInfo> inputMethods; + try { + inputMethods = mIInputMethodManager.getInputMethodList(); + } catch (RemoteException e) { + // Should not happen + return null; + } + final Set<String> systemInputMethods = new ArraySet<>(); + for (InputMethodInfo inputMethodInfo : inputMethods) { + ApplicationInfo applicationInfo = inputMethodInfo.getServiceInfo().applicationInfo; + if (applicationInfo.isSystemApp()) { + systemInputMethods.add(inputMethodInfo.getPackageName()); + } + } + return systemInputMethods; + } + + private Set<String> getRequiredApps(String provisioningAction, String dpcPackageName) { + final Set<String> requiredApps = new ArraySet<>(); + requiredApps.addAll(getRequiredAppsSet(provisioningAction)); + requiredApps.addAll(getVendorRequiredAppsSet(provisioningAction)); + requiredApps.add(dpcPackageName); + return requiredApps; + } + + private Set<String> getDisallowedApps(String provisioningAction) { + final Set<String> disallowedApps = new ArraySet<>(); + disallowedApps.addAll(getDisallowedAppsSet(provisioningAction)); + disallowedApps.addAll(getVendorDisallowedAppsSet(provisioningAction)); + return disallowedApps; + } + + private static IInputMethodManager getIInputMethodManager() { + final IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE); + return IInputMethodManager.Stub.asInterface(b); + } + + private Set<String> getRequiredAppsSet(String provisioningAction) { + final int resId; + switch (provisioningAction) { + case ACTION_PROVISION_MANAGED_USER: + resId = R.array.required_apps_managed_user; + break; + case ACTION_PROVISION_MANAGED_PROFILE: + resId = R.array.required_apps_managed_profile; + break; + case ACTION_PROVISION_MANAGED_DEVICE: + resId = R.array.required_apps_managed_device; + break; + default: + throw new IllegalArgumentException("Provisioning type " + + provisioningAction + " not supported."); + } + return new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray(resId))); + } + + private Set<String> getDisallowedAppsSet(String provisioningAction) { + final int resId; + switch (provisioningAction) { + case ACTION_PROVISION_MANAGED_USER: + resId = R.array.disallowed_apps_managed_user; + break; + case ACTION_PROVISION_MANAGED_PROFILE: + resId = R.array.disallowed_apps_managed_profile; + break; + case ACTION_PROVISION_MANAGED_DEVICE: + resId = R.array.disallowed_apps_managed_device; + break; + default: + throw new IllegalArgumentException("Provisioning type " + + provisioningAction + " not supported."); + } + return new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray(resId))); + } + + private Set<String> getVendorRequiredAppsSet(String provisioningAction) { + final int resId; + switch (provisioningAction) { + case ACTION_PROVISION_MANAGED_USER: + resId = R.array.vendor_required_apps_managed_user; + break; + case ACTION_PROVISION_MANAGED_PROFILE: + resId = R.array.vendor_required_apps_managed_profile; + break; + case ACTION_PROVISION_MANAGED_DEVICE: + resId = R.array.vendor_required_apps_managed_device; + break; + default: + throw new IllegalArgumentException("Provisioning type " + + provisioningAction + " not supported."); + } + return new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray(resId))); + } + + private Set<String> getVendorDisallowedAppsSet(String provisioningAction) { + final int resId; + switch (provisioningAction) { + case ACTION_PROVISION_MANAGED_USER: + resId = R.array.vendor_disallowed_apps_managed_user; + break; + case ACTION_PROVISION_MANAGED_PROFILE: + resId = R.array.vendor_disallowed_apps_managed_profile; + break; + case ACTION_PROVISION_MANAGED_DEVICE: + resId = R.array.vendor_disallowed_apps_managed_device; + break; + default: + throw new IllegalArgumentException("Provisioning type " + + provisioningAction + " not supported."); + } + return new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray(resId))); + } +} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java new file mode 100644 index 000000000000..4447fe91e34e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java @@ -0,0 +1,390 @@ +/* + * Copyright 2017, 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.devicepolicy; + +import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; +import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; +import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +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.os.RemoteException; +import android.support.test.InstrumentationRegistry; +import android.test.AndroidTestCase; +import android.test.mock.MockPackageManager; +import android.view.inputmethod.InputMethodInfo; + +import com.android.internal.R; +import com.android.internal.view.IInputMethodManager; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class OverlayPackagesProviderTest extends AndroidTestCase { + private static final String TEST_DPC_PACKAGE_NAME = "dpc.package.name"; + private static final ComponentName TEST_MDM_COMPONENT_NAME = new ComponentName( + TEST_DPC_PACKAGE_NAME, "pc.package.name.DeviceAdmin"); + private static final int TEST_USER_ID = 123; + + private @Mock + Resources mResources; + private @Mock + IInputMethodManager mIInputMethodManager; + private @Mock + Context mTestContext; + private Resources mRealResources; + + private FakePackageManager mPackageManager; + private String[] mSystemAppsWithLauncher; + private OverlayPackagesProvider mHelper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mPackageManager = new FakePackageManager(); + when(mTestContext.getResources()).thenReturn(mResources); + when(mTestContext.getPackageManager()).thenReturn(mPackageManager); + when(mTestContext.getFilesDir()).thenReturn( + InstrumentationRegistry.getTargetContext().getCacheDir()); + + setSystemInputMethods(); + setRequiredAppsManagedDevice(); + setVendorRequiredAppsManagedDevice(); + setDisallowedAppsManagedDevice(); + setVendorDisallowedAppsManagedDevice(); + setRequiredAppsManagedProfile(); + setVendorRequiredAppsManagedProfile(); + setDisallowedAppsManagedProfile(); + setVendorDisallowedAppsManagedProfile(); + setRequiredAppsManagedUser(); + setVendorRequiredAppsManagedUser(); + setDisallowedAppsManagedUser(); + setVendorDisallowedAppsManagedUser(); + + mRealResources = InstrumentationRegistry.getTargetContext().getResources(); + mHelper = new OverlayPackagesProvider(mTestContext, mIInputMethodManager); + } + + @Test + public void testAppsWithLauncherAreNonRequiredByDefault() { + setSystemAppsWithLauncher("app.a", "app.b"); + + verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_DEVICE, "app.a", "app.b"); + } + + @Test + public void testDeviceOwnerRequiredApps() { + setSystemAppsWithLauncher("app.a", "app.b", "app.c"); + setRequiredAppsManagedDevice("app.a"); + setVendorRequiredAppsManagedDevice("app.b"); + + verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_DEVICE, "app.c"); + } + + @Test + public void testProfileOwnerRequiredApps() { + setSystemAppsWithLauncher("app.a", "app.b", "app.c"); + setRequiredAppsManagedProfile("app.a"); + setVendorRequiredAppsManagedProfile("app.b"); + + verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_PROFILE, "app.c"); + } + + @Test + public void testManagedUserRequiredApps() { + setSystemAppsWithLauncher("app.a", "app.b", "app.c"); + setRequiredAppsManagedUser("app.a"); + setVendorRequiredAppsManagedUser("app.b"); + + verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_USER, "app.c"); + } + + @Test + public void testDpcIsRequired() { + setSystemAppsWithLauncher("app.a", TEST_DPC_PACKAGE_NAME); + + verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_DEVICE, "app.a"); + } + + @Test + public void testDisallowedAppsAreNonRequiredEvenIfNoLauncher() { + setSystemAppsWithLauncher(); + setDisallowedAppsManagedDevice("app.a"); + setVendorDisallowedAppsManagedDevice("app.b"); + + verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_DEVICE, "app.a", "app.b"); + } + + @Test + public void testDeviceOwnerImesAreRequired() { + setSystemAppsWithLauncher("app.a", "app.b"); + setSystemInputMethods("app.a"); + + verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_DEVICE, "app.b"); + } + + @Test + public void testProfileOwnerImesAreNonRequired() { + setSystemAppsWithLauncher("app.a", "app.b"); + setSystemInputMethods("app.a"); + + verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_PROFILE, "app.a", "app.b"); + } + + @Test + public void testManagedUserImesAreRequired() { + setSystemAppsWithLauncher("app.a", "app.b"); + setSystemInputMethods("app.a"); + + verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_USER, "app.b"); + } + + @Test + public void testDisallowedAppsAreNonInstalled() { + setSystemAppsWithLauncher("app.a"); + setDisallowedAppsManagedDevice("app.c"); + + verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_DEVICE, "app.a", "app.c"); + } + + /** + * If an app is listed as both required and disallowed, it should be only in the disallowed + * list. Therefore, it should be present in the non-required list. + */ + @Test + public void testAllowedAndDisallowedAtTheSameTimeManagedDevice() { + setDisallowedAppsManagedDevice(TEST_DPC_PACKAGE_NAME); + setRequiredAppsManagedDevice(TEST_DPC_PACKAGE_NAME); + + verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_DEVICE, TEST_DPC_PACKAGE_NAME); + } + + /** + * @see {@link #testAllowedAndDisallowedAtTheSameTimeManagedDevice} + */ + @Test + public void testAllowedAndDisallowedAtTheSameTimeManagedUser() { + setDisallowedAppsManagedUser(TEST_DPC_PACKAGE_NAME); + setRequiredAppsManagedUser(TEST_DPC_PACKAGE_NAME); + + verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_USER, TEST_DPC_PACKAGE_NAME); + } + + /** + * @see {@link #testAllowedAndDisallowedAtTheSameTimeManagedDevice} + */ + @Test + public void testAllowedAndDisallowedAtTheSameTimeManagedProfile() { + setDisallowedAppsManagedProfile(TEST_DPC_PACKAGE_NAME); + setRequiredAppsManagedProfile(TEST_DPC_PACKAGE_NAME); + + verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_PROFILE, TEST_DPC_PACKAGE_NAME); + } + + @Test + public void testNotRequiredAndDisallowedInResManagedDevice() { + verifyEmptyIntersection(R.array.required_apps_managed_device, + R.array.disallowed_apps_managed_device); + } + + @Test + public void testNotRequiredAndDisallowedInResManagedUser() { + verifyEmptyIntersection(R.array.required_apps_managed_user, + R.array.disallowed_apps_managed_user); + } + + @Test + public void testNotRequiredAndDisallowedInResManagedProfile() { + verifyEmptyIntersection(R.array.required_apps_managed_profile, + R.array.disallowed_apps_managed_profile); + } + + @Test + public void testNotRequiredAndDisallowedInResManagedDeviceVendor() { + verifyEmptyIntersection(R.array.vendor_required_apps_managed_device, + R.array.vendor_disallowed_apps_managed_device); + } + + @Test + public void testNotRequiredAndDisallowedInResManagedUserVendor() { + verifyEmptyIntersection(R.array.vendor_required_apps_managed_user, + R.array.vendor_disallowed_apps_managed_user); + } + + @Test + public void testNotRequiredAndDisallowedInResManagedProfileVendor() { + verifyEmptyIntersection(R.array.vendor_required_apps_managed_profile, + R.array.vendor_disallowed_apps_managed_profile); + } + + private ArrayList<String> getStringArrayInRealResources(int id) { + return new ArrayList<>(Arrays.asList(mRealResources.getStringArray(id))); + } + + private void verifyEmptyIntersection(int requiredId, int disallowedId) { + ArrayList<String> required = getStringArrayInRealResources(requiredId); + ArrayList<String> disallowed = getStringArrayInRealResources(disallowedId); + required.retainAll(disallowed); + assertTrue(required.isEmpty()); + } + + private void verifyAppsAreNonRequired(String action, String... appArray) { + assertEquals(listFromArray(appArray), + mHelper.getNonRequiredApps(TEST_MDM_COMPONENT_NAME, TEST_USER_ID, action)); + } + + private void setRequiredAppsManagedDevice(String... apps) { + setStringArray(R.array.required_apps_managed_device, apps); + } + + private void setVendorRequiredAppsManagedDevice(String... apps) { + setStringArray(R.array.vendor_required_apps_managed_device, apps); + } + + private void setDisallowedAppsManagedDevice(String... apps) { + setStringArray(R.array.disallowed_apps_managed_device, apps); + } + + private void setVendorDisallowedAppsManagedDevice(String... apps) { + setStringArray(R.array.vendor_disallowed_apps_managed_device, apps); + } + + private void setRequiredAppsManagedProfile(String... apps) { + setStringArray(R.array.required_apps_managed_profile, apps); + } + + private void setVendorRequiredAppsManagedProfile(String... apps) { + setStringArray(R.array.vendor_required_apps_managed_profile, apps); + } + + private void setDisallowedAppsManagedProfile(String... apps) { + setStringArray(R.array.disallowed_apps_managed_profile, apps); + } + + private void setVendorDisallowedAppsManagedProfile(String... apps) { + setStringArray(R.array.vendor_disallowed_apps_managed_profile, apps); + } + + private void setRequiredAppsManagedUser(String... apps) { + setStringArray(R.array.required_apps_managed_user, apps); + } + + private void setVendorRequiredAppsManagedUser(String... apps) { + setStringArray(R.array.vendor_required_apps_managed_user, apps); + } + + private void setDisallowedAppsManagedUser(String... apps) { + setStringArray(R.array.disallowed_apps_managed_user, apps); + } + + private void setVendorDisallowedAppsManagedUser(String... apps) { + setStringArray(R.array.vendor_disallowed_apps_managed_user, apps); + } + + private void setStringArray(int resourceId, String[] strs) { + when(mResources.getStringArray(eq(resourceId))) + .thenReturn(strs); + } + + private void setSystemInputMethods(String... packageNames) { + List<InputMethodInfo> inputMethods = new ArrayList<InputMethodInfo>(); + for (String packageName : packageNames) { + ApplicationInfo aInfo = new ApplicationInfo(); + aInfo.flags = ApplicationInfo.FLAG_SYSTEM; + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.applicationInfo = aInfo; + serviceInfo.packageName = packageName; + serviceInfo.name = ""; + ResolveInfo ri = new ResolveInfo(); + ri.serviceInfo = serviceInfo; + InputMethodInfo inputMethodInfo = new InputMethodInfo(ri, false, null, null, 0, false); + inputMethods.add(inputMethodInfo); + } + try { + when(mIInputMethodManager.getInputMethodList()).thenReturn(inputMethods); + } catch (RemoteException e) { + fail(e.toString()); + } + } + + private void setSystemAppsWithLauncher(String... apps) { + mSystemAppsWithLauncher = apps; + } + + private <T> Set<T> setFromArray(T... array) { + if (array == null) { + return null; + } + return new HashSet<T>(Arrays.asList(array)); + } + + private <T> List<T> listFromArray(T... array) { + if (array == null) { + return null; + } + return Arrays.asList(array); + } + + class FakePackageManager extends MockPackageManager { + @Override + public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) { + assertTrue("Expected an intent with action ACTION_MAIN", + Intent.ACTION_MAIN.equals(intent.getAction())); + assertEquals("Expected an intent with category CATEGORY_LAUNCHER", + setFromArray(Intent.CATEGORY_LAUNCHER), intent.getCategories()); + assertTrue("Expected the flag MATCH_UNINSTALLED_PACKAGES", + (flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0); + assertTrue("Expected the flag MATCH_DISABLED_COMPONENTS", + (flags & PackageManager.MATCH_DISABLED_COMPONENTS) != 0); + assertTrue("Expected the flag MATCH_DIRECT_BOOT_AWARE", + (flags & PackageManager.MATCH_DIRECT_BOOT_AWARE) != 0); + assertTrue("Expected the flag MATCH_DIRECT_BOOT_UNAWARE", + (flags & PackageManager.MATCH_DIRECT_BOOT_UNAWARE) != 0); + assertEquals(userId, TEST_USER_ID); + List<ResolveInfo> result = new ArrayList<>(); + if (mSystemAppsWithLauncher == null) { + return result; + } + for (String packageName : mSystemAppsWithLauncher) { + ActivityInfo ai = new ActivityInfo(); + ai.packageName = packageName; + ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = ai; + result.add(ri); + } + return result; + } + } +} diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java index 6ac4b36a6ad7..620922ac1363 100644 --- a/services/usage/java/com/android/server/usage/AppIdleHistory.java +++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java @@ -42,6 +42,8 @@ import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; /** * Keeps track of recent active state changes in apps. @@ -334,6 +336,16 @@ public class AppIdleHistory { return appUsageHistory.currentBucket; } + public Map<String, Integer> getAppStandbyBuckets(int userId, long elapsedRealtime) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + int size = userHistory.size(); + HashMap<String, Integer> buckets = new HashMap<>(size); + for (int i = 0; i < size; i++) { + buckets.put(userHistory.keyAt(i), userHistory.valueAt(i).currentBucket); + } + return buckets; + } + public String getAppStandbyReason(String packageName, int userId, long elapsedRealtime) { ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); AppUsageHistory appUsageHistory = diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java index d8086bb4a92d..46efbd059d52 100644 --- a/services/usage/java/com/android/server/usage/AppStandbyController.java +++ b/services/usage/java/com/android/server/usage/AppStandbyController.java @@ -84,6 +84,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; /** * Manages the standby state of an app, listening to various events. @@ -774,13 +775,23 @@ public class AppStandbyController { return STANDBY_BUCKET_ACTIVE; } - return mAppIdleHistory.getAppStandbyBucket(packageName, userId, elapsedRealtime); + synchronized (mAppIdleLock) { + return mAppIdleHistory.getAppStandbyBucket(packageName, userId, elapsedRealtime); + } + } + + public Map<String, Integer> getAppStandbyBuckets(int userId, long elapsedRealtime) { + synchronized (mAppIdleLock) { + return mAppIdleHistory.getAppStandbyBuckets(userId, elapsedRealtime); + } } void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, String reason, long elapsedRealtime) { - mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, - reason); + synchronized (mAppIdleLock) { + mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, + reason); + } maybeInformListeners(packageName, userId, elapsedRealtime, newBucket); } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 0572771ab6e0..15284d5e3dec 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -68,7 +68,10 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.Map; /** * A service that collects, aggregates, and persists application usage data. @@ -741,6 +744,68 @@ public class UsageStatsService extends SystemService implements } @Override + public Map getAppStandbyBuckets(String callingPackageName, int userId) { + final int callingUid = Binder.getCallingUid(); + try { + userId = ActivityManager.getService().handleIncomingUser( + Binder.getCallingPid(), callingUid, userId, false, false, + "getAppStandbyBucket", null); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + if (!hasPermission(callingPackageName)) { + throw new SecurityException( + "Don't have permission to query app standby bucket"); + } + final long token = Binder.clearCallingIdentity(); + try { + return mAppStandby.getAppStandbyBuckets(userId, + SystemClock.elapsedRealtime()); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void setAppStandbyBuckets(Map appBuckets, int userId) { + getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE, + "No permission to change app standby state"); + + final int callingUid = Binder.getCallingUid(); + try { + userId = ActivityManager.getService().handleIncomingUser( + Binder.getCallingPid(), callingUid, userId, false, true, + "setAppStandbyBucket", null); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + final long token = Binder.clearCallingIdentity(); + try { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + Map<String, Integer> buckets = (Map<String, Integer>) appBuckets; + for (Map.Entry<String, Integer> entry: buckets.entrySet()) { + String packageName = entry.getKey(); + int bucket = entry.getValue(); + if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE + || bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) { + throw new IllegalArgumentException( + "Cannot set the standby bucket to " + bucket); + } + // Caller cannot set their own standby state + if (mPackageManagerInternal.getPackageUid(packageName, + PackageManager.MATCH_ANY_USER, userId) == callingUid) { + throw new IllegalArgumentException("Cannot set your own standby bucket"); + } + mAppStandby.setAppStandbyBucket(packageName, userId, bucket, + UsageStatsManager.REASON_PREDICTED + ":" + callingUid, + elapsedRealtime); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void whitelistAppTemporarily(String packageName, long duration, int userId) throws RemoteException { StringBuilder reason = new StringBuilder(32); diff --git a/test-runner/Android.mk b/test-runner/Android.mk index 67f1354d52bc..706f6364ef8d 100644 --- a/test-runner/Android.mk +++ b/test-runner/Android.mk @@ -116,3 +116,6 @@ update-android-test-runner-api: $(ANDROID_TEST_RUNNER_OUTPUT_API_FILE) | $(ACP) $(hide) $(ACP) $(ANDROID_TEST_RUNNER_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_RUNNER_REMOVED_API_FILE) endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true + +# additionally, build unit tests in a separate .apk +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/test-runner/tests/Android.mk b/test-runner/tests/Android.mk index 7ee047e47058..1a4f6d54fed7 100644 --- a/test-runner/tests/Android.mk +++ b/test-runner/tests/Android.mk @@ -25,8 +25,8 @@ include $(CLEAR_VARS) # LOCAL_MODULE_TAGS := tests -LOCAL_JAVA_LIBRARIES := android.test.runner -LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test +LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock +LOCAL_STATIC_JAVA_LIBRARIES := junit # Include all test java files. LOCAL_SRC_FILES := $(call all-java-files-under, src) diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 13dd93e83b64..d782de55f66a 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -1069,25 +1069,29 @@ class LinkCommand { bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate, const StringPiece& out_package, const JavaClassGeneratorOptions& java_options, const Maybe<std::string>& out_text_symbols_path = {}) { - if (!options_.generate_java_class_path) { + if (!options_.generate_java_class_path && !out_text_symbols_path) { return true; } - std::string out_path = options_.generate_java_class_path.value(); - file::AppendPath(&out_path, file::PackageToPath(out_package)); - if (!file::mkdirs(out_path)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to create directory '" << out_path - << "'"); - return false; - } + std::string out_path; + std::unique_ptr<io::FileOutputStream> fout; + if (options_.generate_java_class_path) { + out_path = options_.generate_java_class_path.value(); + file::AppendPath(&out_path, file::PackageToPath(out_package)); + if (!file::mkdirs(out_path)) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed to create directory '" << out_path << "'"); + return false; + } - file::AppendPath(&out_path, "R.java"); + file::AppendPath(&out_path, "R.java"); - io::FileOutputStream fout(out_path); - if (fout.HadError()) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed writing to '" << out_path - << "': " << fout.GetError()); - return false; + fout = util::make_unique<io::FileOutputStream>(out_path); + if (fout->HadError()) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed writing to '" << out_path + << "': " << fout->GetError()); + return false; + } } std::unique_ptr<io::FileOutputStream> fout_text; @@ -1102,18 +1106,11 @@ class LinkCommand { } JavaClassGenerator generator(context_, table, java_options); - if (!generator.Generate(package_name_to_generate, out_package, &fout, fout_text.get())) { + if (!generator.Generate(package_name_to_generate, out_package, fout.get(), fout_text.get())) { context_->GetDiagnostics()->Error(DiagMessage(out_path) << generator.GetError()); return false; } - fout.Flush(); - - if (fout.HadError()) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed writing to '" << out_path - << "': " << fout.GetError()); - return false; - } return true; } @@ -1934,7 +1931,7 @@ class LinkCommand { return 1; } - if (options_.generate_java_class_path) { + if (options_.generate_java_class_path || options_.generate_text_symbols_path) { if (!GenerateJavaClasses()) { return 1; } diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 1bdb762528b6..eaadfd82629e 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -327,7 +327,6 @@ int Optimize(const std::vector<StringPiece>& args) { Maybe<std::string> config_path; Maybe<std::string> whitelist_path; Maybe<std::string> target_densities; - Maybe<std::string> target_abis; std::vector<std::string> configs; std::vector<std::string> split_args; std::unordered_set<std::string> kept_artifacts; @@ -349,12 +348,6 @@ int Optimize(const std::vector<StringPiece>& args) { "Path to the whitelist.cfg file containing whitelisted resources \n" "whose names should not be altered in final resource tables.", &whitelist_path) - .OptionalFlag( - "--target-abis", - "Comma separated list of the CPU ABIs that the APK will be optimized for.\n" - "All the native libraries that would be unused on devices of the given ABIs will \n" - "be removed from the APK.", - &target_abis) .OptionalFlagList("-c", "Comma separated list of configurations to include. The default\n" "is all configurations.", diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp index b99240f0a40a..852ff176ed7d 100644 --- a/tools/aapt2/configuration/ConfigurationParser.cpp +++ b/tools/aapt2/configuration/ConfigurationParser.cpp @@ -519,14 +519,22 @@ ConfigurationParser::ActionHandler ConfigurationParser::android_sdk_group_handle } else { AndroidSdk entry; for (const auto& attr : child->attributes) { + Maybe<int>* target = nullptr; if (attr.name == "minSdkVersion") { - entry.min_sdk_version = ResourceUtils::ParseSdkVersion(attr.value); + target = &entry.min_sdk_version; } else if (attr.name == "targetSdkVersion") { - entry.target_sdk_version = ResourceUtils::ParseSdkVersion(attr.value); + target = &entry.target_sdk_version; } else if (attr.name == "maxSdkVersion") { - entry.max_sdk_version = ResourceUtils::ParseSdkVersion(attr.value); + target = &entry.max_sdk_version; } else { diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value); + continue; + } + + *target = ResourceUtils::ParseSdkVersion(attr.value); + if (!*target) { + diag->Error(DiagMessage() << "Invalid attribute: " << attr.name << " = " << attr.value); + valid = false; } } diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp index afa155f46eb9..f7153c822bbc 100644 --- a/tools/aapt2/configuration/ConfigurationParser_test.cpp +++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp @@ -18,8 +18,10 @@ #include <string> +#include "android-base/stringprintf.h" #include "androidfw/ResourceTypes.h" +#include "SdkConstants.h" #include "test/Test.h" #include "xml/XmlDom.h" @@ -35,18 +37,19 @@ void PrintTo(const AndroidSdk& sdk, std::ostream* os) { namespace { +using ::aapt::configuration::Abi; +using ::aapt::configuration::AndroidManifest; +using ::aapt::configuration::AndroidSdk; +using ::aapt::configuration::Artifact; +using ::aapt::configuration::DeviceFeature; +using ::aapt::configuration::GlTexture; +using ::aapt::configuration::Locale; +using ::aapt::configuration::PostProcessingConfiguration; +using ::aapt::xml::Element; +using ::aapt::xml::NodeCast; using ::android::ResTable_config; -using configuration::Abi; -using configuration::AndroidSdk; -using configuration::Artifact; -using configuration::PostProcessingConfiguration; -using configuration::DeviceFeature; -using configuration::GlTexture; -using configuration::Locale; -using configuration::AndroidManifest; +using ::android::base::StringPrintf; using ::testing::ElementsAre; -using xml::Element; -using xml::NodeCast; constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?> <post-process xmlns="http://schemas.android.com/tools/aapt"> @@ -421,17 +424,106 @@ TEST_F(ConfigurationParserTest, AndroidSdkGroupAction) { ASSERT_EQ(sdk, out); } +TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_SingleVersion) { + { + static constexpr const char* xml = R"xml( + <android-sdk-group label="v19"> + <android-sdk minSdkVersion="19"></android-sdk> + </android-sdk-group>)xml"; + + auto doc = test::BuildXmlDom(xml); + + PostProcessingConfiguration config; + bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); + ASSERT_TRUE(ok); + + ASSERT_EQ(1ul, config.android_sdk_groups.size()); + ASSERT_EQ(1u, config.android_sdk_groups.count("v19")); + + auto& out = config.android_sdk_groups["v19"]; + EXPECT_EQ(19, out.min_sdk_version.value()); + EXPECT_FALSE(out.max_sdk_version); + EXPECT_FALSE(out.target_sdk_version); + } + + { + static constexpr const char* xml = R"xml( + <android-sdk-group label="v19"> + <android-sdk maxSdkVersion="19"></android-sdk> + </android-sdk-group>)xml"; + + auto doc = test::BuildXmlDom(xml); + + PostProcessingConfiguration config; + bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); + ASSERT_TRUE(ok); + + ASSERT_EQ(1ul, config.android_sdk_groups.size()); + ASSERT_EQ(1u, config.android_sdk_groups.count("v19")); + + auto& out = config.android_sdk_groups["v19"]; + EXPECT_EQ(19, out.max_sdk_version.value()); + EXPECT_FALSE(out.min_sdk_version); + EXPECT_FALSE(out.target_sdk_version); + } + + { + static constexpr const char* xml = R"xml( + <android-sdk-group label="v19"> + <android-sdk targetSdkVersion="19"></android-sdk> + </android-sdk-group>)xml"; + + auto doc = test::BuildXmlDom(xml); + + PostProcessingConfiguration config; + bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); + ASSERT_TRUE(ok); + + ASSERT_EQ(1ul, config.android_sdk_groups.size()); + ASSERT_EQ(1u, config.android_sdk_groups.count("v19")); + + auto& out = config.android_sdk_groups["v19"]; + EXPECT_EQ(19, out.target_sdk_version.value()); + EXPECT_FALSE(out.min_sdk_version); + EXPECT_FALSE(out.max_sdk_version); + } +} + +TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_InvalidVersion) { + static constexpr const char* xml = R"xml( + <android-sdk-group label="v19"> + <android-sdk + minSdkVersion="v19" + targetSdkVersion="v24" + maxSdkVersion="v25"> + <manifest> + <!--- manifest additions here XSLT? TODO --> + </manifest> + </android-sdk> + </android-sdk-group>)xml"; + + auto doc = test::BuildXmlDom(xml); + + PostProcessingConfiguration config; + bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); + ASSERT_FALSE(ok); +} + TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_NonNumeric) { static constexpr const char* xml = R"xml( <android-sdk-group label="P"> <android-sdk - minSdkVersion="M" - targetSdkVersion="P" - maxSdkVersion="P"> + minSdkVersion="25" + targetSdkVersion="%s" + maxSdkVersion="%s"> </android-sdk> </android-sdk-group>)xml"; - auto doc = test::BuildXmlDom(xml); + const auto& dev_sdk = GetDevelopmentSdkCodeNameAndVersion(); + const char* codename = dev_sdk.first.data(); + const ApiVersion& version = dev_sdk.second; + + auto doc = test::BuildXmlDom(StringPrintf(xml, codename, codename)); PostProcessingConfiguration config; bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); @@ -443,9 +535,9 @@ TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_NonNumeric) { auto& out = config.android_sdk_groups["P"]; AndroidSdk sdk; - sdk.min_sdk_version = {}; // Only the latest development version is supported. - sdk.target_sdk_version = 28; - sdk.max_sdk_version = 28; + sdk.min_sdk_version = 25; + sdk.target_sdk_version = version; + sdk.max_sdk_version = version; ASSERT_EQ(sdk, out); } diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index 9861770083a2..8c8c2549609a 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -272,7 +272,7 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res // Build the JavaDoc comment for the Styleable array. This has references to child attributes // and what possible values can be used for them. const size_t attr_count = sorted_attributes.size(); - if (attr_count > 0) { + if (out_class_def != nullptr && attr_count > 0) { std::stringstream styleable_comment; if (!styleable.GetComment().empty()) { styleable_comment << styleable.GetComment() << "\n"; @@ -356,54 +356,56 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res continue; } - StringPiece comment = styleable_attr.attr_ref->GetComment(); - if (styleable_attr.symbol.value().attribute && comment.empty()) { - comment = styleable_attr.symbol.value().attribute->GetComment(); - } + if (out_class_def != nullptr) { + StringPiece comment = styleable_attr.attr_ref->GetComment(); + if (styleable_attr.symbol.value().attribute && comment.empty()) { + comment = styleable_attr.symbol.value().attribute->GetComment(); + } - if (comment.contains("@removed")) { - // Removed attributes are public but hidden from the documentation, so - // don't emit them as part of the class documentation. - continue; - } + if (comment.contains("@removed")) { + // Removed attributes are public but hidden from the documentation, so + // don't emit them as part of the class documentation. + continue; + } - const ResourceName& attr_name = styleable_attr.attr_ref->name.value(); + const ResourceName& attr_name = styleable_attr.attr_ref->name.value(); - StringPiece package_name = attr_name.package; - if (package_name.empty()) { - package_name = context_->GetCompilationPackage(); - } + StringPiece package_name = attr_name.package; + if (package_name.empty()) { + package_name = context_->GetCompilationPackage(); + } - std::unique_ptr<IntMember> index_member = util::make_unique<IntMember>( - sorted_attributes[i].field_name, static_cast<uint32_t>(i)); + std::unique_ptr<IntMember> index_member = + util::make_unique<IntMember>(sorted_attributes[i].field_name, static_cast<uint32_t>(i)); + + AnnotationProcessor* attr_processor = index_member->GetCommentBuilder(); + + if (!comment.empty()) { + attr_processor->AppendComment("<p>\n@attr description"); + attr_processor->AppendComment(comment); + } else { + std::stringstream default_comment; + default_comment << "<p>This symbol is the offset where the " + << "{@link " << package_name << ".R.attr#" + << TransformToFieldName(attr_name.entry) << "}\n" + << "attribute's value can be found in the " + << "{@link #" << array_field_name << "} array."; + attr_processor->AppendComment(default_comment.str()); + } - AnnotationProcessor* attr_processor = index_member->GetCommentBuilder(); + attr_processor->AppendNewLine(); + AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get()); + attr_processor->AppendNewLine(); + attr_processor->AppendComment( + StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data())); - if (!comment.empty()) { - attr_processor->AppendComment("<p>\n@attr description"); - attr_processor->AppendComment(comment); - } else { - std::stringstream default_comment; - default_comment << "<p>This symbol is the offset where the " - << "{@link " << package_name << ".R.attr#" - << TransformToFieldName(attr_name.entry) << "}\n" - << "attribute's value can be found in the " - << "{@link #" << array_field_name << "} array."; - attr_processor->AppendComment(default_comment.str()); + out_class_def->AddMember(std::move(index_member)); } - attr_processor->AppendNewLine(); - AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get()); - attr_processor->AppendNewLine(); - attr_processor->AppendComment( - StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data())); - if (r_txt_printer != nullptr) { r_txt_printer->Println( StringPrintf("int styleable %s %zd", sorted_attributes[i].field_name.c_str(), i)); } - - out_class_def->AddMember(std::move(index_member)); } // If there is a rewrite method to generate, add the statements that rewrite package IDs @@ -434,31 +436,33 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso } const std::string field_name = TransformToFieldName(name.entry); - std::unique_ptr<ResourceMember> resource_member = - util::make_unique<ResourceMember>(field_name, real_id); + if (out_class_def != nullptr) { + std::unique_ptr<ResourceMember> resource_member = + util::make_unique<ResourceMember>(field_name, real_id); - // Build the comments and annotations for this entry. - AnnotationProcessor* processor = resource_member->GetCommentBuilder(); + // Build the comments and annotations for this entry. + AnnotationProcessor* processor = resource_member->GetCommentBuilder(); - // Add the comments from any <public> tags. - if (entry.symbol_status.state != SymbolState::kUndefined) { - processor->AppendComment(entry.symbol_status.comment); - } + // Add the comments from any <public> tags. + if (entry.symbol_status.state != SymbolState::kUndefined) { + processor->AppendComment(entry.symbol_status.comment); + } - // Add the comments from all configurations of this entry. - for (const auto& config_value : entry.values) { - processor->AppendComment(config_value->value->GetComment()); - } + // Add the comments from all configurations of this entry. + for (const auto& config_value : entry.values) { + processor->AppendComment(config_value->value->GetComment()); + } - // If this is an Attribute, append the format Javadoc. - if (!entry.values.empty()) { - if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) { - // We list out the available values for the given attribute. - AddAttributeFormatDoc(processor, attr); + // If this is an Attribute, append the format Javadoc. + if (!entry.values.empty()) { + if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) { + // We list out the available values for the given attribute. + AddAttributeFormatDoc(processor, attr); + } } - } - out_class_def->AddMember(std::move(resource_member)); + out_class_def->AddMember(std::move(resource_member)); + } if (r_txt_printer != nullptr) { r_txt_printer->Print("int ") @@ -576,7 +580,7 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, } // Generate an onResourcesLoaded() callback if requested. - if (options_.rewrite_callback_options) { + if (out != nullptr && options_.rewrite_callback_options) { rewrite_method = util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)"); for (const std::string& package_to_callback : @@ -597,8 +601,12 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, const bool force_creation_if_empty = (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic); - std::unique_ptr<ClassDefinition> class_def = util::make_unique<ClassDefinition>( - to_string(type->type), ClassQualifier::kStatic, force_creation_if_empty); + std::unique_ptr<ClassDefinition> class_def; + if (out != nullptr) { + class_def = util::make_unique<ClassDefinition>( + to_string(type->type), ClassQualifier::kStatic, force_creation_if_empty); + } + if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(), rewrite_method.get(), r_txt_printer.get())) { return false; @@ -615,16 +623,17 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, } } - if (type->type == ResourceType::kStyleable && + if (out != nullptr && type->type == ResourceType::kStyleable && options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) { // When generating a public R class, we don't want Styleable to be part // of the API. It is only emitted for documentation purposes. class_def->GetCommentBuilder()->AppendComment("@doconly"); } - AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder()); - - r_class.AddMember(std::move(class_def)); + if (out != nullptr) { + AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder()); + r_class.AddMember(std::move(class_def)); + } } } @@ -632,8 +641,10 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, r_class.AddMember(std::move(rewrite_method)); } - AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder()); - ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out); + if (out != nullptr) { + AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder()); + ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out); + } return true; } diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp index da3b8792be69..e2d738aec5a2 100644 --- a/tools/aapt2/optimize/MultiApkGenerator.cpp +++ b/tools/aapt2/optimize/MultiApkGenerator.cpp @@ -17,6 +17,7 @@ #include "MultiApkGenerator.h" #include <algorithm> +#include <regex> #include <string> #include "androidfw/StringPiece.h" @@ -125,6 +126,16 @@ class ContextWrapper : public IAaptContext { int min_sdk_ = -1; }; +class SignatureFilter : public IPathFilter { + bool Keep(const std::string& path) override { + static std::regex signature_regex(R"regex(^META-INF/.*\.(RSA|DSA|EC|SF)$)regex"); + if (std::regex_search(path, signature_regex)) { + return false; + } + return !(path == "META-INF/MANIFEST.MF"); + } +}; + MultiApkGenerator::MultiApkGenerator(LoadedApk* apk, IAaptContext* context) : apk_(apk), context_(context) { } @@ -209,6 +220,7 @@ bool MultiApkGenerator::FromBaseApk(const MultiApkGeneratorOptions& options) { diag.Note(DiagMessage() << "Writing output: " << out); } + filters.AddFilter(util::make_unique<SignatureFilter>()); if (!apk_->WriteToArchive(&wrapped_context, table.get(), options.table_flattener_options, &filters, writer.get(), manifest.get())) { return false; |