diff options
213 files changed, 10932 insertions, 3057 deletions
diff --git a/Android.mk b/Android.mk index 9d1b0ccb3ca2..88be12f23a77 100644 --- a/Android.mk +++ b/Android.mk @@ -38,7 +38,9 @@ ifneq ($(ANDROID_BUILD_EMBEDDED),true) include $(CLEAR_VARS) # FRAMEWORKS_BASE_SUBDIRS comes from build/core/pathmap.mk -LOCAL_SRC_FILES := $(call find-other-java-files,$(FRAMEWORKS_BASE_SUBDIRS)) +LOCAL_SRC_FILES := \ + $(call find-other-java-files,$(FRAMEWORKS_BASE_SUBDIRS)) \ + $(call all-proto-files-under, core/proto) # EventLogTags files. LOCAL_SRC_FILES += \ @@ -234,6 +236,9 @@ LOCAL_SRC_FILES += \ core/java/android/os/IDeviceIdentifiersPolicyService.aidl \ core/java/android/os/IDeviceIdleController.aidl \ core/java/android/os/IHardwarePropertiesManager.aidl \ + core/java/android/os/IIncidentManager.aidl \ + core/java/android/os/IIncidentReportCompletedListener.aidl \ + core/java/android/os/IIncidentReportStatusListener.aidl \ core/java/android/os/IMaintenanceActivityListener.aidl \ core/java/android/os/IMessenger.aidl \ core/java/android/os/INetworkActivityListener.aidl \ @@ -528,6 +533,10 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ android.hardware.thermal@1.0-java-constants \ android.hardware.health@1.0-java-constants \ +LOCAL_PROTOC_OPTIMIZE_TYPE := stream +LOCAL_PROTOC_FLAGS := \ + -Iexternal/protobuf/src + LOCAL_MODULE := framework LOCAL_JACK_FLAGS := --multi-dex native @@ -1385,6 +1394,35 @@ endif include $(BUILD_JAVA_LIBRARY) +# ==== c++ proto host library ============================== +include $(CLEAR_VARS) +LOCAL_MODULE := libplatformprotos +LOCAL_PROTOC_OPTIMIZE_TYPE := full +LOCAL_PROTOC_FLAGS := \ + --include_source_info \ + -Iexternal/protobuf/src +LOCAL_SRC_FILES := \ + $(call all-proto-files-under, core/proto) \ + $(call all-proto-files-under, libs/incident/proto) +LOCAL_C_INCLUDES := \ + $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto +LOCAL_EXPORT_C_INCLUDES := \ + $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto +include $(BUILD_HOST_SHARED_LIBRARY) + + +# ==== java proto host library ============================== +include $(CLEAR_VARS) +LOCAL_MODULE := platformprotos +LOCAL_PROTOC_OPTIMIZE_TYPE := full +LOCAL_PROTOC_FLAGS := \ + -Iexternal/protobuf/src +LOCAL_SOURCE_FILES_ALL_GENERATED := true +LOCAL_SRC_FILES := \ + $(call all-proto-files-under, core/proto) \ + $(call all-proto-files-under, libs/incident/proto) +include $(BUILD_HOST_JAVA_LIBRARY) + # Include subdirectory makefiles # ============================================================ diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index c28db57c23c0..75de4a7833f1 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,5 +1,5 @@ [Hook Scripts] -checkstyle_hook = ../../development/tools/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} +checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} -fw core/java/android/animation/ core/java/android/hardware/usb/ core/java/android/print/ diff --git a/api/current.txt b/api/current.txt index cec2b9fc2457..395d7aac70c2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11769,6 +11769,8 @@ package android.graphics { method public static android.graphics.Bitmap createBitmap(android.graphics.Bitmap, int, int, int, int, android.graphics.Matrix, boolean); method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config); + method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config, boolean); + method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config, boolean); method public static android.graphics.Bitmap createBitmap(int[], int, int, int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int[], int, int, int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(int[], int, int, android.graphics.Bitmap.Config); @@ -11833,6 +11835,7 @@ package android.graphics { enum_constant public static final deprecated android.graphics.Bitmap.Config ARGB_4444; enum_constant public static final android.graphics.Bitmap.Config ARGB_8888; enum_constant public static final android.graphics.Bitmap.Config HARDWARE; + enum_constant public static final android.graphics.Bitmap.Config RGBA_F16; enum_constant public static final android.graphics.Bitmap.Config RGB_565; } @@ -12192,6 +12195,7 @@ package android.graphics { method public android.graphics.Bitmap render(); method public android.graphics.ColorSpace.Renderer showWhitePoint(boolean); method public android.graphics.ColorSpace.Renderer size(int); + method public android.graphics.ColorSpace.Renderer uniformChromaticityScale(boolean); } public static class ColorSpace.Rgb extends android.graphics.ColorSpace { @@ -12727,6 +12731,7 @@ package android.graphics { field public static final deprecated int RGBA_4444 = 7; // 0x7 field public static final deprecated int RGBA_5551 = 6; // 0x6 field public static final int RGBA_8888 = 1; // 0x1 + field public static final int RGBA_F16 = 22; // 0x16 field public static final int RGBX_8888 = 2; // 0x2 field public static final deprecated int RGB_332 = 11; // 0xb field public static final int RGB_565 = 4; // 0x4 @@ -61421,31 +61426,31 @@ package java.util.concurrent { ctor public CopyOnWriteArrayList(); ctor public CopyOnWriteArrayList(java.util.Collection<? extends E>); ctor public CopyOnWriteArrayList(E[]); - method public synchronized boolean add(E); - method public synchronized void add(int, E); - method public synchronized boolean addAll(java.util.Collection<? extends E>); - method public synchronized boolean addAll(int, java.util.Collection<? extends E>); - method public synchronized int addAllAbsent(java.util.Collection<? extends E>); - method public synchronized boolean addIfAbsent(E); - method public synchronized void clear(); + method public boolean add(E); + method public void add(int, E); + method public boolean addAll(java.util.Collection<? extends E>); + method public boolean addAll(int, java.util.Collection<? extends E>); + method public int addAllAbsent(java.util.Collection<? extends E>); + method public boolean addIfAbsent(E); + method public void clear(); method public java.lang.Object clone(); method public boolean contains(java.lang.Object); method public boolean containsAll(java.util.Collection<?>); method public void forEach(java.util.function.Consumer<? super E>); method public E get(int); - method public int indexOf(E, int); method public int indexOf(java.lang.Object); + method public int indexOf(E, int); method public boolean isEmpty(); method public java.util.Iterator<E> iterator(); - method public int lastIndexOf(E, int); method public int lastIndexOf(java.lang.Object); - method public java.util.ListIterator<E> listIterator(int); + method public int lastIndexOf(E, int); method public java.util.ListIterator<E> listIterator(); - method public synchronized E remove(int); - method public synchronized boolean remove(java.lang.Object); - method public synchronized boolean removeAll(java.util.Collection<?>); - method public synchronized boolean retainAll(java.util.Collection<?>); - method public synchronized E set(int, E); + method public java.util.ListIterator<E> listIterator(int); + method public E remove(int); + method public boolean remove(java.lang.Object); + method public boolean removeAll(java.util.Collection<?>); + method public boolean retainAll(java.util.Collection<?>); + method public E set(int, E); method public int size(); method public java.util.List<E> subList(int, int); method public java.lang.Object[] toArray(); diff --git a/api/system-current.txt b/api/system-current.txt index b0236ea15fc2..f38b02e7a838 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -12262,6 +12262,8 @@ package android.graphics { method public static android.graphics.Bitmap createBitmap(android.graphics.Bitmap, int, int, int, int, android.graphics.Matrix, boolean); method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config); + method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config, boolean); + method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config, boolean); method public static android.graphics.Bitmap createBitmap(int[], int, int, int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int[], int, int, int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(int[], int, int, android.graphics.Bitmap.Config); @@ -12326,6 +12328,7 @@ package android.graphics { enum_constant public static final deprecated android.graphics.Bitmap.Config ARGB_4444; enum_constant public static final android.graphics.Bitmap.Config ARGB_8888; enum_constant public static final android.graphics.Bitmap.Config HARDWARE; + enum_constant public static final android.graphics.Bitmap.Config RGBA_F16; enum_constant public static final android.graphics.Bitmap.Config RGB_565; } @@ -12685,6 +12688,7 @@ package android.graphics { method public android.graphics.Bitmap render(); method public android.graphics.ColorSpace.Renderer showWhitePoint(boolean); method public android.graphics.ColorSpace.Renderer size(int); + method public android.graphics.ColorSpace.Renderer uniformChromaticityScale(boolean); } public static class ColorSpace.Rgb extends android.graphics.ColorSpace { @@ -13220,6 +13224,7 @@ package android.graphics { field public static final deprecated int RGBA_4444 = 7; // 0x7 field public static final deprecated int RGBA_5551 = 6; // 0x6 field public static final int RGBA_8888 = 1; // 0x1 + field public static final int RGBA_F16 = 22; // 0x16 field public static final int RGBX_8888 = 2; // 0x2 field public static final deprecated int RGB_332 = 11; // 0xb field public static final int RGB_565 = 4; // 0x4 @@ -26051,7 +26056,7 @@ package android.net { field public static final java.lang.String EXTRA_SEQUENCE = "android.net.extra.SEQUENCE"; } - public static final class NetworkRecommendationProvider.ResultCallback { + public static class NetworkRecommendationProvider.ResultCallback { method public void onResult(android.net.RecommendationResult); } @@ -31734,6 +31739,27 @@ package android.os { method public abstract android.os.IBinder asBinder(); } + public class IncidentManager { + method public void reportIncident(android.os.IncidentReportArgs); + method public void reportIncident(java.lang.String, byte[]); + } + + public final class IncidentReportArgs implements android.os.Parcelable { + ctor public IncidentReportArgs(); + ctor public IncidentReportArgs(android.os.Parcel); + method public void addHeader(byte[]); + method public void addSection(int); + method public boolean containsSection(int); + method public int describeContents(); + method public boolean isAll(); + method public static android.os.IncidentReportArgs parseSetting(java.lang.String) throws java.lang.IllegalArgumentException; + method public void readFromParcel(android.os.Parcel); + method public int sectionCount(); + method public void setAll(boolean); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.os.IncidentReportArgs> CREATOR; + } + public final class LocaleList implements android.os.Parcelable { ctor public LocaleList(java.util.Locale...); method public int describeContents(); @@ -64923,31 +64949,31 @@ package java.util.concurrent { ctor public CopyOnWriteArrayList(); ctor public CopyOnWriteArrayList(java.util.Collection<? extends E>); ctor public CopyOnWriteArrayList(E[]); - method public synchronized boolean add(E); - method public synchronized void add(int, E); - method public synchronized boolean addAll(java.util.Collection<? extends E>); - method public synchronized boolean addAll(int, java.util.Collection<? extends E>); - method public synchronized int addAllAbsent(java.util.Collection<? extends E>); - method public synchronized boolean addIfAbsent(E); - method public synchronized void clear(); + method public boolean add(E); + method public void add(int, E); + method public boolean addAll(java.util.Collection<? extends E>); + method public boolean addAll(int, java.util.Collection<? extends E>); + method public int addAllAbsent(java.util.Collection<? extends E>); + method public boolean addIfAbsent(E); + method public void clear(); method public java.lang.Object clone(); method public boolean contains(java.lang.Object); method public boolean containsAll(java.util.Collection<?>); method public void forEach(java.util.function.Consumer<? super E>); method public E get(int); - method public int indexOf(E, int); method public int indexOf(java.lang.Object); + method public int indexOf(E, int); method public boolean isEmpty(); method public java.util.Iterator<E> iterator(); - method public int lastIndexOf(E, int); method public int lastIndexOf(java.lang.Object); - method public java.util.ListIterator<E> listIterator(int); + method public int lastIndexOf(E, int); method public java.util.ListIterator<E> listIterator(); - method public synchronized E remove(int); - method public synchronized boolean remove(java.lang.Object); - method public synchronized boolean removeAll(java.util.Collection<?>); - method public synchronized boolean retainAll(java.util.Collection<?>); - method public synchronized E set(int, E); + method public java.util.ListIterator<E> listIterator(int); + method public E remove(int); + method public boolean remove(java.lang.Object); + method public boolean removeAll(java.util.Collection<?>); + method public boolean retainAll(java.util.Collection<?>); + method public E set(int, E); method public int size(); method public java.util.List<E> subList(int, int); method public java.lang.Object[] toArray(); diff --git a/api/test-current.txt b/api/test-current.txt index c798b5e165cd..e43d1b5426c3 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -11800,6 +11800,8 @@ package android.graphics { method public static android.graphics.Bitmap createBitmap(android.graphics.Bitmap, int, int, int, int, android.graphics.Matrix, boolean); method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config); + method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config, boolean); + method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config, boolean); method public static android.graphics.Bitmap createBitmap(int[], int, int, int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int[], int, int, int, int, android.graphics.Bitmap.Config); method public static android.graphics.Bitmap createBitmap(int[], int, int, android.graphics.Bitmap.Config); @@ -11864,6 +11866,7 @@ package android.graphics { enum_constant public static final deprecated android.graphics.Bitmap.Config ARGB_4444; enum_constant public static final android.graphics.Bitmap.Config ARGB_8888; enum_constant public static final android.graphics.Bitmap.Config HARDWARE; + enum_constant public static final android.graphics.Bitmap.Config RGBA_F16; enum_constant public static final android.graphics.Bitmap.Config RGB_565; } @@ -12223,6 +12226,7 @@ package android.graphics { method public android.graphics.Bitmap render(); method public android.graphics.ColorSpace.Renderer showWhitePoint(boolean); method public android.graphics.ColorSpace.Renderer size(int); + method public android.graphics.ColorSpace.Renderer uniformChromaticityScale(boolean); } public static class ColorSpace.Rgb extends android.graphics.ColorSpace { @@ -12758,6 +12762,7 @@ package android.graphics { field public static final deprecated int RGBA_4444 = 7; // 0x7 field public static final deprecated int RGBA_5551 = 6; // 0x6 field public static final int RGBA_8888 = 1; // 0x1 + field public static final int RGBA_F16 = 22; // 0x16 field public static final int RGBX_8888 = 2; // 0x2 field public static final deprecated int RGB_332 = 11; // 0xb field public static final int RGB_565 = 4; // 0x4 @@ -29307,6 +29312,27 @@ package android.os { method public abstract android.os.IBinder asBinder(); } + public class IncidentManager { + method public void reportIncident(android.os.IncidentReportArgs); + method public void reportIncident(java.lang.String, byte[]); + } + + public final class IncidentReportArgs implements android.os.Parcelable { + ctor public IncidentReportArgs(); + ctor public IncidentReportArgs(android.os.Parcel); + method public void addHeader(byte[]); + method public void addSection(int); + method public boolean containsSection(int); + method public int describeContents(); + method public boolean isAll(); + method public static android.os.IncidentReportArgs parseSetting(java.lang.String) throws java.lang.IllegalArgumentException; + method public void readFromParcel(android.os.Parcel); + method public int sectionCount(); + method public void setAll(boolean); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.os.IncidentReportArgs> CREATOR; + } + public final class LocaleList implements android.os.Parcelable { ctor public LocaleList(java.util.Locale...); method public int describeContents(); @@ -61710,31 +61736,31 @@ package java.util.concurrent { ctor public CopyOnWriteArrayList(); ctor public CopyOnWriteArrayList(java.util.Collection<? extends E>); ctor public CopyOnWriteArrayList(E[]); - method public synchronized boolean add(E); - method public synchronized void add(int, E); - method public synchronized boolean addAll(java.util.Collection<? extends E>); - method public synchronized boolean addAll(int, java.util.Collection<? extends E>); - method public synchronized int addAllAbsent(java.util.Collection<? extends E>); - method public synchronized boolean addIfAbsent(E); - method public synchronized void clear(); + method public boolean add(E); + method public void add(int, E); + method public boolean addAll(java.util.Collection<? extends E>); + method public boolean addAll(int, java.util.Collection<? extends E>); + method public int addAllAbsent(java.util.Collection<? extends E>); + method public boolean addIfAbsent(E); + method public void clear(); method public java.lang.Object clone(); method public boolean contains(java.lang.Object); method public boolean containsAll(java.util.Collection<?>); method public void forEach(java.util.function.Consumer<? super E>); method public E get(int); - method public int indexOf(E, int); method public int indexOf(java.lang.Object); + method public int indexOf(E, int); method public boolean isEmpty(); method public java.util.Iterator<E> iterator(); - method public int lastIndexOf(E, int); method public int lastIndexOf(java.lang.Object); - method public java.util.ListIterator<E> listIterator(int); + method public int lastIndexOf(E, int); method public java.util.ListIterator<E> listIterator(); - method public synchronized E remove(int); - method public synchronized boolean remove(java.lang.Object); - method public synchronized boolean removeAll(java.util.Collection<?>); - method public synchronized boolean retainAll(java.util.Collection<?>); - method public synchronized E set(int, E); + method public java.util.ListIterator<E> listIterator(int); + method public E remove(int); + method public boolean remove(java.lang.Object); + method public boolean removeAll(java.util.Collection<?>); + method public boolean retainAll(java.util.Collection<?>); + method public E set(int, E); method public int size(); method public java.util.List<E> subList(int, int); method public java.lang.Object[] toArray(); diff --git a/cmds/incident/Android.mk b/cmds/incident/Android.mk new file mode 100644 index 000000000000..e1c9b93a8e6d --- /dev/null +++ b/cmds/incident/Android.mk @@ -0,0 +1,48 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + main.cpp + +LOCAL_MODULE := incident + +LOCAL_SHARED_LIBRARIES := \ + libbase \ + libbinder \ + libcutils \ + liblog \ + libutils \ + libincident + +LOCAL_CFLAGS += \ + -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter + +LOCAL_MODULE_CLASS := EXECUTABLES +gen_src_dir := $(local-generated-sources-dir) + +gen := $(gen_src_dir)/incident_sections.cpp +$(gen): $(HOST_OUT_EXECUTABLES)/incident-section-gen +$(gen): PRIVATE_CUSTOM_TOOL = \ + $(HOST_OUT_EXECUTABLES)/incident-section-gen > $@ +$(gen): $(HOST_OUT_EXECUTABLES)/incident-section-gen + $(transform-generated-source) +LOCAL_GENERATED_SOURCES += $(gen) + +gen_src_dir:= +gen:= + +include $(BUILD_EXECUTABLE) diff --git a/cmds/incident/incident_sections.h b/cmds/incident/incident_sections.h new file mode 100644 index 000000000000..1972088fc82c --- /dev/null +++ b/cmds/incident/incident_sections.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCIDENT_SECTIONS_H +#define INCIDENT_SECTIONS_H + +struct IncidentSection +{ + int id; + char const* name; +}; + +extern IncidentSection const INCIDENT_SECTIONS[]; +extern const int INCIDENT_SECTION_COUNT; + +#endif // INCIDENT_SECTIONS_H diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp new file mode 100644 index 000000000000..91b7c22b2038 --- /dev/null +++ b/cmds/incident/main.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "incident" + +#include "incident_sections.h" + +#include <android/os/BnIncidentReportStatusListener.h> +#include <android/os/IIncidentManager.h> +#include <android/os/IncidentReportArgs.h> +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <utils/Looper.h> + +#include <fcntl.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +using namespace android; +using namespace android::base; +using namespace android::binder; +using namespace android::os; + +// ================================================================================ +class StatusListener : public BnIncidentReportStatusListener { +public: + StatusListener(); + virtual ~StatusListener(); + + virtual Status onReportStarted(); + virtual Status onReportSectionStatus(int32_t section, int32_t status); + virtual Status onReportServiceStatus(const String16& service, int32_t status); + virtual Status onReportFinished(); + virtual Status onReportFailed(); +}; + +StatusListener::StatusListener() +{ +} + +StatusListener::~StatusListener() +{ +} + +Status +StatusListener::onReportStarted() +{ + return Status::ok(); +} + +Status +StatusListener::onReportSectionStatus(int32_t section, int32_t status) +{ + fprintf(stderr, "section %d status %d\n", section, status); + return Status::ok(); +} + +Status +StatusListener::onReportServiceStatus(const String16& service, int32_t status) +{ + fprintf(stderr, "service '%s' status %d\n", String8(service).string(), status); + return Status::ok(); +} + +Status +StatusListener::onReportFinished() +{ + fprintf(stderr, "done\n"); + exit(0); + return Status::ok(); +} + +Status +StatusListener::onReportFailed() +{ + fprintf(stderr, "failed\n"); + exit(1); + return Status::ok(); +} + +// ================================================================================ +static IncidentSection const* +find_section(const char* name) +{ + size_t low = 0; + size_t high = INCIDENT_SECTION_COUNT - 1; + + while (low <= high) { + size_t mid = (low + high) >> 1; + IncidentSection const* section = INCIDENT_SECTIONS + mid; + + int cmp = strcmp(section->name, name); + if (cmp < 0) { + low = mid + 1; + } else if (cmp > 0) { + high = mid - 1; + } else { + return section; + } + } + return NULL; +} + +// ================================================================================ +static void +usage(FILE* out) +{ + fprintf(out, "usage: incident OPTIONS [SECTION...]\n"); + fprintf(out, "\n"); + fprintf(out, "Takes an incident report.\n"); + fprintf(out, "\n"); + fprintf(out, "OPTIONS\n"); + fprintf(out, " -b (default) print the report to stdout (in proto format)\n"); + fprintf(out, " -d send the report into dropbox\n"); + fprintf(out, "\n"); + fprintf(out, " SECTION the field numbers of the incident report fields to include\n"); + fprintf(out, "\n"); +} + +int +main(int argc, char** argv) +{ + Status status; + IncidentReportArgs args; + enum { DEST_DROPBOX, DEST_STDOUT } destination = DEST_STDOUT; + + // Parse the args + int opt; + while ((opt = getopt(argc, argv, "bhd")) != -1) { + switch (opt) { + case 'b': + destination = DEST_STDOUT; + break; + case 'h': + usage(stdout); + return 0; + case 'd': + destination = DEST_DROPBOX; + break; + default: + usage(stderr); + return 1; + } + } + + if (optind == argc) { + args.setAll(true); + } else { + for (int i=optind; i<argc; i++) { + const char* arg = argv[i]; + char* end; + if (arg[0] != '\0') { + int section = strtol(arg, &end, 0); + if (*end == '\0') { + args.addSection(section); + } else { + IncidentSection const* ic = find_section(arg); + if (ic == NULL) { + fprintf(stderr, "Invalid section: %s\n", arg); + return 1; + } + args.addSection(ic->id); + } + } + } + } + + + + // Start the thread pool. + sp<ProcessState> ps(ProcessState::self()); + ps->startThreadPool(); + ps->giveThreadPoolName(); + + // Look up the service + sp<IIncidentManager> service = interface_cast<IIncidentManager>( + defaultServiceManager()->getService(android::String16("incident"))); + if (service == NULL) { + fprintf(stderr, "Couldn't look up the incident service\n"); + return 1; + } + + // Construct the stream + int fds[2]; + pipe(fds); + + unique_fd readEnd(fds[0]); + unique_fd writeEnd(fds[1]); + + if (destination == DEST_STDOUT) { + // Call into the service + sp<StatusListener> listener(new StatusListener()); + status = service->reportIncidentToStream(args, listener, writeEnd); + + if (!status.isOk()) { + fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string()); + } + + // Wait for the result and print out the data they send. + //IPCThreadState::self()->joinThreadPool(); + + while (true) { + int amt = splice(fds[0], NULL, STDOUT_FILENO, NULL, 4096, 0); + fprintf(stderr, "spliced %d bytes\n", amt); + if (amt < 0) { + return errno; + } else if (amt == 0) { + return 0; + } + } + } else { + status = service->reportIncident(args); + if (!status.isOk()) { + fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string()); + return 1; + } else { + return 0; + } + } + +} diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk new file mode 100644 index 000000000000..bacf672e81b5 --- /dev/null +++ b/cmds/incidentd/Android.mk @@ -0,0 +1,56 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := incidentd + +LOCAL_SRC_FILES := \ + src/FdBuffer.cpp \ + src/IncidentService.cpp \ + src/Reporter.cpp \ + src/Section.cpp \ + src/main.cpp \ + src/protobuf.cpp \ + src/report_directory.cpp \ + src/section_list.cpp + +LOCAL_CFLAGS += \ + -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter + +ifeq (debug,) + LOCAL_CFLAGS += \ + -g -O0 +else + # optimize for size (protobuf glop can get big) + LOCAL_CFLAGS += \ + -Os +endif + +LOCAL_SHARED_LIBRARIES := \ + libbase \ + libbinder \ + libcutils \ + libincident \ + liblog \ + libselinux \ + libservices \ + libutils + +ifeq (BUILD_WITH_INCIDENTD_RC,true) +LOCAL_INIT_RC := incidentd.rc +endif + +include $(BUILD_EXECUTABLE) diff --git a/cmds/incidentd/incidentd.rc b/cmds/incidentd/incidentd.rc new file mode 100644 index 000000000000..d11e3cf70567 --- /dev/null +++ b/cmds/incidentd/incidentd.rc @@ -0,0 +1,16 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#service incidentd /system/bin/incidentd +# class main diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp new file mode 100644 index 000000000000..527d7eef3a96 --- /dev/null +++ b/cmds/incidentd/src/FdBuffer.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "incidentd" + +#include "FdBuffer.h" + +#include <cutils/log.h> +#include <utils/SystemClock.h> + +#include <fcntl.h> +#include <poll.h> +#include <unistd.h> + +const ssize_t BUFFER_SIZE = 16 * 1024; +const ssize_t MAX_BUFFER_COUNT = 256; // 4 MB max + + +FdBuffer::FdBuffer() + :mBuffers(), + mStartTime(-1), + mFinishTime(-1), + mCurrentWritten(-1), + mTimedOut(false), + mTruncated(false) +{ +} + +FdBuffer::~FdBuffer() +{ + const int N = mBuffers.size(); + for (int i=0; i<N; i++) { + uint8_t* buf = mBuffers[i]; + free(buf); + } +} + +status_t +FdBuffer::read(int fd, int64_t timeout) +{ + struct pollfd pfds = { + .fd = fd, + .events = POLLIN + }; + mStartTime = uptimeMillis(); + + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + + uint8_t* buf = NULL; + while (true) { + if (mCurrentWritten >= BUFFER_SIZE || mCurrentWritten < 0) { + if (mBuffers.size() == MAX_BUFFER_COUNT) { + mTruncated = true; + break; + } + buf = (uint8_t*)malloc(BUFFER_SIZE); + if (buf == NULL) { + return NO_MEMORY; + } + mBuffers.push_back(buf); + mCurrentWritten = 0; + } + + int64_t remainingTime = (mStartTime + timeout) - uptimeMillis(); + if (remainingTime <= 0) { + mTimedOut = true; + break; + } + + int count = poll(&pfds, 1, remainingTime); + if (count == 0) { + mTimedOut = true; + break; + } else if (count < 0) { + return -errno; + } else { + if ((pfds.revents & POLLERR) != 0) { + return errno != 0 ? -errno : UNKNOWN_ERROR; + } else { + ssize_t amt = ::read(fd, buf + mCurrentWritten, BUFFER_SIZE - mCurrentWritten); + if (amt < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + continue; + } else { + return -errno; + } + } else if (amt == 0) { + break; + } + mCurrentWritten += amt; + } + } + } + + mFinishTime = uptimeMillis(); + return NO_ERROR; +} + +size_t +FdBuffer::size() +{ + return ((mBuffers.size() - 1) * BUFFER_SIZE) + mCurrentWritten; +} + +status_t +FdBuffer::write(ReportRequestSet* reporter) +{ + const int N = mBuffers.size() - 1; + for (int i=0; i<N; i++) { + reporter->write(mBuffers[i], BUFFER_SIZE); + } + reporter->write(mBuffers[N], mCurrentWritten); + return NO_ERROR; +} + + diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h new file mode 100644 index 000000000000..e12374f21558 --- /dev/null +++ b/cmds/incidentd/src/FdBuffer.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FD_BUFFER_H +#define FD_BUFFER_H + +#include "Reporter.h" + +#include <utils/Errors.h> + +#include <set> +#include <vector> + +using namespace android; +using namespace std; + +/** + * Reads a file into a buffer, and then writes that data to an FdSet. + */ +class FdBuffer +{ +public: + FdBuffer(); + ~FdBuffer(); + + /** + * Read the data until the timeout is hit or we hit eof. + * Returns NO_ERROR if there were no errors or if we timed out. + * Will mark the file O_NONBLOCK. + */ + status_t read(int fd, int64_t timeoutMs); + + /** + * Whether we timed out. + */ + bool timedOut() { return mTimedOut; } + + /** + * If more than 4 MB is read, we truncate the data and return success. + * Downstream tools must handle truncated incident reports as best as possible + * anyway because they could be cut off for a lot of reasons and it's best + * to get as much useful information out of the system as possible. If this + * happens, truncated() will return true so it can be marked. If the data is + * exactly 4 MB, truncated is still set. Sorry. + */ + bool truncated() { return mTruncated; } + + /** + * How much data was read. + */ + size_t size(); + + /** + * Write the data that we recorded to the fd given. + */ + status_t write(ReportRequestSet* requests); + + /** + * How long the read took in milliseconds. + */ + int64_t durationMs() { return mFinishTime - mStartTime; } + +private: + vector<uint8_t*> mBuffers; + int64_t mStartTime; + int64_t mFinishTime; + ssize_t mCurrentWritten; + bool mTimedOut; + bool mTruncated; +}; + + +#endif // FD_BUFFER_H diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp new file mode 100644 index 000000000000..7c6789e6e5ba --- /dev/null +++ b/cmds/incidentd/src/IncidentService.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "incidentd" + +#include "IncidentService.h" + +#include "Reporter.h" + +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <cutils/log.h> +#include <private/android_filesystem_config.h> +#include <utils/Looper.h> + +#include <unistd.h> + +using namespace android; + +enum { + WHAT_RUN_REPORT = 1, + WHAT_SEND_BACKLOG_TO_DROPBOX = 2 +}; + +//#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL * 60 * 5) +#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL) + +// ================================================================================ +String16 const DUMP_PERMISSION("android.permission.DUMP"); +String16 const USAGE_STATS_PERMISSION("android.permission.PACKAGE_USAGE_STATS"); + +static Status +checkIncidentPermissions() +{ + if (!checkCallingPermission(DUMP_PERMISSION)) { + ALOGW("Calling pid %d and uid %d does not have permission: android.permission.DUMP", + IPCThreadState::self()->getCallingPid(), IPCThreadState::self()->getCallingUid()); + return Status::fromExceptionCode(Status::EX_SECURITY, + "Calling process does not have permission: android.permission.DUMP"); + } + if (!checkCallingPermission(USAGE_STATS_PERMISSION)) { + ALOGW("Calling pid %d and uid %d does not have permission: android.permission.USAGE_STATS", + IPCThreadState::self()->getCallingPid(), IPCThreadState::self()->getCallingUid()); + return Status::fromExceptionCode(Status::EX_SECURITY, + "Calling process does not have permission: android.permission.USAGE_STATS"); + } + return Status::ok(); +} + + +// ================================================================================ +ReportRequestQueue::ReportRequestQueue() +{ +} + +ReportRequestQueue::~ReportRequestQueue() +{ +} + +void +ReportRequestQueue::addRequest(const sp<ReportRequest>& request) +{ + unique_lock<mutex> lock(mLock); + mQueue.push_back(request); +} + +sp<ReportRequest> +ReportRequestQueue::getNextRequest() +{ + unique_lock<mutex> lock(mLock); + if (mQueue.empty()) { + return NULL; + } else { + sp<ReportRequest> front(mQueue.front()); + mQueue.pop_front(); + return front; + } +} + + +// ================================================================================ +ReportHandler::ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue) + :mBacklogDelay(DEFAULT_BACKLOG_DELAY_NS), + mHandlerLooper(handlerLooper), + mQueue(queue) +{ +} + +ReportHandler::~ReportHandler() +{ +} + +void +ReportHandler::handleMessage(const Message& message) +{ + switch (message.what) { + case WHAT_RUN_REPORT: + run_report(); + break; + case WHAT_SEND_BACKLOG_TO_DROPBOX: + send_backlog_to_dropbox(); + break; + } +} + +void +ReportHandler::scheduleRunReport(const sp<ReportRequest>& request) +{ + mQueue->addRequest(request); + mHandlerLooper->removeMessages(this, WHAT_RUN_REPORT); + mHandlerLooper->sendMessage(this, Message(WHAT_RUN_REPORT)); +} + +void +ReportHandler::scheduleSendBacklogToDropbox() +{ + unique_lock<mutex> lock(mLock); + mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS; + schedule_send_backlog_to_dropbox_locked(); +} + +void +ReportHandler::schedule_send_backlog_to_dropbox_locked() +{ + mHandlerLooper->removeMessages(this, WHAT_SEND_BACKLOG_TO_DROPBOX); + mHandlerLooper->sendMessageDelayed(mBacklogDelay, this, + Message(WHAT_SEND_BACKLOG_TO_DROPBOX)); +} + +void +ReportHandler::run_report() +{ + sp<Reporter> reporter = new Reporter(); + + // Merge all of the requests into one that has all of the + // requested fields. + while (true) { + sp<ReportRequest> request = mQueue->getNextRequest(); + if (request == NULL) { + break; + } + reporter->batch.add(request); + reporter->args.merge(request->args); + } + + // Take the report, which might take a while. More requests might queue + // up while we're doing this, and we'll handle them in their next batch. + // TODO: We should further rate-limit the reports to no more than N per time-period. + Reporter::run_report_status_t reportStatus = reporter->runReport(); + if (reportStatus == Reporter::REPORT_NEEDS_DROPBOX) { + unique_lock<mutex> lock(mLock); + schedule_send_backlog_to_dropbox_locked(); + } +} + +void +ReportHandler::send_backlog_to_dropbox() +{ + if (Reporter::upload_backlog() == Reporter::REPORT_NEEDS_DROPBOX) { + // There was a failure. Exponential backoff. + unique_lock<mutex> lock(mLock); + mBacklogDelay *= 2; + ALOGI("Error sending to dropbox. Trying again in %lld minutes", + (mBacklogDelay / (1000000000LL * 60))); + schedule_send_backlog_to_dropbox_locked(); + } else { + mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS; + } +} + +// ================================================================================ +IncidentService::IncidentService(const sp<Looper>& handlerLooper) + :mQueue(new ReportRequestQueue()) +{ + mHandler = new ReportHandler(handlerLooper, mQueue); +} + +IncidentService::~IncidentService() +{ +} + +Status +IncidentService::reportIncident(const IncidentReportArgs& args) +{ + ALOGI("reportIncident"); + + Status status = checkIncidentPermissions(); + if (!status.isOk()) { + return status; + } + + mHandler->scheduleRunReport(new ReportRequest(args, NULL, -1)); + + return Status::ok(); +} + +Status +IncidentService::reportIncidentToStream(const IncidentReportArgs& args, + const sp<IIncidentReportStatusListener>& listener, const unique_fd& stream) +{ + ALOGI("reportIncidentToStream"); + + Status status = checkIncidentPermissions(); + if (!status.isOk()) { + return status; + } + + int fd = dup(stream.get()); + if (fd < 0) { + return Status::fromStatusT(-errno); + } + + mHandler->scheduleRunReport(new ReportRequest(args, listener, fd)); + + return Status::ok(); +} + +Status +IncidentService::systemRunning() +{ + if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) { + return Status::fromExceptionCode(Status::EX_SECURITY, + "Only system uid can call systemRunning"); + } + + // When system_server is up and running, schedule the dropbox task to run. + mHandler->scheduleSendBacklogToDropbox(); + + return Status::ok(); +} + diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h new file mode 100644 index 000000000000..d6f33dfb1a86 --- /dev/null +++ b/cmds/incidentd/src/IncidentService.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCIDENT_SERVICE_H +#define INCIDENT_SERVICE_H + +#include "Reporter.h" + +#include <android/os/BnIncidentManager.h> +#include <utils/Looper.h> + +#include <deque> +#include <mutex> + +using namespace android; +using namespace android::base; +using namespace android::binder; +using namespace android::os; +using namespace std; + +// ================================================================================ +class ReportRequestQueue : public virtual RefBase +{ +public: + ReportRequestQueue(); + virtual ~ReportRequestQueue(); + + void addRequest(const sp<ReportRequest>& request); + sp<ReportRequest> getNextRequest(); + +private: + mutex mLock; + deque<sp<ReportRequest> > mQueue; +}; + + +// ================================================================================ +class ReportHandler : public MessageHandler +{ +public: + ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue); + virtual ~ReportHandler(); + + virtual void handleMessage(const Message& message); + + /** + * Adds a ReportRequest to the queue. + */ + void scheduleRunReport(const sp<ReportRequest>& request); + + /** + * Resets mBacklogDelay to the default and schedules sending + * the messages to dropbox. + */ + void scheduleSendBacklogToDropbox(); + +private: + mutex mLock; + nsecs_t mBacklogDelay; + sp<Looper> mHandlerLooper; + sp<ReportRequestQueue> mQueue; + + /** + * Runs all of the reports that have been queued. + */ + void run_report(); + + /** + * Schedules a dropbox task mBacklogDelay nanoseconds from now. + */ + void schedule_send_backlog_to_dropbox_locked(); + + /** + * Sends the backlog to the dropbox service. + */ + void send_backlog_to_dropbox(); +}; + + +// ================================================================================ +class IncidentService : public BnIncidentManager { +public: + IncidentService(const sp<Looper>& handlerLooper); + virtual ~IncidentService(); + + virtual Status reportIncident(const IncidentReportArgs& args); + + virtual Status reportIncidentToStream(const IncidentReportArgs& args, + const sp<IIncidentReportStatusListener>& listener, const unique_fd& stream); + + virtual Status systemRunning(); + +private: + sp<ReportRequestQueue> mQueue; + sp<ReportHandler> mHandler; +}; + + +#endif // INCIDENT_SERVICE_H diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp new file mode 100644 index 000000000000..1ecb291c84a1 --- /dev/null +++ b/cmds/incidentd/src/Reporter.cpp @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "incidentd" + +#include "Reporter.h" +#include "protobuf.h" + +#include "report_directory.h" +#include "section_list.h" + +#include <private/android_filesystem_config.h> +#include <android/os/DropBoxManager.h> +#include <utils/SystemClock.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <fcntl.h> +#include <errno.h> + +/** + * The directory where the incident reports are stored. + */ +static const String8 INCIDENT_DIRECTORY("/data/incidents"); + +static status_t +write_all(int fd, uint8_t const* buf, size_t size) +{ + while (size > 0) { + ssize_t amt = ::write(fd, buf, size); + if (amt < 0) { + return -errno; + } + size -= amt; + buf += amt; + } + return NO_ERROR; +} + +// ================================================================================ +ReportRequest::ReportRequest(const IncidentReportArgs& a, + const sp<IIncidentReportStatusListener> &l, int f) + :args(a), + listener(l), + fd(f), + err(NO_ERROR) +{ +} + +ReportRequest::~ReportRequest() +{ +} + +// ================================================================================ +ReportRequestSet::ReportRequestSet() + :mRequests(), + mWritableCount(0), + mMainFd(-1) +{ +} + +ReportRequestSet::~ReportRequestSet() +{ +} + +void +ReportRequestSet::add(const sp<ReportRequest>& request) +{ + mRequests.push_back(request); + mWritableCount++; +} + +void +ReportRequestSet::setMainFd(int fd) +{ + mMainFd = fd; + mWritableCount++; +} + +status_t +ReportRequestSet::write(uint8_t const* buf, size_t size) +{ + status_t err = EBADF; + + // The streaming ones + int const N = mRequests.size(); + for (int i=N-1; i>=0; i--) { + sp<ReportRequest> request = mRequests[i]; + if (request->fd >= 0 && request->err == NO_ERROR) { + err = write_all(request->fd, buf, size); + if (err != NO_ERROR) { + request->err = err; + mWritableCount--; + } + } + } + + // The dropbox file + if (mMainFd >= 0) { + err = write_all(mMainFd, buf, size); + if (err != NO_ERROR) { + mMainFd = -1; + mWritableCount--; + } + } + + // Return an error only when there are no FDs to write. + return mWritableCount > 0 ? NO_ERROR : err; +} + + +// ================================================================================ +Reporter::Reporter() + :args(), + batch() +{ + char buf[100]; + + // TODO: Make the max size smaller for user builds. + mMaxSize = 100 * 1024 * 1024; + mMaxCount = 100; + + // There can't be two at the same time because it's on one thread. + mStartTime = time(NULL); + strftime(buf, sizeof(buf), "/incident-%Y%m%d-%H%M%S", localtime(&mStartTime)); + mFilename = INCIDENT_DIRECTORY + buf; +} + +Reporter::~Reporter() +{ +} + +Reporter::run_report_status_t +Reporter::runReport() +{ + + status_t err = NO_ERROR; + bool needMainFd = false; + int mainFd = -1; + + // See if we need the main file + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if ((*it)->fd < 0 && mainFd < 0) { + needMainFd = true; + break; + } + } + if (needMainFd) { + // Create the directory + err = create_directory(INCIDENT_DIRECTORY); + if (err != NO_ERROR) { + goto done; + } + + // If there are too many files in the directory (for whatever reason), + // delete the oldest ones until it's under the limit. Doing this first + // does mean that we can go over, so the max size is not a hard limit. + clean_directory(INCIDENT_DIRECTORY, mMaxSize, mMaxCount); + + // Open the file. + err = create_file(&mainFd); + if (err != NO_ERROR) { + goto done; + } + + // Add to the set + batch.setMainFd(mainFd); + } + + // Tell everyone that we're starting. + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if ((*it)->listener != NULL) { + (*it)->listener->onReportStarted(); + } + } + + // Write the incident headers + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + const sp<ReportRequest> request = (*it); + const vector<vector<int8_t>>& headers = request->args.headers(); + + for (vector<vector<int8_t>>::const_iterator buf=headers.begin(); buf!=headers.end(); + buf++) { + int fd = request->fd >= 0 ? request->fd : mainFd; + + uint8_t buffer[20]; + uint8_t* p = write_length_delimited_tag_header(buffer, FIELD_ID_INCIDENT_HEADER, + buf->size()); + write_all(fd, buffer, p-buffer); + + write_all(fd, (uint8_t const*)buf->data(), buf->size()); + // If there was an error now, there will be an error later and we will remove + // it from the list then. + } + } + + // For each of the report fields, see if we need it, and if so, execute the command + // and report to those that care that we're doing it. + for (const Section** section=SECTION_LIST; *section; section++) { + const int id = (*section)->id; + ALOGD("Taking incident report section %d '%s'", id, (*section)->name.string()); + + if (this->args.containsSection(id)) { + // Notify listener of starting + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if ((*it)->listener != NULL && (*it)->args.containsSection(id)) { + (*it)->listener->onReportSectionStatus(id, + IIncidentReportStatusListener::STATUS_STARTING); + } + } + + // Execute - go get the data and write it into the file descriptors. + err = (*section)->Execute(&batch); + if (err != NO_ERROR) { + ALOGW("Incident section %s (%d) failed. Stopping report.", + (*section)->name.string(), id); + goto done; + } + + // Notify listener of starting + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if ((*it)->listener != NULL && (*it)->args.containsSection(id)) { + (*it)->listener->onReportSectionStatus(id, + IIncidentReportStatusListener::STATUS_FINISHED); + } + } + } + } + +done: + // Close the file. + if (mainFd >= 0) { + close(mainFd); + } + + // Tell everyone that we're done. + for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if ((*it)->listener != NULL) { + if (err == NO_ERROR) { + (*it)->listener->onReportFinished(); + } else { + (*it)->listener->onReportFailed(); + } + } + } + + // Put the report into dropbox. + if (needMainFd && err == NO_ERROR) { + sp<DropBoxManager> dropbox = new DropBoxManager(); + Status status = dropbox->addFile(String16("incident"), mFilename, 0); + ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string()); + if (!status.isOk()) { + return REPORT_NEEDS_DROPBOX; + } + + // If the status was ok, delete the file. If not, leave it around until the next + // boot or the next checkin. If the directory gets too big older files will + // be rotated out. + unlink(mFilename.c_str()); + } + + return REPORT_FINISHED; +} + +/** + * Create our output file and set the access permissions to -rw-rw---- + */ +status_t +Reporter::create_file(int* fd) +{ + const char* filename = mFilename.c_str(); + + *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0660); + if (*fd < 0) { + ALOGE("Couldn't open incident file: %s (%s)", filename, strerror(errno)); + return -errno; + } + + // Override umask. Not super critical. If it fails go on with life. + chmod(filename, 0660); + + if (chown(filename, AID_SYSTEM, AID_SYSTEM)) { + ALOGE("Unable to change ownership of incident file %s: %s\n", filename, strerror(errno)); + status_t err = -errno; + unlink(mFilename.c_str()); + return err; + } + + return NO_ERROR; +} + +// ================================================================================ +Reporter::run_report_status_t +Reporter::upload_backlog() +{ + DIR* dir; + struct dirent* entry; + struct stat st; + + if ((dir = opendir(INCIDENT_DIRECTORY.string())) == NULL) { + ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY.string()); + return REPORT_NEEDS_DROPBOX; + } + + String8 dirbase(INCIDENT_DIRECTORY + "/"); + sp<DropBoxManager> dropbox = new DropBoxManager(); + + // Enumerate, count and add up size + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + String8 filename = dirbase + entry->d_name; + if (stat(filename.string(), &st) != 0) { + ALOGE("Unable to stat file %s", filename.string()); + continue; + } + if (!S_ISREG(st.st_mode)) { + continue; + } + + Status status = dropbox->addFile(String16("incident"), filename.string(), 0); + ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string()); + if (!status.isOk()) { + return REPORT_NEEDS_DROPBOX; + } + + // If the status was ok, delete the file. If not, leave it around until the next + // boot or the next checkin. If the directory gets too big older files will + // be rotated out. + unlink(filename.string()); + } + + closedir(dir); + + return REPORT_FINISHED; +} + diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h new file mode 100644 index 000000000000..5b86561520f8 --- /dev/null +++ b/cmds/incidentd/src/Reporter.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef REPORTER_H +#define REPORTER_H + +#include <android/os/IIncidentReportStatusListener.h> +#include <android/os/IncidentReportArgs.h> + +#include <string> +#include <vector> + +#include <time.h> + +using namespace android; +using namespace android::os; +using namespace std; + +// ================================================================================ +struct ReportRequest : public virtual RefBase +{ + IncidentReportArgs args; + sp<IIncidentReportStatusListener> listener; + int fd; + status_t err; + + ReportRequest(const IncidentReportArgs& args, + const sp<IIncidentReportStatusListener> &listener, int fd); + virtual ~ReportRequest(); +}; + +// ================================================================================ +class ReportRequestSet +{ +public: + ReportRequestSet(); + ~ReportRequestSet(); + + void add(const sp<ReportRequest>& request); + void setMainFd(int fd); + + // Write to all of the fds for the requests. If a write fails, it stops + // writing to that fd and returns NO_ERROR. When we are out of fds to write + // to it returns an error. + status_t write(uint8_t const* buf, size_t size); + + typedef vector<sp<ReportRequest>>::iterator iterator; + + iterator begin() { return mRequests.begin(); } + iterator end() { return mRequests.end(); } + +private: + vector<sp<ReportRequest>> mRequests; + int mWritableCount; + int mMainFd; +}; + +// ================================================================================ +class Reporter : public virtual RefBase +{ +public: + enum run_report_status_t { + REPORT_FINISHED = 0, + REPORT_NEEDS_DROPBOX = 1 + }; + + IncidentReportArgs args; + ReportRequestSet batch; + + Reporter(); + virtual ~Reporter(); + + // Run the report as described in the batch and args parameters. + run_report_status_t runReport(); + + static run_report_status_t upload_backlog(); + +private: + string mFilename; + off_t mMaxSize; + size_t mMaxCount; + time_t mStartTime; + + status_t create_file(int* fd); +}; + + +#endif // REPORTER_H diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp new file mode 100644 index 000000000000..fac299ed0dcd --- /dev/null +++ b/cmds/incidentd/src/Section.cpp @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "incidentd" + +#include "Section.h" +#include "protobuf.h" + +#include <binder/IServiceManager.h> +#include <mutex> + +using namespace std; + +const int64_t REMOTE_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds + +// ================================================================================ +Section::Section(int i) + :id(i) +{ +} + +Section::~Section() +{ +} + +status_t +Section::WriteHeader(ReportRequestSet* requests, size_t size) const +{ + ssize_t amt; + uint8_t buf[20]; + uint8_t* p = write_length_delimited_tag_header(buf, this->id, size); + return requests->write(buf, p-buf); +} + +// ================================================================================ +struct WorkerThreadData : public virtual RefBase +{ + const WorkerThreadSection* section; + int fds[2]; + + // Lock protects these fields + mutex lock; + bool workerDone; + status_t workerError; + + WorkerThreadData(const WorkerThreadSection* section); + virtual ~WorkerThreadData(); + + int readFd() { return fds[0]; } + int writeFd() { return fds[1]; } +}; + +WorkerThreadData::WorkerThreadData(const WorkerThreadSection* sec) + :section(sec), + workerDone(false), + workerError(NO_ERROR) +{ + fds[0] = -1; + fds[1] = -1; +} + +WorkerThreadData::~WorkerThreadData() +{ +} + +// ================================================================================ +WorkerThreadSection::WorkerThreadSection(int id) + :Section(id) +{ +} + +WorkerThreadSection::~WorkerThreadSection() +{ +} + +static void* +worker_thread_func(void* cookie) +{ + WorkerThreadData* data = (WorkerThreadData*)cookie; + status_t err = data->section->BlockingCall(data->writeFd()); + + { + unique_lock<mutex> lock(data->lock); + data->workerDone = true; + data->workerError = err; + } + + close(data->writeFd()); + data->decStrong(data->section); + // data might be gone now. don't use it after this point in this thread. + return NULL; +} + +status_t +WorkerThreadSection::Execute(ReportRequestSet* requests) const +{ + status_t err = NO_ERROR; + pthread_t thread; + pthread_attr_t attr; + bool timedOut = false; + FdBuffer buffer; + + // Data shared between this thread and the worker thread. + sp<WorkerThreadData> data = new WorkerThreadData(this); + + // Create the pipe + err = pipe(data->fds); + if (err != 0) { + return -errno; + } + + // The worker thread needs a reference and we can't let the count go to zero + // if that thread is slow to start. + data->incStrong(this); + + // Create the thread + err = pthread_attr_init(&attr); + if (err != 0) { + return -err; + } + // TODO: Do we need to tweak thread priority? + err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (err != 0) { + pthread_attr_destroy(&attr); + return -err; + } + err = pthread_create(&thread, &attr, worker_thread_func, (void*)data.get()); + if (err != 0) { + pthread_attr_destroy(&attr); + return -err; + } + pthread_attr_destroy(&attr); + + // Loop reading until either the timeout or the worker side is done (i.e. eof). + err = buffer.read(data->readFd(), REMOTE_CALL_TIMEOUT_MS); + if (err != NO_ERROR) { + // TODO: Log this error into the incident report. + ALOGW("WorkerThreadSection '%s' reader failed with error '%s'", this->name.string(), + strerror(-err)); + } + + // Done with the read fd. The worker thread closes the write one so + // we never race and get here first. + close(data->readFd()); + + // If the worker side is finished, then return its error (which may overwrite + // our possible error -- but it's more interesting anyway). If not, then we timed out. + { + unique_lock<mutex> lock(data->lock); + if (!data->workerDone) { + // We timed out + timedOut = true; + } else { + if (data->workerError != NO_ERROR) { + err = data->workerError; + // TODO: Log this error into the incident report. + ALOGW("WorkerThreadSection '%s' worker failed with error '%s'", this->name.string(), + strerror(-err)); + } + } + } + + if (timedOut || buffer.timedOut()) { + ALOGW("WorkerThreadSection '%s' timed out", this->name.string()); + return NO_ERROR; + } + + if (buffer.truncated()) { + // TODO: Log this into the incident report. + } + + // TODO: There was an error with the command or buffering. Report that. For now + // just exit with a log messasge. + if (err != NO_ERROR) { + ALOGW("WorkerThreadSection '%s' failed with error '%s'", this->name.string(), + strerror(-err)); + return NO_ERROR; + } + + // Write the data that was collected + ALOGD("section '%s' wrote %zd bytes in %d ms", name.string(), buffer.size(), + (int)buffer.durationMs()); + WriteHeader(requests, buffer.size()); + err = buffer.write(requests); + if (err != NO_ERROR) { + ALOGW("WorkerThreadSection '%s' failed writing: '%s'", this->name.string(), strerror(-err)); + return err; + } + + return NO_ERROR; +} + +// ================================================================================ +CommandSection::CommandSection(int id, const char* first, ...) + :Section(id) +{ + va_list args; + int count = 0; + + va_start(args, first); + while (va_arg(args, const char*) != NULL) { + count++; + } + va_end(args); + + mCommand = (const char**)malloc(sizeof(const char*) * count); + + mCommand[0] = first; + name = first; + name += " "; + va_start(args, first); + for (int i=0; i<count; i++) { + const char* arg = va_arg(args, const char*); + mCommand[i+1] = arg; + if (arg != NULL) { + name += va_arg(args, const char*); + name += " "; + } + } + va_end(args); +} + +CommandSection::~CommandSection() +{ +} + +status_t +CommandSection::Execute(ReportRequestSet* /*requests*/) const +{ + return NO_ERROR; +} + +// ================================================================================ +DumpsysSection::DumpsysSection(int id, const char* service, ...) + :WorkerThreadSection(id), + mService(service) +{ + name = "dumpsys "; + name += service; + + va_list args; + va_start(args, service); + while (true) { + const char* arg = va_arg(args, const char*); + if (arg == NULL) { + break; + } + mArgs.add(String16(arg)); + name += " "; + name += arg; + } + va_end(args); +} + +DumpsysSection::~DumpsysSection() +{ +} + +status_t +DumpsysSection::BlockingCall(int pipeWriteFd) const +{ + // checkService won't wait for the service to show up like getService will. + sp<IBinder> service = defaultServiceManager()->checkService(mService); + + if (service == NULL) { + // Returning an error interrupts the entire incident report, so just + // log the failure. + // TODO: have a meta record inside the report that would log this + // failure inside the report, because the fact that we can't find + // the service is good data in and of itself. This is running in + // another thread so lock that carefully... + ALOGW("DumpsysSection: Can't lookup service: %s", String8(mService).string()); + return NO_ERROR; + } + + service->dump(pipeWriteFd, mArgs); + + return NO_ERROR; +} diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h new file mode 100644 index 000000000000..35740e9771d5 --- /dev/null +++ b/cmds/incidentd/src/Section.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SECTIONS_H +#define SECTIONS_H + +#include "FdBuffer.h" + +#include <utils/String8.h> +#include <utils/String16.h> +#include <utils/Vector.h> + +using namespace android; + +/** + * Base class for sections + */ +class Section +{ +public: + int id; + String8 name; + + Section(int id); + virtual ~Section(); + + virtual status_t Execute(ReportRequestSet* requests) const = 0; + + status_t WriteHeader(ReportRequestSet* requests, size_t size) const; +}; + +/** + * Section that reads in a file. + */ +class FileSection : public Section +{ +public: + FileSection(int id, const char* filename); + virtual ~FileSection(); + + virtual status_t Execute(ReportRequestSet* requests) const; + +private: + const char* mFilename; +}; + +/** + * Base class for sections that call a command that might need a timeout. + */ +class WorkerThreadSection : public Section +{ +public: + WorkerThreadSection(int id); + virtual ~WorkerThreadSection(); + + virtual status_t Execute(ReportRequestSet* requests) const; + + virtual status_t BlockingCall(int pipeWriteFd) const = 0; +}; + +/** + * Section that forks and execs a command, and puts stdout as the section. + */ +class CommandSection : public Section +{ +public: + CommandSection(int id, const char* first, ...); + virtual ~CommandSection(); + + virtual status_t Execute(ReportRequestSet* requests) const; + +private: + const char** mCommand; +}; + +/** + * Section that calls dumpsys on a system service. + */ +class DumpsysSection : public WorkerThreadSection +{ +public: + DumpsysSection(int id, const char* service, ...); + virtual ~DumpsysSection(); + + virtual status_t BlockingCall(int pipeWriteFd) const; + +private: + String16 mService; + Vector<String16> mArgs; +}; + +#endif // SECTIONS_H + diff --git a/cmds/incidentd/src/main.cpp b/cmds/incidentd/src/main.cpp new file mode 100644 index 000000000000..3a7511d43048 --- /dev/null +++ b/cmds/incidentd/src/main.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "incidentd" + +#include "IncidentService.h" + +#include <binder/IInterface.h> +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <binder/ProcessState.h> +#include <binder/Status.h> +#include <cutils/log.h> +#include <utils/Looper.h> +#include <utils/StrongPointer.h> + +#include <sys/types.h> +#include <sys/stat.h> + +using namespace android; + +// ================================================================================ +int +main(int /*argc*/, char** /*argv*/) +{ + // Set up the looper + sp<Looper> looper(Looper::prepare(0 /* opts */)); + + // Set up the binder + sp<ProcessState> ps(ProcessState::self()); + ps->setThreadPoolMaxThreadCount(1); // everything is oneway, let it queue and save ram + ps->startThreadPool(); + ps->giveThreadPoolName(); + IPCThreadState::self()->disableBackgroundScheduling(true); + + // Create the service + android::sp<IncidentService> service = new IncidentService(looper); + if (defaultServiceManager()->addService(String16("incident"), service) != 0) { + ALOGE("Failed to add service"); + return -1; + } + + // Loop forever -- the reports run on this thread in a handler, and the + // binder calls remain responsive in their pool of one thread. + while (true) { + looper->pollAll(-1 /* timeoutMillis */); + } + ALOGW("incidentd escaped from its loop."); + + return 1; +} diff --git a/cmds/incidentd/src/protobuf.cpp b/cmds/incidentd/src/protobuf.cpp new file mode 100644 index 000000000000..cb864fd3b619 --- /dev/null +++ b/cmds/incidentd/src/protobuf.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "protobuf.h" + +uint8_t* +write_raw_varint(uint8_t* buf, uint32_t val) +{ + uint8_t* p = buf; + while (true) { + if ((val & ~0x7F) == 0) { + *p++ = (uint8_t)val; + return p; + } else { + *p++ = (uint8_t)((val & 0x7F) | 0x80); + val >>= 7; + } + } +} + +uint8_t* +write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size) +{ + buf = write_raw_varint(buf, (fieldId << 3) | 2); + buf = write_raw_varint(buf, size); + return buf; +} + diff --git a/cmds/incidentd/src/protobuf.h b/cmds/incidentd/src/protobuf.h new file mode 100644 index 000000000000..a24399832b00 --- /dev/null +++ b/cmds/incidentd/src/protobuf.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PROTOBUF_H +#define PROTOBUF_H + +#include <stdint.h> + +/** + * Write a varint into the buffer. Return the next position to write at. + * There must be 10 bytes in the buffer. The same as EncodedBuffer.writeRawVarint32 + */ +uint8_t* write_raw_varint(uint8_t* buf, uint32_t val); + +/** + * Write a protobuf WIRE_TYPE_LENGTH_DELIMITED header. Return the next position to write at. + * There must be 20 bytes in the buffer. + */ +uint8_t* write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size); + +enum { + // IncidentProto.header + FIELD_ID_INCIDENT_HEADER = 1 +}; + +#endif // PROTOBUF_H + diff --git a/cmds/incidentd/src/report_directory.cpp b/cmds/incidentd/src/report_directory.cpp new file mode 100644 index 000000000000..f60b8ac46cca --- /dev/null +++ b/cmds/incidentd/src/report_directory.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "incidentd" + +#include "report_directory.h" + +#include <cutils/log.h> +#include <private/android_filesystem_config.h> +#include <utils/String8.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <libgen.h> +#include <unistd.h> + +#include <vector> + +using namespace android; +using namespace std; + +status_t +create_directory(const char* directory) +{ + struct stat st; + status_t err = NO_ERROR; + char* dir = strdup(directory); + + // Skip first slash + char* d = dir + 1; + + // Create directories, assigning them to the system user + bool last = false; + while (!last) { + d = strchr(d, '/'); + if (d != NULL) { + *d = '\0'; + } else { + last = true; + } + if (stat(dir, &st) == 0) { + if (!S_ISDIR(st.st_mode)) { + err = ALREADY_EXISTS; + goto done; + } + } else { + if (mkdir(dir, 0770)) { + ALOGE("No incident reports today. " + "Unable to create incident report dir %s: %s", dir, + strerror(errno)); + err = -errno; + goto done; + } + if (chmod(dir, 0770)) { + ALOGE("No incident reports today. " + "Unable to set permissions for incident report dir %s: %s", dir, + strerror(errno)); + err = -errno; + goto done; + } + if (chown(dir, AID_SYSTEM, AID_SYSTEM)) { + ALOGE("No incident reports today. Unable to change ownership of dir %s: %s\n", + dir, strerror(errno)); + err = -errno; + goto done; + } + } + if (!last) { + *d++ = '/'; + } + } + + // Ensure that the final directory is owned by the system with 0770. If it isn't + // we won't write into it. + if (stat(directory, &st) != 0) { + ALOGE("No incident reports today. Can't stat: %s", directory); + err = -errno; + goto done; + } + if ((st.st_mode & 0777) != 0770) { + ALOGE("No incident reports today. Mode is %0o on report directory %s", + st.st_mode, directory); + err = BAD_VALUE; + goto done; + } + if (st.st_uid != AID_SYSTEM || st.st_gid != AID_SYSTEM) { + ALOGE("No incident reports today. Owner is %d and group is %d on report directory %s", + st.st_uid, st.st_gid, directory); + err = BAD_VALUE; + goto done; + } + +done: + free(dir); + return err; +} + +static bool +stat_mtime_cmp(const pair<String8,struct stat>& a, const pair<String8,struct stat>& b) +{ + return a.second.st_mtime < b.second.st_mtime; +} + +void +clean_directory(const char* directory, off_t maxSize, size_t maxCount) +{ + DIR* dir; + struct dirent* entry; + struct stat st; + + vector<pair<String8,struct stat>> files; + + if ((dir = opendir(directory)) == NULL) { + ALOGE("Couldn't open incident directory: %s", directory); + return; + } + + String8 dirbase(String8(directory) + "/"); + + off_t totalSize = 0; + size_t totalCount = 0; + + // Enumerate, count and add up size + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + String8 filename = dirbase + entry->d_name; + if (stat(filename.string(), &st) != 0) { + ALOGE("Unable to stat file %s", filename.string()); + continue; + } + if (!S_ISREG(st.st_mode)) { + continue; + } + files.push_back(pair<String8,struct stat>(filename, st)); + + totalSize += st.st_size; + totalCount++; + } + + closedir(dir); + + // Count or size is less than max, then we're done. + if (totalSize < maxSize && totalCount < maxCount) { + return; + } + + // Oldest files first. + sort(files.begin(), files.end(), stat_mtime_cmp); + + // Remove files until we're under our limits. + for (vector<pair<String8,struct stat>>::iterator it = files.begin(); + it != files.end() && totalSize >= maxSize && totalCount >= maxCount; it++) { + remove(it->first.string()); + totalSize -= it->second.st_size; + totalCount--; + } +} diff --git a/cmds/incidentd/src/report_directory.h b/cmds/incidentd/src/report_directory.h new file mode 100644 index 000000000000..bed4f869cfe4 --- /dev/null +++ b/cmds/incidentd/src/report_directory.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DIRECTORY_CLEANER_H +#define DIRECTORY_CLEANER_H + +#include <utils/Errors.h> + +#include <sys/types.h> + +using namespace android; + +status_t create_directory(const char* directory); +void clean_directory(const char* directory, off_t maxSize, size_t maxCount); + +#endif // DIRECTORY_CLEANER_H diff --git a/cmds/incidentd/src/section_list.cpp b/cmds/incidentd/src/section_list.cpp new file mode 100644 index 000000000000..b6112ed0f7df --- /dev/null +++ b/cmds/incidentd/src/section_list.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "section_list.h" + +//using namespace android::util; + +/** + * This is the mapping of section IDs to the commands that are run to get those commands. + */ +const Section* SECTION_LIST[] = { + new DumpsysSection(3000, + "fingerprint", "--proto", "--incident", NULL), + NULL +}; + diff --git a/cmds/incidentd/src/section_list.h b/cmds/incidentd/src/section_list.h new file mode 100644 index 000000000000..c97751937d6d --- /dev/null +++ b/cmds/incidentd/src/section_list.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SECTION_LIST_H +#define SECTION_LIST_H + +#include "Section.h" + +/** + * This is the mapping of section IDs to the commands that are run to get those commands. + */ +extern const Section* SECTION_LIST[]; + +#endif // SECTION_LIST_H + diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 9c1778010f43..72ccf72d2d05 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -185,6 +185,11 @@ public class Dialog implements DialogInterface, Window.Callback, mWindow = w; w.setCallback(this); w.setOnWindowDismissedCallback(this); + w.setOnWindowSwipeDismissedCallback(() -> { + if (mCancelable) { + cancel(); + } + }); w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 94d24e49945b..db1162a3cbb3 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -51,6 +51,7 @@ import android.util.SparseArray; import android.view.Display; import android.view.DisplayAdjustments; +import dalvik.system.BaseDexClassLoader; import dalvik.system.VMRuntime; import java.io.File; @@ -600,6 +601,40 @@ public final class LoadedApk { VMRuntime.registerAppInfo(profileFile.getPath(), mApplicationInfo.dataDir, codePaths.toArray(new String[codePaths.size()]), foreignDexProfilesFile.getPath()); + + // Setup the reporter to notify package manager of any relevant dex loads. + // At this point the primary apk is loaded and will not be reported. + // Anything loaded from now on will be tracked as a potential secondary + // or foreign dex file. The goal is to enable: + // 1) monitoring and compilation of secondary dex file + // 2) track foreign dex file usage (used to determined the + // compilation filter of apks). + if (BaseDexClassLoader.getReporter() != DexLoadReporter.INSTANCE) { + // Set the dex load reporter if not already set. + // Note that during the app's life cycle different LoadedApks may be + // created and loaded (e.g. if two different apps share the same runtime). + BaseDexClassLoader.setReporter(DexLoadReporter.INSTANCE); + } + } + + private static class DexLoadReporter implements BaseDexClassLoader.Reporter { + private static final DexLoadReporter INSTANCE = new DexLoadReporter(); + + private DexLoadReporter() {} + + @Override + public void report(List<String> dexPaths) { + if (dexPaths.isEmpty()) { + return; + } + String packageName = ActivityThread.currentPackageName(); + try { + ActivityThread.getPackageManager().notifyDexLoad( + packageName, dexPaths, VMRuntime.getRuntime().vmInstructionSet()); + } catch (RemoteException re) { + Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re); + } + } } /** diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index e1bd93eb3a4c..6bddfba163d0 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -96,6 +96,7 @@ import android.os.IHardwarePropertiesManager; import android.os.IPowerManager; import android.os.IRecoverySystem; import android.os.IUserManager; +import android.os.IncidentManager; import android.os.PowerManager; import android.os.Process; import android.os.RecoverySystem; @@ -774,6 +775,13 @@ final class SystemServiceRegistry { return new ContextHubManager(ctx.getOuterContext(), ctx.mMainThread.getHandler().getLooper()); }}); + + registerService(Context.INCIDENT_SERVICE, IncidentManager.class, + new CachedServiceFetcher<IncidentManager>() { + @Override + public IncidentManager createService(ContextImpl ctx) throws ServiceNotFoundException { + return new IncidentManager(ctx); + }}); } /** diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 21dca385a979..617288428916 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -385,7 +385,7 @@ public class DevicePolicyManager { "com.android.server.action.BUGREPORT_SHARING_DECLINED"; /** - * Action: Bugreport has been collected and is dispatched to {@link DevicePolicyManagerService}. + * Action: Bugreport has been collected and is dispatched to {@code DevicePolicyManagerService}. * * @hide */ @@ -1174,7 +1174,7 @@ public class DevicePolicyManager { public @interface UserProvisioningState {} /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_PROFILE}, {@link #ACTION_PROVISION_MANAGED_USER} and @@ -1185,7 +1185,7 @@ public class DevicePolicyManager { public static final int CODE_OK = 0; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the device already has a device @@ -1196,7 +1196,7 @@ public class DevicePolicyManager { public static final int CODE_HAS_DEVICE_OWNER = 1; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the user has a profile owner and for @@ -1207,7 +1207,7 @@ public class DevicePolicyManager { public static final int CODE_USER_HAS_PROFILE_OWNER = 2; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the user isn't running. @@ -1217,7 +1217,7 @@ public class DevicePolicyManager { public static final int CODE_USER_NOT_RUNNING = 3; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} if the device has already been setup and @@ -1242,7 +1242,7 @@ public class DevicePolicyManager { public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} if the user is not a system user. @@ -1252,7 +1252,7 @@ public class DevicePolicyManager { public static final int CODE_NOT_SYSTEM_USER = 7; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} and {@link #ACTION_PROVISION_MANAGED_USER} @@ -1263,7 +1263,7 @@ public class DevicePolicyManager { public static final int CODE_HAS_PAIRED = 8; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} and * {@link #ACTION_PROVISION_MANAGED_USER} on devices which do not support managed users. @@ -1274,7 +1274,7 @@ public class DevicePolicyManager { public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_USER} if the user is a system user. * @@ -1283,7 +1283,7 @@ public class DevicePolicyManager { public static final int CODE_SYSTEM_USER = 10; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the user cannot have more * managed profiles. @@ -1293,7 +1293,7 @@ public class DevicePolicyManager { public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_USER} and * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} on devices not running with split system @@ -1304,7 +1304,7 @@ public class DevicePolicyManager { public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_PROFILE}, {@link #ACTION_PROVISION_MANAGED_USER} and @@ -1316,7 +1316,7 @@ public class DevicePolicyManager { public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; /** - * Result code for {@link checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPreCondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the device the user is a * system user on a split system user device. @@ -1326,7 +1326,17 @@ public class DevicePolicyManager { public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; /** - * Result codes for {@link checkProvisioningPreCondition} indicating all the provisioning pre + * Result code for {@link #checkProvisioningPreCondition}. + * + * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when adding a managed profile is + * disallowed by {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE}. + * + * @hide + */ + public static final int CODE_ADD_MANAGED_PROFILE_DISALLOWED = 15; + + /** + * Result codes for {@link #checkProvisioningPreCondition} indicating all the provisioning pre * conditions. * * @hide @@ -1336,7 +1346,7 @@ public class DevicePolicyManager { CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED, CODE_MANAGED_USERS_NOT_SUPPORTED, CODE_SYSTEM_USER, CODE_CANNOT_ADD_MANAGED_PROFILE, CODE_NOT_SYSTEM_USER_SPLIT, CODE_DEVICE_ADMIN_NOT_SUPPORTED, - CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER}) + CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER, CODE_ADD_MANAGED_PROFILE_DISALLOWED}) public @interface ProvisioningPreCondition {} /** @@ -2303,7 +2313,7 @@ public class DevicePolicyManager { * Determine whether the current password the user has set is sufficient to meet the policy * requirements (e.g. quality, minimum length) that have been requested by the admins of this * user and its participating profiles. Restrictions on profiles that have a separate challenge - * are not taken into account. + * are not taken into account. The user must be unlocked in order to perform the check. * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has @@ -2316,6 +2326,7 @@ public class DevicePolicyManager { * @return Returns true if the password meets the current requirements, else false. * @throws SecurityException if the calling application does not own an active administrator * that uses {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} + * @throws InvalidStateException if the user is not unlocked. */ public boolean isActivePasswordSufficient() { if (mService != null) { @@ -3826,6 +3837,19 @@ public class DevicePolicyManager { /** * @hide */ + public void reportPasswordChanged(@UserIdInt int userId) { + if (mService != null) { + try { + mService.reportPasswordChanged(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * @hide + */ public void reportFailedPasswordAttempt(int userHandle) { if (mService != null) { try { @@ -6193,34 +6217,40 @@ public class DevicePolicyManager { } /** - * Returns if provisioning a managed profile or device is possible or not. + * Returns whether it is possible for the caller to initiate provisioning of a managed profile + * or device, setting itself as the device or profile owner. + * * @param action One of {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_PROFILE}. - * @return if provisioning a managed profile or device is possible or not. + * @return whether provisioning a managed profile or device is possible. * @throws IllegalArgumentException if the supplied action is not valid. */ - public boolean isProvisioningAllowed(String action) { + public boolean isProvisioningAllowed(@NonNull String action) { throwIfParentInstance("isProvisioningAllowed"); try { - return mService.isProvisioningAllowed(action); + return mService.isProvisioningAllowed(action, mContext.getPackageName()); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** - * Checks if provisioning a managed profile or device is possible and returns one of the - * {@link ProvisioningPreCondition}. + * Checks whether it is possible to initiate provisioning a managed device, + * profile or user, setting the given package as owner. * * @param action One of {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_PROFILE}, * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_USER} + * @param packageName The package of the component that would be set as device, user, or profile + * owner. + * @return A {@link ProvisioningPreCondition} value indicating whether provisioning is allowed. * @hide */ - public @ProvisioningPreCondition int checkProvisioningPreCondition(String action) { + public @ProvisioningPreCondition int checkProvisioningPreCondition( + String action, @NonNull String packageName) { try { - return mService.checkProvisioningPreCondition(action); + return mService.checkProvisioningPreCondition(action, packageName); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -6949,8 +6979,8 @@ public class DevicePolicyManager { * @hide * Force update user setup completed status. This API has no effect on user build. * @throws {@link SecurityException} if the caller has no - * {@link android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS} or the caller is - * not {@link UserHandle.SYSTEM_USER} + * {@code android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS} or the caller is + * not {@link UserHandle#SYSTEM_USER} */ public void forceUpdateUserSetupComplete() { try { diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 3e2282550d4d..9be694e8a319 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -123,6 +123,7 @@ interface IDevicePolicyManager { boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle); void setActivePasswordState(in PasswordMetrics metrics, int userHandle); + void reportPasswordChanged(int userId); void reportFailedPasswordAttempt(int userHandle); void reportSuccessfulPasswordAttempt(int userHandle); void reportFailedFingerprintAttempt(int userHandle); @@ -269,8 +270,8 @@ interface IDevicePolicyManager { boolean setPermissionGrantState(in ComponentName admin, String packageName, String permission, int grantState); int getPermissionGrantState(in ComponentName admin, String packageName, String permission); - boolean isProvisioningAllowed(String action); - int checkProvisioningPreCondition(String action); + boolean isProvisioningAllowed(String action, String packageName); + int checkProvisioningPreCondition(String action, String packageName); void setKeepUninstalledPackages(in ComponentName admin,in List<String> packageList); List<String> getKeepUninstalledPackages(in ComponentName admin); boolean isManagedProfile(in ComponentName admin); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index b9783aa1600a..f0f1d99b9f96 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2499,9 +2499,8 @@ public abstract class Context { * for high frequency calls. * </p> * - * @param service Identifies the service to be started. The Intent must be either - * fully explicit (supplying a component name) or specify a specific package - * name it is targetted to. Additional values + * @param service Identifies the service to be started. The Intent must be + * fully explicit (supplying a component name). Additional values * may be included in the Intent extras to supply arguments along with * this specific start call. * @@ -2579,10 +2578,8 @@ public abstract class Context { * {@link #registerReceiver}, since the lifetime of this BroadcastReceiver * is tied to another object (the one that registered it).</p> * - * @param service Identifies the service to connect to. The Intent may - * specify either an explicit component name, or a logical - * description (action, category, etc) to match an - * {@link IntentFilter} published by a service. + * @param service Identifies the service to connect to. The Intent must + * specify an explicit component name. * @param conn Receives information as the service is started and stopped. * This must be a valid ServiceConnection object; it must not be null. * @param flags Operation options for the binding. May be 0, @@ -2747,7 +2744,8 @@ public abstract class Context { //@hide: SOUND_TRIGGER_SERVICE, SHORTCUT_SERVICE, //@hide: CONTEXTHUB_SERVICE, - SYSTEM_HEALTH_SERVICE + SYSTEM_HEALTH_SERVICE, + //@hide: INCIDENT_SERVICE }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -3705,6 +3703,12 @@ public abstract class Context { public static final String DEVICE_IDENTIFIERS_SERVICE = "device_identifiers"; /** + * Service to report a system health "incident" + * @hide + */ + public static final String INCIDENT_SERVICE = "incident"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 16af5e1c45a2..c7984b3d7f9d 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1131,6 +1131,12 @@ public class Intent implements Parcelable, Cloneable { * for compatibility with old applications. If you don't set a ClipData, * it will be copied there for you when calling {@link Context#startActivity(Intent)}. * <p> + * Starting from {@link android.os.Build.VERSION_CODES#O}, if + * {@link #CATEGORY_TYPED_OPENABLE} is passed, then the Uris passed in + * either {@link #EXTRA_STREAM} or via {@link #setClipData(ClipData)} may + * be openable only as asset typed files using + * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}. + * <p> * Optional standard extras, which may be interpreted by some recipients as * appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC}, * {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}. @@ -1169,6 +1175,12 @@ public class Intent implements Parcelable, Cloneable { * for compatibility with old applications. If you don't set a ClipData, * it will be copied there for you when calling {@link Context#startActivity(Intent)}. * <p> + * Starting from {@link android.os.Build.VERSION_CODES#O}, if + * {@link #CATEGORY_TYPED_OPENABLE} is passed, then the Uris passed in + * either {@link #EXTRA_STREAM} or via {@link #setClipData(ClipData)} may + * be openable only as asset typed files using + * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}. + * <p> * Optional standard extras, which may be interpreted by some recipients as * appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC}, * {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}. diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index d753a6eaa1c6..b9b61dba8623 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -458,6 +458,15 @@ interface IPackageManager { void notifyPackageUse(String packageName, int reason); /** + * Notify the package manager that a list of dex files have been loaded. + * + * @param loadingPackageName the name of the package who performs the load + * @param dexPats the list of the dex files paths that have been loaded + * @param loaderIsa the ISA of the loader process + */ + void notifyDexLoad(String loadingPackageName, in List<String> dexPaths, String loaderIsa); + + /** * Ask the package manager to perform dex-opt (if needed) on the given * package if it already hasn't done so. * diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 74aded6edc43..2236291fd248 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -24,7 +24,10 @@ import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.IntRange; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Intent; @@ -58,7 +61,6 @@ import android.util.jar.StrictJarFile; import android.view.Gravity; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -85,7 +87,6 @@ import java.util.zip.ZipEntry; import libcore.io.IoUtils; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; -import static android.content.pm.ActivityInfo.FLAG_IMMERSIVE; import static android.content.pm.ActivityInfo.FLAG_ON_TOP_LAUNCHER; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY; @@ -2103,63 +2104,22 @@ public class PackageParser { sa.recycle(); - if (minCode != null) { - boolean allowedCodename = false; - for (String codename : SDK_CODENAMES) { - if (minCode.equals(codename)) { - allowedCodename = true; - break; - } - } - if (!allowedCodename) { - if (SDK_CODENAMES.length > 0) { - outError[0] = "Requires development platform " + minCode - + " (current platform is any of " - + Arrays.toString(SDK_CODENAMES) + ")"; - } else { - outError[0] = "Requires development platform " + minCode - + " but this is a release platform."; - } - mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; - return null; - } - pkg.applicationInfo.minSdkVersion = - android.os.Build.VERSION_CODES.CUR_DEVELOPMENT; - } else if (minVers > SDK_VERSION) { - outError[0] = "Requires newer sdk version #" + minVers - + " (current version is #" + SDK_VERSION + ")"; + final int minSdkVersion = PackageParser.computeMinSdkVersion(minVers, minCode, + SDK_VERSION, SDK_CODENAMES, outError); + if (minSdkVersion < 0) { mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; return null; - } else { - pkg.applicationInfo.minSdkVersion = minVers; } - if (targetCode != null) { - boolean allowedCodename = false; - for (String codename : SDK_CODENAMES) { - if (targetCode.equals(codename)) { - allowedCodename = true; - break; - } - } - if (!allowedCodename) { - if (SDK_CODENAMES.length > 0) { - outError[0] = "Requires development platform " + targetCode - + " (current platform is any of " - + Arrays.toString(SDK_CODENAMES) + ")"; - } else { - outError[0] = "Requires development platform " + targetCode - + " but this is a release platform."; - } - mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; - return null; - } - // If the code matches, it definitely targets this SDK. - pkg.applicationInfo.targetSdkVersion - = android.os.Build.VERSION_CODES.CUR_DEVELOPMENT; - } else { - pkg.applicationInfo.targetSdkVersion = targetVers; + final int targetSdkVersion = PackageParser.computeTargetSdkVersion(targetVers, + targetCode, SDK_VERSION, SDK_CODENAMES, outError); + if (targetSdkVersion < 0) { + mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; + return null; } + + pkg.applicationInfo.minSdkVersion = minSdkVersion; + pkg.applicationInfo.targetSdkVersion = targetSdkVersion; } XmlUtils.skipCurrentTag(parser); @@ -2407,6 +2367,137 @@ public class PackageParser { return pkg; } + /** + * Computes the targetSdkVersion to use at runtime. If the package is not + * compatible with this platform, populates {@code outError[0]} with an + * error message. + * <p> + * If {@code targetCode} is not specified, e.g. the value is {@code null}, + * then the {@code targetVers} will be returned unmodified. + * <p> + * Otherwise, the behavior varies based on whether the current platform + * is a pre-release version, e.g. the {@code platformSdkCodenames} array + * has length > 0: + * <ul> + * <li>If this is a pre-release platform and the value specified by + * {@code targetCode} is contained within the array of allowed pre-release + * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}. + * <li>If this is a released platform, this method will return -1 to + * indicate that the package is not compatible with this platform. + * </ul> + * + * @param targetVers targetSdkVersion number, if specified in the + * application manifest, or 0 otherwise + * @param targetCode targetSdkVersion code, if specified in the application + * manifest, or {@code null} otherwise + * @param platformSdkVersion platform SDK version number, typically + * Build.VERSION.SDK_INT + * @param platformSdkCodenames array of allowed pre-release SDK codenames + * for this platform + * @param outError output array to populate with error, if applicable + * @return the targetSdkVersion to use at runtime, or -1 if the package is + * not compatible with this platform + * @hide Exposed for unit testing only. + */ + @TestApi + public static int computeTargetSdkVersion(@IntRange(from = 0) int targetVers, + @Nullable String targetCode, @IntRange(from = 1) int platformSdkVersion, + @NonNull String[] platformSdkCodenames, @NonNull String[] outError) { + // If it's a release SDK, return the version number unmodified. + if (targetCode == null) { + return targetVers; + } + + // If it's a pre-release SDK and the codename matches this platform, it + // definitely targets this SDK. + if (ArrayUtils.contains(platformSdkCodenames, targetCode)) { + return Build.VERSION_CODES.CUR_DEVELOPMENT; + } + + // Otherwise, we're looking at an incompatible pre-release SDK. + if (platformSdkCodenames.length > 0) { + outError[0] = "Requires development platform " + targetCode + + " (current platform is any of " + + Arrays.toString(platformSdkCodenames) + ")"; + } else { + outError[0] = "Requires development platform " + targetCode + + " but this is a release platform."; + } + return -1; + } + + /** + * Computes the minSdkVersion to use at runtime. If the package is not + * compatible with this platform, populates {@code outError[0]} with an + * error message. + * <p> + * If {@code minCode} is not specified, e.g. the value is {@code null}, + * then behavior varies based on the {@code platformSdkVersion}: + * <ul> + * <li>If the platform SDK version is greater than or equal to the + * {@code minVers}, returns the {@code mniVers} unmodified. + * <li>Otherwise, returns -1 to indicate that the package is not + * compatible with this platform. + * </ul> + * <p> + * Otherwise, the behavior varies based on whether the current platform + * is a pre-release version, e.g. the {@code platformSdkCodenames} array + * has length > 0: + * <ul> + * <li>If this is a pre-release platform and the value specified by + * {@code targetCode} is contained within the array of allowed pre-release + * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}. + * <li>If this is a released platform, this method will return -1 to + * indicate that the package is not compatible with this platform. + * </ul> + * + * @param minVers minSdkVersion number, if specified in the application + * manifest, or 1 otherwise + * @param minCode minSdkVersion code, if specified in the application + * manifest, or {@code null} otherwise + * @param platformSdkVersion platform SDK version number, typically + * Build.VERSION.SDK_INT + * @param platformSdkCodenames array of allowed prerelease SDK codenames + * for this platform + * @param outError output array to populate with error, if applicable + * @return the minSdkVersion to use at runtime, or -1 if the package is not + * compatible with this platform + * @hide Exposed for unit testing only. + */ + @TestApi + public static int computeMinSdkVersion(@IntRange(from = 1) int minVers, + @Nullable String minCode, @IntRange(from = 1) int platformSdkVersion, + @NonNull String[] platformSdkCodenames, @NonNull String[] outError) { + // If it's a release SDK, make sure we meet the minimum SDK requirement. + if (minCode == null) { + if (minVers <= platformSdkVersion) { + return minVers; + } + + // We don't meet the minimum SDK requirement. + outError[0] = "Requires newer sdk version #" + minVers + + " (current version is #" + platformSdkVersion + ")"; + return -1; + } + + // If it's a pre-release SDK and the codename matches this platform, we + // definitely meet the minimum SDK requirement. + if (ArrayUtils.contains(platformSdkCodenames, minCode)) { + return Build.VERSION_CODES.CUR_DEVELOPMENT; + } + + // Otherwise, we're looking at an incompatible pre-release SDK. + if (platformSdkCodenames.length > 0) { + outError[0] = "Requires development platform " + minCode + + " (current platform is any of " + + Arrays.toString(platformSdkCodenames) + ")"; + } else { + outError[0] = "Requires development platform " + minCode + + " but this is a release platform."; + } + return -1; + } + private FeatureInfo parseUsesFeature(Resources res, AttributeSet attrs) { FeatureInfo fi = new FeatureInfo(); TypedArray sa = res.obtainAttributes(attrs, @@ -3789,6 +3880,8 @@ public class PackageParser { ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE; } + final boolean hasVisibleToEphemeral = + sa.hasValue(R.styleable.AndroidManifestActivity_visibleToEphemeral); final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0); final boolean visibleToEphemeral = isEphemeral || sa.getBoolean(R.styleable.AndroidManifestActivity_visibleToEphemeral, false); @@ -3827,7 +3920,7 @@ public class PackageParser { return null; } intent.setEphemeral(isEphemeral); - intent.setVisibleToEphemeral(visibleToEphemeral); + intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent)); if (intent.countActions() == 0) { Slog.w(TAG, "No actions in intent filter at " + mArchiveSourcePath + " " @@ -3835,6 +3928,10 @@ public class PackageParser { } else { a.intents.add(intent); } + // adjust activity flags when we implicitly expose it via a browsable filter + if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) { + a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL; + } } else if (!receiver && parser.getName().equals("preferred")) { ActivityIntentInfo intent = new ActivityIntentInfo(a); if (!parseIntent(res, parser, false /*allowGlobs*/, false /*allowAutoVerify*/, @@ -3842,7 +3939,7 @@ public class PackageParser { return null; } intent.setEphemeral(isEphemeral); - intent.setVisibleToEphemeral(visibleToEphemeral); + intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent)); if (intent.countActions() == 0) { Slog.w(TAG, "No actions in preferred at " + mArchiveSourcePath + " " @@ -3853,6 +3950,10 @@ public class PackageParser { } owner.preferredActivityFilters.add(intent); } + // adjust activity flags when we implicitly expose it via a browsable filter + if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) { + a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL; + } } else if (parser.getName().equals("meta-data")) { if ((a.metaData = parseMetaData(res, parser, a.metaData, outError)) == null) { @@ -4093,6 +4194,7 @@ public class PackageParser { } } + // TODO add visibleToInstantApp attribute to activity alias final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0); final boolean visibleToEphemeral = isEphemeral || ((a.info.flags & ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL) != 0); @@ -4124,9 +4226,14 @@ public class PackageParser { + parser.getPositionDescription()); } else { intent.setEphemeral(isEphemeral); - intent.setVisibleToEphemeral(visibleToEphemeral); + intent.setVisibleToEphemeral(visibleToEphemeral + || isWebBrowsableIntent(intent)); a.intents.add(intent); } + // adjust activity flags when we implicitly expose it via a browsable filter + if (intent.isVisibleToEphemeral()) { + a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL; + } } else if (parser.getName().equals("meta-data")) { if ((a.metaData=parseMetaData(res, parser, a.metaData, outError)) == null) { @@ -4262,6 +4369,8 @@ public class PackageParser { ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE; } + final boolean hasVisibleToEphemeral = + sa.hasValue(R.styleable.AndroidManifestProvider_visibleToEphemeral); final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0); final boolean visibleToEphemeral = isEphemeral || sa.getBoolean(R.styleable.AndroidManifestProvider_visibleToEphemeral, false); @@ -4291,7 +4400,8 @@ public class PackageParser { } p.info.authority = cpname.intern(); - if (!parseProviderTags(res, parser, isEphemeral, visibleToEphemeral, p, outError)) { + if (!parseProviderTags( + res, parser, isEphemeral, hasVisibleToEphemeral, visibleToEphemeral, p, outError)) { return null; } @@ -4299,8 +4409,9 @@ public class PackageParser { } private boolean parseProviderTags(Resources res, XmlResourceParser parser, - boolean isEphemeral, boolean visibleToEphemeral, Provider outInfo, String[] outError) - throws XmlPullParserException, IOException { + boolean isEphemeral, boolean hasVisibleToEphemeral, boolean visibleToEphemeral, + Provider outInfo, String[] outError) + throws XmlPullParserException, IOException { int outerDepth = parser.getDepth(); int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -4317,8 +4428,12 @@ public class PackageParser { return false; } intent.setEphemeral(isEphemeral); - intent.setVisibleToEphemeral(visibleToEphemeral); + intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent)); outInfo.intents.add(intent); + // adjust provider flags when we implicitly expose it via a browsable filter + if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) { + outInfo.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_EPHEMERAL; + } } else if (parser.getName().equals("meta-data")) { if ((outInfo.metaData=parseMetaData(res, parser, @@ -4565,6 +4680,8 @@ public class PackageParser { ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE; } + final boolean hasVisibleToEphemeral = + sa.hasValue(R.styleable.AndroidManifestService_visibleToEphemeral); final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0); final boolean visibleToEphemeral = isEphemeral || sa.getBoolean(R.styleable.AndroidManifestService_visibleToEphemeral, false); @@ -4600,8 +4717,11 @@ public class PackageParser { return null; } intent.setEphemeral(isEphemeral); - intent.setVisibleToEphemeral(visibleToEphemeral); - + intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent)); + // adjust activity flags when we implicitly expose it via a browsable filter + if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) { + s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_EPHEMERAL; + } s.intents.add(intent); } else if (parser.getName().equals("meta-data")) { if ((s.metaData=parseMetaData(res, parser, s.metaData, @@ -4629,6 +4749,12 @@ public class PackageParser { return s; } + private boolean isWebBrowsableIntent(IntentInfo intent) { + return intent.hasAction(Intent.ACTION_VIEW) + && intent.hasCategory(Intent.CATEGORY_BROWSABLE) + && (intent.hasDataScheme("http") || intent.hasDataScheme("https")); + } + private boolean parseAllMetaData(Resources res, XmlResourceParser parser, String tag, Component<?> outInfo, String[] outError) throws XmlPullParserException, IOException { int outerDepth = parser.getDepth(); diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 56f914e5f3c3..a4d891650c88 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -129,6 +129,10 @@ public final class ShortcutInfo implements Parcelable { | CLONE_REMOVE_RES_NAMES; /** @hide */ + public static final int CLONE_REMOVE_FOR_LAUNCHER_APPROVAL = CLONE_REMOVE_INTENT + | CLONE_REMOVE_RES_NAMES; + + /** @hide */ @IntDef(flag = true, value = { CLONE_REMOVE_ICON, diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 28e392e978ba..d1d5f40739db 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -230,6 +230,13 @@ public class ConnectivityManager { public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL"; /** + * Key for passing a user agent string to the captive portal login activity. + * {@hide} + */ + public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = + "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; + + /** * Broadcast action to indicate the change of data activity status * (idle or active) on a network in a recent period. * The network becomes active when data transmission is started, or diff --git a/core/java/android/net/NetworkRecommendationProvider.java b/core/java/android/net/NetworkRecommendationProvider.java index af5a052c6bf7..16ae867d81e7 100644 --- a/core/java/android/net/NetworkRecommendationProvider.java +++ b/core/java/android/net/NetworkRecommendationProvider.java @@ -75,7 +75,7 @@ public abstract class NetworkRecommendationProvider { * A callback implementing applications should invoke when a {@link RecommendationResult} * is available. */ - public static final class ResultCallback { + public static class ResultCallback { private final IRemoteCallback mCallback; private final int mSequence; private final AtomicBoolean mCallbackRun; diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index 865b8dd1fc46..4e606ef4eede 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -183,7 +183,7 @@ public class NetworkScoreManager { if (app == null) { return null; } - return app.mPackageName; + return app.packageName; } /** @@ -272,19 +272,11 @@ public class NetworkScoreManager { * @hide */ public boolean requestScores(NetworkKey[] networks) throws SecurityException { - String activeScorer = getActiveScorerPackage(); - if (activeScorer == null) { - return false; + try { + return mService.requestScores(networks); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } - Intent intent = new Intent(ACTION_SCORE_NETWORKS); - intent.setPackage(activeScorer); - intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - intent.putExtra(EXTRA_NETWORKS_TO_SCORE, networks); - // A scorer should never become active if its package doesn't hold SCORE_NETWORKS, but - // ensure the package still holds it to be extra safe. - // TODO: http://b/23422763 - mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM, Manifest.permission.SCORE_NETWORKS); - return true; } /** @@ -344,6 +336,8 @@ public class NetworkScoreManager { /** * Request a recommendation for which network to connect to. * + * <p>It is not safe to call this method from the main thread. + * * @param request a {@link RecommendationRequest} instance containing additional * request details * @return a {@link RecommendationResult} instance containing the recommended network diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java index ebb31c9056a9..4282ca75f2b6 100644 --- a/core/java/android/net/NetworkScorerAppManager.java +++ b/core/java/android/net/NetworkScorerAppManager.java @@ -19,160 +19,176 @@ package android.net; import android.Manifest; import android.Manifest.permission; import android.annotation.Nullable; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; - +import com.android.internal.R; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; /** - * Internal class for managing the primary network scorer application. - * - * TODO: Rename this to something more generic. + * Internal class for discovering and managing the network scorer/recommendation application. * * @hide */ public class NetworkScorerAppManager { private static final String TAG = "NetworkScorerAppManager"; - - private static final Intent SCORE_INTENT = - new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS); - + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); private final Context mContext; public NetworkScorerAppManager(Context context) { mContext = context; } + /** + * Holds metadata about a discovered network scorer/recommendation application. + */ public static class NetworkScorerAppData { /** Package name of this scorer app. */ - public final String mPackageName; + public final String packageName; /** UID of the scorer app. */ - public final int mPackageUid; - - /** Name of this scorer app for display. */ - public final CharSequence mScorerName; + public final int packageUid; /** - * Optional class name of a configuration activity. Null if none is set. - * - * @see NetworkScoreManager#ACTION_CUSTOM_ENABLE + * Name of the recommendation service we can bind to. */ - public final String mConfigurationActivityClassName; + public final String recommendationServiceClassName; - /** - * Optional class name of the scoring service we can bind to. Null if none is set. - */ - public final String mScoringServiceClassName; - - public NetworkScorerAppData(String packageName, int packageUid, CharSequence scorerName, - @Nullable String configurationActivityClassName, - @Nullable String scoringServiceClassName) { - mScorerName = scorerName; - mPackageName = packageName; - mPackageUid = packageUid; - mConfigurationActivityClassName = configurationActivityClassName; - mScoringServiceClassName = scoringServiceClassName; + public NetworkScorerAppData(String packageName, int packageUid, + String recommendationServiceClassName) { + this.packageName = packageName; + this.packageUid = packageUid; + this.recommendationServiceClassName = recommendationServiceClassName; } @Override public String toString() { final StringBuilder sb = new StringBuilder("NetworkScorerAppData{"); - sb.append("mPackageName='").append(mPackageName).append('\''); - sb.append(", mPackageUid=").append(mPackageUid); - sb.append(", mScorerName=").append(mScorerName); - sb.append(", mConfigurationActivityClassName='").append(mConfigurationActivityClassName) - .append('\''); - sb.append(", mScoringServiceClassName='").append(mScoringServiceClassName).append('\''); + sb.append("mPackageName='").append(packageName).append('\''); + sb.append(", packageUid=").append(packageUid); + sb.append(", recommendationServiceClassName='") + .append(recommendationServiceClassName).append('\''); sb.append('}'); return sb.toString(); } } /** - * Returns the list of available scorer apps. + * @return A {@link NetworkScorerAppData} instance containing information about the + * best configured network recommendation provider installed or {@code null} + * if none of the configured packages can recommend networks. * - * <p>A network scorer is any application which: + * <p>A network recommendation provider is any application which: * <ul> + * <li>Is listed in the <code>config_networkRecommendationPackageNames</code> config. * <li>Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission. - * <li>Includes a receiver for {@link NetworkScoreManager#ACTION_SCORE_NETWORKS} guarded by the - * {@link android.Manifest.permission#BROADCAST_NETWORK_PRIVILEGED} permission. + * <li>Includes a Service for {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS}. * </ul> - * - * @return the list of scorers, or the empty list if there are no valid scorers. */ - public Collection<NetworkScorerAppData> getAllValidScorers() { - // Network scorer apps can only run as the primary user so exit early if we're not the - // primary user. + public NetworkScorerAppData getNetworkRecommendationProviderData() { + // Network recommendation apps can only run as the primary user right now. + // http://b/23422763 if (UserHandle.getCallingUserId() != UserHandle.USER_SYSTEM) { - return Collections.emptyList(); + return null; } - List<NetworkScorerAppData> scorers = new ArrayList<>(); - PackageManager pm = mContext.getPackageManager(); - // Only apps installed under the primary user of the device can be scorers. - // TODO: http://b/23422763 - List<ResolveInfo> receivers = - pm.queryBroadcastReceiversAsUser(SCORE_INTENT, 0 /* flags */, UserHandle.USER_SYSTEM); - for (ResolveInfo receiver : receivers) { - // This field is a misnomer, see android.content.pm.ResolveInfo#activityInfo - final ActivityInfo receiverInfo = receiver.activityInfo; - if (receiverInfo == null) { - // Should never happen with queryBroadcastReceivers, but invalid nonetheless. - continue; + final List<String> potentialPkgs = getPotentialRecommendationProviderPackages(); + if (potentialPkgs.isEmpty()) { + if (DEBUG) { + Log.d(TAG, "No Network Recommendation Providers specified."); } - if (!permission.BROADCAST_NETWORK_PRIVILEGED.equals(receiverInfo.permission)) { - // Receiver doesn't require the BROADCAST_NETWORK_PRIVILEGED permission, which - // means anyone could trigger network scoring and flood the framework with score - // requests. - continue; + return null; + } + + final PackageManager pm = mContext.getPackageManager(); + for (int i = 0; i < potentialPkgs.size(); i++) { + final String potentialPkg = potentialPkgs.get(i); + + // Look for the recommendation service class and required receiver. + final ResolveInfo resolveServiceInfo = findRecommendationService(potentialPkg); + if (resolveServiceInfo != null) { + return new NetworkScorerAppData(potentialPkg, + resolveServiceInfo.serviceInfo.applicationInfo.uid, + resolveServiceInfo.serviceInfo.name); + } else { + if (DEBUG) { + Log.d(TAG, potentialPkg + " does not have the required components, skipping."); + } } - if (pm.checkPermission(permission.SCORE_NETWORKS, receiverInfo.packageName) != - PackageManager.PERMISSION_GRANTED) { - // Application doesn't hold the SCORE_NETWORKS permission, so the user never - // approved it as a network scorer. - continue; + } + + // None of the configured packages are valid. + return null; + } + + /** + * @return A priority order list of package names that have been granted the + * permission needed for them to act as a network recommendation provider. + * The packages in the returned list may not contain the other required + * network recommendation provider components so additional checks are required + * before making a package the network recommendation provider. + */ + public List<String> getPotentialRecommendationProviderPackages() { + final String[] packageArray = mContext.getResources().getStringArray( + R.array.config_networkRecommendationPackageNames); + if (packageArray == null || packageArray.length == 0) { + if (DEBUG) { + Log.d(TAG, "No Network Recommendation Providers specified."); } + return Collections.emptyList(); + } + + if (VERBOSE) { + Log.d(TAG, "Configured packages: " + TextUtils.join(", ", packageArray)); + } - // Optionally, this package may specify a configuration activity. - String configurationActivityClassName = null; - Intent intent = new Intent(NetworkScoreManager.ACTION_CUSTOM_ENABLE); - intent.setPackage(receiverInfo.packageName); - List<ResolveInfo> configActivities = pm.queryIntentActivities(intent, 0 /* flags */); - if (configActivities != null && !configActivities.isEmpty()) { - ActivityInfo activityInfo = configActivities.get(0).activityInfo; - if (activityInfo != null) { - configurationActivityClassName = activityInfo.name; + List<String> packages = new ArrayList<>(); + final PackageManager pm = mContext.getPackageManager(); + for (String potentialPkg : packageArray) { + if (pm.checkPermission(permission.SCORE_NETWORKS, potentialPkg) + == PackageManager.PERMISSION_GRANTED) { + packages.add(potentialPkg); + } else { + if (DEBUG) { + Log.d(TAG, potentialPkg + " has not been granted " + permission.SCORE_NETWORKS + + ", skipping."); } } + } - // Find the scoring service class we can bind to, if any. - String scoringServiceClassName = null; - Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS); - serviceIntent.setPackage(receiverInfo.packageName); - ResolveInfo resolveServiceInfo = pm.resolveService(serviceIntent, 0 /* flags */); - if (resolveServiceInfo != null && resolveServiceInfo.serviceInfo != null) { - scoringServiceClassName = resolveServiceInfo.serviceInfo.name; - } + return packages; + } - // NOTE: loadLabel will attempt to load the receiver's label and fall back to the - // app label if none is present. - scorers.add(new NetworkScorerAppData(receiverInfo.packageName, - receiverInfo.applicationInfo.uid, receiverInfo.loadLabel(pm), - configurationActivityClassName, scoringServiceClassName)); + private ResolveInfo findRecommendationService(String packageName) { + final PackageManager pm = mContext.getPackageManager(); + final int resolveFlags = 0; + + final Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS); + serviceIntent.setPackage(packageName); + final ResolveInfo resolveServiceInfo = + pm.resolveService(serviceIntent, resolveFlags); + + if (VERBOSE) { + Log.d(TAG, "Resolved " + serviceIntent + " to " + resolveServiceInfo); + } + + if (resolveServiceInfo != null && resolveServiceInfo.serviceInfo != null) { + return resolveServiceInfo; } - return scorers; + if (VERBOSE) { + Log.v(TAG, packageName + " does not have a service for " + serviceIntent); + } + return null; } /** @@ -182,10 +198,15 @@ public class NetworkScorerAppManager { * selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because * it was disabled or uninstalled). */ + @Nullable public NetworkScorerAppData getActiveScorer() { - String scorerPackage = Settings.Global.getString(mContext.getContentResolver(), - Settings.Global.NETWORK_SCORER_APP); - return getScorer(scorerPackage); + if (isNetworkRecommendationsDisabled()) { + // If recommendations are disabled then there can't be an active scorer. + return null; + } + + // Otherwise return the recommendation provider (which may be null). + return getNetworkRecommendationProviderData(); } /** @@ -195,33 +216,13 @@ public class NetworkScorerAppManager { * * @param packageName the packageName of the new scorer to use. If null, scoring will be * disabled. Otherwise, the scorer will only be set if it is a valid scorer application. - * @return true if the scorer was changed, or false if the package is not a valid scorer. + * @return true if the scorer was changed, or false if the package is not a valid scorer or + * a valid network recommendation provider exists. + * @deprecated Scorers are now selected from a configured list. */ + @Deprecated public boolean setActiveScorer(String packageName) { - String oldPackageName = Settings.Global.getString(mContext.getContentResolver(), - Settings.Global.NETWORK_SCORER_APP); - if (TextUtils.equals(oldPackageName, packageName)) { - // No change. - return true; - } - - Log.i(TAG, "Changing network scorer from " + oldPackageName + " to " + packageName); - - if (packageName == null) { - Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.NETWORK_SCORER_APP, null); - return true; - } else { - // We only make the change if the new package is valid. - if (getScorer(packageName) != null) { - Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.NETWORK_SCORER_APP, packageName); - return true; - } else { - Log.w(TAG, "Requested network scorer is not valid: " + packageName); - return false; - } - } + return false; } /** Determine whether the application with the given UID is the enabled scorer. */ @@ -230,7 +231,7 @@ public class NetworkScorerAppManager { if (defaultApp == null) { return false; } - if (callingUid != defaultApp.mPackageUid) { + if (callingUid != defaultApp.packageUid) { return false; } // To be extra safe, ensure the caller holds the SCORE_NETWORKS permission. It always @@ -239,17 +240,9 @@ public class NetworkScorerAppManager { PackageManager.PERMISSION_GRANTED; } - /** Returns the {@link NetworkScorerAppData} for the given app, or null if it's not a scorer. */ - public NetworkScorerAppData getScorer(String packageName) { - if (TextUtils.isEmpty(packageName)) { - return null; - } - Collection<NetworkScorerAppData> applications = getAllValidScorers(); - for (NetworkScorerAppData app : applications) { - if (packageName.equals(app.mPackageName)) { - return app; - } - } - return null; + private boolean isNetworkRecommendationsDisabled() { + final ContentResolver cr = mContext.getContentResolver(); + // A value of 1 indicates enabled. + return Settings.Global.getInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) != 1; } } diff --git a/core/java/android/net/RecommendationRequest.java b/core/java/android/net/RecommendationRequest.java index 05ca1aa437f9..a96f90d57746 100644 --- a/core/java/android/net/RecommendationRequest.java +++ b/core/java/android/net/RecommendationRequest.java @@ -105,7 +105,16 @@ public final class RecommendationRequest implements Parcelable { } protected RecommendationRequest(Parcel in) { - mScanResults = (ScanResult[]) in.readParcelableArray(ScanResult.class.getClassLoader()); + final int resultCount = in.readInt(); + if (resultCount > 0) { + mScanResults = new ScanResult[resultCount]; + for (int i = 0; i < resultCount; i++) { + mScanResults[i] = in.readParcelable(ScanResult.class.getClassLoader()); + } + } else { + mScanResults = null; + } + mCurrentSelectedConfig = in.readParcelable(WifiConfiguration.class.getClassLoader()); mRequiredCapabilities = in.readParcelable(NetworkCapabilities.class.getClassLoader()); } @@ -117,7 +126,14 @@ public final class RecommendationRequest implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelableArray(mScanResults, flags); + if (mScanResults != null) { + dest.writeInt(mScanResults.length); + for (int i = 0; i < mScanResults.length; i++) { + dest.writeParcelable(mScanResults[i], flags); + } + } else { + dest.writeInt(0); + } dest.writeParcelable(mCurrentSelectedConfig, flags); dest.writeParcelable(mRequiredCapabilities, flags); } diff --git a/core/java/android/os/DropBoxManager.aidl b/core/java/android/os/DropBoxManager.aidl index 6474ec20a1d0..241e93bfd9d0 100644 --- a/core/java/android/os/DropBoxManager.aidl +++ b/core/java/android/os/DropBoxManager.aidl @@ -16,4 +16,4 @@ package android.os; -parcelable DropBoxManager.Entry; +parcelable DropBoxManager.Entry cpp_header "android/os/DropBoxManager.h"; diff --git a/core/java/android/os/IIncidentManager.aidl b/core/java/android/os/IIncidentManager.aidl new file mode 100644 index 000000000000..1a76648c2565 --- /dev/null +++ b/core/java/android/os/IIncidentManager.aidl @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.os.IIncidentReportCompletedListener; +import android.os.IIncidentReportStatusListener; +import android.os.IncidentReportArgs; + +/** + * Binder interface to report system health incidents. + * {@hide} + */ +oneway interface IIncidentManager { + + /** + * Takes a report with the given args, reporting status to the optional listener. + * + * When the report is completed, the system report listener will be notified. + */ + void reportIncident(in IncidentReportArgs args); + + /** + * Takes a report with the given args, reporting status to the optional listener. + * + * When the report is completed, the system report listener will be notified. + */ + void reportIncidentToStream(in IncidentReportArgs args, + @nullable IIncidentReportStatusListener listener, + FileDescriptor stream); + + /** + * Tell the incident daemon that the android system server is up and running. + */ + void systemRunning(); +} diff --git a/core/java/android/os/IIncidentReportCompletedListener.aidl b/core/java/android/os/IIncidentReportCompletedListener.aidl new file mode 100644 index 000000000000..2d66bf635a2a --- /dev/null +++ b/core/java/android/os/IIncidentReportCompletedListener.aidl @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +/** + * Listener for incident report status + * + * {@hide} + */ +oneway interface IIncidentReportCompletedListener { + /** + * Called when there has been an incident report. + * + * The system service implementing this method should delete or move the file + * after it is finished with it. + */ + void onIncidentReport(String filename); +} diff --git a/core/java/android/os/IIncidentReportStatusListener.aidl b/core/java/android/os/IIncidentReportStatusListener.aidl new file mode 100644 index 000000000000..7be2ac8eabe2 --- /dev/null +++ b/core/java/android/os/IIncidentReportStatusListener.aidl @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +/** + * Listener for incident report status + * + * {@hide} + */ +oneway interface IIncidentReportStatusListener { + const int STATUS_STARTING = 1; + const int STATUS_FINISHED = 2; + + void onReportStarted(); + + void onReportSectionStatus(int section, int status); + + void onReportFinished(); + void onReportFailed(); +} diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java new file mode 100644 index 000000000000..976d59472e69 --- /dev/null +++ b/core/java/android/os/IncidentManager.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.content.Context; +import android.os.IIncidentManager; +import android.os.ServiceManager; +import android.provider.Settings; +import android.util.Slog; + +/** + * Class to take an incident report. + * + * @hide + */ +@SystemApi +@TestApi +public class IncidentManager { + private static final String TAG = "incident"; + + private Context mContext; + + /** + * @hide + */ + public IncidentManager(Context context) { + mContext = context; + } + + /** + * Take an incident report and put it in dropbox. + */ + public void reportIncident(IncidentReportArgs args) { + final IIncidentManager service = IIncidentManager.Stub.asInterface( + ServiceManager.getService("incident")); + if (service == null) { + Slog.e(TAG, "reportIncident can't find incident binder service"); + return; + } + + try { + service.reportIncident(args); + } catch (RemoteException ex) { + Slog.e(TAG, "reportIncident failed", ex); + } + } + + /** + * Convenience method to trigger an incident report and put it in dropbox. + * <p> + * The fields that are reported will be looked up in the system setting named by + * the settingName parameter. The setting must match one of these patterns: + * The string "disabled": The report will not be taken. + * The string "all": The report will taken with all sections. + * The string "none": The report will taken with no sections, but with the header. + * A comma separated list of field numbers: The report will have these fields. + * <p> + * The header parameter will be added as a header for the incident report. Fill in a + * {@link android.util.proto.ProtoOutputStream ProtoOutputStream}, and then call the + * {@link android.util.proto.ProtoOutputStream#bytes bytes()} method to retrieve + * the encoded data for the header. + */ + public void reportIncident(String settingName, byte[] headerProto) { + // Sections + String setting = Settings.System.getString(mContext.getContentResolver(), settingName); + IncidentReportArgs args; + try { + args = IncidentReportArgs.parseSetting(setting); + } catch (IllegalArgumentException ex) { + Slog.w(TAG, "Bad value for incident report setting '" + settingName + "'", ex); + return; + } + if (args == null) { + Slog.i(TAG, "Incident report requested but disabled: " + settingName); + return; + } + + // Header + args.addHeader(headerProto); + + // Look up the service + final IIncidentManager service = IIncidentManager.Stub.asInterface( + ServiceManager.getService("incident")); + if (service == null) { + Slog.e(TAG, "reportIncident can't find incident binder service"); + return; + } + + // Call the service + Slog.i(TAG, "Taking incident report: " + settingName); + try { + service.reportIncident(args); + } catch (RemoteException ex) { + Slog.e(TAG, "reportIncident failed", ex); + } + } +} + diff --git a/core/java/android/os/IncidentReportArgs.aidl b/core/java/android/os/IncidentReportArgs.aidl new file mode 100644 index 000000000000..bbddf59d9206 --- /dev/null +++ b/core/java/android/os/IncidentReportArgs.aidl @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +parcelable IncidentReportArgs cpp_header "android/os/IncidentReportArgs.h"; + diff --git a/core/java/android/os/IncidentReportArgs.java b/core/java/android/os/IncidentReportArgs.java new file mode 100644 index 000000000000..ce2ae1071d01 --- /dev/null +++ b/core/java/android/os/IncidentReportArgs.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.IntArray; + +import java.util.ArrayList; + +/** + * The arguments for an incident report. + * {@hide} + */ +@SystemApi +@TestApi +public final class IncidentReportArgs implements Parcelable { + + private final IntArray mSections = new IntArray(); + private final ArrayList<byte[]> mHeaders = new ArrayList<byte[]>(); + private boolean mAll; + + /** + * Construct an incident report args with no fields. + */ + public IncidentReportArgs() { + } + + /** + * Construct an incdent report args from the given parcel. + */ + public IncidentReportArgs(Parcel in) { + readFromParcel(in); + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mAll ? 1 : 0); + + int N = mSections.size(); + out.writeInt(N); + for (int i=0; i<N; i++) { + out.writeInt(mSections.get(i)); + } + + N = mHeaders.size(); + out.writeInt(N); + for (int i=0; i<N; i++) { + out.writeByteArray(mHeaders.get(i)); + } + } + + public void readFromParcel(Parcel in) { + mAll = in.readInt() != 0; + + mSections.clear(); + int N = in.readInt(); + for (int i=0; i<N; i++) { + mSections.add(in.readInt()); + } + + mHeaders.clear(); + N = in.readInt(); + for (int i=0; i<N; i++) { + mHeaders.add(in.createByteArray()); + } + } + + public static final Parcelable.Creator<IncidentReportArgs> CREATOR + = new Parcelable.Creator<IncidentReportArgs>() { + public IncidentReportArgs createFromParcel(Parcel in) { + return new IncidentReportArgs(in); + } + + public IncidentReportArgs[] newArray(int size) { + return new IncidentReportArgs[size]; + } + }; + + /** + * Print this report as a string. + */ + public String toString() { + final StringBuilder sb = new StringBuilder("Incident("); + if (mAll) { + sb.append("all"); + } else { + final int N = mSections.size(); + if (N > 0) { + sb.append(mSections.get(0)); + } + for (int i=1; i<N; i++) { + sb.append(" "); + sb.append(mSections.get(i)); + } + } + sb.append(", "); + sb.append(mHeaders.size()); + sb.append(" headers)"); + return sb.toString(); + } + + /** + * Set this incident report to include all fields. + */ + public void setAll(boolean all) { + mAll = all; + if (all) { + mSections.clear(); + } + } + + /** + * Add this section to the incident report. + */ + public void addSection(int section) { + if (!mAll) { + mSections.add(section); + } + } + + /** + * Returns whether the incident report will include all fields. + */ + public boolean isAll() { + return mAll; + } + + /** + * Returns whether this section will be included in the incident report. + */ + public boolean containsSection(int section) { + return mAll || mSections.indexOf(section) >= 0; + } + + public int sectionCount() { + return mSections.size(); + } + + public void addHeader(byte[] header) { + mHeaders.add(header); + } + + /** + * Parses an incident report config as described in the system setting. + * + * @see IncidentManager#reportIncident + */ + public static IncidentReportArgs parseSetting(String setting) + throws IllegalArgumentException { + if (setting == null || setting.length() == 0) { + return null; + } + setting = setting.trim(); + if (setting.length() == 0 || "disabled".equals(setting)) { + return null; + } + + final IncidentReportArgs args = new IncidentReportArgs(); + + if ("all".equals(setting)) { + args.setAll(true); + return args; + } else if ("none".equals(setting)) { + return args; + } + + final String[] splits = setting.split(","); + final int N = splits.length; + for (int i=0; i<N; i++) { + final String str = splits[i].trim(); + if (str.length() == 0) { + continue; + } + int section; + try { + section = Integer.parseInt(str); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("Malformed setting. Bad integer at section" + + " index " + i + ": section='" + str + "' setting='" + setting + "'"); + } + if (section < 1) { + throw new IllegalArgumentException("Malformed setting. Illegal section at" + + " index " + i + ": section='" + str + "' setting='" + setting + "'"); + } + args.addSection(section); + } + + return args; + } +} + diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 4eee8541f3bb..9cd1a4246a58 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -398,6 +398,10 @@ public class Process { * make easily identifyable processes even if you are using the same base * <var>processClass</var> to start them. * + * When invokeWith is not null, the process will be started as a fresh app + * and not a zygote fork. Note that this is only allowed for uid 0 or when + * debugFlags contains DEBUG_ENABLE_DEBUGGER. + * * @param processClass The class to use as the process's main entry * point. * @param niceName A more readable name to use for the process. @@ -410,6 +414,7 @@ public class Process { * @param abi non-null the ABI this app should be started with. * @param instructionSet null-ok the instruction set to use. * @param appDataDir null-ok the data directory of the app. + * @param invokeWith null-ok the command to invoke with. * @param zygoteArgs Additional arguments to supply to the zygote process. * * @return An object that describes the result of the attempt to start the process. @@ -426,10 +431,11 @@ public class Process { String abi, String instructionSet, String appDataDir, + String invokeWith, String[] zygoteArgs) { return zygoteProcess.start(processClass, niceName, uid, gid, gids, debugFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, zygoteArgs); } /** @hide */ @@ -442,10 +448,11 @@ public class Process { String abi, String instructionSet, String appDataDir, + String invokeWith, String[] zygoteArgs) { return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids, debugFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, zygoteArgs); } /** diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index c45fe5a61852..5ac33a1768c2 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -170,6 +170,10 @@ public class ZygoteProcess { * make easily identifyable processes even if you are using the same base * <var>processClass</var> to start them. * + * When invokeWith is not null, the process will be started as a fresh app + * and not a zygote fork. Note that this is only allowed for uid 0 or when + * debugFlags contains DEBUG_ENABLE_DEBUGGER. + * * @param processClass The class to use as the process's main entry * point. * @param niceName A more readable name to use for the process. @@ -182,6 +186,7 @@ public class ZygoteProcess { * @param abi non-null the ABI this app should be started with. * @param instructionSet null-ok the instruction set to use. * @param appDataDir null-ok the data directory of the app. + * @param invokeWith null-ok the command to invoke with. * @param zygoteArgs Additional arguments to supply to the zygote process. * * @return An object that describes the result of the attempt to start the process. @@ -196,11 +201,12 @@ public class ZygoteProcess { String abi, String instructionSet, String appDataDir, + String invokeWith, String[] zygoteArgs) { try { return startViaZygote(processClass, niceName, uid, gid, gids, debugFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); @@ -330,6 +336,7 @@ public class ZygoteProcess { String abi, String instructionSet, String appDataDir, + String invokeWith, String[] extraArgs) throws ZygoteStartFailedEx { ArrayList<String> argsForZygote = new ArrayList<String>(); @@ -407,6 +414,11 @@ public class ZygoteProcess { argsForZygote.add("--app-data-dir=" + appDataDir); } + if (invokeWith != null) { + argsForZygote.add("--invoke-with"); + argsForZygote.add(invokeWith); + } + argsForZygote.add(processClass); if (extraArgs != null) { diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index b0b206566a43..6276af398c43 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -246,7 +246,7 @@ public class StatusBarNotification implements Parcelable { } /** - * Returns a userHandle for the instance of the app that posted this notification. + * Returns a userid for whom this notification is intended. * * @deprecated Use {@link #getUser()} instead. */ diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java index 6196a97024e6..99f6c2a9c58d 100644 --- a/core/java/android/util/EventLog.java +++ b/core/java/android/util/EventLog.java @@ -56,6 +56,7 @@ public class EventLog { /** A previously logged event read from the logs. Instances are thread safe. */ public static final class Event { private final ByteBuffer mBuffer; + private Exception mLastWtf; // Layout of event log entry received from Android logger. // see system/core/include/log/logger.h @@ -116,13 +117,19 @@ public class EventLog { offset = V1_PAYLOAD_START; } mBuffer.limit(offset + mBuffer.getShort(LENGTH_OFFSET)); + if ((offset + DATA_OFFSET) >= mBuffer.limit()) { + // no payload + return null; + } mBuffer.position(offset + DATA_OFFSET); // Just after the tag. return decodeObject(); } catch (IllegalArgumentException e) { Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e); + mLastWtf = e; return null; } catch (BufferUnderflowException e) { Log.wtf(TAG, "Truncated entry payload: tag=" + getTag(), e); + mLastWtf = e; return null; } } @@ -148,6 +155,7 @@ public class EventLog { return new String(mBuffer.array(), start, length, "UTF-8"); } catch (UnsupportedEncodingException e) { Log.wtf(TAG, "UTF-8 is not supported", e); + mLastWtf = e; return null; } @@ -173,6 +181,24 @@ public class EventLog { byte[] bytes = mBuffer.array(); return Arrays.copyOf(bytes, bytes.length); } + + /** + * Retreive the last WTF error generated by this object. + * @hide + */ + //VisibleForTesting + public Exception getLastError() { + return mLastWtf; + } + + /** + * Clear the error state for this object. + * @hide + */ + //VisibleForTesting + public void clearError() { + mLastWtf = null; + } } // We assume that the native methods deal with any concurrency issues. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index c83298b873a2..7340cf712ca0 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -869,10 +869,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ @Override public View focusSearch(View focused, int direction) { - if (isRootNamespace()) { + if (isRootNamespace() + || isKeyboardNavigationCluster() + && (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD)) { // root namespace means we should consider ourselves the top of the // tree for focus searching; otherwise we could be focus searching - // into other tabs. see LocalActivityManager and TabHost for more info + // into other tabs. see LocalActivityManager and TabHost for more info. + // Cluster's root works same way for the forward and backward navigation. return FocusFinder.getInstance().findNextFocus(this, focused, direction); } else if (mParent != null) { return mParent.focusSearch(focused, direction); @@ -1104,6 +1107,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { + if (isKeyboardNavigationCluster() + && (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD) && !hasFocus()) { + // A cluster cannot be focus-entered from outside using forward/backward navigation. + return; + } + final int focusableCount = views.size(); final int descendantFocusability = getDescendantFocusability(); @@ -3026,7 +3035,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final View[] children = mChildren; for (int i = index; i != end; i += increment) { View child = children[i]; - if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE + && !child.isKeyboardNavigationCluster()) { if (child.requestFocus(direction, previouslyFocusedRect)) { return true; } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 7c1bcee6c8ad..8bc988d8de1a 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -274,6 +274,7 @@ public abstract class Window { private TypedArray mWindowStyle; private Callback mCallback; private OnWindowDismissedCallback mOnWindowDismissedCallback; + private OnWindowSwipeDismissedCallback mOnWindowSwipeDismissedCallback; private WindowControllerCallback mWindowControllerCallback; private OnRestrictedCaptionAreaChangedListener mOnRestrictedCaptionAreaChangedListener; private Rect mRestrictedCaptionAreaRect; @@ -587,6 +588,18 @@ public abstract class Window { } /** @hide */ + public interface OnWindowSwipeDismissedCallback { + /** + * Called when a window is swipe dismissed. This informs the callback that the + * window is gone, and it should finish itself. + * @param finishTask True if the task should also be finished. + * @param suppressWindowTransition True if the resulting exit and enter window transition + * animations should be suppressed. + */ + void onWindowSwipeDismissed(); + } + + /** @hide */ public interface WindowControllerCallback { /** * Moves the activity from @@ -880,6 +893,18 @@ public abstract class Window { } /** @hide */ + public final void setOnWindowSwipeDismissedCallback(OnWindowSwipeDismissedCallback sdcb) { + mOnWindowSwipeDismissedCallback = sdcb; + } + + /** @hide */ + public final void dispatchOnWindowSwipeDismissed() { + if (mOnWindowSwipeDismissedCallback != null) { + mOnWindowSwipeDismissedCallback.onWindowSwipeDismissed(); + } + } + + /** @hide */ public final void setWindowControllerCallback(WindowControllerCallback wccb) { mWindowControllerCallback = wccb; } diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java index b9224f36a922..444ebc520cb7 100644 --- a/core/java/android/widget/Chronometer.java +++ b/core/java/android/widget/Chronometer.java @@ -17,8 +17,11 @@ package android.widget; import android.content.Context; -import android.content.res.Resources; import android.content.res.TypedArray; +import android.icu.text.MeasureFormat; +import android.icu.text.MeasureFormat.FormatWidth; +import android.icu.util.Measure; +import android.icu.util.MeasureUnit; import android.os.SystemClock; import android.text.format.DateUtils; import android.util.AttributeSet; @@ -28,6 +31,7 @@ import android.widget.RemoteViews.RemoteView; import com.android.internal.R; +import java.util.ArrayList; import java.util.Formatter; import java.util.IllegalFormatException; import java.util.Locale; @@ -329,9 +333,6 @@ public class Chronometer extends TextView { private static final int MIN_IN_SEC = 60; private static final int HOUR_IN_SEC = MIN_IN_SEC*60; private static String formatDuration(long ms) { - final Resources res = Resources.getSystem(); - final StringBuilder text = new StringBuilder(); - int duration = (int) (ms / DateUtils.SECOND_IN_MILLIS); if (duration < 0) { duration = -duration; @@ -348,31 +349,19 @@ public class Chronometer extends TextView { m = duration / MIN_IN_SEC; duration -= m * MIN_IN_SEC; } - int s = duration; - - try { - if (h > 0) { - text.append(res.getQuantityString( - com.android.internal.R.plurals.duration_hours, h, h)); - } - if (m > 0) { - if (text.length() > 0) { - text.append(' '); - } - text.append(res.getQuantityString( - com.android.internal.R.plurals.duration_minutes, m, m)); - } + final int s = duration; - if (text.length() > 0) { - text.append(' '); - } - text.append(res.getQuantityString( - com.android.internal.R.plurals.duration_seconds, s, s)); - } catch (Resources.NotFoundException e) { - // Ignore; plurals throws an exception for an untranslated quantity for a given locale. - return null; + final ArrayList<Measure> measures = new ArrayList<Measure>(); + if (h > 0) { + measures.add(new Measure(h, MeasureUnit.HOUR)); } - return text.toString(); + if (m > 0) { + measures.add(new Measure(m, MeasureUnit.MINUTE)); + } + measures.add(new Measure(s, MeasureUnit.SECOND)); + + return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE) + .formatMeasures((Measure[]) measures.toArray()); } @Override diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 5fa1d2b42515..46324a3807e7 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -42,6 +42,7 @@ import android.transition.TransitionSet; import android.util.AttributeSet; import android.view.Gravity; import android.view.KeyEvent; +import android.view.KeyboardShortcutGroup; import android.view.MotionEvent; import android.view.View; import android.view.View.OnAttachStateChangeListener; @@ -57,6 +58,7 @@ import android.view.WindowManager.LayoutParams; import com.android.internal.R; import java.lang.ref.WeakReference; +import java.util.List; /** * <p> @@ -139,6 +141,12 @@ public class PopupWindow { private Context mContext; private WindowManager mWindowManager; + /** + * Keeps track of popup's parent's decor view. This is needed to dispatch + * requestKeyboardShortcuts to the owning Activity. + */ + private WeakReference<View> mParentRootView; + private boolean mIsShowing; private boolean mIsTransitioningToDismiss; private boolean mIsDropdown; @@ -1119,6 +1127,7 @@ public class PopupWindow { * @param y the popup's y location offset */ public void showAtLocation(View parent, int gravity, int x, int y) { + mParentRootView = new WeakReference<>(parent.getRootView()); showAtLocation(parent.getWindowToken(), gravity, x, y); } @@ -2225,6 +2234,7 @@ public class PopupWindow { mAnchor = new WeakReference<>(anchor); mAnchorRoot = new WeakReference<>(anchorRoot); mIsAnchorRootAttached = anchorRoot.isAttachedToWindow(); + mParentRootView = mAnchorRoot; mAnchorXoff = xoff; mAnchorYoff = yoff; @@ -2422,6 +2432,16 @@ public class PopupWindow { TransitionManager.endTransitions(PopupDecorView.this); } }; + + @Override + public void requestKeyboardShortcuts(List<KeyboardShortcutGroup> list, int deviceId) { + if (mParentRootView != null) { + View parentRoot = mParentRootView.get(); + if (parentRoot != null) { + parentRoot.requestKeyboardShortcuts(list, deviceId); + } + } + } } private class PopupBackgroundView extends FrameLayout { diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java index 98d87d349de7..eec3cb0be11f 100644 --- a/core/java/com/android/internal/content/PackageHelper.java +++ b/core/java/com/android/internal/content/PackageHelper.java @@ -16,8 +16,6 @@ package com.android.internal.content; -import static android.net.TrafficStats.MB_IN_BYTES; - import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -38,7 +36,7 @@ import android.provider.Settings; import android.util.ArraySet; import android.util.Log; -import libcore.io.IoUtils; +import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.FileOutputStream; @@ -50,6 +48,11 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; +import libcore.io.IoUtils; + +import static android.net.TrafficStats.MB_IN_BYTES; +import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL; + /** * Constants used internally between the PackageManager * and media container service transports. @@ -74,6 +77,8 @@ public class PackageHelper { public static final int APP_INSTALL_INTERNAL = 1; public static final int APP_INSTALL_EXTERNAL = 2; + private static TestableInterface sDefaultTestableInterface = null; + public static IStorageManager getStorageManager() throws RemoteException { IBinder service = ServiceManager.getService("mount"); if (service != null) { @@ -338,6 +343,65 @@ public class PackageHelper { } /** + * A group of external dependencies used in + * {@link #resolveInstallVolume(Context, String, int, long)}. It can be backed by real values + * from the system or mocked ones for testing purposes. + */ + public static abstract class TestableInterface { + abstract public StorageManager getStorageManager(Context context); + abstract public boolean getForceAllowOnExternalSetting(Context context); + abstract public boolean getAllow3rdPartyOnInternalConfig(Context context); + abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName); + abstract public File getDataDirectory(); + + public boolean fitsOnInternalStorage(Context context, long sizeBytes) { + StorageManager storage = getStorageManager(context); + File target = getDataDirectory(); + return (sizeBytes <= storage.getStorageBytesUntilLow(target)); + } + } + + private synchronized static TestableInterface getDefaultTestableInterface() { + if (sDefaultTestableInterface == null) { + sDefaultTestableInterface = new TestableInterface() { + @Override + public StorageManager getStorageManager(Context context) { + return context.getSystemService(StorageManager.class); + } + + @Override + public boolean getForceAllowOnExternalSetting(Context context) { + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0; + } + + @Override + public boolean getAllow3rdPartyOnInternalConfig(Context context) { + return context.getResources().getBoolean( + com.android.internal.R.bool.config_allow3rdPartyAppOnInternal); + } + + @Override + public ApplicationInfo getExistingAppInfo(Context context, String packageName) { + ApplicationInfo existingInfo = null; + try { + existingInfo = context.getPackageManager().getApplicationInfo(packageName, + PackageManager.MATCH_ANY_USER); + } catch (NameNotFoundException ignored) { + } + return existingInfo; + } + + @Override + public File getDataDirectory() { + return Environment.getDataDirectory(); + } + }; + } + return sDefaultTestableInterface; + } + + /** * Given a requested {@link PackageInfo#installLocation} and calculated * install size, pick the actual volume to install the app. Only considers * internal and private volumes, and prefers to keep an existing package on @@ -348,25 +412,44 @@ public class PackageHelper { */ public static String resolveInstallVolume(Context context, String packageName, int installLocation, long sizeBytes) throws IOException { - final boolean forceAllowOnExternal = Settings.Global.getInt( - context.getContentResolver(), Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0; + TestableInterface testableInterface = getDefaultTestableInterface(); + return resolveInstallVolume(context, packageName, + installLocation, sizeBytes, testableInterface); + } + + @VisibleForTesting + public static String resolveInstallVolume(Context context, String packageName, + int installLocation, long sizeBytes, TestableInterface testInterface) + throws IOException { + final boolean forceAllowOnExternal = testInterface.getForceAllowOnExternalSetting(context); + final boolean allow3rdPartyOnInternal = + testInterface.getAllow3rdPartyOnInternalConfig(context); // TODO: handle existing apps installed in ASEC; currently assumes // they'll end up back on internal storage - ApplicationInfo existingInfo = null; - try { - existingInfo = context.getPackageManager().getApplicationInfo(packageName, - PackageManager.MATCH_ANY_USER); - } catch (NameNotFoundException ignored) { - } + ApplicationInfo existingInfo = testInterface.getExistingAppInfo(context, packageName); - final StorageManager storageManager = context.getSystemService(StorageManager.class); - final boolean fitsOnInternal = fitsOnInternal(context, sizeBytes); + final boolean fitsOnInternal = testInterface.fitsOnInternalStorage(context, sizeBytes); + final StorageManager storageManager = + testInterface.getStorageManager(context); + + // System apps always forced to internal storage + if (existingInfo != null && existingInfo.isSystemApp()) { + if (fitsOnInternal) { + return StorageManager.UUID_PRIVATE_INTERNAL; + } else { + throw new IOException("Not enough space on existing volume " + + existingInfo.volumeUuid + " for system app " + packageName + " upgrade"); + } + } + // Now deal with non-system apps. final ArraySet<String> allCandidates = new ArraySet<>(); VolumeInfo bestCandidate = null; long bestCandidateAvailBytes = Long.MIN_VALUE; for (VolumeInfo vol : storageManager.getVolumes()) { - if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) { + boolean isInternalStorage = ID_PRIVATE_INTERNAL.equals(vol.id); + if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable() + && (!isInternalStorage || allow3rdPartyOnInternal)) { final long availBytes = storageManager.getStorageBytesUntilLow(new File(vol.path)); if (availBytes >= sizeBytes) { allCandidates.add(vol.fsUuid); @@ -378,11 +461,6 @@ public class PackageHelper { } } - // System apps always forced to internal storage - if (existingInfo != null && existingInfo.isSystemApp()) { - installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY; - } - // If app expresses strong desire for internal storage, honor it if (!forceAllowOnExternal && installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { @@ -391,6 +469,11 @@ public class PackageHelper { throw new IOException("Cannot automatically move " + packageName + " from " + existingInfo.volumeUuid + " to internal storage"); } + + if (!allow3rdPartyOnInternal) { + throw new IOException("Not allowed to install non-system apps on internal storage"); + } + if (fitsOnInternal) { return StorageManager.UUID_PRIVATE_INTERNAL; } else { @@ -411,14 +494,13 @@ public class PackageHelper { } } - // We're left with either preferring external or auto, so just pick + // We're left with new installations with either preferring external or auto, so just pick // volume with most space if (bestCandidate != null) { return bestCandidate.fsUuid; - } else if (fitsOnInternal) { - return StorageManager.UUID_PRIVATE_INTERNAL; } else { - throw new IOException("No special requests, but no room anywhere"); + throw new IOException("No special requests, but no room on allowed volumes. " + + " allow3rdPartyOnInternal? " + allow3rdPartyOnInternal); } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 99dbb1c50cbf..6a5bbccb48f0 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -2897,8 +2897,22 @@ public class BatteryStatsImpl extends BatteryStats { mHistoryLastWritten.setTo(mHistoryLastLastWritten); } + boolean recordResetDueToOverflow = false; final int dataSize = mHistoryBuffer.dataSize(); - if (dataSize >= MAX_HISTORY_BUFFER) { + if (dataSize >= MAX_MAX_HISTORY_BUFFER*3) { + // Clients can't deal with history buffers this large. This only + // really happens when the device is on charger and interacted with + // for long periods of time, like in retail mode. Since the device is + // most likely charged, when unplugged, stats would have reset anyways. + // Reset the stats and mark that we overflowed. + // b/32540341 + resetAllStatsLocked(); + + // Mark that we want to set *OVERFLOW* event and the RESET:START + // events. + recordResetDueToOverflow = true; + + } else if (dataSize >= MAX_HISTORY_BUFFER) { if (!mHistoryOverflow) { mHistoryOverflow = true; addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur); @@ -2944,9 +2958,12 @@ public class BatteryStatsImpl extends BatteryStats { return; } - if (dataSize == 0) { + if (dataSize == 0 || recordResetDueToOverflow) { // The history is currently empty; we need it to start with a time stamp. cur.currentTime = System.currentTimeMillis(); + if (recordResetDueToOverflow) { + addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_OVERFLOW, cur); + } addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_RESET, cur); } addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur); diff --git a/core/java/com/android/internal/os/IDropBoxManagerService.aidl b/core/java/com/android/internal/os/IDropBoxManagerService.aidl index d067926a096c..d16579c03d7a 100644 --- a/core/java/com/android/internal/os/IDropBoxManagerService.aidl +++ b/core/java/com/android/internal/os/IDropBoxManagerService.aidl @@ -17,7 +17,6 @@ package com.android.internal.os; import android.os.DropBoxManager; -import android.os.ParcelFileDescriptor; /** * "Backend" interface used by {@link android.os.DropBoxManager} to talk to the diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java index d968e3c939ab..a8a55499f5f3 100644 --- a/core/java/com/android/internal/os/WebViewZygoteInit.java +++ b/core/java/com/android/internal/os/WebViewZygoteInit.java @@ -62,6 +62,9 @@ class WebViewZygoteInit { ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader( packagePath, libsPath); + // Add the APK to the Zygote's list of allowed files for children. + Zygote.nativeAllowFileAcrossFork(packagePath); + // Once we have the classloader, look up the WebViewFactoryProvider implementation and // call preloadInZygote() on it to give it the opportunity to preload the native library // and perform any other initialisation work that should be shared among the children. diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index fc0ccb75de95..293de3d71332 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -153,6 +153,11 @@ public final class Zygote { int[][] rlimits, long permittedCapabilities, long effectiveCapabilities); /** + * Lets children of the zygote inherit open file descriptors to this path. + */ + native protected static void nativeAllowFileAcrossFork(String path); + + /** * Zygote unmount storage space on initializing. * This method is called once. */ diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 7edc938c3742..39cb464de56c 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -695,9 +695,11 @@ class ZygoteConnection { throws ZygoteSecurityException { int peerUid = peer.getUid(); - if (args.invokeWith != null && peerUid != 0) { - throw new ZygoteSecurityException("Peer is not permitted to specify " - + "an explicit invoke-with wrapper command"); + if (args.invokeWith != null && peerUid != 0 && + (args.debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) == 0) { + throw new ZygoteSecurityException("Peer is permitted to specify an" + + "explicit invoke-with wrapper command only for debuggable" + + "applications."); } } diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index ec068a32f814..e68ebc4342fd 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -2997,6 +2997,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { swipeDismiss.setOnDismissedListener(new SwipeDismissLayout.OnDismissedListener() { @Override public void onDismissed(SwipeDismissLayout layout) { + dispatchOnWindowSwipeDismissed(); dispatchOnWindowDismissed(false /*finishTask*/, true /*suppressWindowTransition*/); } }); diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 63b700bde76b..2a8077cf3f70 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -590,8 +590,6 @@ public class LockPatternUtils { setCredentialRequiredToDecrypt(false); } - getDevicePolicyManager().setActivePasswordState(new PasswordMetrics(), userHandle); - onAfterChangingPassword(userHandle); } @@ -644,6 +642,7 @@ public class LockPatternUtils { + MIN_LOCK_PATTERN_SIZE + " dots long."); } + setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId); getLockSettings().setLockPattern(patternToString(pattern), savedPattern, userId); DevicePolicyManager dpm = getDevicePolicyManager(); @@ -659,10 +658,6 @@ public class LockPatternUtils { } setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId); - - setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId); - dpm.setActivePasswordState(new PasswordMetrics( - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern.size()), userId); onAfterChangingPassword(userId); } catch (RemoteException re) { Log.e(TAG, "Couldn't save lock pattern " + re); @@ -775,10 +770,9 @@ public class LockPatternUtils { + "of length " + MIN_LOCK_PASSWORD_SIZE); } + final int computedQuality = PasswordMetrics.computeForPassword(password).quality; + setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle); getLockSettings().setLockPassword(password, savedPassword, userHandle); - getLockSettings().setSeparateProfileChallengeEnabled(userHandle, true, null); - final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password); - final int computedQuality = metrics.quality; // Update the device encryption password. if (userHandle == UserHandle.USER_SYSTEM @@ -796,15 +790,6 @@ public class LockPatternUtils { } } - setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle); - if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { - metrics.quality = Math.max(quality, metrics.quality); - dpm.setActivePasswordState(metrics, userHandle); - } else { - // The password is not anything. - dpm.setActivePasswordState(new PasswordMetrics(), userHandle); - } - // Add the password to the password history. We assume all // password hashes have the same length for simplicity of implementation. String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle); diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 35c9ee360e96..35550579243c 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -188,6 +188,7 @@ LOCAL_SRC_FILES:= \ com_android_internal_util_VirtualRefBasePtr.cpp \ com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp \ hwbinder/EphemeralStorage.cpp \ + fd_utils.cpp \ LOCAL_C_INCLUDES += \ $(LOCAL_PATH)/include \ diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 673cf863391c..8f74bf8a4b6f 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -5,7 +5,11 @@ #include "SkPixelRef.h" #include "SkImageEncoder.h" #include "SkImageInfo.h" +#include "SkColor.h" #include "SkColorPriv.h" +#include "SkHalf.h" +#include "SkPM4f.h" +#include "SkPM4fPriv.h" #include "GraphicsJNI.h" #include "SkDither.h" #include "SkUnPreMultiply.h" @@ -232,6 +236,28 @@ using namespace android::bitmap; typedef void (*FromColorProc)(void* dst, const SkColor src[], int width, int x, int y); +static void FromColor_F16(void* dst, const SkColor src[], int width, + int, int) { + uint64_t* d = (uint64_t*)dst; + + for (int i = 0; i < width; i++) { + *d++ = SkColor4f::FromColor(*src++).premul().toF16(); + } +} + +static void FromColor_F16_Raw(void* dst, const SkColor src[], int width, + int, int) { + uint64_t* d = (uint64_t*)dst; + + for (int i = 0; i < width; i++) { + const float* color = SkColor4f::FromColor(*src++).vec(); + uint16_t* scratch = reinterpret_cast<uint16_t*>(d++); + for (int i = 0; i < 4; ++i) { + scratch[i] = SkFloatToHalf(color[i]); + } + } +} + static void FromColor_D32(void* dst, const SkColor src[], int width, int, int) { SkPMColor* d = (SkPMColor*)dst; @@ -321,6 +347,8 @@ static FromColorProc ChooseFromColorProc(const SkBitmap& bitmap) { return FromColor_D565; case kAlpha_8_SkColorType: return FromColor_DA8; + case kRGBA_F16_SkColorType: + return bitmap.alphaType() == kPremul_SkAlphaType ? FromColor_F16 : FromColor_F16_Raw; default: break; } @@ -351,8 +379,7 @@ bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors, int srcOffset, int dstBitmap.notifyPixelsChanged(); - env->ReleaseIntArrayElements(srcColors, const_cast<jint*>(array), - JNI_ABORT); + env->ReleaseIntArrayElements(srcColors, const_cast<jint*>(array), JNI_ABORT); return true; } @@ -361,6 +388,24 @@ bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors, int srcOffset, int typedef void (*ToColorProc)(SkColor dst[], const void* src, int width, SkColorTable*); +static void ToColor_F16_Alpha(SkColor dst[], const void* src, int width, + SkColorTable*) { + SkASSERT(width > 0); + uint64_t* s = (uint64_t*)src; + do { + *dst++ = SkPM4f::FromF16((const uint16_t*) s++).unpremul().toSkColor(); + } while (--width != 0); +} + +static void ToColor_F16_Raw(SkColor dst[], const void* src, int width, + SkColorTable*) { + SkASSERT(width > 0); + uint64_t* s = (uint64_t*)src; + do { + *dst++ = Sk4f_toS32(swizzle_rb(SkHalfToFloat_finite_ftz(*s++))); + } while (--width != 0); +} + static void ToColor_S32_Alpha(SkColor dst[], const void* src, int width, SkColorTable*) { SkASSERT(width > 0); @@ -520,6 +565,17 @@ static ToColorProc ChooseToColorProc(const SkBitmap& src) { } case kAlpha_8_SkColorType: return ToColor_SA8; + case kRGBA_F16_SkColorType: + switch (src.alphaType()) { + case kOpaque_SkAlphaType: + return ToColor_F16_Raw; + case kPremul_SkAlphaType: + return ToColor_F16_Alpha; + case kUnpremul_SkAlphaType: + return ToColor_F16_Raw; + default: + return NULL; + } default: break; } @@ -554,7 +610,7 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, SkBitmap bitmap; bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType, - GraphicsJNI::defaultColorSpace())); + GraphicsJNI::colorSpaceForType(colorType))); sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap, NULL); if (!nativeBitmap) { @@ -562,8 +618,7 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, } if (jColors != NULL) { - GraphicsJNI::SetPixels(env, jColors, offset, stride, - 0, 0, width, height, bitmap); + GraphicsJNI::SetPixels(env, jColors, offset, stride, 0, 0, width, height, bitmap); } return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable)); @@ -573,6 +628,14 @@ static jobject Bitmap_copy(JNIEnv* env, jobject, jlong srcHandle, jint dstConfigHandle, jboolean isMutable) { SkBitmap src; reinterpret_cast<BitmapWrapper*>(srcHandle)->getSkBitmap(&src); + if (dstConfigHandle == GraphicsJNI::hardwareLegacyBitmapConfig()) { + sk_sp<Bitmap> bitmap(Bitmap::allocateHardwareBitmap(src)); + if (!bitmap.get()) { + return NULL; + } + return createBitmap(env, bitmap.release(), kBitmapCreateFlag_None); + } + SkColorType dstCT = GraphicsJNI::legacyBitmapConfigToColorType(dstConfigHandle); SkBitmap result; HeapAllocator allocator; @@ -790,6 +853,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { const int density = p->readInt32(); if (kN32_SkColorType != colorType && + kRGBA_F16_SkColorType != colorType && kRGB_565_SkColorType != colorType && kARGB_4444_SkColorType != colorType && kIndex_8_SkColorType != colorType && @@ -800,8 +864,15 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { std::unique_ptr<SkBitmap> bitmap(new SkBitmap); - if (!bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType, - isSRGB ? SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named) : nullptr), rowBytes)) { + sk_sp<SkColorSpace> colorSpace; + if (kRGBA_F16_SkColorType == colorType) { + colorSpace = SkColorSpace::MakeNamed(SkColorSpace::kSRGBLinear_Named); + } else { + colorSpace = isSRGB ? SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named) : nullptr; + } + + if (!bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType, colorSpace), + rowBytes)) { return NULL; } @@ -1127,8 +1198,7 @@ static void Bitmap_copyPixelsFromBuffer(JNIEnv* env, jobject, } } -static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, - jlong bm1Handle) { +static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, jlong bm1Handle) { SkBitmap bm0; SkBitmap bm1; @@ -1144,11 +1214,11 @@ static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, bitmap0->bitmap().getSkBitmap(&bm0); bitmap1->bitmap().getSkBitmap(&bm1); - if (bm0.width() != bm1.width() || - bm0.height() != bm1.height() || - bm0.colorType() != bm1.colorType() || - bm0.alphaType() != bm1.alphaType() || - bm0.colorSpace() != bm1.colorSpace()) { + if (bm0.width() != bm1.width() + || bm0.height() != bm1.height() + || bm0.colorType() != bm1.colorType() + || bm0.alphaType() != bm1.alphaType() + || !SkColorSpace::Equals(bm0.colorSpace(), bm1.colorSpace())) { return JNI_FALSE; } diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 18ed0ed72190..724fccc2e91b 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -283,8 +283,8 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding // Create the codec. NinePatchPeeker peeker; - std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(streamDeleter.release(), - &peeker)); + std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream( + streamDeleter.release(), &peeker)); if (!codec.get()) { return nullObjectReturn("SkAndroidCodec::NewFromStream returned null"); } @@ -395,11 +395,12 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied); const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), - decodeColorType, alphaType, codec->computeOutputColorSpace(decodeColorType)); + decodeColorType, alphaType); // We always decode to sRGB, but only mark the bitmap with a color space if linear // blending is enabled. - SkImageInfo bitmapInfo = decodeInfo.makeColorSpace(GraphicsJNI::defaultColorSpace()); + SkImageInfo bitmapInfo = decodeInfo.makeColorSpace( + GraphicsJNI::colorSpaceForType(decodeColorType)); if (decodeColorType == kGray_8_SkColorType) { // The legacy implementation of BitmapFactory used kAlpha8 for // grayscale images (before kGray8 existed). While the codec diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index d8984d336746..6f97c600a1cc 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -297,13 +297,16 @@ enum LegacyBitmapConfig { kRGB_565_LegacyBitmapConfig = 3, kARGB_4444_LegacyBitmapConfig = 4, kARGB_8888_LegacyBitmapConfig = 5, - kHardware_LegacyBitmapConfig = 6, + kRGBA_16F_LegacyBitmapConfig = 6, + kHardware_LegacyBitmapConfig = 7, kLastEnum_LegacyBitmapConfig = kHardware_LegacyBitmapConfig }; jint GraphicsJNI::colorTypeToLegacyBitmapConfig(SkColorType colorType) { switch (colorType) { + case kRGBA_F16_SkColorType: + return kRGBA_16F_LegacyBitmapConfig; case kN32_SkColorType: return kARGB_8888_LegacyBitmapConfig; case kARGB_4444_SkColorType: @@ -329,6 +332,7 @@ SkColorType GraphicsJNI::legacyBitmapConfigToColorType(jint legacyConfig) { kRGB_565_SkColorType, kARGB_4444_SkColorType, kN32_SkColorType, + kRGBA_F16_SkColorType, kN32_SkColorType }; @@ -458,6 +462,19 @@ sk_sp<SkColorSpace> GraphicsJNI::defaultColorSpace() { #endif } +sk_sp<SkColorSpace> GraphicsJNI::linearColorSpace() { + return SkColorSpace::MakeNamed(SkColorSpace::kSRGBLinear_Named); +} + +sk_sp<SkColorSpace> GraphicsJNI::colorSpaceForType(SkColorType type) { + switch (type) { + case kRGBA_F16_SkColorType: + return linearColorSpace(); + default: + return defaultColorSpace(); + } +} + /////////////////////////////////////////////////////////////////////////////// bool HeapAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { mStorage = android::Bitmap::allocateHeapBitmap(bitmap, ctable); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index 03dc1fbe82fa..508c9ffd0f20 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -95,6 +95,8 @@ public: const SkBitmap& dstBitmap); static sk_sp<SkColorSpace> defaultColorSpace(); + static sk_sp<SkColorSpace> linearColorSpace(); + static sk_sp<SkColorSpace> colorSpaceForType(SkColorType type); }; class HeapAllocator : public SkBRDAllocator { diff --git a/core/jni/android_view_GraphicBuffer.cpp b/core/jni/android_view_GraphicBuffer.cpp index b6b524597896..f18837f2f72a 100644 --- a/core/jni/android_view_GraphicBuffer.cpp +++ b/core/jni/android_view_GraphicBuffer.cpp @@ -142,6 +142,8 @@ static inline SkColorType convertPixelFormat(int32_t format) { return kN32_SkColorType; case PIXEL_FORMAT_RGBX_8888: return kN32_SkColorType; + case PIXEL_FORMAT_RGBA_FP16: + return kRGBA_F16_SkColorType; case PIXEL_FORMAT_RGB_565: return kRGB_565_SkColorType; default: diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 92693b710424..96e6f819d592 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -168,6 +168,7 @@ PublicFormat android_view_Surface_mapHalFormatDataspaceToPublicFormat( switch(format) { case HAL_PIXEL_FORMAT_RGBA_8888: case HAL_PIXEL_FORMAT_RGBX_8888: + case HAL_PIXEL_FORMAT_RGBA_FP16: case HAL_PIXEL_FORMAT_RGB_888: case HAL_PIXEL_FORMAT_RGB_565: case HAL_PIXEL_FORMAT_Y8: @@ -283,6 +284,7 @@ static inline SkColorType convertPixelFormat(PixelFormat format) { switch (format) { case PIXEL_FORMAT_RGBX_8888: return kN32_SkColorType; case PIXEL_FORMAT_RGBA_8888: return kN32_SkColorType; + case PIXEL_FORMAT_RGBA_FP16: return kRGBA_F16_SkColorType; case PIXEL_FORMAT_RGB_565: return kRGB_565_SkColorType; default: return kUnknown_SkColorType; } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 5b8818171b6f..ed071cd0f45e 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -164,6 +164,11 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, alphaType = kPremul_SkAlphaType; break; } + case PIXEL_FORMAT_RGBA_FP16: { + colorType = kRGBA_F16_SkColorType; + alphaType = kPremul_SkAlphaType; + break; + } case PIXEL_FORMAT_RGB_565: { colorType = kRGB_565_SkColorType; alphaType = kOpaque_SkAlphaType; diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp index 3c8db7fd3c3d..613e0402412a 100644 --- a/core/jni/android_view_TextureView.cpp +++ b/core/jni/android_view_TextureView.cpp @@ -83,6 +83,10 @@ static inline SkImageInfo convertPixelFormat(const ANativeWindow_Buffer& buffer) colorType = kN32_SkColorType; alphaType = kOpaque_SkAlphaType; break; + case WINDOW_FORMAT_RGBA_FP16: + colorType = kRGBA_F16_SkColorType; + alphaType = kPremul_SkAlphaType; + break; case WINDOW_FORMAT_RGB_565: colorType = kRGB_565_SkColorType; alphaType = kOpaque_SkAlphaType; diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index 850c9c6534e9..f8f9efe2804f 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -243,7 +243,7 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr return INSTALL_FAILED_INTERNAL_ERROR; } - *(localFileName + nativeLibPath.size()) = '/'; + *(localTmpFileName + nativeLibPath.size()) = '/'; if (strlcpy(localTmpFileName + nativeLibPath.size(), TMP_FILE_PATTERN, TMP_FILE_PATTERN_LEN - nativeLibPath.size()) != TMP_FILE_PATTERN_LEN) { @@ -344,6 +344,11 @@ public: const char* lastSlash = strrchr(fileName, '/'); ALOG_ASSERT(lastSlash != NULL, "last slash was null somehow for %s\n", fileName); + // Skip directories. + if (*(lastSlash + 1) == 0) { + continue; + } + // Make sure the filename is safe. if (!isFilenameSafe(lastSlash + 1)) { continue; diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index da059e3bfe67..cc7b95894a1f 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -56,7 +56,7 @@ #include "ScopedLocalRef.h" #include "ScopedPrimitiveArray.h" #include "ScopedUtfChars.h" -#include "fd_utils-inl.h" +#include "fd_utils.h" #include "nativebridge/native_bridge.h" @@ -709,6 +709,16 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( return pid; } +static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork( + JNIEnv* env, jclass, jstring path) { + ScopedUtfChars path_native(env, path); + const char* path_cstr = path_native.c_str(); + if (!path_cstr) { + RuntimeAbort(env, __LINE__, "path_cstr == NULL"); + } + FileDescriptorWhitelist::Get()->Allow(path_cstr); +} + static void com_android_internal_os_Zygote_nativeUnmountStorageOnInit(JNIEnv* env, jclass) { // Zygote process unmount root storage space initially before every child processes are forked. // Every forked child processes (include SystemServer) only mount their own root storage space @@ -753,6 +763,8 @@ static const JNINativeMethod gMethods[] = { (void *) com_android_internal_os_Zygote_nativeForkAndSpecialize }, { "nativeForkSystemServer", "(II[II[[IJJ)I", (void *) com_android_internal_os_Zygote_nativeForkSystemServer }, + { "nativeAllowFileAcrossFork", "(Ljava/lang/String;)V", + (void *) com_android_internal_os_Zygote_nativeAllowFileAcrossFork }, { "nativeUnmountStorageOnInit", "()V", (void *) com_android_internal_os_Zygote_nativeUnmountStorageOnInit } }; diff --git a/core/jni/fd_utils-inl.h b/core/jni/fd_utils-inl.h deleted file mode 100644 index e270911de5e7..000000000000 --- a/core/jni/fd_utils-inl.h +++ /dev/null @@ -1,587 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <string> -#include <unordered_map> -#include <set> -#include <vector> -#include <algorithm> - -#include <android-base/strings.h> -#include <dirent.h> -#include <fcntl.h> -#include <grp.h> -#include <inttypes.h> -#include <stdlib.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/un.h> -#include <unistd.h> - -#include <cutils/log.h> -#include "JNIHelp.h" -#include "ScopedPrimitiveArray.h" - -// Whitelist of open paths that the zygote is allowed to keep open. -// -// In addition to the paths listed here, all files ending with -// ".jar" under /system/framework" are whitelisted. See -// FileDescriptorInfo::IsWhitelisted for the canonical definition. -// -// If the whitelisted path is associated with a regular file or a -// character device, the file is reopened after a fork with the same -// offset and mode. If the whilelisted path is associated with a -// AF_UNIX socket, the socket will refer to /dev/null after each -// fork, and all operations on it will fail. -static const char* kPathWhitelist[] = { - "/dev/null", - "/dev/socket/zygote", - "/dev/socket/zygote_secondary", - "/dev/socket/webview_zygote", - "/sys/kernel/debug/tracing/trace_marker", - "/system/framework/framework-res.apk", - "/dev/urandom", - "/dev/ion", - "/dev/dri/renderD129", // Fixes b/31172436 -}; - -static const char* kFdPath = "/proc/self/fd"; - -// Keeps track of all relevant information (flags, offset etc.) of an -// open zygote file descriptor. -class FileDescriptorInfo { - public: - // Create a FileDescriptorInfo for a given file descriptor. Returns - // |NULL| if an error occurred. - static FileDescriptorInfo* createFromFd(int fd) { - struct stat f_stat; - // This should never happen; the zygote should always have the right set - // of permissions required to stat all its open files. - if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { - ALOGE("Unable to stat fd %d : %s", fd, strerror(errno)); - return NULL; - } - - if (S_ISSOCK(f_stat.st_mode)) { - std::string socket_name; - if (!GetSocketName(fd, &socket_name)) { - return NULL; - } - - if (!IsWhitelisted(socket_name)) { - ALOGE("Socket name not whitelisted : %s (fd=%d)", socket_name.c_str(), fd); - return NULL; - } - - return new FileDescriptorInfo(fd); - } - - // We only handle whitelisted regular files and character devices. Whitelisted - // character devices must provide a guarantee of sensible behaviour when - // reopened. - // - // S_ISDIR : Not supported. (We could if we wanted to, but it's unused). - // S_ISLINK : Not supported. - // S_ISBLK : Not supported. - // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate - // with the child process across forks but those should have been closed - // before we got to this point. - if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) { - ALOGE("Unsupported st_mode %d", f_stat.st_mode); - return NULL; - } - - std::string file_path; - if (!Readlink(fd, &file_path)) { - return NULL; - } - - if (!IsWhitelisted(file_path)) { - ALOGE("Not whitelisted : %s", file_path.c_str()); - return NULL; - } - - // File descriptor flags : currently on FD_CLOEXEC. We can set these - // using F_SETFD - we're single threaded at this point of execution so - // there won't be any races. - const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD)); - if (fd_flags == -1) { - ALOGE("Failed fcntl(%d, F_GETFD) : %s", fd, strerror(errno)); - return NULL; - } - - // File status flags : - // - File access mode : (O_RDONLY, O_WRONLY...) we'll pass these through - // to the open() call. - // - // - File creation flags : (O_CREAT, O_EXCL...) - there's not much we can - // do about these, since the file has already been created. We shall ignore - // them here. - // - // - Other flags : We'll have to set these via F_SETFL. On linux, F_SETFL - // can only set O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK. - // In particular, it can't set O_SYNC and O_DSYNC. We'll have to test for - // their presence and pass them in to open(). - int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL)); - if (fs_flags == -1) { - ALOGE("Failed fcntl(%d, F_GETFL) : %s", fd, strerror(errno)); - return NULL; - } - - // File offset : Ignore the offset for non seekable files. - const off_t offset = TEMP_FAILURE_RETRY(lseek64(fd, 0, SEEK_CUR)); - - // We pass the flags that open accepts to open, and use F_SETFL for - // the rest of them. - static const int kOpenFlags = (O_RDONLY | O_WRONLY | O_RDWR | O_DSYNC | O_SYNC); - int open_flags = fs_flags & (kOpenFlags); - fs_flags = fs_flags & (~(kOpenFlags)); - - return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset); - } - - // Checks whether the file descriptor associated with this object - // refers to the same description. - bool Restat() const { - struct stat f_stat; - if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { - return false; - } - - return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev; - } - - bool ReopenOrDetach() const { - if (is_sock) { - return DetachSocket(); - } - - // NOTE: This might happen if the file was unlinked after being opened. - // It's a common pattern in the case of temporary files and the like but - // we should not allow such usage from the zygote. - const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags)); - - if (new_fd == -1) { - ALOGE("Failed open(%s, %d) : %s", file_path.c_str(), open_flags, strerror(errno)); - return false; - } - - if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) { - close(new_fd); - ALOGE("Failed fcntl(%d, F_SETFD, %x) : %s", new_fd, fd_flags, strerror(errno)); - return false; - } - - if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) { - close(new_fd); - ALOGE("Failed fcntl(%d, F_SETFL, %x) : %s", new_fd, fs_flags, strerror(errno)); - return false; - } - - if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) { - close(new_fd); - ALOGE("Failed lseek64(%d, SEEK_SET) : %s", new_fd, strerror(errno)); - return false; - } - - if (TEMP_FAILURE_RETRY(dup2(new_fd, fd)) == -1) { - close(new_fd); - ALOGE("Failed dup2(%d, %d) : %s", fd, new_fd, strerror(errno)); - return false; - } - - close(new_fd); - - return true; - } - - const int fd; - const struct stat stat; - const std::string file_path; - const int open_flags; - const int fd_flags; - const int fs_flags; - const off_t offset; - const bool is_sock; - - private: - FileDescriptorInfo(int fd) : - fd(fd), - stat(), - open_flags(0), - fd_flags(0), - fs_flags(0), - offset(0), - is_sock(true) { - } - - FileDescriptorInfo(struct stat stat, const std::string& file_path, int fd, int open_flags, - int fd_flags, int fs_flags, off_t offset) : - fd(fd), - stat(stat), - file_path(file_path), - open_flags(open_flags), - fd_flags(fd_flags), - fs_flags(fs_flags), - offset(offset), - is_sock(false) { - } - - static bool StartsWith(const std::string& str, const std::string& prefix) { - return str.compare(0, prefix.size(), prefix) == 0; - } - - static bool EndsWith(const std::string& str, const std::string& suffix) { - if (suffix.size() > str.size()) { - return false; - } - - return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; - } - - // Returns true iff. a given path is whitelisted. A path is whitelisted - // if it belongs to the whitelist (see kPathWhitelist) or if it's a path - // under /system/framework that ends with ".jar" or if it is a system - // framework overlay. - static bool IsWhitelisted(const std::string& path) { - for (size_t i = 0; i < (sizeof(kPathWhitelist) / sizeof(kPathWhitelist[0])); ++i) { - if (kPathWhitelist[i] == path) { - return true; - } - } - - static const std::string kFrameworksPrefix = "/system/framework/"; - static const std::string kJarSuffix = ".jar"; - if (StartsWith(path, kFrameworksPrefix) && EndsWith(path, kJarSuffix)) { - return true; - } - - // Whitelist files needed for Runtime Resource Overlay, like these: - // /system/vendor/overlay/framework-res.apk - // /system/vendor/overlay-subdir/pg/framework-res.apk - // /vendor/overlay/framework-res.apk - // /vendor/overlay/PG/android-framework-runtime-resource-overlay.apk - // /data/resource-cache/system@vendor@overlay@framework-res.apk@idmap - // /data/resource-cache/system@vendor@overlay-subdir@pg@framework-res.apk@idmap - // See AssetManager.cpp for more details on overlay-subdir. - static const std::string kOverlayDir = "/system/vendor/overlay/"; - static const std::string kVendorOverlayDir = "/vendor/overlay"; - static const std::string kOverlaySubdir = "/system/vendor/overlay-subdir/"; - static const std::string kApkSuffix = ".apk"; - - if ((StartsWith(path, kOverlayDir) || StartsWith(path, kOverlaySubdir) - || StartsWith(path, kVendorOverlayDir)) - && EndsWith(path, kApkSuffix) - && path.find("/../") == std::string::npos) { - return true; - } - - static const std::string kOverlayIdmapPrefix = "/data/resource-cache/"; - static const std::string kOverlayIdmapSuffix = ".apk@idmap"; - if (StartsWith(path, kOverlayIdmapPrefix) && EndsWith(path, kOverlayIdmapSuffix) - && path.find("/../") == std::string::npos) { - return true; - } - - // All regular files that are placed under this path are whitelisted automatically. - static const std::string kZygoteWhitelistPath = "/vendor/zygote_whitelist/"; - if (StartsWith(path, kZygoteWhitelistPath) && path.find("/../") == std::string::npos) { - return true; - } - - return false; - } - - // TODO: Call android::base::Readlink instead of copying the code here. - static bool Readlink(const int fd, std::string* result) { - char path[64]; - snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); - - // Code copied from android::base::Readlink starts here : - - // Annoyingly, the readlink system call returns EINVAL for a zero-sized buffer, - // and truncates to whatever size you do supply, so it can't be used to query. - // We could call lstat first, but that would introduce a race condition that - // we couldn't detect. - // ext2 and ext4 both have PAGE_SIZE limitations, so we assume that here. - char buf[4096]; - ssize_t len = readlink(path, buf, sizeof(buf)); - if (len == -1) return false; - - result->assign(buf, len); - return true; - } - - // Returns the locally-bound name of the socket |fd|. Returns true - // iff. all of the following hold : - // - // - the socket's sa_family is AF_UNIX. - // - the length of the path is greater than zero (i.e, not an unnamed socket). - // - the first byte of the path isn't zero (i.e, not a socket with an abstract - // address). - static bool GetSocketName(const int fd, std::string* result) { - sockaddr_storage ss; - sockaddr* addr = reinterpret_cast<sockaddr*>(&ss); - socklen_t addr_len = sizeof(ss); - - if (TEMP_FAILURE_RETRY(getsockname(fd, addr, &addr_len)) == -1) { - ALOGE("Failed getsockname(%d) : %s", fd, strerror(errno)); - return false; - } - - if (addr->sa_family != AF_UNIX) { - ALOGE("Unsupported socket (fd=%d) with family %d", fd, addr->sa_family); - return false; - } - - const sockaddr_un* unix_addr = reinterpret_cast<const sockaddr_un*>(&ss); - - size_t path_len = addr_len - offsetof(struct sockaddr_un, sun_path); - // This is an unnamed local socket, we do not accept it. - if (path_len == 0) { - ALOGE("Unsupported AF_UNIX socket (fd=%d) with empty path.", fd); - return false; - } - - // This is a local socket with an abstract address, we do not accept it. - if (unix_addr->sun_path[0] == '\0') { - ALOGE("Unsupported AF_UNIX socket (fd=%d) with abstract address.", fd); - return false; - } - - // If we're here, sun_path must refer to a null terminated filesystem - // pathname (man 7 unix). Remove the terminator before assigning it to an - // std::string. - if (unix_addr->sun_path[path_len - 1] == '\0') { - --path_len; - } - - result->assign(unix_addr->sun_path, path_len); - return true; - } - - bool DetachSocket() const { - const int dev_null_fd = open("/dev/null", O_RDWR); - if (dev_null_fd < 0) { - ALOGE("Failed to open /dev/null : %s", strerror(errno)); - return false; - } - - if (dup2(dev_null_fd, fd) == -1) { - ALOGE("Failed dup2 on socket descriptor %d : %s", fd, strerror(errno)); - return false; - } - - if (close(dev_null_fd) == -1) { - ALOGE("Failed close(%d) : %s", dev_null_fd, strerror(errno)); - return false; - } - - return true; - } - - DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo); -}; - -// A FileDescriptorTable is a collection of FileDescriptorInfo objects -// keyed by their FDs. -class FileDescriptorTable { - public: - // Creates a new FileDescriptorTable. This function scans - // /proc/self/fd for the list of open file descriptors and collects - // information about them. Returns NULL if an error occurs. - static FileDescriptorTable* Create() { - DIR* d = opendir(kFdPath); - if (d == NULL) { - ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno)); - return NULL; - } - int dir_fd = dirfd(d); - dirent* e; - - std::unordered_map<int, FileDescriptorInfo*> open_fd_map; - while ((e = readdir(d)) != NULL) { - const int fd = ParseFd(e, dir_fd); - if (fd == -1) { - continue; - } - - FileDescriptorInfo* info = FileDescriptorInfo::createFromFd(fd); - if (info == NULL) { - if (closedir(d) == -1) { - ALOGE("Unable to close directory : %s", strerror(errno)); - } - return NULL; - } - open_fd_map[fd] = info; - } - - if (closedir(d) == -1) { - ALOGE("Unable to close directory : %s", strerror(errno)); - return NULL; - } - return new FileDescriptorTable(open_fd_map); - } - - bool Restat() { - std::set<int> open_fds; - - // First get the list of open descriptors. - DIR* d = opendir(kFdPath); - if (d == NULL) { - ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno)); - return false; - } - - int dir_fd = dirfd(d); - dirent* e; - while ((e = readdir(d)) != NULL) { - const int fd = ParseFd(e, dir_fd); - if (fd == -1) { - continue; - } - - open_fds.insert(fd); - } - - if (closedir(d) == -1) { - ALOGE("Unable to close directory : %s", strerror(errno)); - return false; - } - - return RestatInternal(open_fds); - } - - // Reopens all file descriptors that are contained in the table. Returns true - // if all descriptors were successfully re-opened or detached, and false if an - // error occurred. - bool ReopenOrDetach() { - std::unordered_map<int, FileDescriptorInfo*>::const_iterator it; - for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) { - const FileDescriptorInfo* info = it->second; - if (info == NULL || !info->ReopenOrDetach()) { - return false; - } - } - - return true; - } - - private: - FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map) - : open_fd_map_(map) { - } - - bool RestatInternal(std::set<int>& open_fds) { - bool error = false; - - // Iterate through the list of file descriptors we've already recorded - // and check whether : - // - // (a) they continue to be open. - // (b) they refer to the same file. - std::unordered_map<int, FileDescriptorInfo*>::iterator it = open_fd_map_.begin(); - while (it != open_fd_map_.end()) { - std::set<int>::const_iterator element = open_fds.find(it->first); - if (element == open_fds.end()) { - // The entry from the file descriptor table is no longer in the list - // of open files. We warn about this condition and remove it from - // the list of FDs under consideration. - // - // TODO(narayan): This will be an error in a future android release. - // error = true; - // ALOGW("Zygote closed file descriptor %d.", it->first); - it = open_fd_map_.erase(it); - } else { - // The entry from the file descriptor table is still open. Restat - // it and check whether it refers to the same file. - const bool same_file = it->second->Restat(); - if (!same_file) { - // The file descriptor refers to a different description. We must - // update our entry in the table. - delete it->second; - it->second = FileDescriptorInfo::createFromFd(*element); - if (it->second == NULL) { - // The descriptor no longer no longer refers to a whitelisted file. - // We flag an error and remove it from the list of files we're - // tracking. - error = true; - it = open_fd_map_.erase(it); - } else { - // Successfully restatted the file, move on to the next open FD. - ++it; - } - } else { - // It's the same file. Nothing to do here. Move on to the next open - // FD. - ++it; - } - - // Finally, remove the FD from the set of open_fds. We do this last because - // |element| will not remain valid after a call to erase. - open_fds.erase(element); - } - } - - if (open_fds.size() > 0) { - // The zygote has opened new file descriptors since our last inspection. - // We warn about this condition and add them to our table. - // - // TODO(narayan): This will be an error in a future android release. - // error = true; - // ALOGW("Zygote opened %zd new file descriptor(s).", open_fds.size()); - - // TODO(narayan): This code will be removed in a future android release. - std::set<int>::const_iterator it; - for (it = open_fds.begin(); it != open_fds.end(); ++it) { - const int fd = (*it); - FileDescriptorInfo* info = FileDescriptorInfo::createFromFd(fd); - if (info == NULL) { - // A newly opened file is not on the whitelist. Flag an error and - // continue. - error = true; - } else { - // Track the newly opened file. - open_fd_map_[fd] = info; - } - } - } - - return !error; - } - - static int ParseFd(dirent* e, int dir_fd) { - char* end; - const int fd = strtol(e->d_name, &end, 10); - if ((*end) != '\0') { - return -1; - } - - // Don't bother with the standard input/output/error, they're handled - // specially post-fork anyway. - if (fd <= STDERR_FILENO || fd == dir_fd) { - return -1; - } - - return fd; - } - - // Invariant: All values in this unordered_map are non-NULL. - std::unordered_map<int, FileDescriptorInfo*> open_fd_map_; - - DISALLOW_COPY_AND_ASSIGN(FileDescriptorTable); -}; diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp new file mode 100644 index 000000000000..969d336f3cad --- /dev/null +++ b/core/jni/fd_utils.cpp @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fd_utils.h" + +#include <algorithm> + +#include <fcntl.h> +#include <grp.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/un.h> +#include <unistd.h> + +#include <android-base/strings.h> +#include <cutils/log.h> + +// Static whitelist of open paths that the zygote is allowed to keep open. +static const char* kPathWhitelist[] = { + "/dev/null", + "/dev/socket/zygote", + "/dev/socket/zygote_secondary", + "/dev/socket/webview_zygote", + "/sys/kernel/debug/tracing/trace_marker", + "/system/framework/framework-res.apk", + "/dev/urandom", + "/dev/ion", + "/dev/dri/renderD129", // Fixes b/31172436 +}; + +static const char kFdPath[] = "/proc/self/fd"; + +// static +FileDescriptorWhitelist* FileDescriptorWhitelist::Get() { + if (instance_ == nullptr) { + instance_ = new FileDescriptorWhitelist(); + } + return instance_; +} + +bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { + // Check the static whitelist path. + for (const auto& whitelist_path : kPathWhitelist) { + if (path == whitelist_path) + return true; + } + + // Check any paths added to the dynamic whitelist. + for (const auto& whitelist_path : whitelist_) { + if (path == whitelist_path) + return true; + } + + static const std::string kFrameworksPrefix = "/system/framework/"; + static const std::string kJarSuffix = ".jar"; + if (StartsWith(path, kFrameworksPrefix) && EndsWith(path, kJarSuffix)) { + return true; + } + + // Whitelist files needed for Runtime Resource Overlay, like these: + // /system/vendor/overlay/framework-res.apk + // /system/vendor/overlay-subdir/pg/framework-res.apk + // /vendor/overlay/framework-res.apk + // /vendor/overlay/PG/android-framework-runtime-resource-overlay.apk + // /data/resource-cache/system@vendor@overlay@framework-res.apk@idmap + // /data/resource-cache/system@vendor@overlay-subdir@pg@framework-res.apk@idmap + // See AssetManager.cpp for more details on overlay-subdir. + static const std::string kOverlayDir = "/system/vendor/overlay/"; + static const std::string kVendorOverlayDir = "/vendor/overlay"; + static const std::string kOverlaySubdir = "/system/vendor/overlay-subdir/"; + static const std::string kApkSuffix = ".apk"; + + if ((StartsWith(path, kOverlayDir) || StartsWith(path, kOverlaySubdir) + || StartsWith(path, kVendorOverlayDir)) + && EndsWith(path, kApkSuffix) + && path.find("/../") == std::string::npos) { + return true; + } + + static const std::string kOverlayIdmapPrefix = "/data/resource-cache/"; + static const std::string kOverlayIdmapSuffix = ".apk@idmap"; + if (StartsWith(path, kOverlayIdmapPrefix) && EndsWith(path, kOverlayIdmapSuffix) + && path.find("/../") == std::string::npos) { + return true; + } + + // All regular files that are placed under this path are whitelisted automatically. + static const std::string kZygoteWhitelistPath = "/vendor/zygote_whitelist/"; + if (StartsWith(path, kZygoteWhitelistPath) && path.find("/../") == std::string::npos) { + return true; + } + + return false; +} + +FileDescriptorWhitelist::FileDescriptorWhitelist() + : whitelist_() { +} + +// TODO: Call android::base::StartsWith instead of copying the code here. +// static +bool FileDescriptorWhitelist::StartsWith(const std::string& str, + const std::string& prefix) { + return str.compare(0, prefix.size(), prefix) == 0; +} + +// TODO: Call android::base::EndsWith instead of copying the code here. +// static +bool FileDescriptorWhitelist::EndsWith(const std::string& str, + const std::string& suffix) { + if (suffix.size() > str.size()) { + return false; + } + + return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +FileDescriptorWhitelist* FileDescriptorWhitelist::instance_ = nullptr; + +// static +FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd) { + struct stat f_stat; + // This should never happen; the zygote should always have the right set + // of permissions required to stat all its open files. + if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { + ALOGE("Unable to stat fd %d : %s", fd, strerror(errno)); + return NULL; + } + + const FileDescriptorWhitelist* whitelist = FileDescriptorWhitelist::Get(); + + if (S_ISSOCK(f_stat.st_mode)) { + std::string socket_name; + if (!GetSocketName(fd, &socket_name)) { + return NULL; + } + + if (!whitelist->IsAllowed(socket_name)) { + ALOGE("Socket name not whitelisted : %s (fd=%d)", socket_name.c_str(), fd); + return NULL; + } + + return new FileDescriptorInfo(fd); + } + + // We only handle whitelisted regular files and character devices. Whitelisted + // character devices must provide a guarantee of sensible behaviour when + // reopened. + // + // S_ISDIR : Not supported. (We could if we wanted to, but it's unused). + // S_ISLINK : Not supported. + // S_ISBLK : Not supported. + // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate + // with the child process across forks but those should have been closed + // before we got to this point. + if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) { + ALOGE("Unsupported st_mode %d", f_stat.st_mode); + return NULL; + } + + std::string file_path; + if (!Readlink(fd, &file_path)) { + return NULL; + } + + if (!whitelist->IsAllowed(file_path)) { + ALOGE("Not whitelisted : %s", file_path.c_str()); + return NULL; + } + + // File descriptor flags : currently on FD_CLOEXEC. We can set these + // using F_SETFD - we're single threaded at this point of execution so + // there won't be any races. + const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD)); + if (fd_flags == -1) { + ALOGE("Failed fcntl(%d, F_GETFD) : %s", fd, strerror(errno)); + return NULL; + } + + // File status flags : + // - File access mode : (O_RDONLY, O_WRONLY...) we'll pass these through + // to the open() call. + // + // - File creation flags : (O_CREAT, O_EXCL...) - there's not much we can + // do about these, since the file has already been created. We shall ignore + // them here. + // + // - Other flags : We'll have to set these via F_SETFL. On linux, F_SETFL + // can only set O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK. + // In particular, it can't set O_SYNC and O_DSYNC. We'll have to test for + // their presence and pass them in to open(). + int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL)); + if (fs_flags == -1) { + ALOGE("Failed fcntl(%d, F_GETFL) : %s", fd, strerror(errno)); + return NULL; + } + + // File offset : Ignore the offset for non seekable files. + const off_t offset = TEMP_FAILURE_RETRY(lseek64(fd, 0, SEEK_CUR)); + + // We pass the flags that open accepts to open, and use F_SETFL for + // the rest of them. + static const int kOpenFlags = (O_RDONLY | O_WRONLY | O_RDWR | O_DSYNC | O_SYNC); + int open_flags = fs_flags & (kOpenFlags); + fs_flags = fs_flags & (~(kOpenFlags)); + + return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset); +} + +bool FileDescriptorInfo::Restat() const { + struct stat f_stat; + if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { + return false; + } + + return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev; +} + +bool FileDescriptorInfo::ReopenOrDetach() const { + if (is_sock) { + return DetachSocket(); + } + + // NOTE: This might happen if the file was unlinked after being opened. + // It's a common pattern in the case of temporary files and the like but + // we should not allow such usage from the zygote. + const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags)); + + if (new_fd == -1) { + ALOGE("Failed open(%s, %d) : %s", file_path.c_str(), open_flags, strerror(errno)); + return false; + } + + if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) { + close(new_fd); + ALOGE("Failed fcntl(%d, F_SETFD, %x) : %s", new_fd, fd_flags, strerror(errno)); + return false; + } + + if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) { + close(new_fd); + ALOGE("Failed fcntl(%d, F_SETFL, %x) : %s", new_fd, fs_flags, strerror(errno)); + return false; + } + + if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) { + close(new_fd); + ALOGE("Failed lseek64(%d, SEEK_SET) : %s", new_fd, strerror(errno)); + return false; + } + + if (TEMP_FAILURE_RETRY(dup2(new_fd, fd)) == -1) { + close(new_fd); + ALOGE("Failed dup2(%d, %d) : %s", fd, new_fd, strerror(errno)); + return false; + } + + close(new_fd); + + return true; +} + +FileDescriptorInfo::FileDescriptorInfo(int fd) : + fd(fd), + stat(), + open_flags(0), + fd_flags(0), + fs_flags(0), + offset(0), + is_sock(true) { +} + +FileDescriptorInfo::FileDescriptorInfo(struct stat stat, const std::string& file_path, + int fd, int open_flags, int fd_flags, int fs_flags, + off_t offset) : + fd(fd), + stat(stat), + file_path(file_path), + open_flags(open_flags), + fd_flags(fd_flags), + fs_flags(fs_flags), + offset(offset), + is_sock(false) { +} + +// TODO: Call android::base::Readlink instead of copying the code here. +// static +bool FileDescriptorInfo::Readlink(const int fd, std::string* result) { + char path[64]; + snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); + + // Code copied from android::base::Readlink starts here : + + // Annoyingly, the readlink system call returns EINVAL for a zero-sized buffer, + // and truncates to whatever size you do supply, so it can't be used to query. + // We could call lstat first, but that would introduce a race condition that + // we couldn't detect. + // ext2 and ext4 both have PAGE_SIZE limitations, so we assume that here. + char buf[4096]; + ssize_t len = readlink(path, buf, sizeof(buf)); + if (len == -1) return false; + + result->assign(buf, len); + return true; +} + +// static +bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) { + sockaddr_storage ss; + sockaddr* addr = reinterpret_cast<sockaddr*>(&ss); + socklen_t addr_len = sizeof(ss); + + if (TEMP_FAILURE_RETRY(getsockname(fd, addr, &addr_len)) == -1) { + ALOGE("Failed getsockname(%d) : %s", fd, strerror(errno)); + return false; + } + + if (addr->sa_family != AF_UNIX) { + ALOGE("Unsupported socket (fd=%d) with family %d", fd, addr->sa_family); + return false; + } + + const sockaddr_un* unix_addr = reinterpret_cast<const sockaddr_un*>(&ss); + + size_t path_len = addr_len - offsetof(struct sockaddr_un, sun_path); + // This is an unnamed local socket, we do not accept it. + if (path_len == 0) { + ALOGE("Unsupported AF_UNIX socket (fd=%d) with empty path.", fd); + return false; + } + + // This is a local socket with an abstract address, we do not accept it. + if (unix_addr->sun_path[0] == '\0') { + ALOGE("Unsupported AF_UNIX socket (fd=%d) with abstract address.", fd); + return false; + } + + // If we're here, sun_path must refer to a null terminated filesystem + // pathname (man 7 unix). Remove the terminator before assigning it to an + // std::string. + if (unix_addr->sun_path[path_len - 1] == '\0') { + --path_len; + } + + result->assign(unix_addr->sun_path, path_len); + return true; +} + +bool FileDescriptorInfo::DetachSocket() const { + const int dev_null_fd = open("/dev/null", O_RDWR); + if (dev_null_fd < 0) { + ALOGE("Failed to open /dev/null : %s", strerror(errno)); + return false; + } + + if (dup2(dev_null_fd, fd) == -1) { + ALOGE("Failed dup2 on socket descriptor %d : %s", fd, strerror(errno)); + return false; + } + + if (close(dev_null_fd) == -1) { + ALOGE("Failed close(%d) : %s", dev_null_fd, strerror(errno)); + return false; + } + + return true; +} + +// static +FileDescriptorTable* FileDescriptorTable::Create() { + DIR* d = opendir(kFdPath); + if (d == NULL) { + ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno)); + return NULL; + } + int dir_fd = dirfd(d); + dirent* e; + + std::unordered_map<int, FileDescriptorInfo*> open_fd_map; + while ((e = readdir(d)) != NULL) { + const int fd = ParseFd(e, dir_fd); + if (fd == -1) { + continue; + } + + FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd); + if (info == NULL) { + if (closedir(d) == -1) { + ALOGE("Unable to close directory : %s", strerror(errno)); + } + return NULL; + } + open_fd_map[fd] = info; + } + + if (closedir(d) == -1) { + ALOGE("Unable to close directory : %s", strerror(errno)); + return NULL; + } + return new FileDescriptorTable(open_fd_map); +} + +bool FileDescriptorTable::Restat() { + std::set<int> open_fds; + + // First get the list of open descriptors. + DIR* d = opendir(kFdPath); + if (d == NULL) { + ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno)); + return false; + } + + int dir_fd = dirfd(d); + dirent* e; + while ((e = readdir(d)) != NULL) { + const int fd = ParseFd(e, dir_fd); + if (fd == -1) { + continue; + } + + open_fds.insert(fd); + } + + if (closedir(d) == -1) { + ALOGE("Unable to close directory : %s", strerror(errno)); + return false; + } + + return RestatInternal(open_fds); +} + +// Reopens all file descriptors that are contained in the table. Returns true +// if all descriptors were successfully re-opened or detached, and false if an +// error occurred. +bool FileDescriptorTable::ReopenOrDetach() { + std::unordered_map<int, FileDescriptorInfo*>::const_iterator it; + for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) { + const FileDescriptorInfo* info = it->second; + if (info == NULL || !info->ReopenOrDetach()) { + return false; + } + } + + return true; +} + +FileDescriptorTable::FileDescriptorTable( + const std::unordered_map<int, FileDescriptorInfo*>& map) + : open_fd_map_(map) { +} + +bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds) { + bool error = false; + + // Iterate through the list of file descriptors we've already recorded + // and check whether : + // + // (a) they continue to be open. + // (b) they refer to the same file. + std::unordered_map<int, FileDescriptorInfo*>::iterator it = open_fd_map_.begin(); + while (it != open_fd_map_.end()) { + std::set<int>::const_iterator element = open_fds.find(it->first); + if (element == open_fds.end()) { + // The entry from the file descriptor table is no longer in the list + // of open files. We warn about this condition and remove it from + // the list of FDs under consideration. + // + // TODO(narayan): This will be an error in a future android release. + // error = true; + // ALOGW("Zygote closed file descriptor %d.", it->first); + it = open_fd_map_.erase(it); + } else { + // The entry from the file descriptor table is still open. Restat + // it and check whether it refers to the same file. + const bool same_file = it->second->Restat(); + if (!same_file) { + // The file descriptor refers to a different description. We must + // update our entry in the table. + delete it->second; + it->second = FileDescriptorInfo::CreateFromFd(*element); + if (it->second == NULL) { + // The descriptor no longer no longer refers to a whitelisted file. + // We flag an error and remove it from the list of files we're + // tracking. + error = true; + it = open_fd_map_.erase(it); + } else { + // Successfully restatted the file, move on to the next open FD. + ++it; + } + } else { + // It's the same file. Nothing to do here. Move on to the next open + // FD. + ++it; + } + + // Finally, remove the FD from the set of open_fds. We do this last because + // |element| will not remain valid after a call to erase. + open_fds.erase(element); + } + } + + if (open_fds.size() > 0) { + // The zygote has opened new file descriptors since our last inspection. + // We warn about this condition and add them to our table. + // + // TODO(narayan): This will be an error in a future android release. + // error = true; + // ALOGW("Zygote opened %zd new file descriptor(s).", open_fds.size()); + + // TODO(narayan): This code will be removed in a future android release. + std::set<int>::const_iterator it; + for (it = open_fds.begin(); it != open_fds.end(); ++it) { + const int fd = (*it); + FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd); + if (info == NULL) { + // A newly opened file is not on the whitelist. Flag an error and + // continue. + error = true; + } else { + // Track the newly opened file. + open_fd_map_[fd] = info; + } + } + } + + return !error; +} + +// static +int FileDescriptorTable::ParseFd(dirent* e, int dir_fd) { + char* end; + const int fd = strtol(e->d_name, &end, 10); + if ((*end) != '\0') { + return -1; + } + + // Don't bother with the standard input/output/error, they're handled + // specially post-fork anyway. + if (fd <= STDERR_FILENO || fd == dir_fd) { + return -1; + } + + return fd; +} diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h new file mode 100644 index 000000000000..9e3afd910914 --- /dev/null +++ b/core/jni/fd_utils.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FRAMEWORKS_BASE_CORE_JNI_FD_UTILS_H_ +#define FRAMEWORKS_BASE_CORE_JNI_FD_UTILS_H_ + +#include <set> +#include <string> +#include <unordered_map> +#include <vector> + +#include <dirent.h> +#include <inttypes.h> +#include <sys/stat.h> + +#include <android-base/macros.h> + +// Whitelist of open paths that the zygote is allowed to keep open. +// +// In addition to the paths listed in kPathWhitelist in file_utils.cpp, and +// paths dynamically added with Allow(), all files ending with ".jar" +// under /system/framework" are whitelisted. See IsAllowed() for the canonical +// definition. +// +// If the whitelisted path is associated with a regular file or a +// character device, the file is reopened after a fork with the same +// offset and mode. If the whilelisted path is associated with a +// AF_UNIX socket, the socket will refer to /dev/null after each +// fork, and all operations on it will fail. +class FileDescriptorWhitelist { + public: + // Lazily creates the global whitelist. + static FileDescriptorWhitelist* Get(); + + // Adds a path to the whitelist. + void Allow(const std::string& path) { + whitelist_.push_back(path); + } + + // Returns true iff. a given path is whitelisted. A path is whitelisted + // if it belongs to the whitelist (see kPathWhitelist) or if it's a path + // under /system/framework that ends with ".jar" or if it is a system + // framework overlay. + bool IsAllowed(const std::string& path) const; + + private: + FileDescriptorWhitelist(); + + static bool StartsWith(const std::string& str, const std::string& prefix); + + static bool EndsWith(const std::string& str, const std::string& suffix); + + static FileDescriptorWhitelist* instance_; + + std::vector<std::string> whitelist_; + + DISALLOW_COPY_AND_ASSIGN(FileDescriptorWhitelist); +}; + +// Keeps track of all relevant information (flags, offset etc.) of an +// open zygote file descriptor. +class FileDescriptorInfo { + public: + // Create a FileDescriptorInfo for a given file descriptor. Returns + // |NULL| if an error occurred. + static FileDescriptorInfo* CreateFromFd(int fd); + + // Checks whether the file descriptor associated with this object + // refers to the same description. + bool Restat() const; + + bool ReopenOrDetach() const; + + const int fd; + const struct stat stat; + const std::string file_path; + const int open_flags; + const int fd_flags; + const int fs_flags; + const off_t offset; + const bool is_sock; + + private: + FileDescriptorInfo(int fd); + + FileDescriptorInfo(struct stat stat, const std::string& file_path, int fd, int open_flags, + int fd_flags, int fs_flags, off_t offset); + + static bool Readlink(const int fd, std::string* result); + + // Returns the locally-bound name of the socket |fd|. Returns true + // iff. all of the following hold : + // + // - the socket's sa_family is AF_UNIX. + // - the length of the path is greater than zero (i.e, not an unnamed socket). + // - the first byte of the path isn't zero (i.e, not a socket with an abstract + // address). + static bool GetSocketName(const int fd, std::string* result); + + bool DetachSocket() const; + + DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo); +}; + +// A FileDescriptorTable is a collection of FileDescriptorInfo objects +// keyed by their FDs. +class FileDescriptorTable { + public: + // Creates a new FileDescriptorTable. This function scans + // /proc/self/fd for the list of open file descriptors and collects + // information about them. Returns NULL if an error occurs. + static FileDescriptorTable* Create(); + + bool Restat(); + + // Reopens all file descriptors that are contained in the table. Returns true + // if all descriptors were successfully re-opened or detached, and false if an + // error occurred. + bool ReopenOrDetach(); + + private: + FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map); + + bool RestatInternal(std::set<int>& open_fds); + + static int ParseFd(dirent* e, int dir_fd); + + // Invariant: All values in this unordered_map are non-NULL. + std::unordered_map<int, FileDescriptorInfo*> open_fd_map_; + + DISALLOW_COPY_AND_ASSIGN(FileDescriptorTable); +}; + +#endif // FRAMEWORKS_BASE_CORE_JNI_FD_UTILS_H_ diff --git a/core/jni/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h index b1e552a810e8..1bc85212090a 100644 --- a/core/jni/include/android_runtime/android_view_Surface.h +++ b/core/jni/include/android_runtime/android_view_Surface.h @@ -42,6 +42,7 @@ enum class PublicFormat { NV16 = 0x10, NV21 = 0x11, YUY2 = 0x14, + RGBA_FP16 = 0x16, RAW_SENSOR = 0x20, PRIVATE = 0x22, YUV_420_888 = 0x23, diff --git a/core/proto/android/content/component_name.proto b/core/proto/android/content/component_name.proto new file mode 100644 index 000000000000..7908af9d2a36 --- /dev/null +++ b/core/proto/android/content/component_name.proto @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +option java_package = "android.content"; +option java_multiple_files = true; + +package android.content; + +/** + * An android.content.ComponentName object. + */ +message ComponentNameProto { + optional string package_name = 1; + optional string class_name = 2; +} + diff --git a/core/proto/android/content/configuration.proto b/core/proto/android/content/configuration.proto new file mode 100644 index 000000000000..683e7aacd3ad --- /dev/null +++ b/core/proto/android/content/configuration.proto @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +option java_package = "android.content"; +option java_multiple_files = true; + +package android.content; + +import "frameworks/base/core/proto/android/content/locale.proto"; + +/** + * An android resource configuration. + */ +message ConfigurationProto { + optional float font_scale = 1; + optional uint32 mcc = 2; + optional uint32 mnc = 3; + repeated LocaleProto locales = 4; + optional uint32 screen_layout = 5; + optional uint32 touchscreen = 6; + optional uint32 keyboard_hidden = 7; + optional uint32 hard_keyboard_hidden = 8; + optional uint32 navigation = 9; + optional uint32 navigation_hidden = 10; + optional uint32 orientation = 11; + optional uint32 ui_mode = 12; + optional uint32 screen_width_dp = 13; + optional uint32 screen_height_dp = 14; + optional uint32 smallest_screen_width_dp = 15; + optional uint32 density_dpi = 16; +} + diff --git a/core/proto/android/content/locale.proto b/core/proto/android/content/locale.proto new file mode 100644 index 000000000000..55ce68eed15f --- /dev/null +++ b/core/proto/android/content/locale.proto @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +option java_package = "android.content"; +option java_multiple_files = true; + +package android.content; + +message LocaleProto { + optional string language = 1; + optional string country = 2; + optional string variant = 3; +} + diff --git a/core/proto/android/os/incident_proto.proto b/core/proto/android/os/incident_proto.proto new file mode 100644 index 000000000000..1708b8135567 --- /dev/null +++ b/core/proto/android/os/incident_proto.proto @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +option java_package = "android.os"; +option java_multiple_files = true; + +import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/service/fingerprint_proto.proto"; + +package android.os; + +message IncidentHeaderProto { + enum Cause { + CAUSE_UNKNOWN = 0; + CAUSE_USER = 1; + CAUSE_ANR = 2; + CAUSE_CRASH = 3; + } + + optional Cause cause = 1; +} + +message IncidentProto { + // Incident header + repeated IncidentHeaderProto header = 1; + + // Device information + //optional SystemProperties system_properties = 1000; + + // Linux services + //optional Procrank procrank = 2000; + //optional PageTypeInfo page_type_info = 2001; + //optional KernelWakeSources kernel_wake_sources = 2002; + + // System Services + optional android.service.fingerprint.FingerprintServiceDumpProto fingerprint = 3000; +} diff --git a/core/proto/android/service/fingerprint_proto.proto b/core/proto/android/service/fingerprint_proto.proto new file mode 100644 index 000000000000..b2c500029661 --- /dev/null +++ b/core/proto/android/service/fingerprint_proto.proto @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package android.service.fingerprint; + +option java_multiple_files = true; + +message FingerprintServiceDumpProto { + // Each log may include multiple tuples of (user_id, num_fingerprints). + repeated FingerprintUserStatsProto users = 1; +} + +message FingerprintUserStatsProto { + // Should be 0, 10, 11, 12, etc. where 0 is the owner. + optional int32 user_id = 1; + + // The number of fingerprints registered to this user. + optional int32 num_fingerprints = 2; + + // Normal fingerprint authentications (e.g. lockscreen). + optional FingerprintActionStatsProto normal = 3; + + // Crypto authentications (e.g. to unlock password storage, make secure + // purchases, etc). + optional FingerprintActionStatsProto crypto = 4; +} + +message FingerprintActionStatsProto { + // Number of accepted fingerprints. + optional int32 accept = 1; + + // Number of rejected fingerprints. + optional int32 reject = 2; + + // Total number of acquisitions. Should be >= accept+reject due to poor + // image acquisition in some cases (too fast, too slow, dirty sensor, etc.) + optional int32 acquire = 3; + + // Total number of lockouts. + optional int32 lockout = 4; +} diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 69c7b6012740..5967c692fb94 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1267,7 +1267,8 @@ This may be empty if network scoring and recommending isn't supported. --> <string-array name="config_networkRecommendationPackageNames" translatable="false"> - <!-- Add packages here --> + <!-- The standard AOSP network recommendation provider --> + <item>com.android.networkrecommendation</item> </string-array> <!-- Whether to enable Hardware FLP overlay which allows Hardware FLP to be @@ -2613,6 +2614,9 @@ <!-- Component that is the default launcher when demo mode is enabled. --> <string name="config_demoModeLauncherComponent">com.android.retaildemo/.DemoPlayer</string> + <!-- Hashed password (SHA-256) used to restrict demo mode operation --> + <string name="config_demoModePassword" translatable="false"></string> + <!-- Flag indicating whether round icons should be parsed from the application manifest. --> <bool name="config_useRoundIcon">false</bool> @@ -2690,4 +2694,7 @@ user-set value if toggled by settings so the "Transition animation scale" setting should also be hidden if intended to be permanent. --> <item name="config_appTransitionAnimationDurationScaleDefault" format="float" type="dimen">1.0</item> + + <!-- Flag indicates that whether non-system apps can be installed on internal storage. --> + <bool name="config_allow3rdPartyAppOnInternal">true</bool> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 1cdfbf7e0220..880944e659e6 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -206,6 +206,12 @@ <!-- Displayed to tell the user that all service is blocked by access control. --> <string name="RestrictedOnAll">All voice/data/SMS services are blocked.</string> + <!-- Displayed to tell the user that they should switch their network preference. --> + <string name="NetworkPreferenceSwitchTitle">Can\u2019t reach network</string> + <!-- Displayed to tell the user that they should switch their network preference. --> + <string name="NetworkPreferenceSwitchSummary">To improve reception, try changing the type selected at Settings > Cellular networks > Preferred network type."</string> + + <!-- Displayed to tell the user that peer changed TTY mode --> <string name="peerTtyModeFull">Peer requested TTY Mode FULL</string> <string name="peerTtyModeHco">Peer requested TTY Mode HCO</string> @@ -2386,22 +2392,6 @@ <!-- Appened to express the value is this unit of time. --> <string name="years">years</string> - <!-- Phrase describing a time duration using seconds [CHAR LIMIT=16] --> - <plurals name="duration_seconds"> - <item quantity="one">1 second</item> - <item quantity="other"><xliff:g id="count">%d</xliff:g> seconds</item> - </plurals> - <!-- Phrase describing a time duration using minutes [CHAR LIMIT=16] --> - <plurals name="duration_minutes"> - <item quantity="one">1 minute</item> - <item quantity="other"><xliff:g id="count">%d</xliff:g> minutes</item> - </plurals> - <!-- Phrase describing a time duration using hours [CHAR LIMIT=16] --> - <plurals name="duration_hours"> - <item quantity="one">1 hour</item> - <item quantity="other"><xliff:g id="count">%d</xliff:g> hours</item> - </plurals> - <!-- A string denoting the current point in time that should be as short as possible. Abbreviations are preferred to full strings as this might be shown repetitively. It is used in the header of notifications. [CHAR LIMIT=8]--> <string name="now_string_shortest">now</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 1e6156bfde82..8efb13112c75 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -494,6 +494,8 @@ <java-symbol type="string" name="RestrictedOnData" /> <java-symbol type="string" name="RestrictedOnEmergency" /> <java-symbol type="string" name="RestrictedOnNormal" /> + <java-symbol type="string" name="NetworkPreferenceSwitchSummary" /> + <java-symbol type="string" name="NetworkPreferenceSwitchTitle" /> <java-symbol type="string" name="SetupCallDefault" /> <java-symbol type="string" name="accept" /> <java-symbol type="string" name="accessibility_enabled" /> @@ -1128,6 +1130,7 @@ <java-symbol type="string" name="config_ethernet_tcp_buffers" /> <java-symbol type="string" name="config_wifi_tcp_buffers" /> <java-symbol type="string" name="config_demoModeLauncherComponent" /> + <java-symbol type="string" name="config_demoModePassword" /> <java-symbol type="string" name="demo_starting_message" /> <java-symbol type="string" name="demo_restarting_message" /> <java-symbol type="string" name="conference_call" /> @@ -1135,9 +1138,6 @@ <java-symbol type="plurals" name="bugreport_countdown" /> - <java-symbol type="plurals" name="duration_hours" /> - <java-symbol type="plurals" name="duration_minutes" /> - <java-symbol type="plurals" name="duration_seconds" /> <java-symbol type="plurals" name="last_num_days" /> <java-symbol type="plurals" name="matches_found" /> <java-symbol type="plurals" name="restr_pin_countdown" /> @@ -2758,6 +2758,9 @@ <java-symbol type="dimen" name="config_appTransitionAnimationDurationScaleDefault" /> -<!-- Network Recommendation --> + <!-- Network Recommendation --> <java-symbol type="array" name="config_networkRecommendationPackageNames" /> + + <!-- Whether allow 3rd party apps on internal storage. --> + <java-symbol type="bool" name="config_allow3rdPartyAppOnInternal" /> </resources> diff --git a/core/tests/coretests/README b/core/tests/coretests/README new file mode 100644 index 000000000000..4a6984320e61 --- /dev/null +++ b/core/tests/coretests/README @@ -0,0 +1,50 @@ +* Copyright (C) 2016 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. + + +INTRODUCTION + +The Android platform core tests (APCT) consist of unit tests for core platform +functionality. These differ from CTS in that they are not necessarily testing +public APIs and are not guaranteed to work outside of AOSP builds. + + +INSTRUCTIONS + +To run a test or set of tests, first build the FrameworksCoreTests package: + + make FrameworksCoreTests + +Next, install the resulting APK and run tests as you would normal JUnit tests: + + adb install out/target/product/.../data/app/FrameworksCoreTests/FrameworksCoreTests.apk + adb shell am instrument -w \ + com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner + +To run a tests within a specific package, add the following argument AFTER -w: + + -e package android.content.pm + +To run a specific test or method within a test: + + -e class android.content.pm.PackageParserTest + -e class android.content.pm.PackageParserTest#testComputeMinSdkVersion + +To run tests in debug mode: + + -e debug true + +For more arguments, see the guide to command=line testing: + + https://developer.android.com/studio/test/command-line.html diff --git a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java b/core/tests/coretests/src/android/content/pm/PackageHelperTests.java index 5af2667d29ff..c4d00c677cc8 100644 --- a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java +++ b/core/tests/coretests/src/android/content/pm/PackageHelperTests.java @@ -16,17 +16,28 @@ package android.content.pm; -import static android.net.TrafficStats.MB_IN_BYTES; - +import android.content.Context; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.storage.IStorageManager; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; import android.test.AndroidTestCase; import android.util.Log; import com.android.internal.content.PackageHelper; +import org.mockito.Mockito; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static android.net.TrafficStats.MB_IN_BYTES; +import static android.os.storage.VolumeInfo.STATE_MOUNTED; + public class PackageHelperTests extends AndroidTestCase { private static final boolean localLOGV = true; public static final String TAG = "PackageHelperTests"; @@ -35,6 +46,94 @@ public class PackageHelperTests extends AndroidTestCase { private String fullId; private String fullId2; + private static final String sInternalVolPath = "/data"; + private static final String sAdoptedVolPath = "/mnt/expand/123"; + private static final String sPublicVolPath = "/emulated"; + + private static final String sInternalVolUuid = StorageManager.UUID_PRIVATE_INTERNAL; + private static final String sAdoptedVolUuid = "adopted"; + private static final String sPublicVolUuid = "emulated"; + + private static final long sInternalSize = 20000; + private static final long sAdoptedSize = 10000; + private static final long sPublicSize = 1000000; + + private static final StorageManager sStorageManager = createStorageManagerMock(); + + private static StorageManager createStorageManagerMock() { + VolumeInfo internalVol = new VolumeInfo("private", + VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/); + internalVol.path = sInternalVolPath; + internalVol.state = STATE_MOUNTED; + internalVol.fsUuid = sInternalVolUuid; + + VolumeInfo adoptedVol = new VolumeInfo("adopted", + VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/); + adoptedVol.path = sAdoptedVolPath; + adoptedVol.state = STATE_MOUNTED; + adoptedVol.fsUuid = sAdoptedVolUuid; + + VolumeInfo publicVol = new VolumeInfo("public", + VolumeInfo.TYPE_PUBLIC, null /*DiskInfo*/, null /*partGuid*/); + publicVol.state = STATE_MOUNTED; + publicVol.path = sPublicVolPath; + publicVol.fsUuid = sPublicVolUuid; + + List<VolumeInfo> volumes = new ArrayList<>(); + volumes.add(internalVol); + volumes.add(adoptedVol); + volumes.add(publicVol); + + StorageManager storageManager = Mockito.mock(StorageManager.class); + Mockito.when(storageManager.getVolumes()).thenReturn(volumes); + + File internalFile = new File(sInternalVolPath); + File adoptedFile = new File(sAdoptedVolPath); + File publicFile = new File(sPublicVolPath); + Mockito.when(storageManager.getStorageBytesUntilLow(internalFile)).thenReturn(sInternalSize); + Mockito.when(storageManager.getStorageBytesUntilLow(adoptedFile)).thenReturn(sAdoptedSize); + Mockito.when(storageManager.getStorageBytesUntilLow(publicFile)).thenReturn(sPublicSize); + return storageManager; + } + + private static final class MockedInterface extends PackageHelper.TestableInterface { + private boolean mForceAllowOnExternal = false; + private boolean mAllow3rdPartyOnInternal = true; + private ApplicationInfo mApplicationInfo = null; + + public void setMockValues(ApplicationInfo applicationInfo, + boolean forceAllowOnExternal, boolean allow3rdPartyOnInternal) { + mForceAllowOnExternal = forceAllowOnExternal; + mAllow3rdPartyOnInternal = allow3rdPartyOnInternal; + mApplicationInfo = applicationInfo; + } + + @Override + public StorageManager getStorageManager(Context context) { + return sStorageManager; + } + + @Override + public boolean getForceAllowOnExternalSetting(Context context) { + return mForceAllowOnExternal; + } + + @Override + public boolean getAllow3rdPartyOnInternalConfig(Context context) { + return mAllow3rdPartyOnInternal; + } + + @Override + public ApplicationInfo getExistingAppInfo(Context context, String packagename) { + return mApplicationInfo; + } + + @Override + public File getDataDirectory() { + return new File(sInternalVolPath); + } + } + private IStorageManager getSm() { IBinder service = ServiceManager.getService("mount"); if (service != null) { @@ -131,4 +230,328 @@ public class PackageHelperTests extends AndroidTestCase { }; return r; } + + public void testResolveInstallVolumeInternal_SystemApp() throws IOException { + ApplicationInfo systemAppInfo = new ApplicationInfo(); + systemAppInfo.flags = ApplicationInfo.FLAG_SYSTEM; + + // All test cases for when the system app fits on internal. + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); + + mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); + + mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); + + mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); + + + // All test cases for when the system app does not fit on internal. + // Exception should be thrown. + mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch(IOException e) { + // expected + } + + mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch(IOException e) { + // expected + } + + mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch(IOException e) { + // expected + } + + mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch(IOException e) { + // expected + } + } + + public void testResolveInstallVolumeInternal_3rdParty_existing_not_too_big() + throws IOException { + // Existing apps always stay on the same volume. + // Test cases for existing app on internal. + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.volumeUuid = sInternalVolUuid; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sInternalVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sInternalVolUuid, volume); + } + + public void testResolveInstallVolumeInternal_3rdParty_existing_not_too_big_adopted() + throws IOException { + // Test cases for existing app on the adopted media. + ApplicationInfo appInfo = new ApplicationInfo(); + MockedInterface mockedInterface = new MockedInterface(); + String volume; + appInfo.volumeUuid = sAdoptedVolUuid; + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + } + + public void testResolveInstallVolumeAdopted_3rdParty_existing_too_big() { + // Test: update size too big, will throw exception. + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.volumeUuid = sAdoptedVolUuid; + + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); + fail("Expected exception was not thrown " + appInfo.volumeUuid); + } catch (IOException e) { + //expected + } + + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); + fail("Expected exception was not thrown " + appInfo.volumeUuid); + } catch (IOException e) { + //expected + } + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); + fail("Expected exception was not thrown " + appInfo.volumeUuid); + } catch (IOException e) { + //expected + } + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + try { + PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); + fail("Expected exception was not thrown " + appInfo.volumeUuid); + } catch (IOException e) { + //expected + } + } + + public void testResolveInstallVolumeInternal_3rdParty_auto() throws IOException { + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the volume with bigger available space. + assertEquals(sInternalVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the volume with bigger available space. + assertEquals(sInternalVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the volume with bigger available space. + assertEquals(sAdoptedVolUuid, volume); + + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the volume with bigger available space. + assertEquals(sAdoptedVolUuid, volume); + + + } + + public void testResolveInstallVolumeInternal_3rdParty_internal_only() throws IOException { + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sInternalVolUuid, volume); + + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + true /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sInternalVolUuid, volume); + + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + try { + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch (IOException e) { + //expected + } + + appInfo = null; + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + } + + public void testResolveInstallVolumeInternal_3rdParty_not_allowed_on_internal() + throws IOException { + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the non-internal volume. + assertEquals(sAdoptedVolUuid, volume); + + appInfo = null; + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); + // Should return the non-internal volume. + assertEquals(sAdoptedVolUuid, volume); + } + + public void testResolveInstallVolumeInternal_3rdParty_internal_only_too_big() { + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + true /*allow 3rd party on internal*/); + String volume = null; + try { + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal ONLY*/, + 1000000 /*size too big*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch (IOException e) { + //expected + } + } + + public void testResolveInstallVolumeInternal_3rdParty_internal_only_not_allowed() + throws IOException { + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, false /*force allow on external*/, + false /*allow 3rd party on internal*/); + String volume = null; + try { + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); + fail("Expected exception in resolveInstallVolume was not thrown"); + } catch (IOException e) { + //expected + } + + appInfo = null; + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + + } + + public void testResolveInstallVolumeInternal_3rdParty_internal_only_forced_to_external() + throws IOException { + // New/existing installation: New + // app request location: Internal Only + // 3rd party allowed on internal: False + // Force allow external in setting: True + // Size fit? Yes + ApplicationInfo appInfo = null; + MockedInterface mockedInterface = new MockedInterface(); + mockedInterface.setMockValues(appInfo, true /*force allow on external*/, + false /*allow 3rd party on internal*/); + String volume = null; + volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); + assertEquals(sAdoptedVolUuid, volume); + } } diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java new file mode 100644 index 000000000000..2a3c22c64ec2 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import android.os.Build; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class PackageParserTest { + private static final String RELEASED = null; + private static final String OLDER_PRE_RELEASE = "A"; + private static final String PRE_RELEASE = "B"; + private static final String NEWER_PRE_RELEASE = "C"; + + private static final String[] CODENAMES_RELEASED = { /* empty */ }; + private static final String[] CODENAMES_PRE_RELEASE = { PRE_RELEASE }; + + private static final int OLDER_VERSION = 10; + private static final int PLATFORM_VERSION = 20; + private static final int NEWER_VERSION = 30; + + private void verifyComputeMinSdkVersion(int minSdkVersion, String minSdkCodename, + boolean isPlatformReleased, int expectedMinSdk) { + final String[] outError = new String[1]; + final int result = PackageParser.computeMinSdkVersion( + minSdkVersion, + minSdkCodename, + PLATFORM_VERSION, + isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE, + outError); + + assertEquals(result, expectedMinSdk); + + if (expectedMinSdk == -1) { + assertNotNull(outError[0]); + } else { + assertNull(outError[0]); + } + } + + @Test + public void testComputeMinSdkVersion_preReleasePlatform() { + // Do allow older release minSdkVersion on pre-release platform. + // APP: Released API 10 + // DEV: Pre-release API 20 + verifyComputeMinSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION); + + // Do allow same release minSdkVersion on pre-release platform. + // APP: Released API 20 + // DEV: Pre-release API 20 + verifyComputeMinSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION); + + // Don't allow newer release minSdkVersion on pre-release platform. + // APP: Released API 30 + // DEV: Pre-release API 20 + verifyComputeMinSdkVersion(NEWER_VERSION, RELEASED, false, -1); + + // Don't allow older pre-release minSdkVersion on pre-release platform. + // APP: Pre-release API 10 + // DEV: Pre-release API 20 + verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1); + + // Do allow same pre-release minSdkVersion on pre-release platform, + // but overwrite the specified version with CUR_DEVELOPMENT. + // APP: Pre-release API 20 + // DEV: Pre-release API 20 + verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false, + Build.VERSION_CODES.CUR_DEVELOPMENT); + + // Don't allow newer pre-release minSdkVersion on pre-release platform. + // APP: Pre-release API 30 + // DEV: Pre-release API 20 + verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1); + } + + @Test + public void testComputeMinSdkVersion_releasedPlatform() { + // Do allow older release minSdkVersion on released platform. + // APP: Released API 10 + // DEV: Released API 20 + verifyComputeMinSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION); + + // Do allow same release minSdkVersion on released platform. + // APP: Released API 20 + // DEV: Released API 20 + verifyComputeMinSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION); + + // Don't allow newer release minSdkVersion on released platform. + // APP: Released API 30 + // DEV: Released API 20 + verifyComputeMinSdkVersion(NEWER_VERSION, RELEASED, true, -1); + + // Don't allow older pre-release minSdkVersion on released platform. + // APP: Pre-release API 10 + // DEV: Released API 20 + verifyComputeMinSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1); + + // Don't allow same pre-release minSdkVersion on released platform. + // APP: Pre-release API 20 + // DEV: Released API 20 + verifyComputeMinSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1); + + // Don't allow newer pre-release minSdkVersion on released platform. + // APP: Pre-release API 30 + // DEV: Released API 20 + verifyComputeMinSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1); + } + + private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename, + boolean isPlatformReleased, int expectedTargetSdk) { + final String[] outError = new String[1]; + final int result = PackageParser.computeTargetSdkVersion( + targetSdkVersion, + targetSdkCodename, + PLATFORM_VERSION, + isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE, + outError); + + assertEquals(result, expectedTargetSdk); + + if (expectedTargetSdk == -1) { + assertNotNull(outError[0]); + } else { + assertNull(outError[0]); + } + } + + @Test + public void testComputeTargetSdkVersion_preReleasePlatform() { + // Do allow older release targetSdkVersion on pre-release platform. + // APP: Released API 10 + // DEV: Pre-release API 20 + verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION); + + // Do allow same release targetSdkVersion on pre-release platform. + // APP: Released API 20 + // DEV: Pre-release API 20 + verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION); + + // Do allow newer release targetSdkVersion on pre-release platform. + // APP: Released API 30 + // DEV: Pre-release API 20 + verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, NEWER_VERSION); + + // Don't allow older pre-release targetSdkVersion on pre-release platform. + // APP: Pre-release API 10 + // DEV: Pre-release API 20 + verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1); + + // Do allow same pre-release targetSdkVersion on pre-release platform, + // but overwrite the specified version with CUR_DEVELOPMENT. + // APP: Pre-release API 20 + // DEV: Pre-release API 20 + verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false, + Build.VERSION_CODES.CUR_DEVELOPMENT); + + // Don't allow newer pre-release targetSdkVersion on pre-release platform. + // APP: Pre-release API 30 + // DEV: Pre-release API 20 + verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1); + } + + @Test + public void testComputeTargetSdkVersion_releasedPlatform() { + // Do allow older release targetSdkVersion on released platform. + // APP: Released API 10 + // DEV: Released API 20 + verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION); + + // Do allow same release targetSdkVersion on released platform. + // APP: Released API 20 + // DEV: Released API 20 + verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION); + + // Do allow newer release targetSdkVersion on released platform. + // APP: Released API 30 + // DEV: Released API 20 + verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, NEWER_VERSION); + + // Don't allow older pre-release targetSdkVersion on released platform. + // APP: Pre-release API 10 + // DEV: Released API 20 + verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1); + + // Don't allow same pre-release targetSdkVersion on released platform. + // APP: Pre-release API 20 + // DEV: Released API 20 + verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1); + + // Don't allow newer pre-release targetSdkVersion on released platform. + // APP: Pre-release API 30 + // DEV: Released API 20 + verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1); + } +} diff --git a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java index 02c25170bb74..5bfff26b0813 100644 --- a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java +++ b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java @@ -16,32 +16,33 @@ package android.net; +import static org.mockito.Mockito.when; + import android.Manifest.permission; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.res.Resources; import android.net.NetworkScorerAppManager.NetworkScorerAppData; -import android.os.UserHandle; +import android.provider.Settings; import android.test.InstrumentationTestCase; - +import com.android.internal.R; +import java.util.List; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - public class NetworkScorerAppManagerTest extends InstrumentationTestCase { @Mock private Context mMockContext; @Mock private PackageManager mMockPm; - + @Mock private Resources mResources; + @Mock private ContentResolver mContentResolver; + private Context mTargetContext; private NetworkScorerAppManager mNetworkScorerAppManager; @Override @@ -49,154 +50,161 @@ public class NetworkScorerAppManagerTest extends InstrumentationTestCase { super.setUp(); // Configuration needed to make mockito/dexcache work. - System.setProperty("dexmaker.dexcache", - getInstrumentation().getTargetContext().getCacheDir().getPath()); + mTargetContext = getInstrumentation().getTargetContext(); + System.setProperty("dexmaker.dexcache", mTargetContext.getCacheDir().getPath()); ClassLoader newClassLoader = getInstrumentation().getClass().getClassLoader(); Thread.currentThread().setContextClassLoader(newClassLoader); MockitoAnnotations.initMocks(this); - Mockito.when(mMockContext.getPackageManager()).thenReturn(mMockPm); + when(mMockContext.getPackageManager()).thenReturn(mMockPm); + when(mMockContext.getResources()).thenReturn(mResources); + when(mMockContext.getContentResolver()).thenReturn(mTargetContext.getContentResolver()); mNetworkScorerAppManager = new NetworkScorerAppManager(mMockContext); } - public void testGetAllValidScorers() throws Exception { - // Package 1 - Valid scorer. - ResolveInfoHolder package1 = buildResolveInfo("package1", 1, true, true, false, false); - - // Package 2 - Receiver does not have BROADCAST_NETWORK_PRIVILEGED permission. - ResolveInfoHolder package2 = buildResolveInfo("package2", 2, false, true, false, false); - - // Package 3 - App does not have SCORE_NETWORKS permission. - ResolveInfoHolder package3 = buildResolveInfo("package3", 3, true, false, false, false); - - // Package 4 - Valid scorer w/ optional config activity. - ResolveInfoHolder package4 = buildResolveInfo("package4", 4, true, true, true, false); - - // Package 5 - Valid scorer w/ optional service to bind to. - ResolveInfoHolder package5 = buildResolveInfo("package5", 5, true, true, false, true); - - List<ResolveInfoHolder> scorers = new ArrayList<>(); - scorers.add(package1); - scorers.add(package2); - scorers.add(package3); - scorers.add(package4); - scorers.add(package5); - setScorers(scorers); - - Iterator<NetworkScorerAppData> result = - mNetworkScorerAppManager.getAllValidScorers().iterator(); - - assertTrue(result.hasNext()); - NetworkScorerAppData next = result.next(); - assertEquals("package1", next.mPackageName); - assertEquals(1, next.mPackageUid); - assertNull(next.mConfigurationActivityClassName); - - assertTrue(result.hasNext()); - next = result.next(); - assertEquals("package4", next.mPackageName); - assertEquals(4, next.mPackageUid); - assertEquals(".ConfigActivity", next.mConfigurationActivityClassName); - - assertTrue(result.hasNext()); - next = result.next(); - assertEquals("package5", next.mPackageName); - assertEquals(5, next.mPackageUid); - assertEquals(".ScoringService", next.mScoringServiceClassName); - - assertFalse(result.hasNext()); - } - - private void setScorers(List<ResolveInfoHolder> scorers) { - List<ResolveInfo> receivers = new ArrayList<>(); - for (final ResolveInfoHolder scorer : scorers) { - receivers.add(scorer.scorerResolveInfo); - if (scorer.configActivityResolveInfo != null) { - // This scorer has a config activity. - Mockito.when(mMockPm.queryIntentActivities( - Mockito.argThat(new ArgumentMatcher<Intent>() { - @Override - public boolean matches(Object object) { - Intent intent = (Intent) object; - return NetworkScoreManager.ACTION_CUSTOM_ENABLE.equals( - intent.getAction()) - && scorer.scorerResolveInfo.activityInfo.packageName.equals( - intent.getPackage()); - } - }), Mockito.eq(0))).thenReturn( - Collections.singletonList(scorer.configActivityResolveInfo)); - } - - if (scorer.serviceResolveInfo != null) { - // This scorer has a service to bind to - Mockito.when(mMockPm.resolveService( - Mockito.argThat(new ArgumentMatcher<Intent>() { - @Override - public boolean matches(Object object) { - Intent intent = (Intent) object; - return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals( - intent.getAction()) - && scorer.scorerResolveInfo.activityInfo.packageName.equals( - intent.getPackage()); - } - }), Mockito.eq(0))).thenReturn(scorer.serviceResolveInfo); - } - } + public void testGetPotentialRecommendationProviderPackages_emptyConfig() throws Exception { + setNetworkRecommendationPackageNames(/*no configured packages*/); + assertTrue(mNetworkScorerAppManager.getPotentialRecommendationProviderPackages().isEmpty()); + } - Mockito.when(mMockPm.queryBroadcastReceiversAsUser( - Mockito.argThat(new ArgumentMatcher<Intent>() { - @Override - public boolean matches(Object object) { - Intent intent = (Intent) object; - return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals(intent.getAction()); - } - }), Mockito.eq(0), Mockito.eq(UserHandle.USER_SYSTEM))) - .thenReturn(receivers); - } - - private ResolveInfoHolder buildResolveInfo(String packageName, int packageUid, - boolean hasReceiverPermission, boolean hasScorePermission, boolean hasConfigActivity, - boolean hasServiceInfo) throws Exception { - Mockito.when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName)) - .thenReturn(hasScorePermission ? - PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED); - - ResolveInfo resolveInfo = new ResolveInfo(); - resolveInfo.activityInfo = new ActivityInfo(); - resolveInfo.activityInfo.packageName = packageName; - resolveInfo.activityInfo.applicationInfo = new ApplicationInfo(); - resolveInfo.activityInfo.applicationInfo.uid = packageUid; - if (hasReceiverPermission) { - resolveInfo.activityInfo.permission = permission.BROADCAST_NETWORK_PRIVILEGED; - } + public void testGetPotentialRecommendationProviderPackages_permissionNotGranted() + throws Exception { + setNetworkRecommendationPackageNames("package1"); + mockScoreNetworksDenied("package1"); - ResolveInfo configActivityInfo = null; - if (hasConfigActivity) { - configActivityInfo = new ResolveInfo(); - configActivityInfo.activityInfo = new ActivityInfo(); - configActivityInfo.activityInfo.name = ".ConfigActivity"; - } + assertTrue(mNetworkScorerAppManager.getPotentialRecommendationProviderPackages().isEmpty()); + } - ResolveInfo serviceInfo = null; - if (hasServiceInfo) { - serviceInfo = new ResolveInfo(); - serviceInfo.serviceInfo = new ServiceInfo(); - serviceInfo.serviceInfo.name = ".ScoringService"; - } + public void testGetPotentialRecommendationProviderPackages_permissionGranted() + throws Exception { + setNetworkRecommendationPackageNames("package1"); + mockScoreNetworksGranted("package1"); + + List<String> potentialProviderPackages = + mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); + + assertFalse(potentialProviderPackages.isEmpty()); + assertEquals("package1", potentialProviderPackages.get(0)); + } + + public void testGetPotentialRecommendationProviderPackages_multipleConfigured() + throws Exception { + setNetworkRecommendationPackageNames("package1", "package2"); + mockScoreNetworksDenied("package1"); + mockScoreNetworksGranted("package2"); + + List<String> potentialProviderPackages = + mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); + + assertEquals(1, potentialProviderPackages.size()); + assertEquals("package2", potentialProviderPackages.get(0)); + } + + public void testGetNetworkRecommendationProviderData_noPotentialPackages() throws Exception { + setNetworkRecommendationPackageNames(/*no configured packages*/); + assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData()); + } + + public void testGetNetworkRecommendationProviderData_serviceMissing() throws Exception { + setNetworkRecommendationPackageNames("package1"); + mockScoreNetworksGranted("package1"); - return new ResolveInfoHolder(resolveInfo, configActivityInfo, serviceInfo); + assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData()); } - private static class ResolveInfoHolder { - final ResolveInfo scorerResolveInfo; - final ResolveInfo configActivityResolveInfo; - final ResolveInfo serviceResolveInfo; + public void testGetNetworkRecommendationProviderData_scoreNetworksNotGranted() + throws Exception { + setNetworkRecommendationPackageNames("package1"); + mockScoreNetworksDenied("package1"); + mockRecommendationServiceAvailable("package1", 924 /* packageUid */); - public ResolveInfoHolder(ResolveInfo scorerResolveInfo, - ResolveInfo configActivityResolveInfo, ResolveInfo serviceResolveInfo) { - this.scorerResolveInfo = scorerResolveInfo; - this.configActivityResolveInfo = configActivityResolveInfo; - this.serviceResolveInfo = serviceResolveInfo; + assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData()); + } + + public void testGetNetworkRecommendationProviderData_available() throws Exception { + setNetworkRecommendationPackageNames("package1"); + mockScoreNetworksGranted("package1"); + mockRecommendationServiceAvailable("package1", 924 /* packageUid */); + + NetworkScorerAppData appData = + mNetworkScorerAppManager.getNetworkRecommendationProviderData(); + assertNotNull(appData); + assertEquals("package1", appData.packageName); + assertEquals(924, appData.packageUid); + assertEquals(".RecommendationService", appData.recommendationServiceClassName); + } + + public void testGetActiveScorer_providerAvailable() throws Exception { + setNetworkRecommendationPackageNames("package1"); + mockScoreNetworksGranted("package1"); + mockRecommendationServiceAvailable("package1", 924 /* packageUid */); + + ContentResolver cr = mTargetContext.getContentResolver(); + Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1); + + final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); + assertNotNull(activeScorer); + assertEquals("package1", activeScorer.packageName); + assertEquals(924, activeScorer.packageUid); + assertEquals(".RecommendationService", activeScorer.recommendationServiceClassName); + } + + public void testGetActiveScorer_providerNotAvailable() + throws Exception { + ContentResolver cr = mTargetContext.getContentResolver(); + Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1); + + final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); + assertNull(activeScorer); + } + + public void testGetActiveScorer_recommendationsDisabled() throws Exception { + setNetworkRecommendationPackageNames("package1"); + mockScoreNetworksGranted("package1"); + mockRecommendationServiceAvailable("package1", 924 /* packageUid */); + ContentResolver cr = mTargetContext.getContentResolver(); + Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0); + + final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer(); + assertNull(activeScorer); + } + + private void setNetworkRecommendationPackageNames(String... names) { + if (names == null) { + names = new String[0]; } + when(mResources.getStringArray(R.array.config_networkRecommendationPackageNames)) + .thenReturn(names); + } + + private void mockScoreNetworksGranted(String packageName) { + when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + } + + private void mockScoreNetworksDenied(String packageName) { + when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName)) + .thenReturn(PackageManager.PERMISSION_DENIED); + } + + private void mockRecommendationServiceAvailable(final String packageName, int packageUid) { + final ResolveInfo serviceInfo = new ResolveInfo(); + serviceInfo.serviceInfo = new ServiceInfo(); + serviceInfo.serviceInfo.name = ".RecommendationService"; + serviceInfo.serviceInfo.packageName = packageName; + serviceInfo.serviceInfo.applicationInfo = new ApplicationInfo(); + serviceInfo.serviceInfo.applicationInfo.uid = packageUid; + + final int flags = 0; + when(mMockPm.resolveService( + Mockito.argThat(new ArgumentMatcher<Intent>() { + @Override + public boolean matches(Object object) { + Intent intent = (Intent) object; + return NetworkScoreManager.ACTION_RECOMMEND_NETWORKS + .equals(intent.getAction()) + && packageName.equals(intent.getPackage()); + } + }), Mockito.eq(flags))).thenReturn(serviceInfo); } } diff --git a/core/tests/coretests/src/android/net/RecommendationRequestTest.java b/core/tests/coretests/src/android/net/RecommendationRequestTest.java new file mode 100644 index 000000000000..31560b0ac8c3 --- /dev/null +++ b/core/tests/coretests/src/android/net/RecommendationRequestTest.java @@ -0,0 +1,84 @@ +package android.net; + +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.os.Parcel; +import android.test.AndroidTestCase; + +public class RecommendationRequestTest extends AndroidTestCase { + private ScanResult[] mScanResults; + private WifiConfiguration mConfiguration; + private NetworkCapabilities mCapabilities; + + @Override + public void setUp() throws Exception { + mScanResults = new ScanResult[2]; + mScanResults[0] = new ScanResult(); + mScanResults[1] = new ScanResult( + "ssid", + "bssid", + 0L /*hessid*/, + 1 /*anqpDominId*/, + "caps", + 2 /*level*/, + 3 /*frequency*/, + 4L /*tsf*/, + 5 /*distCm*/, + 6 /*distSdCm*/, + 7 /*channelWidth*/, + 8 /*centerFreq0*/, + 9 /*centerFreq1*/, + false /*is80211McRTTResponder*/); + mConfiguration = new WifiConfiguration(); + mConfiguration.SSID = "RecommendationRequestTest"; + mCapabilities = new NetworkCapabilities() + .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED); + } + + public void testParceling() throws Exception { + RecommendationRequest request = new RecommendationRequest.Builder() + .setCurrentRecommendedWifiConfig(mConfiguration) + .setScanResults(mScanResults) + .setNetworkCapabilities(mCapabilities) + .build(); + + RecommendationRequest parceled = passThroughParcel(request); + assertEquals(request.getCurrentSelectedConfig().SSID, + parceled.getCurrentSelectedConfig().SSID); + assertEquals(request.getRequiredCapabilities(), parceled.getRequiredCapabilities()); + ScanResult[] parceledScanResults = parceled.getScanResults(); + assertNotNull(parceledScanResults); + assertEquals(mScanResults.length, parceledScanResults.length); + for (int i = 0; i < mScanResults.length; i++) { + assertEquals(mScanResults[i].SSID, parceledScanResults[i].SSID); + } + } + + public void testParceling_nullScanResults() throws Exception { + RecommendationRequest request = new RecommendationRequest.Builder() + .setCurrentRecommendedWifiConfig(mConfiguration) + .setNetworkCapabilities(mCapabilities) + .build(); + + RecommendationRequest parceled = passThroughParcel(request); + assertEquals(request.getCurrentSelectedConfig().SSID, + parceled.getCurrentSelectedConfig().SSID); + assertEquals(request.getRequiredCapabilities(), parceled.getRequiredCapabilities()); + ScanResult[] parceledScanResults = parceled.getScanResults(); + assertNull(parceledScanResults); + } + + private RecommendationRequest passThroughParcel(RecommendationRequest request) { + Parcel p = Parcel.obtain(); + RecommendationRequest output = null; + try { + request.writeToParcel(p, 0); + p.setDataPosition(0); + output = RecommendationRequest.CREATOR.createFromParcel(p); + } finally { + p.recycle(); + } + assertNotNull(output); + return output; + } +} diff --git a/core/tests/coretests/src/android/text/method/WordIteratorTest.java b/core/tests/coretests/src/android/text/method/WordIteratorTest.java index 28a480dd2b2e..66cf65f7bc05 100644 --- a/core/tests/coretests/src/android/text/method/WordIteratorTest.java +++ b/core/tests/coretests/src/android/text/method/WordIteratorTest.java @@ -516,4 +516,34 @@ public class WordIteratorTest extends AndroidTestCase { assertFalse(wordIterator.isOnPunctuation(text.length())); assertFalse(wordIterator.isOnPunctuation(text.length() + 1)); } + + @SmallTest + public void testApostropheMiddleOfWord() { + // These tests confirm that the word "isn't" is treated like one word. + final String text = "isn't he"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + wordIterator.setCharSequence(text, 0, text.length()); + + assertEquals(text.indexOf('i'), wordIterator.preceding(text.indexOf('h'))); + assertEquals(text.indexOf('t') + 1, wordIterator.following(text.indexOf('i'))); + + assertTrue(wordIterator.isBoundary(text.indexOf('i'))); + assertFalse(wordIterator.isBoundary(text.indexOf('\''))); + assertFalse(wordIterator.isBoundary(text.indexOf('t'))); + assertTrue(wordIterator.isBoundary(text.indexOf('t') + 1)); + assertTrue(wordIterator.isBoundary(text.indexOf('h'))); + + assertEquals(text.indexOf('i'), wordIterator.getBeginning(text.indexOf('i'))); + assertEquals(text.indexOf('i'), wordIterator.getBeginning(text.indexOf('n'))); + assertEquals(text.indexOf('i'), wordIterator.getBeginning(text.indexOf('\''))); + assertEquals(text.indexOf('i'), wordIterator.getBeginning(text.indexOf('t'))); + assertEquals(text.indexOf('i'), wordIterator.getBeginning(text.indexOf('t') + 1)); + assertEquals(text.indexOf('h'), wordIterator.getBeginning(text.indexOf('h'))); + + assertEquals(text.indexOf('t') + 1, wordIterator.getEnd(text.indexOf('i'))); + assertEquals(text.indexOf('t') + 1, wordIterator.getEnd(text.indexOf('n'))); + assertEquals(text.indexOf('t') + 1, wordIterator.getEnd(text.indexOf('\''))); + assertEquals(text.indexOf('t') + 1, wordIterator.getEnd(text.indexOf('t'))); + assertEquals(text.indexOf('e') + 1, wordIterator.getEnd(text.indexOf('h'))); + } } diff --git a/core/tests/coretests/src/android/view/ViewCaptureTest.java b/core/tests/coretests/src/android/view/ViewCaptureTest.java index 15cfe230eb57..1b4d4a2fcd3a 100644 --- a/core/tests/coretests/src/android/view/ViewCaptureTest.java +++ b/core/tests/coretests/src/android/view/ViewCaptureTest.java @@ -16,6 +16,8 @@ package android.view; +import static org.junit.Assert.assertTrue; + import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -32,8 +34,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.assertTrue; - @RunWith(AndroidJUnit4.class) public class ViewCaptureTest { @@ -70,6 +70,7 @@ public class ViewCaptureTest { private void testCreateSnapshot(boolean skipChildren, int goldenResId) { Bitmap result = mViewToCapture.createSnapshot(Bitmap.Config.ARGB_8888, 0, skipChildren); + result.setHasAlpha(false); // resource will have no alpha, since content is opaque Bitmap golden = BitmapFactory.decodeResource(mActivity.getResources(), goldenResId); assertTrue(golden.sameAs(result)); } diff --git a/docs/__DEPRECATED__DO_NOT_EDIT__.txt b/docs/__DEPRECATED__DO_NOT_EDIT__.txt new file mode 100644 index 000000000000..3f8fc80c3b0b --- /dev/null +++ b/docs/__DEPRECATED__DO_NOT_EDIT__.txt @@ -0,0 +1,16 @@ +### DEPRECATED: DO NOT EDIT ### + +The source files for developer.android.com are NO LONGER MAINTAINED HERE, as +of 12/2016. Migration of content was completed on 10/16/2016. + +All authoring of content has been moved to Piper (go/dac-source). + +Exceptions and Caveats: + +- Reference documentation is still maintained via building of .java source files, + so you may continue to update JavaDoc comments to update documentation. + +- Sample code documentation is not maintained in Piper, but is published from + a separate code repository. + +For answers to further questions, please email: android-writers@google.com diff --git a/docs/html/__DEPRECATED__DO_NOT_EDIT__.txt b/docs/html/__DEPRECATED__DO_NOT_EDIT__.txt new file mode 100644 index 000000000000..3f8fc80c3b0b --- /dev/null +++ b/docs/html/__DEPRECATED__DO_NOT_EDIT__.txt @@ -0,0 +1,16 @@ +### DEPRECATED: DO NOT EDIT ### + +The source files for developer.android.com are NO LONGER MAINTAINED HERE, as +of 12/2016. Migration of content was completed on 10/16/2016. + +All authoring of content has been moved to Piper (go/dac-source). + +Exceptions and Caveats: + +- Reference documentation is still maintained via building of .java source files, + so you may continue to update JavaDoc comments to update documentation. + +- Sample code documentation is not maintained in Piper, but is published from + a separate code repository. + +For answers to further questions, please email: android-writers@google.com diff --git a/docs/html/reference/images/graphics/colorspace_ucs.png b/docs/html/reference/images/graphics/colorspace_ucs.png Binary files differnew file mode 100644 index 000000000000..3e0f0c6f0cc4 --- /dev/null +++ b/docs/html/reference/images/graphics/colorspace_ucs.png diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index cd75fe95121a..4b899f6e20b3 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -140,7 +140,7 @@ public final class Bitmap implements Parcelable { * Native bitmap has been reconfigured, so set premult and cached * width/height values */ - // called from JNI + @SuppressWarnings("unused") // called from JNI void reinit(int width, int height, boolean requestPremultiplied) { mWidth = width; mHeight = height; @@ -465,6 +465,15 @@ public final class Bitmap implements Parcelable { */ ARGB_8888 (5), + /** + * Each pixels is stored on 8 bytes. Each channel (RGB and alpha + * for translucency) is stored as a + * {@link android.util.Half half-precision floating point value}. + * + * This configuration is particularly suited for wide-gamut and + * HDR content. + */ + RGBA_F16 (6), /** * Special configuration, when bitmap is stored only in graphic memory. @@ -473,12 +482,12 @@ public final class Bitmap implements Parcelable { * It is optimal for cases, when the only operation with the bitmap is to draw it on a * screen. */ - HARDWARE (6); + HARDWARE (7); final int nativeInt; private static Config sConfigs[] = { - null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, HARDWARE + null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE }; Config(int ni) { @@ -583,9 +592,13 @@ public final class Bitmap implements Parcelable { * @param isMutable True if the resulting bitmap should be mutable (i.e. * its pixels can be modified) * @return the new bitmap, or null if the copy could not be made. + * @throws IllegalArgumentException if config is {@link Config#HARDWARE} and isMutable is true */ public Bitmap copy(Config config, boolean isMutable) { checkRecycled("Can't copy a recycled bitmap"); + if (config == Config.HARDWARE && isMutable) { + throw new IllegalArgumentException("Hardware bitmaps are always immutable"); + } Bitmap b = nativeCopy(mNativePtr, config.nativeInt, isMutable); if (b != null) { b.setPremultiplied(mRequestPremultiplied); @@ -760,6 +773,9 @@ public final class Bitmap implements Parcelable { case ALPHA_8: newConfig = Config.ALPHA_8; break; + case RGBA_F16: + newConfig = Config.RGBA_F16; + break; //noinspection deprecation case ARGB_4444: case ARGB_8888: @@ -781,8 +797,13 @@ public final class Bitmap implements Parcelable { neww = Math.round(deviceR.width()); newh = Math.round(deviceR.height()); - bitmap = createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig, - transformed || source.hasAlpha()); + Config transformedConfig = newConfig; + if (transformed) { + if (transformedConfig != Config.ARGB_8888 && transformedConfig != Config.RGBA_F16) { + transformedConfig = Config.ARGB_8888; + } + } + bitmap = createBitmap(neww, newh, transformedConfig, transformed || source.hasAlpha()); canvas.translate(-deviceR.left, -deviceR.top); canvas.concat(m); @@ -845,14 +866,14 @@ public final class Bitmap implements Parcelable { * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create. - * @param hasAlpha If the bitmap is ARGB_8888 this flag can be used to mark the - * bitmap as opaque. Doing so will clear the bitmap in black + * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to + * mark the bitmap as opaque. Doing so will clear the bitmap in black * instead of transparent. * * @throws IllegalArgumentException if the width or height are <= 0, or if * Config is Config.HARDWARE, because hardware bitmaps are always immutable */ - private static Bitmap createBitmap(int width, int height, Config config, boolean hasAlpha) { + public static Bitmap createBitmap(int width, int height, Config config, boolean hasAlpha) { return createBitmap(null, width, height, config, hasAlpha); } @@ -865,14 +886,14 @@ public final class Bitmap implements Parcelable { * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create. - * @param hasAlpha If the bitmap is ARGB_8888 this flag can be used to mark the - * bitmap as opaque. Doing so will clear the bitmap in black + * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to + * mark the bitmap as opaque. Doing so will clear the bitmap in black * instead of transparent. * * @throws IllegalArgumentException if the width or height are <= 0, or if * Config is Config.HARDWARE, because hardware bitmaps are always immutable */ - private static Bitmap createBitmap(DisplayMetrics display, int width, int height, + public static Bitmap createBitmap(DisplayMetrics display, int width, int height, Config config, boolean hasAlpha) { if (width <= 0 || height <= 0) { throw new IllegalArgumentException("width and height must be > 0"); @@ -885,7 +906,7 @@ public final class Bitmap implements Parcelable { bm.mDensity = display.densityDpi; } bm.setHasAlpha(hasAlpha); - if (config == Config.ARGB_8888 && !hasAlpha) { + if ((config == Config.ARGB_8888 || config == Config.RGBA_F16) && !hasAlpha) { nativeErase(bm.mNativePtr, 0xff000000); } // No need to initialize the bitmap to zeroes with other configs; @@ -1526,7 +1547,7 @@ public final class Bitmap implements Parcelable { /** * <p>Replace pixels in the bitmap with the colors in the array. Each element - * in the array is a packed int prepresenting a non-premultiplied ARGB + * in the array is a packed int representing a non-premultiplied ARGB * {@link Color}.</p> * * @param pixels The colors to write to the bitmap diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index a5517f091d3c..64a726babce0 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -51,8 +51,8 @@ public class BitmapFactory { /** * If set, decode methods that take the Options object will attempt to * reuse this bitmap when loading content. If the decode operation - * cannot use this bitmap, the decode method will return - * <code>null</code> and will throw an IllegalArgumentException. The + * cannot use this bitmap, the decode method will throw an + * {@link java.lang.IllegalArgumentException}. The * current implementation necessitates that the reused bitmap be * mutable, and the resulting reused bitmap will continue to remain * mutable even when decoding a resource which would normally result in diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 7dc5de3051e6..d968516f36bc 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -1671,6 +1671,28 @@ public abstract class ColorSpace { } /** + * Converts values from CIE xyY to CIE L*u*v*. Y is assumed to be 1 so the + * input xyY array only contains the x and y components. After this method + * returns, the xyY array contains the converted u and v components. + * + * @param xyY The xyY value to convert to XYZ, cannot be null, + * length must be a multiple of 2 + */ + private static void xyYToUv(@NonNull @Size(multiple = 2) float[] xyY) { + for (int i = 0; i < xyY.length; i += 2) { + float x = xyY[i]; + float y = xyY[i + 1]; + + float d = -2.0f * x + 12.0f * y + 3; + float u = (4.0f * x) / d; + float v = (9.0f * y) / d; + + xyY[i] = u; + xyY[i + 1] = v; + } + } + + /** * <p>Computes the chromatic adaptation transform from the specified * source white point to the specified destination white point.</p> * @@ -3162,10 +3184,10 @@ public abstract class ColorSpace { /** * <p>A color space renderer can be used to visualize and compare the gamut and * white point of one or more color spaces. The output is an sRGB {@link Bitmap} - * showing a CIE 1931 xyY chromaticity diagram.</p> + * showing a CIE 1931 xyY or a CIE 1976 UCS chromaticity diagram.</p> * * <p>The following code snippet shows how to compare the {@link Named#SRGB} - * and {@link Named#DCI_P3} color spaces:</p> + * and {@link Named#DCI_P3} color spaces in a CIE 1931 diagram:</p> * * <pre class="prettyprint"> * Bitmap bitmap = ColorSpace.createRenderer() @@ -3188,12 +3210,18 @@ public abstract class ColorSpace { */ public static class Renderer { private static final int NATIVE_SIZE = 1440; + private static final float UCS_SCALE = 9.0f / 6.0f; + + // Number of subdivision of the inside of the spectral locus + private static final int CHROMATICITY_RESOLUTION = 32; + private static final double ONE_THIRD = 1.0 / 3.0; @IntRange(from = 128, to = Integer.MAX_VALUE) private int mSize = 1024; private boolean mShowWhitePoint = true; private boolean mClip = false; + private boolean mUcs = false; private final List<Pair<ColorSpace, Integer>> mColorSpaces = new ArrayList<>(2); private final List<Point> mPoints = new ArrayList<>(0); @@ -3241,6 +3269,35 @@ public abstract class ColorSpace { } /** + * <p>Defines whether the chromaticity diagram should use the uniform + * chromaticity scale (CIE 1976 UCS). When the uniform chromaticity scale + * is used, the distance between two points on the diagram is approximately + * proportional to the perceived color difference.</p> + * + * <p>The following code snippet shows how to enable the uniform chromaticity + * scale. The image below shows the result:</p> + * <pre class="prettyprint"> + * Bitmap bitmap = ColorSpace.createRenderer() + * .uniformChromaticityScale(true) + * .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff) + * .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845) + * .render(); + * </pre> + * <p> + * <img src="{@docRoot}reference/android/images/graphics/colorspace_ucs.png" /> + * <figcaption style="text-align: center;">CIE 1976 UCS diagram</figcaption> + * </p> + * + * @param ucs True to render the chromaticity diagram as the CIE 1976 UCS diagram + * @return This instance of {@link Renderer} + */ + @NonNull + public Renderer uniformChromaticityScale(boolean ucs) { + mUcs = ucs; + return this; + } + + /** * Sets the dimensions (width and height) in pixels of the output bitmap. * The size must be at least 128px and defaults to 1024px. * @@ -3302,7 +3359,7 @@ public abstract class ColorSpace { * </pre> * <p> * <img src="{@docRoot}reference/android/images/graphics/colorspace_comparison2.png" /> - * <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption> + * <figcaption style="text-align: center;">sRGB, DCI-P3, ACES and scRGB</figcaption> * </p> * * @param colorSpace The color space whose gamut to render on the diagram @@ -3385,6 +3442,7 @@ public abstract class ColorSpace { setTransform(canvas, width, height, primaries); drawBox(canvas, width, height, paint, path); + setUcsTransform(canvas, height); drawLocus(canvas, width, height, paint, path, primaries); drawGamuts(canvas, width, height, paint, path, primaries, whitePoint); drawPoints(canvas, width, height, paint); @@ -3406,7 +3464,11 @@ public abstract class ColorSpace { paint.setStyle(Paint.Style.FILL); + float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f); + float[] v = new float[3]; + float[] xy = new float[2]; + for (final Point point : mPoints) { v[0] = point.mRgb[0]; v[1] = point.mRgb[1]; @@ -3415,10 +3477,13 @@ public abstract class ColorSpace { paint.setColor(point.mColor); - // XYZ to xyY, assuming Y=1.0 + // XYZ to xyY, assuming Y=1.0, then to L*u*v* if needed float sum = v[0] + v[1] + v[2]; - canvas.drawCircle(width * v[0] / sum, height - height * v[1] / sum, - 4.0f, paint); + xy[0] = v[0] / sum; + xy[1] = v[1] / sum; + if (mUcs) xyYToUv(xy); + + canvas.drawCircle(width * xy[0], height - height * xy[1], radius, paint); } } @@ -3440,6 +3505,8 @@ public abstract class ColorSpace { @NonNull Paint paint, @NonNull Path path, @NonNull @Size(6) float[] primaries, @NonNull @Size(2) float[] whitePoint) { + float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f); + for (final Pair<ColorSpace, Integer> item : mColorSpaces) { ColorSpace colorSpace = item.first; int color = item.second; @@ -3447,7 +3514,7 @@ public abstract class ColorSpace { if (colorSpace.getModel() != Model.RGB) continue; Rgb rgb = (Rgb) colorSpace; - getPrimaries(rgb, primaries); + getPrimaries(rgb, primaries, mUcs); path.rewind(); path.moveTo(width * primaries[0], height - height * primaries[1]); @@ -3462,11 +3529,12 @@ public abstract class ColorSpace { // Draw the white point if (mShowWhitePoint) { rgb.getWhitePoint(whitePoint); + if (mUcs) xyYToUv(whitePoint); paint.setStyle(Paint.Style.FILL); paint.setColor(color); - canvas.drawCircle(width * whitePoint[0], height - height * whitePoint[1], - 4.0f, paint); + canvas.drawCircle( + width * whitePoint[0], height - height * whitePoint[1], radius, paint); } } } @@ -3477,10 +3545,12 @@ public abstract class ColorSpace { * * @param rgb The color space whose primaries to extract * @param primaries A pre-allocated array of 6 floats that will hold the result + * @param asUcs True if the primaries should be returned in Luv, false for xyY */ @NonNull @Size(6) - private static float[] getPrimaries(@NonNull Rgb rgb, @NonNull @Size(6) float[] primaries) { + private static float[] getPrimaries(@NonNull Rgb rgb, + @NonNull @Size(6) float[] primaries, boolean asUcs) { // TODO: We should find a better way to handle these cases if (rgb.equals(ColorSpace.get(Named.EXTENDED_SRGB)) || rgb.equals(ColorSpace.get(Named.LINEAR_EXTENDED_SRGB))) { @@ -3490,9 +3560,11 @@ public abstract class ColorSpace { primaries[3] = 1.24f; primaries[4] = -0.23f; primaries[5] = -0.57f; - return primaries; + } else { + rgb.getPrimaries(primaries); } - return rgb.getPrimaries(primaries); + if (asUcs) xyYToUv(primaries); + return primaries; } /** @@ -3513,7 +3585,13 @@ public abstract class ColorSpace { int vertexCount = SPECTRUM_LOCUS_X.length * CHROMATICITY_RESOLUTION * 6; float[] vertices = new float[vertexCount * 2]; int[] colors = new int[vertices.length]; - computeChromaticityMesh(NATIVE_SIZE, NATIVE_SIZE, vertices, colors); + computeChromaticityMesh(vertices, colors); + + if (mUcs) xyYToUv(vertices); + for (int i = 0; i < vertices.length; i += 2) { + vertices[i] *= width; + vertices[i + 1] = height - vertices[i + 1] * height; + } // Draw the spectral locus if (mClip && mColorSpaces.size() > 0) { @@ -3522,7 +3600,8 @@ public abstract class ColorSpace { if (colorSpace.getModel() != Model.RGB) continue; Rgb rgb = (Rgb) colorSpace; - getPrimaries(rgb, primaries); + getPrimaries(rgb, primaries, mUcs); + break; } @@ -3559,6 +3638,7 @@ public abstract class ColorSpace { } path.close(); + paint.setStrokeWidth(4.0f / (mUcs ? UCS_SCALE : 1.0f)); paint.setStyle(Paint.Style.STROKE); paint.setColor(0xff000000); canvas.drawPath(path, paint); @@ -3576,25 +3656,38 @@ public abstract class ColorSpace { */ private void drawBox(@NonNull Canvas canvas, int width, int height, @NonNull Paint paint, @NonNull Path path) { + + int lineCount = 10; + float scale = 1.0f; + if (mUcs) { + lineCount = 7; + scale = UCS_SCALE; + } + // Draw the unit grid paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(2.0f); paint.setColor(0xffc0c0c0); - for (int i = 1; i <= 9; i++) { - canvas.drawLine(0.0f, height - (height * i / 10.0f), - 0.9f * width, height - (height * i / 10.0f), paint); - canvas.drawLine(width * i / 10.0f, height, - width * i / 10.0f, 0.1f * height, paint); + + for (int i = 1; i < lineCount - 1; i++) { + float v = i / 10.0f; + float x = (width * v) * scale; + float y = height - (height * v) * scale; + + canvas.drawLine(0.0f, y, 0.9f * width, y, paint); + canvas.drawLine(x, height, x, 0.1f * height, paint); } // Draw tick marks paint.setStrokeWidth(4.0f); paint.setColor(0xff000000); - for (int i = 1; i <= 9; i++) { - canvas.drawLine(0.0f, height - (height * i / 10.0f), - width / 100.0f, height - (height * i / 10.0f), paint); - canvas.drawLine(width * i / 10.0f, height, - width * i / 10.0f, height - (height / 100.0f), paint); + for (int i = 1; i < lineCount - 1; i++) { + float v = i / 10.0f; + float x = (width * v) * scale; + float y = height - (height * v) * scale; + + canvas.drawLine(0.0f, y, width / 100.0f, y, paint); + canvas.drawLine(x, height, x, height - (height / 100.0f), paint); } // Draw the axis labels @@ -3603,14 +3696,15 @@ public abstract class ColorSpace { paint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); Rect bounds = new Rect(); - for (int i = 1; i < 9; i++) { + for (int i = 1; i < lineCount - 1; i++) { String text = "0." + i; paint.getTextBounds(text, 0, text.length(), bounds); - float y = height - (height * i / 10.0f); - canvas.drawText(text, -0.05f * width + 10, y + bounds.height() / 2.0f, paint); + float v = i / 10.0f; + float x = (width * v) * scale; + float y = height - (height * v) * scale; - float x = width * i / 10.0f; + canvas.drawText(text, -0.05f * width + 10, y + bounds.height() / 2.0f, paint); canvas.drawText(text, x - bounds.width() / 2.0f, height + bounds.height() + 16, paint); } @@ -3643,7 +3737,7 @@ public abstract class ColorSpace { if (colorSpace.getModel() != Model.RGB) continue; Rgb rgb = (Rgb) colorSpace; - getPrimaries(rgb, primaries); + getPrimaries(rgb, primaries, mUcs); primariesBounds.left = Math.min(primariesBounds.left, primaries[4]); primariesBounds.top = Math.min(primariesBounds.top, primaries[5]); @@ -3651,26 +3745,42 @@ public abstract class ColorSpace { primariesBounds.bottom = Math.max(primariesBounds.bottom, primaries[3]); } + float max = mUcs ? 0.6f : 0.9f; + primariesBounds.left = Math.min(0.0f, primariesBounds.left); primariesBounds.top = Math.min(0.0f, primariesBounds.top); - primariesBounds.right = Math.max(0.9f, primariesBounds.right); - primariesBounds.bottom = Math.max(0.9f, primariesBounds.bottom); + primariesBounds.right = Math.max(max, primariesBounds.right); + primariesBounds.bottom = Math.max(max, primariesBounds.bottom); - float scaleX = 0.9f / primariesBounds.width(); - float scaleY = 0.9f / primariesBounds.height(); + float scaleX = max / primariesBounds.width(); + float scaleY = max / primariesBounds.height(); float scale = Math.min(scaleX, scaleY); canvas.scale(mSize / (float) NATIVE_SIZE, mSize / (float) NATIVE_SIZE); canvas.scale(scale, scale); canvas.translate( - (primariesBounds.width() - 0.9f) * width / 2.0f, - (primariesBounds.height() - 0.9f) * height / 2.0f); + (primariesBounds.width() - max) * width / 2.0f, + (primariesBounds.height() - max) * height / 2.0f); // The spectrum extends ~0.85 vertically and ~0.65 horizontally // We shift the canvas a little bit to get nicer margins canvas.translate(0.05f * width, -0.05f * height); } + /** + * Computes and applies the Canvas transforms required to render the CIE + * 197 UCS chromaticity diagram. + * + * @param canvas The canvas to transform + * @param height Height in pixel of the final image + */ + private void setUcsTransform(@NonNull Canvas canvas, int height) { + if (mUcs) { + canvas.translate(0.0f, (height - height * UCS_SCALE)); + canvas.scale(UCS_SCALE, UCS_SCALE); + } + } + // X coordinates of the spectral locus in CIE 1931 private static final float[] SPECTRUM_LOCUS_X = { 0.175596f, 0.172787f, 0.170806f, 0.170085f, 0.160343f, @@ -3716,21 +3826,15 @@ public abstract class ColorSpace { 0.037799f, 0.029673f, 0.021547f, 0.013421f, 0.005295f }; - // Number of subdivision of the inside of the spectral locus - private static final int CHROMATICITY_RESOLUTION = 32; - private static final double ONE_THIRD = 1.0 / 3.0; - /** * Computes a 2D mesh representation of the CIE 1931 chromaticity * diagram. * - * @param width Width in pixels of the mesh - * @param height Height in pixels of the mesh * @param vertices Array of floats that will hold the mesh vertices * @param colors Array of floats that will hold the mesh colors */ - private static void computeChromaticityMesh(int width, int height, - @NonNull float[] vertices, @NonNull int[] colors) { + private static void computeChromaticityMesh(@NonNull float[] vertices, + @NonNull int[] colors) { ColorSpace colorSpace = get(Named.SRGB); @@ -3796,18 +3900,18 @@ public abstract class ColorSpace { colorIndex += 6; // Flip the mesh upside down to match Canvas' coordinates system - vertices[vertexIndex++] = v1x * width; - vertices[vertexIndex++] = height - v1y * height; - vertices[vertexIndex++] = v2x * width; - vertices[vertexIndex++] = height - v2y * height; - vertices[vertexIndex++] = v3x * width; - vertices[vertexIndex++] = height - v3y * height; - vertices[vertexIndex++] = v1x * width; - vertices[vertexIndex++] = height - v1y * height; - vertices[vertexIndex++] = v3x * width; - vertices[vertexIndex++] = height - v3y * height; - vertices[vertexIndex++] = v4x * width; - vertices[vertexIndex++] = height - v4y * height; + vertices[vertexIndex++] = v1x; + vertices[vertexIndex++] = v1y; + vertices[vertexIndex++] = v2x; + vertices[vertexIndex++] = v2y; + vertices[vertexIndex++] = v3x; + vertices[vertexIndex++] = v3y; + vertices[vertexIndex++] = v1x; + vertices[vertexIndex++] = v1y; + vertices[vertexIndex++] = v3x; + vertices[vertexIndex++] = v3y; + vertices[vertexIndex++] = v4x; + vertices[vertexIndex++] = v4y; } } } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index fc873c40524f..7815ae16e7e2 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -1357,7 +1357,7 @@ public class Paint { /** * Return the paint's text size. * - * @return the paint's text size. + * @return the paint's text size in pixel units. */ public float getTextSize() { return nGetTextSize(mNativePaint); @@ -1366,7 +1366,7 @@ public class Paint { /** * Set the paint's text size. This value must be > 0 * - * @param textSize set the paint's text size. + * @param textSize set the paint's text size in pixel units. */ public void setTextSize(float textSize) { nSetTextSize(mNativePaint, textSize); diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java index 98082cae8379..c3a944327fb5 100644 --- a/graphics/java/android/graphics/PixelFormat.java +++ b/graphics/java/android/graphics/PixelFormat.java @@ -22,13 +22,17 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; public class PixelFormat { - /** @hide */ @IntDef({UNKNOWN, TRANSLUCENT, TRANSPARENT, OPAQUE}) @Retention(RetentionPolicy.SOURCE) public @interface Opacity {} - /* these constants need to match those in hardware/hardware.h */ + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({RGBA_8888, RGBX_8888, RGBA_F16, RGB_888, RGB_565}) + public @interface Format { }; + + // NOTE: these constants must match the values from graphics/common/x.x/types.hal public static final int UNKNOWN = 0; @@ -62,7 +66,6 @@ public class PixelFormat { @Deprecated public static final int RGB_332 = 0xB; - /** * @deprecated use {@link android.graphics.ImageFormat#NV16 * ImageFormat.NV16} instead. @@ -84,6 +87,8 @@ public class PixelFormat { @Deprecated public static final int YCbCr_422_I = 0x14; + public static final int RGBA_F16 = 0x16; + /** * @deprecated use {@link android.graphics.ImageFormat#JPEG * ImageFormat.JPEG} instead. @@ -91,7 +96,10 @@ public class PixelFormat { @Deprecated public static final int JPEG = 0x100; - public static void getPixelFormatInfo(int format, PixelFormat info) { + public int bytesPerPixel; + public int bitsPerPixel; + + public static void getPixelFormatInfo(@Format int format, PixelFormat info) { switch (format) { case RGBA_8888: case RGBX_8888: @@ -124,18 +132,23 @@ public class PixelFormat { info.bitsPerPixel = 12; info.bytesPerPixel = 1; break; + case RGBA_F16: + info.bitsPerPixel = 64; + info.bytesPerPixel = 8; + break; default: throw new IllegalArgumentException("unknown pixel format " + format); } } - public static boolean formatHasAlpha(int format) { + public static boolean formatHasAlpha(@Format int format) { switch (format) { case PixelFormat.A_8: case PixelFormat.LA_88: case PixelFormat.RGBA_4444: case PixelFormat.RGBA_5551: case PixelFormat.RGBA_8888: + case PixelFormat.RGBA_F16: case PixelFormat.TRANSLUCENT: case PixelFormat.TRANSPARENT: return true; @@ -143,9 +156,6 @@ public class PixelFormat { return false; } - public int bytesPerPixel; - public int bitsPerPixel; - /** * Determine whether or not this is a public-visible and non-deprecated {@code format}. * @@ -159,12 +169,13 @@ public class PixelFormat { * * @hide */ - public static boolean isPublicFormat(int format) { + public static boolean isPublicFormat(@Format int format) { switch (format) { case RGBA_8888: case RGBX_8888: case RGB_888: case RGB_565: + case RGBA_F16: return true; } diff --git a/libs/hwui/OpenGLReadback.cpp b/libs/hwui/OpenGLReadback.cpp index 4c8abc5da832..938b6efa7901 100644 --- a/libs/hwui/OpenGLReadback.cpp +++ b/libs/hwui/OpenGLReadback.cpp @@ -131,6 +131,13 @@ inline CopyResult copyTextureInto(Caches& caches, RenderState& renderState, destWidth, destHeight, caches.maxTextureSize); return CopyResult::DestinationInvalid; } + + // TODO: Add support for RGBA_F16 destinations + if (bitmap->colorType() == kRGBA_F16_SkColorType) { + ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported"); + return CopyResult::DestinationInvalid; + } + GLuint fbo = renderState.createFramebuffer(); if (!fbo) { ALOGW("Could not obtain an FBO"); diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp index 5b5b74e1c3f3..705395e1e2b7 100644 --- a/libs/hwui/Texture.cpp +++ b/libs/hwui/Texture.cpp @@ -225,6 +225,12 @@ void Texture::colorTypeToGlFormatAndType(const Caches& caches, SkColorType color *outInternalFormat = GL_LUMINANCE; *outType = GL_UNSIGNED_BYTE; break; + case kRGBA_F16_SkColorType: + // This format is always linear + *outFormat = GL_RGBA; + *outInternalFormat = GL_RGBA16F; + *outType = GL_HALF_FLOAT; + break; default: LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", colorType); break; diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 2177af1773e5..f6585d6d04c1 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -98,8 +98,6 @@ static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, si // TODO: handle SRGB sanely static PixelFormat internalFormatToPixelFormat(GLint internalFormat) { switch (internalFormat) { - case GL_ALPHA: - return PIXEL_FORMAT_TRANSPARENT; case GL_LUMINANCE: return PIXEL_FORMAT_RGBA_8888; case GL_SRGB8_ALPHA8: @@ -108,6 +106,8 @@ static PixelFormat internalFormatToPixelFormat(GLint internalFormat) { return PIXEL_FORMAT_RGBA_8888; case GL_RGB: return PIXEL_FORMAT_RGB_565; + case GL_RGBA16F: + return PIXEL_FORMAT_RGBA_FP16; default: LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", internalFormat); return PIXEL_FORMAT_UNKNOWN; @@ -215,8 +215,8 @@ sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(uirenderer::renderthread::RenderThr } const SkImageInfo& info = skBitmap.info(); - if (info.colorType() == kUnknown_SkColorType) { - ALOGW("unable to create hardware bitmap of configuration"); + if (info.colorType() == kUnknown_SkColorType || info.colorType() == kAlpha_8_SkColorType) { + ALOGW("unable to create hardware bitmap of colortype: %d", info.colorType()); return nullptr; } @@ -249,7 +249,7 @@ sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(uirenderer::renderthread::RenderThr if (!uploadBitmapToGraphicBuffer(caches, bitmap, *buffer, format, type)) { return nullptr; } - return sk_sp<Bitmap>(new Bitmap(buffer.get(), info)); + return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info())); } sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(SkBitmap& bitmap) { @@ -306,11 +306,13 @@ sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, SkPixelRef& pixelRef) sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer) { PixelFormat format = graphicBuffer->getPixelFormat(); - if (!graphicBuffer.get() || format != PIXEL_FORMAT_RGBA_8888) { + if (!graphicBuffer.get() || + (format != PIXEL_FORMAT_RGBA_8888 && format != PIXEL_FORMAT_RGBA_FP16)) { return nullptr; } SkImageInfo info = SkImageInfo::Make(graphicBuffer->getWidth(), graphicBuffer->getHeight(), - kRGBA_8888_SkColorType, kPremul_SkAlphaType); + kRGBA_8888_SkColorType, kPremul_SkAlphaType, + SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named)); return sk_sp<Bitmap>(new Bitmap(graphicBuffer.get(), info)); } diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 376346f05e1b..430d6bea70c1 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -100,7 +100,7 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) int saveCount = layerCanvas->save(); SkASSERT(saveCount == 1); - layerCanvas->clipRect(layerDamage.toSkRect(), SkClipOp::kReplace); + layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect()); auto savedLightCenter = mLightCenter; // map current light center into RenderNode's coordinate space @@ -233,8 +233,8 @@ static Rect nodeBounds(RenderNode& node) { void SkiaPipeline::renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect &contentDrawBounds, SkCanvas* canvas) { - - canvas->clipRect(clip, SkClipOp::kReplace); + SkAutoCanvasRestore saver(canvas, true); + canvas->androidFramework_setDeviceClipRestriction(clip.roundOut()); if (!opaque) { canvas->clear(SK_ColorTRANSPARENT); @@ -242,7 +242,6 @@ void SkiaPipeline::renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& if (1 == nodes.size()) { if (!nodes[0]->nothingToDraw()) { - SkAutoCanvasRestore acr(canvas, true); RenderNodeDrawable root(nodes[0].get(), canvas); root.draw(canvas); } diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 722faf6268cd..f3a663e802da 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -324,11 +324,8 @@ RENDERTHREAD_TEST(SkiaPipeline, clip_replace) { } void onDrawPaint(const SkPaint&) { EXPECT_EQ(0, mDrawCounter++); - //TODO: this unit test is failing on the commented check below, because of a missing - //feature. In Snapshot::applyClip HWUI is intersecting the clip with the clip root, - //even for kReplace_Op clips. We need to implement the same for Skia pipelines. - //EXPECT_EQ(SkRect::MakeLTRB(20, 10, 30, 40), TestUtils::getClipBounds(this)) //got instead 20 0 30 50 - // << "Expect resolved clip to be intersection of viewport clip and clip op"; + EXPECT_EQ(SkRect::MakeLTRB(20, 10, 30, 40), TestUtils::getClipBounds(this)) + << "Expect resolved clip to be intersection of viewport clip and clip op"; } int mDrawCounter = 0; }; diff --git a/libs/incident/Android.mk b/libs/incident/Android.mk new file mode 100644 index 000000000000..439e86deba5d --- /dev/null +++ b/libs/incident/Android.mk @@ -0,0 +1,41 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := libincident + +LOCAL_CFLAGS := \ + -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter + +LOCAL_SHARED_LIBRARIES := \ + libbinder \ + liblog \ + libutils + +LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/../../core/java +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include + +LOCAL_SRC_FILES := \ + ../../core/java/android/os/IIncidentManager.aidl \ + ../../core/java/android/os/IIncidentReportCompletedListener.aidl \ + ../../core/java/android/os/IIncidentReportStatusListener.aidl \ + src/IncidentReportArgs.cpp + +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include + +include $(BUILD_SHARED_LIBRARY) + diff --git a/libs/incident/include/android/os/IncidentReportArgs.h b/libs/incident/include/android/os/IncidentReportArgs.h new file mode 100644 index 000000000000..956ef6c39b99 --- /dev/null +++ b/libs/incident/include/android/os/IncidentReportArgs.h @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_OS_DUMPSTATE_ARGS_H_ +#define ANDROID_OS_DUMPSTATE_ARGS_H_ + +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <utils/String16.h> + +#include <set> +#include <vector> + +namespace android { +namespace os { + +using namespace std; + +class IncidentReportArgs : public Parcelable { +public: + IncidentReportArgs(); + explicit IncidentReportArgs(const IncidentReportArgs& that); + virtual ~IncidentReportArgs(); + + virtual status_t writeToParcel(Parcel* out) const; + virtual status_t readFromParcel(const Parcel* in); + + void setAll(bool all); + void addSection(int section); + void addHeader(const vector<int8_t>& header); + + inline bool all() const { return mAll; }; + bool containsSection(int section) const; + + inline const set<int>& sections() const { return mSections; } + inline const vector<vector<int8_t>>& headers() const { return mHeaders; } + + void merge(const IncidentReportArgs& that); + +private: + set<int> mSections; + vector<vector<int8_t>> mHeaders; + bool mAll; +}; + +} +} + +#endif // ANDROID_OS_DUMPSTATE_ARGS_H_ diff --git a/libs/incident/proto/android/privacy.proto b/libs/incident/proto/android/privacy.proto new file mode 100644 index 000000000000..ae5af0e4afa7 --- /dev/null +++ b/libs/incident/proto/android/privacy.proto @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +option java_package = "android"; +option java_multiple_files = true; + +import "google/protobuf/descriptor.proto"; + +package android; + +// TODO: It's better to track this by semantic types and set policy for those. +// Do this for now to bootstrap the tools. +enum Destination { + // Fields or messages annotated with DEST_LOCAL must never be + // extracted from the device automatically. They can be accessed + // by tools on the developer's workstation, and if they are sent + // to another device that must be by the user, with a PII warning. (TBD) + DEST_LOCAL = 0; + + // Fields or messages annotated with DEST_EXPLICIT can be sent + // off the device with an explicit user action. + DEST_EXPLICIT = 1; + + // Fields or messages annotated with DEST_LOCAL can be sent by + // automatic means, without per-sending user consent. The user + // still must have previously accepted a consent to share this + // information. + DEST_AUTOMATIC = 2; + + // There is no more permissive option than DEST_AUTOMATIC. +} + +message PrivacyFlags { + optional Destination dest = 1 [ + default = DEST_LOCAL + ]; +} + +extend google.protobuf.FieldOptions { + // Flags for automatically filtering statistics + optional PrivacyFlags privacy = 102672883; +} diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp new file mode 100644 index 000000000000..f60490911aed --- /dev/null +++ b/libs/incident/src/IncidentReportArgs.cpp @@ -0,0 +1,172 @@ +/** + * Copyright (c) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "dumpstate" + +#include <android/os/IncidentReportArgs.h> + +#include <cutils/log.h> + +namespace android { +namespace os { + +IncidentReportArgs::IncidentReportArgs() + :mSections(), + mAll(false) +{ +} + +IncidentReportArgs::IncidentReportArgs(const IncidentReportArgs& that) + :mSections(that.mSections), + mHeaders(that.mHeaders), + mAll(that.mAll) +{ +} + +IncidentReportArgs::~IncidentReportArgs() +{ +} + +status_t +IncidentReportArgs::writeToParcel(Parcel* out) const +{ + status_t err; + + err = out->writeInt32(mAll); + if (err != NO_ERROR) { + return err; + } + + err = out->writeInt32(mSections.size()); + if (err != NO_ERROR) { + return err; + } + + for (set<int>::const_iterator it=mSections.begin(); it!=mSections.end(); it++) { + err = out->writeInt32(*it); + if (err != NO_ERROR) { + return err; + } + } + + err = out->writeInt32(mHeaders.size()); + if (err != NO_ERROR) { + return err; + } + + for (vector<vector<int8_t>>::const_iterator it = mHeaders.begin(); it != mHeaders.end(); it++) { + err = out->writeByteVector(*it); + if (err != NO_ERROR) { + return err; + } + } + + return NO_ERROR; +} + +status_t +IncidentReportArgs::readFromParcel(const Parcel* in) +{ + status_t err; + + int32_t all; + err = in->readInt32(&all); + if (err != NO_ERROR) { + return err; + } + if (all != 0) { + mAll = all; + } + + mSections.clear(); + int32_t sectionCount; + err = in->readInt32(§ionCount); + if (err != NO_ERROR) { + return err; + } + for (int i=0; i<sectionCount; i++) { + int32_t section; + err = in->readInt32(§ion); + if (err != NO_ERROR) { + return err; + } + + mSections.insert(section); + } + + int32_t headerCount; + err = in->readInt32(&headerCount); + if (err != NO_ERROR) { + return err; + } + mHeaders.resize(headerCount); + for (int i=0; i<headerCount; i++) { + err = in->readByteVector(&mHeaders[i]); + if (err != NO_ERROR) { + return err; + } + } + + return OK; +} + +void +IncidentReportArgs::setAll(bool all) +{ + mAll = all; + if (all) { + mSections.clear(); + } +} + +void +IncidentReportArgs::addSection(int section) +{ + if (!mAll) { + mSections.insert(section); + } +} + +void +IncidentReportArgs::addHeader(const vector<int8_t>& header) +{ + mHeaders.push_back(header); +} + +bool +IncidentReportArgs::containsSection(int section) const +{ + return mAll || mSections.find(section) != mSections.end(); +} + +void +IncidentReportArgs::merge(const IncidentReportArgs& that) +{ + if (mAll) { + return; + } else if (that.mAll) { + mAll = true; + mSections.clear(); + } else { + for (set<int>::const_iterator it=that.mSections.begin(); + it!=that.mSections.end(); it++) { + mSections.insert(*it); + } + } +} + +} +} diff --git a/libs/services/Android.mk b/libs/services/Android.mk new file mode 100644 index 000000000000..cbfd4b3f9f10 --- /dev/null +++ b/libs/services/Android.mk @@ -0,0 +1,43 @@ +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) + +# Provides C++ wrappers for system services. + +include $(CLEAR_VARS) + +LOCAL_MODULE := libservices +LOCAL_SRC_FILES := \ + ../../core/java/com/android/internal/os/IDropBoxManagerService.aidl \ + src/os/DropBoxManager.cpp + +LOCAL_AIDL_INCLUDES := \ + $(LOCAL_PATH)/../../core/java +LOCAL_C_INCLUDES := \ + system/core/include +LOCAL_SHARED_LIBRARIES := \ + libbinder \ + liblog \ + libcutils \ + libutils + +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +LOCAL_C_INCLUDES += $(LOCAL_PATH)/include + +LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code + +include $(BUILD_SHARED_LIBRARY) + + diff --git a/libs/services/include/android/os/DropBoxManager.h b/libs/services/include/android/os/DropBoxManager.h new file mode 100644 index 000000000000..8717178bb7d6 --- /dev/null +++ b/libs/services/include/android/os/DropBoxManager.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_OS_DROPBOXMANAGER_H +#define _ANDROID_OS_DROPBOXMANAGER_H + +#include <android-base/unique_fd.h> +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <binder/Status.h> +#include <utils/RefBase.h> + +#include <vector> + +namespace android { +namespace os { + +using namespace android; +using namespace android::base; +using namespace android::binder; +using namespace std; + +class DropBoxManager : public virtual RefBase +{ +public: + enum { + IS_EMPTY = 1, + IS_TEXT = 2, + IS_GZIPPED = 4 + }; + + DropBoxManager(); + virtual ~DropBoxManager(); + + static sp<DropBoxManager> create(); + + // Create a new entry with plain text contents. + Status addText(const String16& tag, const string& text); + + // Create a new Entry with byte array contents. Makes a copy of the data. + Status addData(const String16& tag, uint8_t const* data, size_t size, int flags); + + // Create a new Entry from a file. The file will be opened in this process + // and a handle will be passed to the system process, so no additional permissions + // are required from the system process. Returns NULL if the file can't be opened. + Status addFile(const String16& tag, const string& filename, int flags); + + class Entry : public virtual RefBase, public Parcelable { + public: + Entry(); + virtual ~Entry(); + + virtual status_t writeToParcel(Parcel* out) const; + virtual status_t readFromParcel(const Parcel* in); + + private: + Entry(const String16& tag, int32_t flags); + Entry(const String16& tag, int32_t flags, int fd); + + String16 mTag; + int64_t mTimeMillis; + int32_t mFlags; + + vector<uint8_t> mData; + unique_fd mFd; + + friend class DropBoxManager; + }; + +private: + enum { + HAS_BYTE_ARRAY = 8 + }; + + Status add(const Entry& entry); +}; + +}} // namespace android::os + +#endif // _ANDROID_OS_DROPBOXMANAGER_H + diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp new file mode 100644 index 000000000000..bbb45f022a87 --- /dev/null +++ b/libs/services/src/os/DropBoxManager.cpp @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "DropBoxManager" + +#include <android/os/DropBoxManager.h> + +#include <binder/IServiceManager.h> +#include <com/android/internal/os/IDropBoxManagerService.h> +#include <cutils/log.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +namespace android { +namespace os { + +using namespace ::com::android::internal::os; + +DropBoxManager::Entry::Entry() + :mTag(), + mTimeMillis(0), + mFlags(IS_EMPTY), + mData(), + mFd() +{ + mFlags = IS_EMPTY; +} + +DropBoxManager::Entry::Entry(const String16& tag, int32_t flags) + :mTag(tag), + mTimeMillis(0), + mFlags(flags), + mData(), + mFd() +{ +} + +DropBoxManager::Entry::Entry(const String16& tag, int32_t flags, int fd) + :mTag(tag), + mTimeMillis(0), + mFlags(flags), + mData(), + mFd(fd) +{ +} + +DropBoxManager::Entry::~Entry() +{ +} + +status_t +DropBoxManager::Entry::writeToParcel(Parcel* out) const +{ + status_t err; + + err = out->writeString16(mTag); + if (err != NO_ERROR) { + return err; + } + + err = out->writeInt64(mTimeMillis); + if (err != NO_ERROR) { + return err; + } + + if (mFd.get() != -1) { + err = out->writeInt32(mFlags & ~HAS_BYTE_ARRAY); // Clear bit just to be safe + if (err != NO_ERROR) { + return err; + } + ALOGD("writing fd %d\n", mFd.get()); + err = out->writeParcelFileDescriptor(mFd); + if (err != NO_ERROR) { + return err; + } + } else { + err = out->writeInt32(mFlags | HAS_BYTE_ARRAY); + if (err != NO_ERROR) { + return err; + } + err = out->writeByteVector(mData); + if (err != NO_ERROR) { + return err; + } + } + return NO_ERROR; +} + +status_t +DropBoxManager::Entry::readFromParcel(const Parcel* in) +{ + status_t err; + + err = in->readString16(&mTag); + if (err != NO_ERROR) { + return err; + } + + err = in->readInt64(&mTimeMillis); + if (err != NO_ERROR) { + return err; + } + + err = in->readInt32(&mFlags); + if (err != NO_ERROR) { + return err; + } + + if ((mFlags & HAS_BYTE_ARRAY) != 0) { + err = in->readByteVector(&mData); + if (err != NO_ERROR) { + return err; + } + mFlags &= ~HAS_BYTE_ARRAY; + } else { + int fd; + fd = in->readParcelFileDescriptor(); + if (fd == -1) { + return EBADF; + } + fd = dup(fd); + if (fd == -1) { + return errno; + } + mFd.reset(fd); + } + + return NO_ERROR; +} + + +DropBoxManager::DropBoxManager() +{ +} + +DropBoxManager::~DropBoxManager() +{ +} + +Status +DropBoxManager::addText(const String16& tag, const string& text) +{ + Entry entry(tag, IS_TEXT); + entry.mData.assign(text.c_str(), text.c_str() + text.size()); + return add(entry); +} + +Status +DropBoxManager::addData(const String16& tag, uint8_t const* data, + size_t size, int flags) +{ + Entry entry(tag, flags); + entry.mData.assign(data, data+size); + return add(entry); +} + +Status +DropBoxManager::addFile(const String16& tag, const string& filename, int flags) +{ + int fd = open(filename.c_str(), O_RDONLY); + if (fd == -1) { + string message("addFile can't open file: "); + message += filename; + ALOGW("DropboxManager: %s", message.c_str()); + return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, message.c_str()); + } + + Entry entry(tag, flags, fd); + return add(entry); +} + +Status +DropBoxManager::add(const Entry& entry) +{ + sp<IDropBoxManagerService> service = interface_cast<IDropBoxManagerService>( + defaultServiceManager()->getService(android::String16("dropbox"))); + if (service == NULL) { + return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service"); + } + return service->add(entry); +} + +}} // namespace android::os + diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java index 03f0b4d5d5da..23a8655a3bb6 100644 --- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -63,6 +63,7 @@ public class CaptivePortalLoginActivity extends Activity { private enum Result { DISMISSED, UNWANTED, WANTED_AS_IS }; private URL mUrl; + private String mUserAgent; private Network mNetwork; private CaptivePortal mCaptivePortal; private NetworkCallback mNetworkCallback; @@ -76,6 +77,8 @@ public class CaptivePortalLoginActivity extends Activity { mCm = ConnectivityManager.from(this); mNetwork = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_NETWORK); mCaptivePortal = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL); + mUserAgent = getIntent().getParcelableExtra( + ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT); mUrl = getUrl(); if (mUrl == null) { // getUrl() failed to parse the url provided in the intent: bail out in a way that @@ -252,6 +255,7 @@ public class CaptivePortalLoginActivity extends Activity { } private void testForCaptivePortal() { + // TODO: reuse NetworkMonitor facilities for consistent captive portal detection. new Thread(new Runnable() { public void run() { // Give time for captive portal to open. @@ -262,11 +266,14 @@ public class CaptivePortalLoginActivity extends Activity { HttpURLConnection urlConnection = null; int httpResponseCode = 500; try { - urlConnection = (HttpURLConnection) mUrl.openConnection(); + urlConnection = (HttpURLConnection) mNetwork.openConnection(mUrl); urlConnection.setInstanceFollowRedirects(false); urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); urlConnection.setUseCaches(false); + if (mUserAgent != null) { + urlConnection.setRequestProperty("User-Agent", mUserAgent); + } urlConnection.getInputStream(); httpResponseCode = urlConnection.getResponseCode(); } catch (IOException e) { diff --git a/packages/PrintRecommendationService/AndroidManifest.xml b/packages/PrintRecommendationService/AndroidManifest.xml index c6736d7bac23..2e9342c9354a 100644 --- a/packages/PrintRecommendationService/AndroidManifest.xml +++ b/packages/PrintRecommendationService/AndroidManifest.xml @@ -18,11 +18,11 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.printservice.recommendation" - android:versionCode="1" - android:versionName="1.0.0"> + android:versionCode="2" + android:versionName="1.1.0"> <uses-sdk android:minSdkVersion="24" - android:targetSdkVersion="24" /> + android:targetSdkVersion="25" /> <uses-permission android:name="android.permission.INTERNET" /> diff --git a/packages/SettingsLib/res/layout/settings_with_drawer.xml b/packages/SettingsLib/res/layout/settings_with_drawer.xml index 1d080196485b..a68a44e09371 100644 --- a/packages/SettingsLib/res/layout/settings_with_drawer.xml +++ b/packages/SettingsLib/res/layout/settings_with_drawer.xml @@ -37,7 +37,8 @@ android:layout_height="wrap_content" android:navigationContentDescription="@*android:string/action_bar_up_description" android:theme="?android:attr/actionBarTheme" - style="?android:attr/toolbarStyle"/> + style="?android:attr/toolbarStyle" + android:background="?android:attr/colorPrimary" /> </FrameLayout> <FrameLayout android:id="@+id/content_header_container" diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java index f3658c38ca35..6ccba92e2e5f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java @@ -50,19 +50,25 @@ public class CategoryManager { private final Map<String, DashboardCategory> mCategoryByKeyMap; private List<DashboardCategory> mCategories; + private String mExtraAction; public static CategoryManager get(Context context) { + return get(context, null); + } + + public static CategoryManager get(Context context, String action) { if (sInstance == null) { - sInstance = new CategoryManager(context); + sInstance = new CategoryManager(context, action); } return sInstance; } - CategoryManager(Context context) { + CategoryManager(Context context, String action) { mTileByComponentCache = new ArrayMap<>(); mCategoryByKeyMap = new ArrayMap<>(); mInterestingConfigChanges = new InterestingConfigChanges(); mInterestingConfigChanges.applyNewConfig(context.getResources()); + mExtraAction = action; } public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) { @@ -111,7 +117,7 @@ public class CategoryManager { } mCategoryByKeyMap.clear(); mCategories = TileUtils.getCategories(context, mTileByComponentCache, - false /* categoryDefinedInManifest */); + false /* categoryDefinedInManifest */, mExtraAction); for (DashboardCategory category : mCategories) { mCategoryByKeyMap.put(category.key, category); } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java index d12e8c09b8c0..6c5a09d23c61 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java @@ -144,6 +144,19 @@ public class TileUtils { */ public static List<DashboardCategory> getCategories(Context context, Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest) { + return getCategories(context, cache, categoryDefinedInManifest, null); + } + + /** + * Build a list of DashboardCategory. + * @param categoryDefinedInManifest If true, an dummy activity must exists in manifest to + * represent this category (eg: .Settings$DeviceSettings) + * @param extraAction additional intent filter action to be used to build the dashboard + * categories + */ + public static List<DashboardCategory> getCategories(Context context, + Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest, + String extraAction) { final long startTime = System.currentTimeMillis(); boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) != 0; @@ -162,6 +175,9 @@ public class TileUtils { if (setup) { getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false); getTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false); + if (extraAction != null) { + getTilesForAction(context, user, extraAction, cache, null, tiles, false); + } } } diff --git a/packages/SettingsLib/tests/robotests/Android.mk b/packages/SettingsLib/tests/robotests/Android.mk index c1108f6fcc91..208fa6df05ee 100644 --- a/packages/SettingsLib/tests/robotests/Android.mk +++ b/packages/SettingsLib/tests/robotests/Android.mk @@ -26,7 +26,7 @@ LOCAL_MODULE_TAGS := optional LOCAL_PRIVILEGED_MODULE := true LOCAL_JAVA_LIBRARIES := \ - junit4-target \ + junit \ platform-robolectric-prebuilt LOCAL_STATIC_JAVA_LIBRARIES := \ @@ -55,7 +55,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ truth-prebuilt LOCAL_JAVA_LIBRARIES := \ - junit4-target \ + junit \ platform-robolectric-prebuilt LOCAL_INSTRUMENTATION_FOR := SettingsLibShell diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java index 86b210ac4aef..c6c6aad22cf0 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java @@ -26,6 +26,8 @@ import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.os.Bundle; import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings.Global; import android.util.ArrayMap; import android.util.Pair; @@ -34,6 +36,7 @@ import com.android.settingslib.TestConfig; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; @@ -46,6 +49,7 @@ import java.util.Map; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; @@ -59,6 +63,8 @@ public class TileUtilsTest { private PackageManager mPackageManager; @Mock private Resources mResources; + @Mock + private UserManager mUserManager; @Before public void setUp() throws NameNotFoundException { @@ -127,6 +133,30 @@ public class TileUtilsTest { assertThat(outTiles.isEmpty()).isTrue(); } + @Test + public void getCategories_shouldHandleExtraIntentAction() { + final String testCategory = "category1"; + final String testAction = "action1"; + Map<Pair<String, String>, Tile> cache = new ArrayMap<>(); + List<ResolveInfo> info = new ArrayList<>(); + info.add(newInfo(true, testCategory)); + Global.putInt(mContext.getContentResolver(), Global.DEVICE_PROVISIONED, 1); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + List<UserHandle> userHandleList = new ArrayList<>(); + userHandleList.add(UserHandle.CURRENT); + when(mUserManager.getUserProfiles()).thenReturn(userHandleList); + + when(mPackageManager.queryIntentActivitiesAsUser(argThat(new ArgumentMatcher<Intent>() { + public boolean matches(Object event) { + return testAction.equals(((Intent) event).getAction()); + } + }), anyInt(), anyInt())).thenReturn(info); + + List<DashboardCategory> categoryList = TileUtils.getCategories( + mContext, cache, false /* categoryDefinedInManifest */, testAction); + + assertThat(categoryList.get(0).tiles.get(0).category).isEqualTo(testCategory); + } private ResolveInfo newInfo(boolean systemApp, String category) { return newInfo(systemApp, category, null); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index d232e00d02e2..5c2787b0f04a 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -287,22 +287,6 @@ </intent-filter> </activity> - <activity android:name=".recents.grid.RecentsGridActivity" - android:label="@string/accessibility_desc_recent_apps" - android:exported="false" - android:launchMode="singleInstance" - android:excludeFromRecents="true" - android:stateNotNeeded="true" - android:resumeWhilePausing="true" - android:screenOrientation="behind" - android:resizeableActivity="true" - android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" - android:theme="@style/RecentsTheme.Grid"> - <intent-filter> - <action android:name="com.android.systemui.recents.TOGGLE_RECENTS" /> - </intent-filter> - </activity> - <activity android:name=".recents.tv.RecentsTvActivity" android:label="@string/accessibility_desc_recent_apps" android:exported="false" diff --git a/packages/SystemUI/res/drawable/header_dot.xml b/packages/SystemUI/res/drawable/header_dot.xml index 568a9c29175a..dbc6b378bd63 100644 --- a/packages/SystemUI/res/drawable/header_dot.xml +++ b/packages/SystemUI/res/drawable/header_dot.xml @@ -2,26 +2,27 @@ <!-- ** Copyright 2015, The Android Open Source Project ** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at ** -** http://www.apache.org/licenses/LICENSE-2.0 +** http://www.apache.org/licenses/LICENSE-2.0 ** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and ** limitations under the License. --> <shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="oval"> + android:shape="oval" + android:tint="?android:attr/colorControlNormal"> - <solid + <solid android:color="#FFFFFF"/> - <size + <size android:width="3dp" android:height="3dp"/> </shape> diff --git a/packages/SystemUI/res/drawable/ic_qs_network_logging.xml b/packages/SystemUI/res/drawable/ic_qs_network_logging.xml index 1340ae161729..87b5a14153f7 100644 --- a/packages/SystemUI/res/drawable/ic_qs_network_logging.xml +++ b/packages/SystemUI/res/drawable/ic_qs_network_logging.xml @@ -24,6 +24,6 @@ Copyright (C) 2016 The Android Open Source Project android:tint="#4DFFFFFF" > <path android:fillColor="#FFFFFFFF" - android:pathData="M7,18v-2h6v2H7z M7,14v-2h10v2H7z M8.5,9 12,5.5 15.5,9 13,9 13,13 11,13 11,9z"/> + android:pathData="M2,24v-4h12v4H2z M2,16v-4h20v4H2z M5,7 12,0 19,7 14,7 14,15 10,15 10,7z"/> </vector> diff --git a/packages/SystemUI/res/layout/battery_detail.xml b/packages/SystemUI/res/layout/battery_detail.xml index 8abfcf6057aa..6162d650f768 100644 --- a/packages/SystemUI/res/layout/battery_detail.xml +++ b/packages/SystemUI/res/layout/battery_detail.xml @@ -41,7 +41,7 @@ android:layout_marginEnd="24dp" systemui:sideLabels="@array/battery_labels" android:colorAccent="?android:attr/colorAccent" - systemui:textColor="#66FFFFFF" /> + systemui:textColor="?android:attr/textColorSecondary" /> <com.android.systemui.ResizingSpace android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml new file mode 100644 index 000000000000..2ba04fd31c0f --- /dev/null +++ b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/scrollView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipToPadding="false"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="?android:attr/dialogPreferredPadding" + android:paddingRight="?android:attr/dialogPreferredPadding" + android:paddingLeft="?android:attr/dialogPreferredPadding" + android:paddingBottom="?android:attr/dialogPreferredPadding" + android:orientation="vertical"> + <TextView + android:id="@+id/device_owner_warning" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@android:style/TextAppearance.Material.Subhead" + android:textColor="?android:attr/textColorPrimaryInverse" + /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <ImageView + android:id="@+id/vpn_icon" + android:layout_width="@dimen/qs_footer_dialog_icon_size" + android:layout_height="wrap_content" + android:paddingTop="?android:attr/dialogPreferredPadding" + android:layout_marginStart="@dimen/qs_footer_dialog_icon_margin" + android:layout_marginEnd="@dimen/qs_footer_dialog_icon_margin" + android:scaleType="fitCenter" + android:src="@drawable/ic_qs_vpn" + android:tint="?android:attr/textColorPrimaryInverse" + android:adjustViewBounds="true"/> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + <TextView + android:id="@+id/vpn_subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="?android:attr/dialogPreferredPadding" + android:text="@string/monitoring_subtitle_vpn" + style="@android:style/TextAppearance.Material.Title" + android:textColor="?android:attr/textColorPrimaryInverse" + /> + <TextView + android:id="@+id/vpn_warning" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@null" + style="@android:style/TextAppearance.Material.Subhead" + android:textColor="?android:attr/textColorPrimaryInverse" + /> + </LinearLayout> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <ImageView + android:id="@+id/network_logging_icon" + android:layout_width="@dimen/qs_footer_dialog_icon_size" + android:layout_height="wrap_content" + android:paddingTop="?android:attr/dialogPreferredPadding" + android:layout_marginStart="@dimen/qs_footer_dialog_icon_margin" + android:layout_marginEnd="@dimen/qs_footer_dialog_icon_margin" + android:scaleType="fitCenter" + android:src="@drawable/ic_qs_network_logging" + android:tint="?android:attr/textColorPrimaryInverse" + android:alpha="@dimen/qs_footer_dialog_network_logging_icon_alpha" + android:adjustViewBounds="true"/> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + <TextView + android:id="@+id/network_logging_subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="?android:attr/dialogPreferredPadding" + android:text="@string/monitoring_subtitle_network_logging" + style="@android:style/TextAppearance.Material.Title" + android:textColor="?android:attr/textColorPrimaryInverse" + /> + <TextView + android:id="@+id/network_logging_warning" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/monitoring_description_network_logging" + style="@android:style/TextAppearance.Material.Subhead" + android:textColor="?android:attr/textColorPrimaryInverse" + /> + </LinearLayout> + </LinearLayout> + </LinearLayout> +</ScrollView> diff --git a/packages/SystemUI/res/layout/recents_grid_task_view.xml b/packages/SystemUI/res/layout/recents_grid_task_view.xml new file mode 100644 index 000000000000..53bec70bd096 --- /dev/null +++ b/packages/SystemUI/res/layout/recents_grid_task_view.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<com.android.systemui.recents.views.grid.GridTaskView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:focusable="true"> + <com.android.systemui.recents.views.TaskViewThumbnail + android:id="@+id/task_view_thumbnail" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <include layout="@layout/recents_task_view_header" /> + + <!-- TODO: Move this into a view stub --> + <include layout="@layout/recents_task_view_lock_to_app"/> + + <!-- The incompatible app toast --> + <include layout="@layout/recents_task_view_incompatible_app_toast"/> +</com.android.systemui.recents.views.grid.GridTaskView> + + diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml index c8e5b6124196..015e4a2006bb 100644 --- a/packages/SystemUI/res/layout/recents_task_view.xml +++ b/packages/SystemUI/res/layout/recents_task_view.xml @@ -4,9 +4,9 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -26,35 +26,10 @@ <include layout="@layout/recents_task_view_header" /> <!-- TODO: Move this into a view stub --> - <com.android.systemui.statusbar.AlphaOptimizedFrameLayout - android:id="@+id/lock_to_app_fab" - android:layout_width="@dimen/recents_lock_to_app_size" - android:layout_height="@dimen/recents_lock_to_app_size" - android:layout_gravity="bottom|end" - android:layout_marginEnd="15dp" - android:layout_marginBottom="15dp" - android:translationZ="4dp" - android:contentDescription="@string/recents_lock_to_app_button_label" - android:background="@drawable/recents_lock_to_task_button_bg" - android:visibility="invisible" - android:alpha="0"> - <ImageView - android:layout_width="@dimen/recents_lock_to_app_icon_size" - android:layout_height="@dimen/recents_lock_to_app_icon_size" - android:layout_gravity="center" - android:src="@drawable/recents_lock_to_app_pin" /> - </com.android.systemui.statusbar.AlphaOptimizedFrameLayout> + <include layout="@layout/recents_task_view_lock_to_app"/> <!-- The incompatible app toast --> - <ViewStub android:id="@+id/incompatible_app_toast_stub" - android:inflatedId="@+id/incompatible_app_toast" - android:layout="@*android:layout/transient_notification" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="top|center_horizontal" - android:layout_marginTop="48dp" - android:layout_marginLeft="16dp" - android:layout_marginRight="16dp" /> + <include layout="@layout/recents_task_view_incompatible_app_toast"/> </com.android.systemui.recents.views.TaskView> diff --git a/packages/SystemUI/res/layout-sw600dp/recents_grid.xml b/packages/SystemUI/res/layout/recents_task_view_incompatible_app_toast.xml index 1a054a534725..d573d6b881d2 100644 --- a/packages/SystemUI/res/layout-sw600dp/recents_grid.xml +++ b/packages/SystemUI/res/layout/recents_task_view_incompatible_app_toast.xml @@ -13,11 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. --> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:id="@+id/recents_view" - android:gravity="center" - android:background="#99000000"> - <include layout="@layout/recents_stack_action_button" /> -</FrameLayout>
\ No newline at end of file +<ViewStub + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/incompatible_app_toast_stub" + android:inflatedId="@+id/incompatible_app_toast" + android:layout="@*android:layout/transient_notification" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|center_horizontal" + android:layout_marginTop="48dp" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" />
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/recents_task_view_lock_to_app.xml b/packages/SystemUI/res/layout/recents_task_view_lock_to_app.xml new file mode 100644 index 000000000000..8cece1149ce7 --- /dev/null +++ b/packages/SystemUI/res/layout/recents_task_view_lock_to_app.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<com.android.systemui.statusbar.AlphaOptimizedFrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/lock_to_app_fab" + android:layout_width="@dimen/recents_lock_to_app_size" + android:layout_height="@dimen/recents_lock_to_app_size" + android:layout_gravity="bottom|end" + android:layout_marginEnd="15dp" + android:layout_marginBottom="15dp" + android:translationZ="4dp" + android:contentDescription="@string/recents_lock_to_app_button_label" + android:background="@drawable/recents_lock_to_task_button_bg" + android:visibility="invisible" + android:alpha="0"> + <ImageView + android:layout_width="@dimen/recents_lock_to_app_icon_size" + android:layout_height="@dimen/recents_lock_to_app_icon_size" + android:layout_gravity="center" + android:src="@drawable/recents_lock_to_app_pin" /> +</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index fe5606e408f2..ff4ec5bdaf3f 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -258,6 +258,13 @@ <!-- How far the expanded QS panel peeks from the header in collapsed state. --> <dimen name="qs_peek_height">0dp</dimen> + <!-- How large the icons in the quick settings footer dialog are --> + <dimen name="qs_footer_dialog_icon_size">24sp</dimen> + <!-- Left and right margin of the icons --> + <dimen name="qs_footer_dialog_icon_margin">8sp</dimen> + <!-- Alpha value of network logging icon --> + <dimen name="qs_footer_dialog_network_logging_icon_alpha">0.3</dimen> + <!-- Zen mode panel: condition item button padding --> <dimen name="zen_mode_condition_detail_button_padding">8dp</dimen> @@ -720,18 +727,4 @@ <!-- The size of a PIP menu action icon. --> <dimen name="pip_menu_action_icon_size">32dp</dimen> - <!-- Values specific to grid-based recents. --> - <!-- Margins around recent tasks. --> - <dimen name="recents_grid_margin_left">15dp</dimen> - <dimen name="recents_grid_margin_top">70dp</dimen> - <dimen name="recents_grid_margin_right">15dp</dimen> - <dimen name="recents_grid_margin_bottom">90dp</dimen> - <!-- Margins around the "Clear all" button. --> - <dimen name="recents_grid_clear_all_margin_left">0dp</dimen> - <dimen name="recents_grid_clear_all_margin_top">30dp</dimen> - <dimen name="recents_grid_clear_all_margin_right">15dp</dimen> - <dimen name="recents_grid_clear_all_margin_bottom">0dp</dimen> - <!-- Padding in between task views. --> - <dimen name="recents_grid_inter_task_padding">15dp</dimen> - </resources> diff --git a/packages/SystemUI/res/values/dimens_grid.xml b/packages/SystemUI/res/values/dimens_grid.xml new file mode 100644 index 000000000000..94ffdb18a80d --- /dev/null +++ b/packages/SystemUI/res/values/dimens_grid.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (c) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ +--> +<resources> + <dimen name="recents_grid_padding_left_right">32dp</dimen> + <dimen name="recents_grid_padding_top_bottom">150dp</dimen> + <dimen name="recents_grid_padding_task_view">20dp</dimen> + <dimen name="recents_grid_task_view_header_height">44dp</dimen> + <dimen name="recents_grid_task_view_header_button_padding">8dp</dimen> +</resources> + diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 31b83cc8d8bb..d5638a89b2fd 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1010,6 +1010,13 @@ <!-- Monitoring dialog title for normal devices [CHAR LIMIT=35]--> <string name="monitoring_title">Network monitoring</string> + <!-- STOPSHIP Monitoring strings still need to be finalized and approved --> + <!-- Monitoring dialog subtitle for the section describing VPN [CHAR LIMIT=TODO]--> + <string name="monitoring_subtitle_vpn">VPN</string> + + <!-- Monitoring dialog subtitle for the section describing network logging [CHAR LIMIT=TODO]--> + <string name="monitoring_subtitle_network_logging">Network Logging</string> + <!-- Monitoring dialog disable vpn button [CHAR LIMIT=30] --> <string name="disable_vpn">Disable VPN</string> @@ -1025,15 +1032,25 @@ <!-- Monitoring dialog: Part of text body explaining what a Device Owner app can do [CHAR LIMIT=130] --> <string name="monitoring_description_do_body">Your administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.</string> - <!-- Monitoring dialog: Part of text body explaining that a VPN is connected and what it can do, for devices managed by a Device Owner app [CHAR LIMIT=130] --> - <string name="monitoring_description_do_body_vpn">You\'re connected to <xliff:g id="vpn_app">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string> - <!-- Monitoring dialog: Space that separates the body text and the "learn more" link that follows it. [CHAR LIMIT=5] --> <string name="monitoring_description_do_learn_more_separator">" "</string> <!-- Monitoring dialog: Link to learn more about what a Device Owner app can do [CHAR LIMIT=55] --> <string name="monitoring_description_do_learn_more">Learn more</string> + <!-- Monitoring dialog: Part of text body explaining that a VPN is connected and what it can do, for devices managed by a Device Owner app [CHAR LIMIT=130] --> + <string name="monitoring_description_do_body_vpn">You\'re connected to <xliff:g id="vpn_app">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string> + + <!-- Monitoring dialog: Space that separates the VPN body text and the "Open VPN Settings" link that follows it. [CHAR LIMIT=5] --> + <string name="monitoring_description_vpn_settings_separator">" "</string> + + <!-- Monitoring dialog: Link to open the VPN settings page [CHAR LIMIT=TODO] --> + <string name="monitoring_description_vpn_settings">Open VPN Settings</string> + + <!-- Monitoring dialog: Network logging text [CHAR LIMIT=TODO] --> + <string name="monitoring_description_network_logging">Your admin has turned on network logging, which monitors traffic on your device.\n\nFor more information contact your admin.</string> + + <!-- Monitoring dialog VPN text [CHAR LIMIT=400] --> <string name="monitoring_description_vpn">You gave an app permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps, and websites.</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 08b8988c79a4..93b696591c1f 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -44,22 +44,6 @@ <item name="android:layout_marginBottom">0dp</item> </style> - <!-- Grid-based Recents theme. --> - <style name="RecentsTheme.Grid"> - <item name="android:windowBackground">@color/transparent</item> - <item name="android:colorBackgroundCacheHint">@null</item> - <item name="android:windowShowWallpaper">true</item> - <item name="android:windowDisablePreview">true</item> - <item name="clearAllStyle">@style/ClearAllButtonLargeMargins</item> - </style> - - <style name="ClearAllButtonLargeMargins"> - <item name="android:layout_marginStart">@dimen/recents_grid_clear_all_margin_left</item> - <item name="android:layout_marginTop">@dimen/recents_grid_clear_all_margin_top</item> - <item name="android:layout_marginEnd">@dimen/recents_grid_clear_all_margin_right</item> - <item name="android:layout_marginBottom">@dimen/recents_grid_clear_all_margin_bottom</item> - </style> - <!-- Performance optimized Recents theme (no wallpaper) --> <style name="RecentsTheme.NoWallpaper"> <item name="android:windowBackground">@android:color/black</item> diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index 13e047cceb30..6a868d53d409 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -218,7 +218,8 @@ public class DozeMachine { Preconditions.checkState(mState == State.DOZE_REQUEST_PULSE); break; case DOZE_PULSE_DONE: - Preconditions.checkState(mState == State.DOZE_PULSING); + Preconditions.checkState( + mState == State.DOZE_REQUEST_PULSE || mState == State.DOZE_PULSING); break; default: break; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java index 337b854c38b3..03cd9595d354 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java @@ -31,6 +31,7 @@ import android.text.style.ClickableSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.view.View.OnClickListener; import android.view.Window; import android.widget.ImageView; @@ -179,6 +180,7 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene private void createDialog() { final String deviceOwnerPackage = mSecurityController.getDeviceOwnerName(); final String profileOwnerPackage = mSecurityController.getProfileOwnerName(); + final boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled(); final String primaryVpn = mSecurityController.getPrimaryVpnName(); final String profileVpn = mSecurityController.getProfileVpnName(); final CharSequence deviceOwnerOrganization = @@ -190,24 +192,47 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene if (!isBranded) { mDialog.setTitle(getTitle(deviceOwnerPackage)); } - mDialog.setMessage(getMessage(deviceOwnerPackage, profileOwnerPackage, primaryVpn, - profileVpn, deviceOwnerOrganization, hasProfileOwner, isBranded)); - - mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(isBranded), this); - if (mSecurityController.isVpnEnabled() && !mSecurityController.isVpnRestricted()) { - mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getSettingsButton(), this); + CharSequence msg = getMessage(deviceOwnerPackage, profileOwnerPackage, primaryVpn, + profileVpn, deviceOwnerOrganization, hasProfileOwner, isBranded); + if (deviceOwnerPackage == null) { + mDialog.setMessage(msg); + } else { + View dialogView = LayoutInflater.from(mContext) + .inflate(R.layout.quick_settings_footer_dialog, null, false); + mDialog.setView(dialogView); + TextView deviceOwnerWarning = + (TextView) dialogView.findViewById(R.id.device_owner_warning); + deviceOwnerWarning.setText(msg); + // Make the link "learn more" clickable. + deviceOwnerWarning.setMovementMethod(new LinkMovementMethod()); + if (primaryVpn == null) { + dialogView.findViewById(R.id.vpn_icon).setVisibility(View.GONE); + dialogView.findViewById(R.id.vpn_subtitle).setVisibility(View.GONE); + dialogView.findViewById(R.id.vpn_warning).setVisibility(View.GONE); + } else { + final SpannableStringBuilder message = new SpannableStringBuilder(); + message.append(mContext.getString(R.string.monitoring_description_do_body_vpn, + primaryVpn)); + message.append(mContext.getString( + R.string.monitoring_description_vpn_settings_separator)); + message.append(mContext.getString(R.string.monitoring_description_vpn_settings), + new VpnSpan(), 0); + + TextView vpnWarning = (TextView) dialogView.findViewById(R.id.vpn_warning); + vpnWarning.setText(message); + // Make the link "Open VPN Settings" clickable. + vpnWarning.setMovementMethod(new LinkMovementMethod()); + } + if (!isNetworkLoggingEnabled) { + dialogView.findViewById(R.id.network_logging_icon).setVisibility(View.GONE); + dialogView.findViewById(R.id.network_logging_subtitle).setVisibility(View.GONE); + dialogView.findViewById(R.id.network_logging_warning).setVisibility(View.GONE); + } } - // Make the link "learn more" clickable. - // TODO: Reaching into SystemUIDialog's internal View hierarchy is ugly and error-prone. - // This is a temporary solution. b/33126622 will introduce a custom View hierarchy for this - // dialog, allowing us to set the movement method on the appropriate View without any - // knowledge of SystemUIDialog's internals. - mDialog.create(); - ((TextView) mDialog.getWindow().findViewById(com.android.internal.R.id.message)) - .setMovementMethod(new LinkMovementMethod()); - + mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(isBranded), this); mDialog.show(); + mDialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } private String getSettingsButton() { @@ -233,11 +258,6 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene } message.append("\n\n"); message.append(mContext.getString(R.string.monitoring_description_do_body)); - if (primaryVpn != null) { - message.append("\n\n"); - message.append(mContext.getString(R.string.monitoring_description_do_body_vpn, - primaryVpn)); - } message.append(mContext.getString( R.string.monitoring_description_do_learn_more_separator)); message.append(mContext.getString(R.string.monitoring_description_do_learn_more), @@ -344,4 +364,14 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene return object instanceof EnterprisePrivacySpan; } } + + protected class VpnSpan extends ClickableSpan { + @Override + public void onClick(View widget) { + final Intent intent = new Intent(Settings.ACTION_VPN_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mDialog.dismiss(); + mContext.startActivity(intent); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index 9bbead46c33b..0be53b4b7c26 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -60,8 +60,6 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene private final QSDetailClipper mClipper; - private PhoneStatusBar mPhoneStatusBar; - private boolean isShown; private QSTileHost mHost; private RecyclerView mRecyclerView; @@ -119,7 +117,6 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene public void setHost(QSTileHost host) { mHost = host; - mPhoneStatusBar = host.getPhoneStatusBar(); mTileAdapter.setHost(host); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 790f3f66f6e4..0265c9e40112 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -57,7 +57,6 @@ import com.android.systemui.recents.events.component.ShowUserToastEvent; import com.android.systemui.recents.events.ui.RecentsDrawnEvent; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.model.RecentsTaskLoader; -import com.android.systemui.recents.grid.RecentsGridImpl; import com.android.systemui.recents.tv.RecentsTvImpl; import com.android.systemui.stackdivider.Divider; @@ -84,7 +83,6 @@ public class Recents extends SystemUI static { RECENTS_ACTIVITIES.add(RecentsImpl.RECENTS_ACTIVITY); RECENTS_ACTIVITIES.add(RecentsTvImpl.RECENTS_TV_ACTIVITY); - RECENTS_ACTIVITIES.add(RecentsGridImpl.RECENTS_MOSAIC_ACTIVITY); } // Purely for experimentation @@ -207,8 +205,6 @@ public class Recents extends SystemUI getSystemService(Context.UI_MODE_SERVICE); if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { mImpl = new RecentsTvImpl(mContext); - } else if (SystemProperties.getBoolean("ro.recents.grid", false) == true) { - mImpl = new RecentsGridImpl(mContext); } else { mImpl = new RecentsImpl(mContext); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java index 73c6e6e89c58..711f0c679b6d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; +import android.os.SystemProperties; import com.android.systemui.R; import com.android.systemui.recents.misc.SystemServicesProxy; @@ -58,6 +59,10 @@ public class RecentsConfiguration { public boolean fakeShadows; public int svelteLevel; + // Whether this product supports Grid-based Recents. If this is field is set to true, then + // Recents will layout task views in a grid mode when there's enough space in the screen. + public boolean isGridEnabled; + public RecentsConfiguration(Context context) { // Load only resources that can not change after the first load either through developer // settings or via multi window @@ -66,6 +71,7 @@ public class RecentsConfiguration { Resources res = appContext.getResources(); fakeShadows = res.getBoolean(R.bool.config_recents_fake_shadows); svelteLevel = res.getInteger(R.integer.recents_svelte_level); + isGridEnabled = SystemProperties.getBoolean("ro.recents.grid", false); float screenDensity = context.getResources().getDisplayMetrics().density; smallestWidth = ssp.getDeviceSmallestWidth(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index c9c4f2bef31f..137138127775 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -576,7 +576,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener R.dimen.recents_task_view_header_height, R.dimen.recents_task_view_header_height_tablet_land, R.dimen.recents_task_view_header_height, - R.dimen.recents_task_view_header_height_tablet_land); + R.dimen.recents_task_view_header_height_tablet_land, + R.dimen.recents_grid_task_view_header_height); LayoutInflater inflater = LayoutInflater.from(mContext); mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header, @@ -720,7 +721,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener if (task.isFreeformTask()) { mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task, stackScroller.getStackScroll(), mTmpTransform, null, - windowOverrideRect); + windowOverrideRect, false /* useGridLayout */); Bitmap thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform, mThumbTransitionBitmapCache); Rect toTaskRect = new Rect(); @@ -770,7 +771,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener stackView.updateLayoutAlgorithm(true /* boundScroll */); stackView.updateToInitialState(); stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask, - stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect); + stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect, + Recents.getConfiguration().isGridEnabled); return mTmpTransform; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java b/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java deleted file mode 100644 index 71c21489554b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java +++ /dev/null @@ -1,504 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.recents.grid; - -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; - -import android.app.Activity; -import android.content.Intent; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.Bundle; -import android.util.DisplayMetrics; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.ScaleAnimation; -import android.view.animation.TranslateAnimation; -import android.widget.FrameLayout; -import android.widget.TextView; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.R; -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.RecentsActivity; -import com.android.systemui.recents.RecentsActivityLaunchState; -import com.android.systemui.recents.RecentsConfiguration; -import com.android.systemui.recents.RecentsImpl; -import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.activity.ConfigurationChangedEvent; -import com.android.systemui.recents.events.activity.HideRecentsEvent; -import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent; -import com.android.systemui.recents.events.activity.LaunchTaskEvent; -import com.android.systemui.recents.events.activity.ToggleRecentsEvent; -import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; -import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent; -import com.android.systemui.recents.events.ui.DeleteTaskDataEvent; -import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; -import com.android.systemui.recents.events.ui.DismissTaskViewEvent; -import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.Utilities; -import com.android.systemui.recents.model.RecentsTaskLoadPlan; -import com.android.systemui.recents.model.RecentsTaskLoader; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.TaskStack; -import com.android.systemui.recents.views.TaskView; - -import java.util.ArrayList; -import java.util.Arrays; - -/** - * The main grid recents activity started by the RecentsImpl. - */ -public class RecentsGridActivity extends Activity { - public final static int MAX_VISIBLE_TASKS = 9; - - private final static String TAG = "RecentsGridActivity"; - private final static int TITLE_BAR_HEIGHT_DP = 64; - - private ArrayList<Integer> mMargins = new ArrayList<>(); - - private TaskStack mTaskStack; - private ArrayList<Task> mTasks = new ArrayList<>(); - private ArrayList<TaskView> mTaskViews = new ArrayList<>(); - private ArrayList<Rect> mTaskViewRects; - private FrameLayout mRecentsView; - private TextView mEmptyView; - private View mClearAllButton; - private int mLastDisplayOrientation = Configuration.ORIENTATION_UNDEFINED; - private int mLastDisplayDensity; - private Rect mDisplayRect = new Rect(); - private LayoutInflater mInflater; - private boolean mTouchExplorationEnabled; - private Point mScreenSize; - private int mTitleBarHeightPx; - private int mStatusBarHeightPx; - private int mNavigationBarHeightPx; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.recents_grid); - SystemServicesProxy ssp = Recents.getSystemServices(); - - Resources res = getResources(); - Integer[] margins = { - res.getDimensionPixelSize(R.dimen.recents_grid_margin_left), - res.getDimensionPixelSize(R.dimen.recents_grid_margin_top), - res.getDimensionPixelSize(R.dimen.recents_grid_margin_right), - res.getDimensionPixelSize(R.dimen.recents_grid_margin_bottom), - }; - mMargins.addAll(Arrays.asList(margins)); - - mInflater = LayoutInflater.from(this); - Configuration appConfiguration = Utilities.getAppConfiguration(this); - mDisplayRect = ssp.getDisplayRect(); - mLastDisplayOrientation = appConfiguration.orientation; - mLastDisplayDensity = appConfiguration.densityDpi; - mTouchExplorationEnabled = ssp.isTouchExplorationEnabled(); - mScreenSize = new Point(); - getWindowManager().getDefaultDisplay().getRealSize(mScreenSize); - DisplayMetrics metrics = res.getDisplayMetrics(); - mTitleBarHeightPx = (int) (TITLE_BAR_HEIGHT_DP * - ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)); - mStatusBarHeightPx = res.getDimensionPixelSize(R.dimen.status_bar_height); - mNavigationBarHeightPx = res.getDimensionPixelSize(R.dimen.navigation_bar_height); - - mRecentsView = (FrameLayout) findViewById(R.id.recents_view); - mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); - getWindow().getAttributes().privateFlags |= - WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; - mEmptyView = (TextView) mInflater.inflate(R.layout.recents_empty, mRecentsView, false); - mClearAllButton = findViewById(R.id.button); - - FrameLayout.LayoutParams emptyViewLayoutParams = new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); - emptyViewLayoutParams.gravity = Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL; - mEmptyView.setLayoutParams(emptyViewLayoutParams); - mRecentsView.addView(mEmptyView); - - mClearAllButton.setVisibility(View.VISIBLE); - FrameLayout.LayoutParams lp = - (FrameLayout.LayoutParams) mClearAllButton.getLayoutParams(); - lp.gravity = Gravity.END; - - mClearAllButton.setOnClickListener(v -> { - EventBus.getDefault().send(new DismissAllTaskViewsEvent()); - }); - - mRecentsView.setOnClickListener(v -> { - EventBus.getDefault().send(new HideRecentsEvent( - false /* triggeredFromAltTab */, false /* triggeredFromHomeKey */)); - }); - - EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY); - } - - private TaskView createView() { - return (TaskView) mInflater.inflate(R.layout.recents_task_view, mRecentsView, false); - } - - private void removeTaskViews() { - for (View taskView : mTaskViews) { - ViewGroup parent = (ViewGroup) taskView.getParent(); - if (parent != null) { - parent.removeView(taskView); - } - } - } - - private void clearTaskViews() { - removeTaskViews(); - mTaskViews.clear(); - } - - private TaskView getChildViewForTask(Task task) { - for (TaskView tv : mTaskViews) { - if (tv.getTask() == task) { - return tv; - } - } - return null; - } - - /** - * Starts animations for each task view to either enlarge it to the size of the screen (when - * launching a task), or (if {@code reverse} is true, to reduce it from the size of the screen - * back to its place in the recents layout (when opening recents). - * @param animationListener An animation listener for executing code before or after the - * animations run. - * @param reverse Whether the blow-up animations should be run in reverse. - */ - private void startBlowUpAnimations(Animation.AnimationListener animationListener, - boolean reverse) { - if (mTaskViews.size() == 0) { - return; - } - int screenWidth = mLastDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE - ? mScreenSize.x : mScreenSize.y; - int screenHeight = mLastDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE - ? mScreenSize.y : mScreenSize.x; - screenHeight -= mStatusBarHeightPx + mNavigationBarHeightPx; - for (int i = 0; i < mTaskViews.size(); i++) { - View tv = mTaskViews.get(i); - AnimationSet animations = new AnimationSet(true /* shareInterpolator */); - animations.setInterpolator(new DecelerateInterpolator()); - if (i == 0 && animationListener != null) { - animations.setAnimationListener(animationListener); - } - animations.setFillBefore(reverse); - animations.setFillAfter(!reverse); - Rect initialRect = mTaskViewRects.get(mTaskViewRects.size() - 1 - i); - int xDelta = - initialRect.left; - int yDelta = - initialRect.top - mTitleBarHeightPx + mStatusBarHeightPx; - TranslateAnimation translate = new TranslateAnimation( - reverse ? xDelta : 0, reverse ? 0 : xDelta, - reverse ? yDelta : 0, reverse ? 0 : yDelta); - translate.setDuration(250); - animations.addAnimation(translate); - - - float xScale = (float) screenWidth / (float) initialRect.width(); - float yScale = (float) screenHeight / - ((float) initialRect.height() - mTitleBarHeightPx); - ScaleAnimation scale = new ScaleAnimation( - reverse ? xScale : 1, reverse ? 1 : xScale, - reverse ? yScale : 1, reverse ? 1 : yScale, - Animation.ABSOLUTE, 0, Animation.ABSOLUTE, mStatusBarHeightPx); - scale.setDuration(300); - animations.addAnimation(scale); - - tv.startAnimation(animations); - } - } - - private void updateControlVisibility() { - boolean empty = (mTasks.size() == 0); - mClearAllButton.setVisibility(empty ? View.INVISIBLE : View.VISIBLE); - mEmptyView.setVisibility(empty ? View.VISIBLE : View.INVISIBLE); - if (empty) { - mEmptyView.bringToFront(); - } - } - - private void updateModel() { - RecentsTaskLoader loader = Recents.getTaskLoader(); - RecentsTaskLoadPlan plan = RecentsImpl.consumeInstanceLoadPlan(); - if (plan == null) { - plan = loader.createLoadPlan(this); - } - RecentsConfiguration config = Recents.getConfiguration(); - RecentsActivityLaunchState launchState = config.getLaunchState(); - if (!plan.hasTasks()) { - loader.preloadTasks(plan, -1, !launchState.launchedFromHome); - } - mTaskStack = plan.getTaskStack(); - RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options(); - loadOpts.runningTaskId = launchState.launchedToTaskId; - loadOpts.numVisibleTasks = MAX_VISIBLE_TASKS; - loadOpts.numVisibleTaskThumbnails = MAX_VISIBLE_TASKS; - loader.loadTasks(this, plan, loadOpts); - - mTasks = mTaskStack.getStackTasks(); - } - - private void updateViews() { - int screenWidth = mLastDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE - ? mScreenSize.x : mScreenSize.y; - int screenHeight = mLastDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE - ? mScreenSize.y : mScreenSize.x; - int paddingPixels = getResources().getDimensionPixelSize( - R.dimen.recents_grid_inter_task_padding); - mTaskViewRects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - mTasks.size(), screenWidth, screenHeight, getAppRectRatio(), paddingPixels, - mMargins, mTitleBarHeightPx); - boolean recycleViews = (mTaskViews.size() == mTasks.size()); - if (!recycleViews) { - clearTaskViews(); - } - for (int i = 0; i < mTasks.size(); i++) { - Task task = mTasks.get(i); - // We keep the same ordering in the model as other Recents flavors (older tasks are - // first in the stack) so that the logic can be similar, but we reverse the order - // when placing views on the screen so that most recent tasks are displayed first. - Rect rect = mTaskViewRects.get(mTaskViewRects.size() - 1 - i); - TaskView taskView; - if (recycleViews) { - taskView = mTaskViews.get(i); - } else { - taskView = createView(); - } - taskView.onTaskBound(task, mTouchExplorationEnabled, mLastDisplayOrientation, - mDisplayRect); - Recents.getTaskLoader().loadTaskData(task); - taskView.setTouchEnabled(true); - // Show dismiss button right away. - taskView.startNoUserInteractionAnimation(); - taskView.setLayoutParams(new FrameLayout.LayoutParams(rect.width(), rect.height())); - taskView.setTranslationX(rect.left); - taskView.setTranslationY(rect.top); - if (!recycleViews) { - mRecentsView.addView(taskView); - mTaskViews.add(taskView); - } - } - updateControlVisibility(); - } - - private float getAppRectRatio() { - if (mLastDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE) { - return (float) mScreenSize.x / - (float) (mScreenSize.y - mStatusBarHeightPx - mNavigationBarHeightPx); - } else { - return (float) mScreenSize.y / - (float) (mScreenSize.x - mStatusBarHeightPx - mNavigationBarHeightPx); - } - } - - @Override - protected void onStart() { - super.onStart(); - EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true)); - updateModel(); - updateViews(); - if (mTaskViews.size() > 0) { - mTaskViews.get(mTaskViews.size() - 1).bringToFront(); - } - startBlowUpAnimations(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { } - - @Override - public void onAnimationEnd(Animation animation) { - updateViews(); - } - - @Override - public void onAnimationRepeat(Animation animation) { } - }, true /* reverse */); - } - - @Override - protected void onStop() { - super.onStop(); - EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false)); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - EventBus.getDefault().unregister(this); - } - - @Override - public void onBackPressed() { - // Back behaves like the recents button so just trigger a toggle event. - EventBus.getDefault().send(new ToggleRecentsEvent()); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - // Notify of the config change. - Configuration newDeviceConfiguration = Utilities.getAppConfiguration(this); - mDisplayRect = Recents.getSystemServices().getDisplayRect(); - int numStackTasks = mTaskStack.getStackTaskCount(); - EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */, - mLastDisplayOrientation != newDeviceConfiguration.orientation, - mLastDisplayDensity != newDeviceConfiguration.densityDpi, numStackTasks > 0)); - mLastDisplayOrientation = newDeviceConfiguration.orientation; - mLastDisplayDensity = newDeviceConfiguration.densityDpi; - updateViews(); - } - - void dismissRecentsToHome() { - Intent startMain = new Intent(Intent.ACTION_MAIN); - startMain.addCategory(Intent.CATEGORY_HOME); - startActivity(startMain); - } - - /** Launches the task that recents was launched from if possible. */ - boolean launchPreviousTask() { - if (mRecentsView != null) { - Task task = mTaskStack.getLaunchTarget(); - if (task != null) { - TaskView taskView = getChildViewForTask(task); - EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, - INVALID_STACK_ID, false)); - return true; - } - } - return false; - } - - /** Dismisses recents back to the launch target task. */ - boolean dismissRecentsToLaunchTargetTaskOrHome() { - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.isRecentsActivityVisible()) { - // If we can launch the task that Recents was launched from, do that, otherwise go home. - if (launchPreviousTask()) return true; - dismissRecentsToHome(); - } - return false; - } - - /**** EventBus events ****/ - - public final void onBusEvent(HideRecentsEvent event) { - if (event.triggeredFromAltTab) { - dismissRecentsToLaunchTargetTaskOrHome(); - } else if (event.triggeredFromHomeKey) { - dismissRecentsToHome(); - } else { - // Fall through tap on the background view but not on any of the tasks. - dismissRecentsToHome(); - } - } - - public final void onBusEvent(ToggleRecentsEvent event) { - dismissRecentsToLaunchTargetTaskOrHome(); - } - - public final void onBusEvent(DismissTaskViewEvent event) { - int taskIndex = mTaskViews.indexOf(event.taskView); - if (taskIndex != -1) { - mTasks.remove(taskIndex); - ((ViewGroup) event.taskView.getParent()).removeView(event.taskView); - mTaskViews.remove(taskIndex); - EventBus.getDefault().send( - new TaskViewDismissedEvent(event.taskView.getTask(), event.taskView, null)); - } - } - - public final void onBusEvent(TaskViewDismissedEvent event) { - mRecentsView.announceForAccessibility(this.getString( - R.string.accessibility_recents_item_dismissed, event.task.title)); - updateControlVisibility(); - - EventBus.getDefault().send(new DeleteTaskDataEvent(event.task)); - - MetricsLogger.action(this, MetricsEvent.OVERVIEW_DISMISS, - event.task.key.getComponent().toString()); - } - - public final void onBusEvent(DeleteTaskDataEvent event) { - // Remove any stored data from the loader. - RecentsTaskLoader loader = Recents.getTaskLoader(); - loader.deleteTaskData(event.task, false); - - // Remove the task from activity manager. - SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.removeTask(event.task.key.id); - } - - public final void onBusEvent(final DismissAllTaskViewsEvent event) { - // Keep track of the tasks which will have their data removed. - ArrayList<Task> tasks = new ArrayList<>(mTaskStack.getStackTasks()); - mRecentsView.announceForAccessibility(this.getString( - R.string.accessibility_recents_all_items_dismissed)); - mTaskStack.removeAllTasks(); - for (int i = tasks.size() - 1; i >= 0; i--) { - EventBus.getDefault().send(new DeleteTaskDataEvent(tasks.get(i))); - } - mTasks = new ArrayList<>(); - updateModel(); - updateViews(); - - MetricsLogger.action(this, MetricsEvent.OVERVIEW_DISMISS_ALL); - } - - public final void onBusEvent(AllTaskViewsDismissedEvent event) { - SystemServicesProxy ssp = Recents.getSystemServices(); - if (!ssp.hasDockedTask()) { - dismissRecentsToHome(); - } - } - - public final void onBusEvent(LaunchNextTaskRequestEvent event) { - if (mTaskStack.getTaskCount() > 0) { - Task launchTask = mTaskStack.getNextLaunchTarget(); - TaskView launchTaskView = getChildViewForTask(launchTask); - if (launchTaskView != null) { - EventBus.getDefault().send(new LaunchTaskEvent(launchTaskView, - launchTask, null, INVALID_STACK_ID, false /* screenPinningRequested */)); - MetricsLogger.action(this, MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK, - launchTask.key.getComponent().toString()); - return; - } - } - // We couldn't find a matching task view, or there are no tasks. Just hide recents back - // to home. - EventBus.getDefault().send(new HideRecentsEvent(false, true)); - } - - public final void onBusEvent(LaunchTaskEvent event) { - event.taskView.bringToFront(); - startActivity(event.task.key.baseIntent); - // Eventually we should start blow-up animations here, but we need to make sure it's done - // in parallel with starting the activity so that we don't introduce unneeded latency. - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridImpl.java b/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridImpl.java deleted file mode 100644 index 41acaa0a619a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridImpl.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.recents.grid; - -import android.app.ActivityManager; -import android.content.Context; -import android.content.Intent; -import android.os.UserHandle; - -import com.android.systemui.recents.RecentsImpl; -import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; - -public class RecentsGridImpl extends RecentsImpl { - public static final String RECENTS_MOSAIC_ACTIVITY = - "com.android.systemui.recents.grid.RecentsGridActivity"; - - public RecentsGridImpl(Context context) { - super(context); - } - - @Override - protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask, - boolean isHomeStackVisible, boolean animate, int growTarget) { - Intent intent = new Intent(); - intent.setClassName(RECENTS_PACKAGE, RECENTS_MOSAIC_ACTIVITY); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - | Intent.FLAG_ACTIVITY_TASK_ON_HOME); - - mContext.startActivityAsUser(intent, UserHandle.CURRENT); - EventBus.getDefault().send(new RecentsActivityStartingEvent()); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java deleted file mode 100644 index 648f2f023fdf..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.recents.grid; - -import android.graphics.Rect; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -class TaskGridLayoutAlgorithm { - - public enum VerticalGravity { - START, END, CENTER - } - - public static final List<Integer> ZERO_MARGIN = new ArrayList<>(); - static { - Integer[] zero = {0, 0, 0, 0}; - ZERO_MARGIN.addAll(Arrays.asList(zero)); - } - private static final String TAG = "TaskGridLayoutAlgorithm"; - - /** - * Calculates the adequate rectangles for the specified number of tasks to be layed out on - * the screen. - * @param count The number of task views to layout. - * @param containerWidth The width of the whole area containing those tasks. - * @param containerHeight The height of the whole area containing those tasks. - * @param screenRatio The ratio of the device's screen, so that tasks have the same aspect - * ratio (ignoring the title bar). - * @param padding The amount of padding, in pixels, in between task views. - * @param margins The amount of space to be left blank around the area on the left, top, right - * and bottom. - * @param titleBarHeight The height, in pixels, of the task views title bar. - * @return A list of rectangles to be used for layout. - */ - static ArrayList<Rect> getRectsForTaskCount(int count, int containerWidth, int containerHeight, - float screenRatio, int padding, List<Integer> margins, int titleBarHeight) { - return getRectsForTaskCount(count, containerWidth, containerHeight, screenRatio, padding, - margins, titleBarHeight, null, VerticalGravity.CENTER); - } - - private static ArrayList<Rect> getRectsForTaskCount(int count, int containerWidth, - int containerHeight, float screenRatio, int padding, List<Integer> margins, - int titleBarHeight, Rect preCalculatedTile, VerticalGravity gravity) { - ArrayList<Rect> rects = new ArrayList<>(count); - boolean landscape = (containerWidth > containerHeight); - containerWidth -= margins.get(0) + margins.get(2); - containerHeight -= margins.get(1) + margins.get(3); - - // We support at most 9 tasks in this layout. - count = Math.min(count, RecentsGridActivity.MAX_VISIBLE_TASKS); - - if (count == 0) { - return rects; - } - if (count <= 3) { - // Base case: single line. - int taskWidth, taskHeight; - if (preCalculatedTile != null) { - taskWidth = preCalculatedTile.width(); - taskHeight = preCalculatedTile.height(); - } else { - // Divide available width in equal parts. - int maxTaskWidth = (containerWidth - (count - 1) * padding) / count; - int maxTaskHeight = containerHeight; - if (maxTaskHeight >= maxTaskWidth / screenRatio + titleBarHeight) { - // Width bound. - taskWidth = maxTaskWidth; - taskHeight = (int) (maxTaskWidth / screenRatio + titleBarHeight); - } else { - // Height bound. - taskHeight = maxTaskHeight; - taskWidth = (int) ((taskHeight - titleBarHeight) * screenRatio); - } - } - int emptySpaceX = containerWidth - (count * taskWidth) - (count - 1) * padding; - int emptySpaceY = containerHeight - taskHeight; - for (int i = 0; i < count; i++) { - int left = emptySpaceX / 2 + i * taskWidth + i * padding; - int top; - switch (gravity) { - case CENTER: - top = emptySpaceY / 2; - break; - case END: - top = emptySpaceY; - break; - case START: - default: - top = 0; - break; - } - Rect rect = new Rect(left, top, left + taskWidth, top + taskHeight); - rect.offset(margins.get(0), margins.get(1)); - rects.add(rect); - } - } else if (count < 7) { - // Two lines. - int lineHeight = (containerHeight - padding) / 2; - int lineTaskCount = (int) Math.ceil((double) count / 2); - List<Rect> rectsA = getRectsForTaskCount(lineTaskCount, containerWidth, lineHeight, - screenRatio, padding, ZERO_MARGIN, titleBarHeight, null, VerticalGravity.END); - List<Rect> rectsB = getRectsForTaskCount(count - lineTaskCount, containerWidth, - lineHeight, screenRatio, padding, ZERO_MARGIN, titleBarHeight, rectsA.get(0), - VerticalGravity.START); - for (int i = 0; i < rectsA.size(); i++) { - rectsA.get(i).offset(margins.get(0), margins.get(1)); - } - for (int i = 0; i < rectsB.size(); i++) { - rectsB.get(i).offset(margins.get(0), margins.get(1) + lineHeight + padding); - } - rects.addAll(rectsA); - rects.addAll(rectsB); - } else { - // Three lines. - int lineHeight = (containerHeight - 2 * padding) / 3; - int lineTaskCount = (int) Math.ceil((double) count / 3); - List<Rect> rectsA = getRectsForTaskCount(lineTaskCount, containerWidth, lineHeight, - screenRatio, padding, ZERO_MARGIN, titleBarHeight, null, VerticalGravity.END); - List<Rect> rectsB = getRectsForTaskCount(lineTaskCount, containerWidth, lineHeight, - screenRatio, padding, ZERO_MARGIN, titleBarHeight, rectsA.get(0), - VerticalGravity.END); - List<Rect> rectsC = getRectsForTaskCount(count - (2 * lineTaskCount), containerWidth, - lineHeight, screenRatio, padding, ZERO_MARGIN, titleBarHeight, rectsA.get(0), - VerticalGravity.START); - for (int i = 0; i < rectsA.size(); i++) { - rectsA.get(i).offset(margins.get(0), margins.get(1)); - } - for (int i = 0; i < rectsB.size(); i++) { - rectsB.get(i).offset(margins.get(0), margins.get(1) + lineHeight + padding); - } - for (int i = 0; i < rectsC.size(); i++) { - rectsC.get(i).offset(margins.get(0), margins.get(1) + 2 * (lineHeight + padding)); - } - rects.addAll(rectsA); - rects.addAll(rectsB); - rects.addAll(rectsC); - } - return rects; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index ea50d890ae2e..ddffea2446a4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -74,6 +74,7 @@ import android.view.WindowManager; import android.view.WindowManager.KeyboardShortcutsReceiver; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; +import android.app.KeyguardManager; import com.android.internal.app.AssistUtils; import com.android.internal.os.BackgroundThread; @@ -124,6 +125,7 @@ public class SystemServicesProxy { AssistUtils mAssistUtils; WindowManager mWm; IWindowManager mIwm; + KeyguardManager mKgm; UserManager mUm; Display mDisplay; String mRecentsPackage; @@ -212,6 +214,7 @@ public class SystemServicesProxy { mAssistUtils = new AssistUtils(context); mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mIwm = WindowManagerGlobal.getWindowManagerService(); + mKgm = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); mUm = UserManager.get(context); mDisplay = mWm.getDefaultDisplay(); mRecentsPackage = context.getPackageName(); @@ -862,6 +865,16 @@ public class SystemServicesProxy { return label; } + /** + * Returns whether the provided {@param userId} is currently locked (and showing Keyguard). + */ + public boolean isDeviceLocked(int userId) { + if (mKgm == null) { + return false; + } + return mKgm.isDeviceLocked(userId); + } + /** Returns the package name of the home activity. */ public String getHomeActivityPackageName() { if (mPm == null) return null; diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java index 2c5c437bfab0..4349e30f60e0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java @@ -86,7 +86,7 @@ public class Utilities { public static <T extends View> T findParent(View v, Class<T> parentClass) { ViewParent parent = v.getParent(); while (parent != null) { - if (parent.getClass().equals(parentClass)) { + if (parentClass.isAssignableFrom(parent.getClass())) { return (T) parent; } parent = parent.getParent(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java index 9b48e4d02623..587744065d77 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -28,6 +28,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.ArraySet; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; import com.android.systemui.Prefs; @@ -130,6 +131,7 @@ public class RecentsTaskLoadPlan { SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>(); SparseIntArray affiliatedTaskCounts = new SparseIntArray(); + SparseBooleanArray lockedUsers = new SparseBooleanArray(); String dismissDescFormat = mContext.getString( R.string.accessibility_recents_item_will_be_dismissed); String appInfoDescFormat = mContext.getString( @@ -177,12 +179,17 @@ public class RecentsTaskLoadPlan { int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription); boolean isSystemApp = (info != null) && ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); + if (lockedUsers.indexOfKey(t.userId) < 0) { + lockedUsers.put(t.userId, Recents.getSystemServices().isDeviceLocked(t.userId)); + } + boolean isLocked = lockedUsers.get(t.userId); // Add the task to the stack Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon, thumbnail, title, titleDescription, dismissDescription, appInfoDescription, activityColor, backgroundColor, isLaunchTarget, isStackTask, isSystemApp, - t.isDockable, t.bounds, t.taskDescription, t.resizeMode, t.topActivity); + t.isDockable, t.bounds, t.taskDescription, t.resizeMode, t.topActivity, + isLocked); allTasks.add(task); affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java index 86a0315496a1..53f713a83cb0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -189,6 +189,9 @@ public class Task { @ViewDebug.ExportedProperty(category="recents") public ComponentName topActivity; + @ViewDebug.ExportedProperty(category="recents") + public boolean isLocked; + private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>(); public Task() { @@ -200,7 +203,7 @@ public class Task { String appInfoDescription, int colorPrimary, int colorBackground, boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp, boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription, - int resizeMode, ComponentName topActivity) { + int resizeMode, ComponentName topActivity, boolean isLocked) { boolean isInAffiliationGroup = (affiliationTaskId != key.id); boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0); this.key = key; @@ -224,6 +227,7 @@ public class Task { this.isDockable = isDockable; this.resizeMode = resizeMode; this.topActivity = topActivity; + this.isLocked = isLocked; } /** @@ -250,6 +254,7 @@ public class Task { this.isSystemApp = o.isSystemApp; this.isDockable = o.isDockable; this.resizeMode = o.resizeMode; + this.isLocked = o.isLocked; this.topActivity = o.topActivity; } @@ -355,6 +360,9 @@ public class Task { if (isFreeformTask()) { writer.print(" freeform=Y"); } + if (isLocked) { + writer.print(" locked=Y"); + } writer.print(" "); writer.print(title); writer.println(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java index 253d06a5f5f8..dba085e37427 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java @@ -30,17 +30,17 @@ public class AnimateableViewBounds extends ViewOutlineProvider { private static final float MIN_ALPHA = 0.1f; private static final float MAX_ALPHA = 0.8f; - View mSourceView; + protected View mSourceView; @ViewDebug.ExportedProperty(category="recents") - Rect mClipRect = new Rect(); + protected Rect mClipRect = new Rect(); @ViewDebug.ExportedProperty(category="recents") - Rect mClipBounds = new Rect(); + protected Rect mClipBounds = new Rect(); @ViewDebug.ExportedProperty(category="recents") - Rect mLastClipBounds = new Rect(); + protected Rect mLastClipBounds = new Rect(); @ViewDebug.ExportedProperty(category="recents") - int mCornerRadius; + protected int mCornerRadius; @ViewDebug.ExportedProperty(category="recents") - float mAlpha = 1f; + protected float mAlpha = 1f; public AnimateableViewBounds(View source, int cornerRadius) { mSourceView = source; @@ -110,7 +110,7 @@ public class AnimateableViewBounds extends ViewOutlineProvider { return mClipRect.bottom; } - private void updateClipBounds() { + protected void updateClipBounds() { mClipBounds.set(Math.max(0, mClipRect.left), Math.max(0, mClipRect.top), mSourceView.getWidth() - Math.max(0, mClipRect.right), mSourceView.getHeight() - Math.max(0, mClipRect.bottom)); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index 3c7012a16dfc..d8fdd7a281e8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -337,7 +337,8 @@ public class RecentsView extends FrameLayout { if (RecentsDebugFlags.Static.EnableStackActionButton) { // Measure the stack action button within the constraints of the space above the stack - Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect; + Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect( + mTaskStackView.useGridLayout()); measureChild(mStackActionButton, MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST)); @@ -772,7 +773,8 @@ public class RecentsView extends FrameLayout { * @return the bounds of the stack action button. */ private Rect getStackActionButtonBoundsFromStackLayout() { - Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect); + Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect( + mTaskStackView.useGridLayout())); int left = isLayoutRtl() ? actionButtonRect.left - mStackActionButton.getPaddingLeft() : actionButtonRect.right + mStackActionButton.getPaddingRight() diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java index 493e6187c83e..c1f4c8a4c1dd 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java @@ -157,7 +157,7 @@ public class TaskStackAnimationHelper { // Get the current transform for the task, which will be used to position it offscreen stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, - null); + null, mStackView.useGridLayout()); if (hideTask) { tv.setVisibility(View.INVISIBLE); @@ -230,7 +230,7 @@ public class TaskStackAnimationHelper { // Get the current transform for the task, which will be updated to the final transform // to animate to depending on how recents was invoked stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, - null); + null, mStackView.useGridLayout()); if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) { if (task.isLaunchTarget) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index 571c0f67e18e..052985647453 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -38,7 +38,7 @@ import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; - +import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -240,14 +240,14 @@ public class TaskStackLayoutAlgorithm { // This is the current system insets @ViewDebug.ExportedProperty(category="recents") public Rect mSystemInsets = new Rect(); - // This is the bounds of the stack action above the stack rect - @ViewDebug.ExportedProperty(category="recents") - public Rect mStackActionButtonRect = new Rect(); // The visible ranges when the stack is focused and unfocused private Range mUnfocusedRange; private Range mFocusedRange; + // This is the bounds of the stack action above the stack rect + @ViewDebug.ExportedProperty(category="recents") + private Rect mStackActionButtonRect = new Rect(); // The base top margin for the stack from the system insets @ViewDebug.ExportedProperty(category="recents") private int mBaseTopMargin; @@ -326,7 +326,7 @@ public class TaskStackLayoutAlgorithm { @ViewDebug.ExportedProperty(category="recents") int mMinTranslationZ; @ViewDebug.ExportedProperty(category="recents") - int mMaxTranslationZ; + public int mMaxTranslationZ; // Optimization, allows for quick lookup of task -> index private SparseIntArray mTaskIndexMap = new SparseIntArray(); @@ -334,6 +334,7 @@ public class TaskStackLayoutAlgorithm { // The freeform workspace layout FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm; + TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm; // The transform to place TaskViews at the front and back of the stack respectively TaskViewTransform mBackOfStackTransform = new TaskViewTransform(); @@ -344,6 +345,7 @@ public class TaskStackLayoutAlgorithm { mContext = context; mCb = cb; mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context); + mTaskGridLayoutAlgorithm = new TaskGridLayoutAlgorithm(context); reloadOnConfigurationChange(context); } @@ -368,6 +370,7 @@ public class TaskStackLayoutAlgorithm { R.dimen.recents_layout_initial_top_offset_tablet, R.dimen.recents_layout_initial_top_offset_tablet, R.dimen.recents_layout_initial_top_offset_tablet, + R.dimen.recents_layout_initial_top_offset_tablet, R.dimen.recents_layout_initial_top_offset_tablet); mBaseInitialBottomOffset = getDimensionForDevice(context, R.dimen.recents_layout_initial_bottom_offset_phone_port, @@ -375,17 +378,21 @@ public class TaskStackLayoutAlgorithm { R.dimen.recents_layout_initial_bottom_offset_tablet, R.dimen.recents_layout_initial_bottom_offset_tablet, R.dimen.recents_layout_initial_bottom_offset_tablet, + R.dimen.recents_layout_initial_bottom_offset_tablet, R.dimen.recents_layout_initial_bottom_offset_tablet); mFreeformLayoutAlgorithm.reloadOnConfigurationChange(context); + mTaskGridLayoutAlgorithm.reloadOnConfigurationChange(context); mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin); mBaseTopMargin = getDimensionForDevice(context, R.dimen.recents_layout_top_margin_phone, R.dimen.recents_layout_top_margin_tablet, - R.dimen.recents_layout_top_margin_tablet_xlarge); + R.dimen.recents_layout_top_margin_tablet_xlarge, + R.dimen.recents_layout_top_margin_tablet); mBaseSideMargin = getDimensionForDevice(context, R.dimen.recents_layout_side_margin_phone, R.dimen.recents_layout_side_margin_tablet, - R.dimen.recents_layout_side_margin_tablet_xlarge); + R.dimen.recents_layout_side_margin_tablet_xlarge, + R.dimen.recents_layout_side_margin_tablet); mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin); mFreeformStackGap = res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin); @@ -405,6 +412,7 @@ public class TaskStackLayoutAlgorithm { public boolean setSystemInsets(Rect systemInsets) { boolean changed = !mSystemInsets.equals(systemInsets); mSystemInsets.set(systemInsets); + mTaskGridLayoutAlgorithm.setSystemInsets(systemInsets); return changed; } @@ -470,6 +478,9 @@ public class TaskStackLayoutAlgorithm { updateFrontBackTransforms(); } + + // Initialize the grid layout + mTaskGridLayoutAlgorithm.initialize(displayRect, windowRect); } /** @@ -721,6 +732,11 @@ public class TaskStackLayoutAlgorithm { } } + public Rect getStackActionButtonRect(boolean useGridLayout) { + return useGridLayout + ? mTaskGridLayoutAlgorithm.getStackActionButtonRect() : mStackActionButtonRect; + } + /** * Returns the TaskViewTransform that would put the task just off the back of the stack. */ @@ -825,24 +841,30 @@ public class TaskStackLayoutAlgorithm { * is what the view is measured and laid out with. */ public TaskViewTransform getStackTransform(Task task, float stackScroll, - TaskViewTransform transformOut, TaskViewTransform frontTransform) { + TaskViewTransform transformOut, TaskViewTransform frontTransform, + boolean useGridLayout) { return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform, - false /* forceUpdate */, false /* ignoreTaskOverrides */); + false /* forceUpdate */, false /* ignoreTaskOverrides */, useGridLayout); } public TaskViewTransform getStackTransform(Task task, float stackScroll, TaskViewTransform transformOut, TaskViewTransform frontTransform, - boolean ignoreTaskOverrides) { + boolean ignoreTaskOverrides, boolean useGridLayout) { return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform, - false /* forceUpdate */, ignoreTaskOverrides); + false /* forceUpdate */, ignoreTaskOverrides, useGridLayout); } public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState, TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate, - boolean ignoreTaskOverrides) { + boolean ignoreTaskOverrides, boolean useGridLayout) { if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) { mFreeformLayoutAlgorithm.getTransform(task, transformOut, this); return transformOut; + } else if (useGridLayout) { + int taskIndex = mTaskIndexMap.get(task.key.id); + int taskCount = mTaskIndexMap.size(); + mTaskGridLayoutAlgorithm.getTransform(taskIndex, taskCount, transformOut, this); + return transformOut; } else { // Return early if we have an invalid index int nonOverrideTaskProgress = mTaskIndexMap.get(task.key.id, -1); @@ -864,10 +886,10 @@ public class TaskStackLayoutAlgorithm { */ public TaskViewTransform getStackTransformScreenCoordinates(Task task, float stackScroll, TaskViewTransform transformOut, TaskViewTransform frontTransform, - Rect windowOverrideRect) { + Rect windowOverrideRect, boolean useGridLayout) { TaskViewTransform transform = getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform, true /* forceUpdate */, - false /* ignoreTaskOverrides */); + false /* ignoreTaskOverrides */, useGridLayout); return transformToScreenCoordinates(transform, windowOverrideRect); } @@ -1088,9 +1110,9 @@ public class TaskStackLayoutAlgorithm { * Retrieves resources that are constant regardless of the current configuration of the device. */ public static int getDimensionForDevice(Context ctx, int phoneResId, - int tabletResId, int xlargeTabletResId) { + int tabletResId, int xlargeTabletResId, int gridLayoutResId) { return getDimensionForDevice(ctx, phoneResId, phoneResId, tabletResId, tabletResId, - xlargeTabletResId, xlargeTabletResId); + xlargeTabletResId, xlargeTabletResId, gridLayoutResId); } /** @@ -1098,12 +1120,14 @@ public class TaskStackLayoutAlgorithm { */ public static int getDimensionForDevice(Context ctx, int phonePortResId, int phoneLandResId, int tabletPortResId, int tabletLandResId, int xlargeTabletPortResId, - int xlargeTabletLandResId) { + int xlargeTabletLandResId, int gridLayoutResId) { RecentsConfiguration config = Recents.getConfiguration(); Resources res = ctx.getResources(); boolean isLandscape = Utilities.getAppConfiguration(ctx).orientation == Configuration.ORIENTATION_LANDSCAPE; - if (config.isXLargeScreen) { + if (config.isGridEnabled) { + return res.getDimensionPixelSize(gridLayoutResId); + } else if (config.isXLargeScreen) { return res.getDimensionPixelSize(isLandscape ? xlargeTabletLandResId : xlargeTabletPortResId); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 8c94c3578e96..4625ca76c5cc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -92,6 +92,7 @@ import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.recents.views.grid.GridTaskView; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -158,6 +159,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal private int mTaskCornerRadiusPx; private int mDividerSize; private int mStartTimerIndicatorDuration; + private boolean mDraggingOverDockState; @ViewDebug.ExportedProperty(category="recents") private boolean mTaskViewsClipDirty = true; @@ -499,13 +501,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Calculate the current and (if necessary) the target transform for the task transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll, - taskTransforms.get(i), frontTransform, ignoreTaskOverrides); + taskTransforms.get(i), frontTransform, ignoreTaskOverrides, useGridLayout()); if (useTargetStackScroll && !transform.visible) { // If we have a target stack scroll and the task is not currently visible, then we // just update the transform at the new scroll // TODO: Optimize this - transformAtTarget = mLayoutAlgorithm.getStackTransform(task, - targetStackScroll, new TaskViewTransform(), frontTransformAtTarget); + transformAtTarget = mLayoutAlgorithm.getStackTransform(task, targetStackScroll, + new TaskViewTransform(), frontTransformAtTarget, useGridLayout()); if (transformAtTarget.visible) { transform.copyFrom(transformAtTarget); } @@ -736,7 +738,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } else { mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), focusState, transform, null, true /* forceUpdate */, - false /* ignoreTaskOverrides */); + false /* ignoreTaskOverrides */, useGridLayout()); } transform.visible = true; } @@ -753,7 +755,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal Task task = tasks.get(i); TaskViewTransform transform = transformsOut.get(i); mLayoutAlgorithm.getStackTransform(task, stackScroll, focusState, transform, null, - true /* forceUpdate */, ignoreTaskOverrides); + true /* forceUpdate */, ignoreTaskOverrides, useGridLayout()); transform.visible = true; } } @@ -782,6 +784,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal * Updates the clip for each of the task views from back to front. */ private void clipTaskViews() { + // We never clip task views in grid layout + if (Recents.getConfiguration().isGridEnabled) { + return; + } + // Update the clip on each task child List<TaskView> taskViews = getTaskViews(); TaskView tmpTv = null; @@ -1338,14 +1345,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal setFocusedTask(focusedTaskIndex, false /* scrollToTask */, false /* requestViewFocus */); } - - // Update the stack action button visibility - if (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && - mStack.getTaskCount() > 0) { - EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */)); - } else { - EventBus.getDefault().send(new HideStackActionButtonEvent()); - } + updateStackActionButtonVisibility(); } public boolean isTouchPointInView(float x, float y, TaskView tv) { @@ -1506,7 +1506,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override public TaskView createView(Context context) { - return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); + if (Recents.getConfiguration().isGridEnabled) { + return (GridTaskView) mInflater.inflate(R.layout.recents_grid_task_view, this, false); + } else { + return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); + } } @Override @@ -1636,7 +1640,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal relayoutTaskViewsOnNextFrame(animation); } - if (mEnterAnimationComplete) { + // In grid layout, the stack action button always remains visible. + if (mEnterAnimationComplete && !useGridLayout()) { if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && mStack.getTaskCount() > 0) { @@ -1830,7 +1835,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Enlarge the dragged view slightly float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR; mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(), - mTmpTransform, null); + mTmpTransform, null, useGridLayout()); mTmpTransform.scale = finalScale; mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1; mTmpTransform.dimAlpha = 0f; @@ -1851,6 +1856,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal Interpolators.FAST_OUT_SLOW_IN); boolean ignoreTaskOverrides = false; if (event.dropTarget instanceof TaskStack.DockState) { + mDraggingOverDockState = true; // Calculate the new task stack bounds that matches the window size that Recents will // have after the drop final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; @@ -1870,6 +1876,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal updateLayoutAlgorithm(true /* boundScroll */); ignoreTaskOverrides = true; } else { + mDraggingOverDockState = false; // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging // task view, so add it back to the ignore set after updating the layout removeIgnoreTask(event.task); @@ -1880,6 +1887,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } public final void onBusEvent(final DragEndEvent event) { + mDraggingOverDockState = false; // We don't handle drops on the dock regions if (event.dropTarget instanceof TaskStack.DockState) { // However, we do need to reset the overrides, since the last state of this task stack @@ -2049,6 +2057,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } + // Update the Clear All button in case we're switching in or out of grid layout. + updateStackActionButtonVisibility(); + // Trigger a new layout and update to the initial state if necessary if (event.fromMultiWindow) { mInitialState = INITIAL_STATE_UPDATE_LAYOUT_ONLY; @@ -2119,6 +2130,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** + * Check whether we should use the grid layout. + * We use the grid layout for Recents iff all the following is true: + * 1. Grid-mode is enabled. + * 2. The activity is not in multi-window mode. + * 3. The user is not dragging a task view over the dock state. + * @return True if we should use the grid layout. + */ + public boolean useGridLayout() { + return Recents.getConfiguration().isGridEnabled + && !((RecentsActivity) mContext).isInMultiWindowMode() + && !mDraggingOverDockState; + } + + /** * Reads current system flags related to accessibility and screen pinning. */ private void readSystemFlags() { @@ -2128,6 +2153,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal Settings.System.LOCK_TO_APP_ENABLED) != 0; } + private void updateStackActionButtonVisibility() { + // Always show the button in grid layout. + if (useGridLayout() || + (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && + mStack.getTaskCount() > 0)) { + EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */)); + } else { + EventBus.getDefault().send(new HideStackActionButtonEvent()); + } + } + public void dump(String prefix, PrintWriter writer) { String innerPrefix = prefix + " "; String id = Integer.toHexString(System.identityHashCode(this)); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index 71f559be6775..33fa3b0eb10a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -168,7 +168,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { /** Touch preprocessing for handling below */ public boolean onInterceptTouchEvent(MotionEvent ev) { // Pass through to swipe helper if we are swiping - mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev); + mInterceptedBySwipeHelper = isSwipingEnabled() && mSwipeHelper.onInterceptTouchEvent(ev); if (mInterceptedBySwipeHelper) { return true; } @@ -680,4 +680,11 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { public float getScaledDismissSize() { return 1.5f * Math.max(mSv.getWidth(), mSv.getHeight()); } + + /** + * Returns whether swiping is enabled. + */ + private boolean isSwipingEnabled() { + return !mSv.useGridLayout(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index de7def6c95c9..2f4ad6a4d7ce 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -148,7 +148,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks private ArrayList<Animator> mTmpAnimators = new ArrayList<>(); @ViewDebug.ExportedProperty(deepExport=true, prefix="thumbnail_") - TaskViewThumbnail mThumbnailView; + protected TaskViewThumbnail mThumbnailView; @ViewDebug.ExportedProperty(deepExport=true, prefix="header_") TaskViewHeader mHeaderView; private View mActionButtonView; @@ -176,8 +176,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks super(context, attrs, defStyleAttr, defStyleRes); RecentsConfiguration config = Recents.getConfiguration(); Resources res = context.getResources(); - mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize( - R.dimen.recents_task_view_shadow_rounded_corners_radius)); + mViewBounds = createOutlineProvider(); if (config.fakeShadows) { setBackground(new FakeShadowDrawable(res, config)); } @@ -207,6 +206,12 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks return mTask; } + /* Create an outline provider to clip and outline the view */ + protected AnimateableViewBounds createOutlineProvider() { + return new AnimateableViewBounds(this, mContext.getResources().getDimensionPixelSize( + R.dimen.recents_task_view_shadow_rounded_corners_radius)); + } + /** Returns the view bounds. */ AnimateableViewBounds getViewBounds() { return mViewBounds; @@ -234,7 +239,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks /** * Update the task view when the configuration changes. */ - void onConfigurationChanged() { + protected void onConfigurationChanged() { mHeaderView.onConfigurationChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index 6be169117e5f..c0cc83fa0243 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -290,14 +290,16 @@ public class TaskViewHeader extends FrameLayout R.dimen.recents_task_view_header_height, R.dimen.recents_task_view_header_height_tablet_land, R.dimen.recents_task_view_header_height, - R.dimen.recents_task_view_header_height_tablet_land); + R.dimen.recents_task_view_header_height_tablet_land, + R.dimen.recents_grid_task_view_header_height); int headerButtonPadding = TaskStackLayoutAlgorithm.getDimensionForDevice(getContext(), R.dimen.recents_task_view_header_button_padding, R.dimen.recents_task_view_header_button_padding, R.dimen.recents_task_view_header_button_padding, R.dimen.recents_task_view_header_button_padding_tablet_land, R.dimen.recents_task_view_header_button_padding, - R.dimen.recents_task_view_header_button_padding_tablet_land); + R.dimen.recents_task_view_header_button_padding_tablet_land, + R.dimen.recents_grid_task_view_header_button_padding); if (headerBarHeight != mHeaderBarHeight || headerButtonPadding != mHeaderButtonPadding) { mHeaderBarHeight = headerBarHeight; mHeaderButtonPadding = headerButtonPadding; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java index c46adf15861f..d109807c3ca4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java @@ -60,6 +60,8 @@ public class TaskViewThumbnail extends View { @ViewDebug.ExportedProperty(category="recents") private float mThumbnailScale; private float mFullscreenThumbnailScale; + private boolean mSizeToFit = false; + private boolean mOverlayHeaderOnThumbnailActionBar = true; private ActivityManager.TaskThumbnailInfo mThumbnailInfo; private int mCornerRadius; @@ -67,6 +69,7 @@ public class TaskViewThumbnail extends View { private float mDimAlpha; private Matrix mScaleMatrix = new Matrix(); private Paint mDrawPaint = new Paint(); + private Paint mLockedPaint = new Paint(); private Paint mBgFillPaint = new Paint(); private BitmapShader mBitmapShader; private LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0); @@ -102,6 +105,7 @@ public class TaskViewThumbnail extends View { mCornerRadius = getResources().getDimensionPixelSize( R.dimen.recents_task_view_rounded_corners_radius); mBgFillPaint.setColor(Color.WHITE); + mLockedPaint.setColor(Color.WHITE); mFullscreenThumbnailScale = context.getResources().getFraction( com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1); } @@ -133,10 +137,15 @@ public class TaskViewThumbnail extends View { (int) (mThumbnailRect.width() * mThumbnailScale)); int thumbnailHeight = Math.min(viewHeight, (int) (mThumbnailRect.height() * mThumbnailScale)); - if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) { - int topOffset = mTaskBar != null - ? mTaskBar.getHeight() - mCornerRadius - : 0; + + if (mTask != null && mTask.isLocked) { + canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius, + mLockedPaint); + } else if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) { + int topOffset = 0; + if (mTaskBar != null && mOverlayHeaderOnThumbnailActionBar) { + topOffset = mTaskBar.getHeight() - mCornerRadius; + } // Draw the background, there will be some small overdraw with the thumbnail if (thumbnailWidth < viewWidth) { @@ -200,11 +209,13 @@ public class TaskViewThumbnail extends View { ColorMatrixColorFilter filter = new ColorMatrixColorFilter(TMP_FILTER_COLOR_MATRIX); mDrawPaint.setColorFilter(filter); mBgFillPaint.setColorFilter(filter); + mLockedPaint.setColorFilter(filter); } else { mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul)); mDrawPaint.setColorFilter(mLightingColorFilter); mDrawPaint.setColor(0xFFffffff); mBgFillPaint.setColorFilter(mLightingColorFilter); + mLockedPaint.setColorFilter(mLightingColorFilter); } } else { int grey = mul; @@ -230,7 +241,7 @@ public class TaskViewThumbnail extends View { // If we haven't measured or the thumbnail is invalid, skip the thumbnail drawing // and only draw the background color mThumbnailScale = 0f; - } else if (isStackTask) { + } else if (isStackTask && !mSizeToFit) { float invThumbnailScale = 1f / mFullscreenThumbnailScale; if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) { if (mThumbnailInfo.screenOrientation == Configuration.ORIENTATION_PORTRAIT) { @@ -262,6 +273,19 @@ public class TaskViewThumbnail extends View { } } + /** Sets whether the thumbnail should be resized to fit the task view in all orientations. */ + public void setSizeToFit(boolean flag) { + mSizeToFit = flag; + } + + /** + * Sets whether the header should overlap (and hide) the action bar in the thumbnail, or + * be stacked just above it. + */ + public void setOverlayHeaderOnThumbnailActionBar(boolean flag) { + mOverlayHeaderOnThumbnailActionBar = flag; + } + /** Updates the clip rect based on the given task bar. */ void updateClipToTaskBar(View taskBar) { mTaskBar = taskBar; @@ -299,6 +323,7 @@ public class TaskViewThumbnail extends View { if (t.colorBackground != 0) { mBgFillPaint.setColor(t.colorBackground); } + mLockedPaint.setColor(t.colorPrimary); } /** diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java new file mode 100644 index 000000000000..a029478c2045 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recents.views.grid; + +import android.view.View; +import com.android.systemui.recents.views.AnimateableViewBounds; + +/* An outline provider for grid-based task views. */ +class AnimateableGridViewBounds extends AnimateableViewBounds { + + public AnimateableGridViewBounds(View source, int cornerRadius) { + super(source, cornerRadius); + } + + @Override + protected void updateClipBounds() { + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java new file mode 100644 index 000000000000..630040098d10 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recents.views.grid; + +import android.content.Context; +import android.util.AttributeSet; +import com.android.systemui.R; +import com.android.systemui.recents.views.AnimateableViewBounds; +import com.android.systemui.recents.views.TaskView; + +public class GridTaskView extends TaskView { + + /** The height, in pixels, of the header view. */ + private int mHeaderHeight; + + public GridTaskView(Context context) { + this(context, null); + } + + public GridTaskView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public GridTaskView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public GridTaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mHeaderHeight = context.getResources().getDimensionPixelSize( + R.dimen.recents_grid_task_view_header_height); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + // Show the full thumbnail and don't overlap with the header. + mThumbnailView.setSizeToFit(true); + mThumbnailView.setOverlayHeaderOnThumbnailActionBar(false); + mThumbnailView.updateThumbnailScale(); + mThumbnailView.setTranslationY(mHeaderHeight); + } + + @Override + protected AnimateableViewBounds createOutlineProvider() { + return new AnimateableGridViewBounds(this, mContext.getResources().getDimensionPixelSize( + R.dimen.recents_task_view_shadow_rounded_corners_radius)); + } + + @Override + protected void onConfigurationChanged() { + super.onConfigurationChanged(); + mHeaderHeight = mContext.getResources().getDimensionPixelSize( + R.dimen.recents_grid_task_view_header_height); + mThumbnailView.setTranslationY(mHeaderHeight); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java new file mode 100644 index 000000000000..0d39f5b269a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recents.views.grid; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.Rect; +import android.view.WindowManager; + +import com.android.systemui.R; +import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; +import com.android.systemui.recents.views.TaskViewTransform; + +public class TaskGridLayoutAlgorithm { + + private final String TAG = "TaskGridLayoutAlgorithm"; + private final int MAX_LAYOUT_TASK_COUNT = 8; + + /** The horizontal padding around the whole recents view. */ + private int mPaddingLeftRight; + /** The vertical padding around the whole recents view. */ + private int mPaddingTopBottom; + /** The padding between task views. */ + private int mPaddingTaskView; + + private Rect mDisplayRect; + private Rect mWindowRect; + private Point mScreenSize = new Point(); + + private Rect mTaskGridRect; + + /** The height, in pixels, of each task view's title bar. */ + private int mTitleBarHeight; + + /** The aspect ratio of each task thumbnail, without the title bar. */ + private float mAppAspectRatio; + private Rect mSystemInsets = new Rect(); + + public TaskGridLayoutAlgorithm(Context context) { + reloadOnConfigurationChange(context); + } + + public void reloadOnConfigurationChange(Context context) { + Resources res = context.getResources(); + mPaddingLeftRight = res.getDimensionPixelSize(R.dimen.recents_grid_padding_left_right); + mPaddingTopBottom = res.getDimensionPixelSize(R.dimen.recents_grid_padding_top_bottom); + mPaddingTaskView = res.getDimensionPixelSize(R.dimen.recents_grid_padding_task_view); + + mTaskGridRect = new Rect(); + mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_header_height); + + WindowManager windowManager = (WindowManager) context + .getSystemService(Context.WINDOW_SERVICE); + windowManager.getDefaultDisplay().getRealSize(mScreenSize); + + updateAppAspectRatio(); + } + + public TaskViewTransform getTransform(int taskIndex, int taskCount, + TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) { + + int layoutTaskCount = Math.min(MAX_LAYOUT_TASK_COUNT, taskCount); + + // We also need to invert the index in order to display the most recent tasks first. + int taskLayoutIndex = taskCount - taskIndex - 1; + + int tasksPerLine = layoutTaskCount < 2 ? 1 : ( + layoutTaskCount < 5 ? 2 : ( + layoutTaskCount < 7 ? 3 : 4)); + int lines = layoutTaskCount < 3 ? 1 : 2; + + int taskWidth, taskHeight; + int maxTaskWidth = (mDisplayRect.width() - 2 * mPaddingLeftRight + - (tasksPerLine - 1) * mPaddingTaskView) / tasksPerLine; + int maxTaskHeight = (mDisplayRect.height() - 2 * mPaddingTopBottom + - (lines - 1) * mPaddingTaskView) / lines; + + if (maxTaskHeight >= maxTaskWidth / mAppAspectRatio + mTitleBarHeight) { + // Width bound. + taskWidth = maxTaskWidth; + taskHeight = (int) (maxTaskWidth / mAppAspectRatio + mTitleBarHeight); + } else { + // Height bound. + taskHeight = maxTaskHeight; + taskWidth = (int) ((taskHeight - mTitleBarHeight) * mAppAspectRatio); + } + int emptySpaceX = mDisplayRect.width() - 2 * mPaddingLeftRight + - (tasksPerLine * taskWidth) - (tasksPerLine - 1) * mPaddingTaskView; + int emptySpaceY = mDisplayRect.height() - 2 * mPaddingTopBottom + - (lines * taskHeight) - (lines - 1) * mPaddingTaskView; + + mTaskGridRect.set(0, 0, taskWidth, taskHeight); + + int xIndex = taskLayoutIndex % tasksPerLine; + int yIndex = taskLayoutIndex / tasksPerLine; + int x = emptySpaceX / 2 + mPaddingLeftRight + (taskWidth + mPaddingTaskView) * xIndex; + int y = emptySpaceY / 2 + mPaddingTopBottom + (taskHeight + mPaddingTaskView) * yIndex; + float z = stackLayout.mMaxTranslationZ; + + float dimAlpha = 0f; + float viewOutlineAlpha = 0f; + boolean isTaskViewVisible = (taskLayoutIndex < MAX_LAYOUT_TASK_COUNT); + + // Fill out the transform + transformOut.scale = 1f; + transformOut.alpha = isTaskViewVisible ? 1f : 0f; + transformOut.translationZ = z; + transformOut.dimAlpha = dimAlpha; + transformOut.viewOutlineAlpha = viewOutlineAlpha; + transformOut.rect.set(mTaskGridRect); + transformOut.rect.offset(x, y); + Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale); + // We only show the 8 most recent tasks. + transformOut.visible = isTaskViewVisible; + return transformOut; + } + + public void initialize(Rect displayRect, Rect windowRect) { + mDisplayRect = displayRect; + mWindowRect = windowRect; + } + + public void setSystemInsets(Rect systemInsets) { + mSystemInsets = systemInsets; + updateAppAspectRatio(); + } + + private void updateAppAspectRatio() { + int usableWidth = mScreenSize.x - mSystemInsets.left - mSystemInsets.right; + int usableHeight = mScreenSize.y - mSystemInsets.top - mSystemInsets.bottom; + mAppAspectRatio = (float) usableWidth / (float) usableHeight; + } + + public Rect getStackActionButtonRect() { + Rect buttonRect = new Rect(mDisplayRect); + buttonRect.right -= mPaddingLeftRight; + buttonRect.left += mPaddingLeftRight; + buttonRect.bottom = buttonRect.top + mPaddingTopBottom; + return buttonRect; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java index 567ab3b5966d..227ebdfacbda 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java @@ -165,10 +165,6 @@ public class QSTileHost implements QSTile.Host, Tunable { mHeader = view; } - public PhoneStatusBar getPhoneStatusBar() { - return mStatusBar; - } - public void destroy() { mHandlerThread.quitSafely(); mTiles.values().forEach(tile -> tile.destroy()); diff --git a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java b/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java index f42092130af1..5cfe677efd57 100644 --- a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java @@ -81,11 +81,23 @@ public class LayoutInflaterBuilder { * @return Builder object post-modification. */ public LayoutInflaterBuilder replace(@NonNull Class from, @NonNull Class to) { + return replace(from.getName(), to); + } + + /** + * Instructs the Builder to configure the LayoutInflater such that all instances + * of one {@link View} will be replaced with instances of another during inflation. + * + * @param tag Instances of this tag will be replaced during inflation. + * @param to Instances of this class will be inflated as replacements. + * @return Builder object post-modification. + */ + public LayoutInflaterBuilder replace(@NonNull String tag, @NonNull Class to) { assertIfAlreadyBuilt(); if (mReplaceMap == null) { mReplaceMap = new ArrayMap<String, String>(); } - mReplaceMap.put(from.getName(), to.getName()); + mReplaceMap.put(tag, to.getName()); return this; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java index 008580ad6944..d1d752099d19 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java @@ -37,6 +37,7 @@ public abstract class SysuiTestCase { @Before public void SysuiSetup() throws Exception { + System.setProperty("dexmaker.share_classloader", "true"); mContext = new TestableContext(InstrumentationRegistry.getTargetContext(), this); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index 8b99d725fd73..c3948258f11c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -214,6 +214,16 @@ public class DozeMachineTest { @Test @UiThreadTest + public void testSuppressingPulse_doesntCrash() { + mMachine.requestState(INITIALIZED); + + mMachine.requestState(DOZE); + mMachine.requestState(DOZE_REQUEST_PULSE); + mMachine.requestState(DOZE_PULSE_DONE); + } + + @Test + @UiThreadTest public void testScreen_offInDoze() { mMachine.requestState(INITIALIZED); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java index 4c25c62e82a3..8acd6ba5e1d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java @@ -14,37 +14,31 @@ package com.android.systemui.qs; +import static junit.framework.Assert.assertEquals; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import android.content.Context; -import android.content.res.Resources; +import android.os.Handler; import android.os.Looper; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import android.text.SpannableStringBuilder; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; import android.widget.TextView; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.policy.SecurityController; +import com.android.systemui.util.LayoutInflaterBuilder; +import com.android.systemui.utils.TestableImageView; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import static junit.framework.Assert.assertEquals; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - @SmallTest @RunWith(AndroidJUnit4.class) public class QSFooterTest extends SysuiTestCase { @@ -53,28 +47,26 @@ public class QSFooterTest extends SysuiTestCase { private final String DEVICE_OWNER_PACKAGE = "TestDPC"; private final String VPN_PACKAGE = "TestVPN"; - private ViewGroup mRootView = mock(ViewGroup.class); - private TextView mFooterText = mock(TextView.class); - private ImageView mFooterIcon = mock(ImageView.class); - private ImageView mFooterIcon2 = mock(ImageView.class); + private ViewGroup mRootView; + private TextView mFooterText; + private TestableImageView mFooterIcon; + private TestableImageView mFooterIcon2; private QSFooter mFooter; - private Resources mResources; private SecurityController mSecurityController = mock(SecurityController.class); @Before public void setUp() { - when(mRootView.findViewById(R.id.footer_text)).thenReturn(mFooterText); - when(mRootView.findViewById(R.id.footer_icon)).thenReturn(mFooterIcon); - when(mRootView.findViewById(R.id.footer_icon2)).thenReturn(mFooterIcon2); - final LayoutInflater layoutInflater = mock(LayoutInflater.class); - when(layoutInflater.inflate(eq(R.layout.quick_settings_footer), anyObject(), anyBoolean())) - .thenReturn(mRootView); - final Context context = mock(Context.class); - when(context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).thenReturn(layoutInflater); - mResources = mContext.getResources(); - when(context.getResources()).thenReturn(mResources); - mFooter = new QSFooter(null, context); - reset(mRootView); + mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, + new LayoutInflaterBuilder(mContext) + .replace("ImageView", TestableImageView.class) + .build()); + Handler h = new Handler(Looper.getMainLooper()); + h.post(() -> mFooter = new QSFooter(null, mContext)); + waitForIdleSync(h); + mRootView = (ViewGroup) mFooter.getView(); + mFooterText = (TextView) mRootView.findViewById(R.id.footer_text); + mFooterIcon = (TestableImageView) mRootView.findViewById(R.id.footer_icon); + mFooterIcon2 = (TestableImageView) mRootView.findViewById(R.id.footer_icon2); mFooter.setHostEnvironment(null, mSecurityController, Looper.getMainLooper()); } @@ -86,8 +78,7 @@ public class QSFooterTest extends SysuiTestCase { mFooter.refreshState(); waitForIdleSync(mFooter.mHandler); - verify(mRootView).setVisibility(View.GONE); - verifyNoMoreInteractions(mRootView); + assertEquals(View.GONE, mRootView.getVisibility()); } @Test @@ -97,10 +88,8 @@ public class QSFooterTest extends SysuiTestCase { mFooter.refreshState(); waitForIdleSync(mFooter.mHandler); - verify(mFooterText).setText(mResources.getString(R.string.do_disclosure_generic)); - verifyNoMoreInteractions(mFooterText); - verify(mRootView).setVisibility(View.VISIBLE); - verifyNoMoreInteractions(mRootView); + assertEquals(mContext.getString(R.string.do_disclosure_generic), mFooterText.getText()); + assertEquals(View.VISIBLE, mRootView.getVisibility()); } @Test @@ -111,11 +100,9 @@ public class QSFooterTest extends SysuiTestCase { mFooter.refreshState(); waitForIdleSync(mFooter.mHandler); - verify(mFooterText).setText(mResources.getString(R.string.do_disclosure_with_name, - MANAGING_ORGANIZATION)); - verifyNoMoreInteractions(mFooterText); - verify(mRootView).setVisibility(View.VISIBLE); - verifyNoMoreInteractions(mRootView); + assertEquals(mContext.getString(R.string.do_disclosure_with_name, MANAGING_ORGANIZATION), + mFooterText.getText()); + assertEquals(View.VISIBLE, mRootView.getVisibility()); } @Test @@ -126,9 +113,9 @@ public class QSFooterTest extends SysuiTestCase { mFooter.refreshState(); waitForIdleSync(mFooter.mHandler); - verify(mFooterIcon).setVisibility(View.VISIBLE); - verify(mFooterIcon).setImageResource(R.drawable.ic_qs_network_logging); - verify(mFooterIcon2).setVisibility(View.INVISIBLE); + assertEquals(View.VISIBLE, mFooterIcon.getVisibility()); + assertEquals(R.drawable.ic_qs_network_logging, mFooterIcon.getLastImageResource()); + assertEquals(View.INVISIBLE, mFooterIcon2.getVisibility()); } @Test @@ -140,9 +127,10 @@ public class QSFooterTest extends SysuiTestCase { mFooter.refreshState(); waitForIdleSync(mFooter.mHandler); - verify(mFooterIcon).setVisibility(View.VISIBLE); - verify(mFooterIcon, never()).setImageResource(anyInt()); - verify(mFooterIcon2).setVisibility(View.INVISIBLE); + assertEquals(View.VISIBLE, mFooterIcon.getVisibility()); + // -1 == never set. + assertEquals(-1, mFooterIcon.getLastImageResource()); + assertEquals(View.INVISIBLE, mFooterIcon2.getVisibility()); } @Test @@ -154,10 +142,11 @@ public class QSFooterTest extends SysuiTestCase { mFooter.refreshState(); waitForIdleSync(mFooter.mHandler); - verify(mFooterIcon).setVisibility(View.VISIBLE); - verify(mFooterIcon, never()).setImageResource(anyInt()); - verify(mFooterIcon2).setVisibility(View.VISIBLE); - verify(mFooterIcon2, never()).setImageResource(anyInt()); + assertEquals(View.VISIBLE, mFooterIcon.getVisibility()); + assertEquals(View.VISIBLE, mFooterIcon2.getVisibility()); + // -1 == never set. + assertEquals(-1, mFooterIcon.getLastImageResource()); + assertEquals(-1, mFooterIcon2.getLastImageResource()); } @Test @@ -211,20 +200,15 @@ public class QSFooterTest extends SysuiTestCase { private CharSequence getExpectedMessage(boolean hasDeviceOwnerOrganization, boolean hasVPN) { final SpannableStringBuilder message = new SpannableStringBuilder(); message.append(hasDeviceOwnerOrganization ? - mResources.getString(R.string.monitoring_description_do_header_with_name, + mContext.getString(R.string.monitoring_description_do_header_with_name, MANAGING_ORGANIZATION, DEVICE_OWNER_PACKAGE) : - mResources.getString(R.string.monitoring_description_do_header_generic, + mContext.getString(R.string.monitoring_description_do_header_generic, DEVICE_OWNER_PACKAGE)); message.append("\n\n"); - message.append(mResources.getString(R.string.monitoring_description_do_body)); - if (hasVPN) { - message.append("\n\n"); - message.append(mResources.getString(R.string.monitoring_description_do_body_vpn, - VPN_PACKAGE)); - } - message.append(mResources.getString( + message.append(mContext.getString(R.string.monitoring_description_do_body)); + message.append(mContext.getString( R.string.monitoring_description_do_learn_more_separator)); - message.append(mResources.getString(R.string.monitoring_description_do_learn_more), + message.append(mContext.getString(R.string.monitoring_description_do_learn_more), mFooter.new EnterprisePrivacySpan(), 0); return message; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index 350a95ff66f4..c0d5bbd35690 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -63,7 +63,7 @@ public class QSFragmentTest extends FragmentTestCase { when(userSwitcher.getKeyguardMonitor()).thenReturn(keyguardMonitor); when(userSwitcher.getUsers()).thenReturn(new ArrayList<>()); QSTileHost host = new QSTileHost(mContext, - mock(PhoneStatusBar.class), + null, getLeakChecker(BluetoothController.class), getLeakChecker(LocationController.class), getLeakChecker(RotationLockController.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index 782a4890ba55..66ec7dd3f270 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -15,8 +15,10 @@ */ package com.android.systemui.qs.external; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; @@ -26,9 +28,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.content.pm.PackageInfo; import android.content.pm.ServiceInfo; import android.net.Uri; @@ -50,15 +50,12 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; @SmallTest @RunWith(AndroidJUnit4.class) public class TileLifecycleManagerTest extends SysuiTestCase { private static final int TEST_FAIL_TIMEOUT = 5000; - private final Context mMockContext = Mockito.mock(Context.class); private final PackageManagerAdapter mMockPackageManagerAdapter = Mockito.mock(PackageManagerAdapter.class); private final IQSTileService.Stub mMockTileService = Mockito.mock(IQSTileService.Stub.class); @@ -77,19 +74,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { // Stub.asInterface will just return itself. when(mMockTileService.queryLocalInterface(anyString())).thenReturn(mMockTileService); - // Default behavior for bind is success and connects as mMockTileService. - when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())) - .thenAnswer( - new Answer<Boolean>() { - @Override - public Boolean answer(InvocationOnMock invocation) { - ServiceConnection connection = - (ServiceConnection) invocation.getArguments()[1]; - connection.onServiceConnected( - mTileServiceComponentName, mMockTileService); - return true; - } - }); + mContext.addMockService(mTileServiceComponentName, mMockTileService); mTileServiceIntent = new Intent().setComponent(mTileServiceComponentName); @@ -97,7 +82,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mThread = new HandlerThread("TestThread"); mThread.start(); mHandler = new Handler(mThread.getLooper()); - mStateManager = new TileLifecycleManager(mHandler, mMockContext, + mStateManager = new TileLifecycleManager(mHandler, mContext, Mockito.mock(IQSService.class), new Tile(), mTileServiceIntent, mUser, @@ -126,11 +111,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { } private void verifyBind(int times) { - verify(mMockContext, times(times)).bindServiceAsUser( - mTileServiceIntent, - mStateManager, - Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, - mUser); + assertEquals(times > 0, mContext.isBound(mTileServiceComponentName)); } @Test @@ -143,7 +124,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { public void testUnbind() { mStateManager.setBindService(true); mStateManager.setBindService(false); - verify(mMockContext).unbindService(mStateManager); + assertFalse(mContext.isBound(mTileServiceComponentName)); } @Test @@ -203,7 +184,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { verifyBind(1); mStateManager.setBindService(false); - verify(mMockContext).unbindService(mStateManager); + assertFalse(mContext.isBound(mTileServiceComponentName)); verify(mMockTileService, never()).onStartListening(); } @@ -217,7 +198,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { verifyBind(1); mStateManager.setBindService(false); - verify(mMockContext).unbindService(mStateManager); + assertFalse(mContext.isBound(mTileServiceComponentName)); verify(mMockTileService, never()).onClick(null); } @@ -233,7 +214,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { // Package is re-enabled. setPackageEnabled(true); mStateManager.onReceive( - mMockContext, + mContext, new Intent( Intent.ACTION_PACKAGE_CHANGED, Uri.fromParts( diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithmTest.java deleted file mode 100644 index a9f811b2cee6..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithmTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ -package com.android.systemui.recents.grid; - -import android.graphics.Rect; -import android.test.suitebuilder.annotation.SmallTest; -import com.android.systemui.SysuiTestCase; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertTrue; - -import org.junit.Test; - -@SmallTest -public class TaskGridLayoutAlgorithmTest extends SysuiTestCase { - - private static final List<Integer> ZERO_MARGIN = TaskGridLayoutAlgorithm.ZERO_MARGIN; - - @Test - public void testOneTile() { - List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - 1, 1000, 1000, 1 /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0); - assertEquals(1, rects.size()); - Rect singleRect = rects.get(0); - assertEquals(1000, singleRect.width()); - } - - @Test - public void testTwoTilesLandscape() { - List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - 2, 1200, 500, 1.2f /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0); - assertEquals(2, rects.size()); - for (Rect rect : rects) { - assertEquals(600, rect.width()); - assertEquals(499, rect.height()); - } - } - - @Test - public void testTwoTilesLandscapeWithPadding() { - List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - 2, 1200, 500, 1.19f /* screenRatio */, 10 /* padding */, ZERO_MARGIN, 0); - assertEquals(2, rects.size()); - Rect rectA = rects.get(0); - Rect rectB = rects.get(1); - assertEquals(595, rectA.width()); - assertEquals(595, rectB.width()); - assertEquals(605, rectB.left); - } - - @Test - public void testTwoTilesPortrait() { - List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - 2, 500, 1200, 1 /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0); - assertEquals(2, rects.size()); - for (Rect rect : rects) { - assertEquals(250, rect.width()); - assertEquals(250, rect.height()); - } - } - - @Test - public void testThreeTiles() { - List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - 3, 1200, 500, 2 /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0); - assertEquals(3, rects.size()); - for (Rect rect : rects) { - assertEquals(400, rect.width()); - assertEquals(200, rect.height()); - } - } - - @Test - public void testFourTiles() { - List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - 4, 1200, 500, 2.4f /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0); - assertEquals(4, rects.size()); - for (Rect rect : rects) { - assertEquals(600, rect.width()); - assertEquals(249, rect.height()); - } - Rect rectD = rects.get(3); - assertEquals(600, rectD.left); - assertEquals(250, rectD.top); - } - - @Test - public void testNineTiles() { - List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount( - 9, 1200, 600, 2 /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0); - assertEquals(9, rects.size()); - for (Rect rect : rects) { - assertEquals(400, rect.width()); - assertEquals(200, rect.height()); - } - Rect rectE = rects.get(4); - assertEquals(400, rectE.left); - assertEquals(200, rectE.top); - Rect rectI = rects.get(8); - assertEquals(800, rectI.left); - assertEquals(400, rectI.top); - }} - diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 639c8daf586b..7335af3fff9d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -16,15 +16,18 @@ package com.android.systemui.statusbar; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; -import android.content.ContentResolver; import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Resources; +import android.hardware.fingerprint.FingerprintManager; import android.os.Looper; import android.support.test.runner.AndroidJUnit4; -import android.telephony.SubscriptionManager; import android.test.suitebuilder.annotation.SmallTest; import android.view.View; import android.view.ViewGroup; @@ -37,22 +40,15 @@ import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; @SmallTest @RunWith(AndroidJUnit4.class) public class KeyguardIndicationControllerTest extends SysuiTestCase { private final String ORGANIZATION_NAME = "organization"; - private final String DISCLOSURE_WITH_ORGANIZATION_NAME = "managed by organization"; - private Context mContext = mock(Context.class); + private String mDisclosureWithOrganization; + private DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class); private ViewGroup mIndicationArea = mock(ViewGroup.class); private KeyguardIndicationTextView mDisclosure = mock(KeyguardIndicationTextView.class); @@ -61,19 +57,11 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - final Resources resources = mock(Resources.class); - when(mContext.getResources()).thenReturn(resources); - when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class)); - when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class)); - when(mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)).thenReturn( - mock(SubscriptionManager.class)); - when(mContext.getSystemService(Context.TRUST_SERVICE)).thenReturn( - mock(TrustManager.class)); - when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn( - mDevicePolicyManager); - - when(resources.getString(R.string.do_disclosure_with_name, ORGANIZATION_NAME)) - .thenReturn(DISCLOSURE_WITH_ORGANIZATION_NAME); + mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager); + mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class)); + mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class)); + mDisclosureWithOrganization = mContext.getString(R.string.do_disclosure_with_name, + ORGANIZATION_NAME); when(mIndicationArea.findViewById(R.id.keyguard_indication_enterprise_disclosure)) .thenReturn(mDisclosure); @@ -113,7 +101,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { createController(); verify(mDisclosure).setVisibility(View.VISIBLE); - verify(mDisclosure).switchIndication(DISCLOSURE_WITH_ORGANIZATION_NAME); + verify(mDisclosure).switchIndication(mDisclosureWithOrganization); verifyNoMoreInteractions(mDisclosure); } @@ -140,7 +128,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { monitor.onKeyguardVisibilityChanged(true); verify(mDisclosure).setVisibility(View.VISIBLE); - verify(mDisclosure).switchIndication(DISCLOSURE_WITH_ORGANIZATION_NAME); + verify(mDisclosure).switchIndication(mDisclosureWithOrganization); verifyNoMoreInteractions(mDisclosure); reset(mDisclosure); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java index 9a697eedc2e1..87c4c664f352 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java @@ -16,26 +16,24 @@ package com.android.systemui.statusbar.policy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import android.app.admin.DevicePolicyManager; import android.content.Context; -import android.content.pm.UserInfo; import android.net.ConnectivityManager; -import android.os.UserManager; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import com.android.systemui.SysuiTestCase; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - @SmallTest @RunWith(AndroidJUnit4.class) @@ -45,16 +43,9 @@ public class SecurityControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - final Context context = mock(Context.class); - when(context.getSystemService(Context.DEVICE_POLICY_SERVICE)) - .thenReturn(mDevicePolicyManager); - when(context.getSystemService(Context.CONNECTIVITY_SERVICE)) - .thenReturn(mock(ConnectivityManager.class)); - final UserManager userManager = mock(UserManager.class); - when(userManager.getUserInfo(anyInt())).thenReturn(mock(UserInfo.class)); - when(context.getSystemService(Context.USER_SERVICE)) - .thenReturn(userManager); - mSecurityController = new SecurityControllerImpl(context); + mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager); + mContext.addMockSystemService(Context.CONNECTIVITY_SERVICE, mock(ConnectivityManager.class)); + mSecurityController = new SecurityControllerImpl(mContext); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java index bf73416ffcff..a95280641aa1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java @@ -16,15 +16,19 @@ package com.android.systemui.utils; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks; +import android.content.ComponentName; import android.content.ContentProviderClient; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.res.Resources; import android.os.Handler; +import android.os.IBinder; import android.os.UserHandle; import android.provider.Settings; +import android.util.ArrayMap; import com.android.systemui.utils.leaks.Tracker; import com.android.systemui.SysuiTestCase; @@ -34,6 +38,10 @@ public class TestableContext extends ContextWrapper { private final FakeContentResolver mFakeContentResolver; private final FakeSettingsProvider mSettingsProvider; + private ArrayMap<String, Object> mMockSystemServices; + private ArrayMap<ComponentName, IBinder> mMockServices; + private ArrayMap<ServiceConnection, ComponentName> mActiveServices; + private Tracker mReceiver; private Tracker mService; private Tracker mComponent; @@ -51,6 +59,33 @@ public class TestableContext extends ContextWrapper { mComponent = test.getTracker("component"); } + @Override + public Resources getResources() { + return super.getResources(); + } + + public void addMockSystemService(String name, Object service) { + mMockSystemServices = lazyInit(mMockSystemServices); + mMockSystemServices.put(name, service); + } + + public void addMockService(ComponentName component, IBinder service) { + mMockServices = lazyInit(mMockServices); + mMockServices.put(component, service); + } + + private <T, V> ArrayMap<T, V> lazyInit(ArrayMap<T, V> services) { + return services != null ? services : new ArrayMap<T, V>(); + } + + @Override + public Object getSystemService(String name) { + if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) { + return mMockSystemServices.get(name); + } + return super.getSystemService(name); + } + public FakeSettingsProvider getSettingsProvider() { return mSettingsProvider; } @@ -96,6 +131,7 @@ public class TestableContext extends ContextWrapper { @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); + if (checkMocks(service.getComponent(), conn)) return true; return super.bindService(service, conn, flags); } @@ -103,6 +139,7 @@ public class TestableContext extends ContextWrapper { public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user) { if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); + if (checkMocks(service.getComponent(), conn)) return true; return super.bindServiceAsUser(service, conn, flags, handler, user); } @@ -110,15 +147,35 @@ public class TestableContext extends ContextWrapper { public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, UserHandle user) { if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); + if (checkMocks(service.getComponent(), conn)) return true; return super.bindServiceAsUser(service, conn, flags, user); } + private boolean checkMocks(ComponentName component, ServiceConnection conn) { + if (mMockServices != null && component != null && mMockServices.containsKey(component)) { + mActiveServices = lazyInit(mActiveServices); + mActiveServices.put(conn, component); + conn.onServiceConnected(component, mMockServices.get(component)); + return true; + } + return false; + } + @Override public void unbindService(ServiceConnection conn) { if (mService != null) mService.getLeakInfo(conn).clearAllocations(); + if (mActiveServices != null && mActiveServices.containsKey(conn)) { + conn.onServiceDisconnected(mActiveServices.get(conn)); + mActiveServices.remove(conn); + return; + } super.unbindService(conn); } + public boolean isBound(ComponentName component) { + return mActiveServices != null && mActiveServices.containsValue(component); + } + @Override public void registerComponentCallbacks(ComponentCallbacks callback) { if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java new file mode 100644 index 000000000000..b131460e1eff --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.utils; + +import android.annotation.DrawableRes; +import android.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ImageView; + +public class TestableImageView extends ImageView { + + private int mLastResId = -1; + + public TestableImageView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setImageResource(@DrawableRes int resId) { + mLastResId = resId; + super.setImageResource(resId); + } + + public int getLastImageResource() { + return mLastResId; + } +} diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index d3110a449a4e..0ea4722bdc66 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -339,13 +339,15 @@ class BluetoothManagerService extends IBluetoothManager.Stub { /** * Save the Bluetooth on/off state - * */ private void persistBluetoothSetting(int value) { if (DBG) Slog.d(TAG, "Persisting Bluetooth Setting: " + value); + // waive WRITE_SECURE_SETTINGS permission check + long callingIdentity = Binder.clearCallingIdentity(); Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.BLUETOOTH_ON, value); + Binder.restoreCallingIdentity(callingIdentity); } /** @@ -610,20 +612,26 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } /** - * Action taken when GattService is turned off + * Action taken when GattService is turned on */ private void onBluetoothGattServiceUp() { if (DBG) Slog.d(TAG,"BluetoothGatt Service is Up"); try { mBluetoothLock.readLock().lock(); - if (isBleAppPresent() == false && mBluetooth != null - && mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) { + if (mBluetooth == null) { + if (DBG) Slog.w(TAG, "onBluetoothServiceUp: mBluetooth is null!"); + return; + } + int st = mBluetooth.getState(); + if (st != BluetoothAdapter.STATE_BLE_ON) { + if (DBG) Slog.v(TAG, "onBluetoothServiceUp: state isn't BLE_ON: " + + BluetoothAdapter.nameForState(st)); + return; + } + if (isBluetoothPersistedStateOnBluetooth() || !isBleAppPresent()) { + // This triggers transition to STATE_ON mBluetooth.onLeServiceUp(); - - // waive WRITE_SECURE_SETTINGS permission check - long callingIdentity = Binder.clearCallingIdentity(); persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); - Binder.restoreCallingIdentity(callingIdentity); } } catch (RemoteException e) { Slog.e(TAG,"Unable to call onServiceUp", e); @@ -763,10 +771,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { synchronized(mReceiver) { if (persist) { - // waive WRITE_SECURE_SETTINGS permission check - long callingIdentity = Binder.clearCallingIdentity(); persistBluetoothSetting(BLUETOOTH_OFF); - Binder.restoreCallingIdentity(callingIdentity); } mEnableExternal = false; sendDisableMsg(); diff --git a/services/core/java/com/android/server/ConsumerIrService.java b/services/core/java/com/android/server/ConsumerIrService.java index ea6812d2df85..2ed6c77baa0d 100644 --- a/services/core/java/com/android/server/ConsumerIrService.java +++ b/services/core/java/com/android/server/ConsumerIrService.java @@ -29,13 +29,13 @@ public class ConsumerIrService extends IConsumerIrService.Stub { private static final int MAX_XMIT_TIME = 2000000; /* in microseconds */ - private static native long halOpen(); - private static native int halTransmit(long halObject, int carrierFrequency, int[] pattern); - private static native int[] halGetCarrierFrequencies(long halObject); + private static native boolean halOpen(); + private static native int halTransmit(int carrierFrequency, int[] pattern); + private static native int[] halGetCarrierFrequencies(); private final Context mContext; private final PowerManager.WakeLock mWakeLock; - private final long mNativeHal; + private final boolean mHasNativeHal; private final Object mHalLock = new Object(); ConsumerIrService(Context context) { @@ -45,23 +45,23 @@ public class ConsumerIrService extends IConsumerIrService.Stub { mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mWakeLock.setReferenceCounted(true); - mNativeHal = halOpen(); + mHasNativeHal = halOpen(); if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONSUMER_IR)) { - if (mNativeHal == 0) { + if (!mHasNativeHal) { throw new RuntimeException("FEATURE_CONSUMER_IR present, but no IR HAL loaded!"); } - } else if (mNativeHal != 0) { + } else if (mHasNativeHal) { throw new RuntimeException("IR HAL present, but FEATURE_CONSUMER_IR is not set!"); } } @Override public boolean hasIrEmitter() { - return mNativeHal != 0; + return mHasNativeHal; } private void throwIfNoIrEmitter() { - if (mNativeHal == 0) { + if (!mHasNativeHal) { throw new UnsupportedOperationException("IR emitter not available"); } } @@ -91,7 +91,7 @@ public class ConsumerIrService extends IConsumerIrService.Stub { // Right now there is no mechanism to ensure fair queing of IR requests synchronized (mHalLock) { - int err = halTransmit(mNativeHal, carrierFrequency, pattern); + int err = halTransmit(carrierFrequency, pattern); if (err < 0) { Slog.e(TAG, "Error transmitting: " + err); @@ -109,7 +109,7 @@ public class ConsumerIrService extends IConsumerIrService.Stub { throwIfNoIrEmitter(); synchronized(mHalLock) { - return halGetCarrierFrequencies(mNativeHal); + return halGetCarrierFrequencies(); } } } diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index 13985304bd87..4d6ffe6e99c6 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -16,12 +16,14 @@ package com.android.server; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; +import android.app.admin.PasswordMetrics; import android.app.backup.BackupManager; import android.app.trust.IStrongAuthTracker; import android.app.trust.TrustManager; @@ -931,6 +933,7 @@ public class LockSettingsService extends ILockSettings.Stub { synchronized (mSeparateChallengeLock) { setLockPatternInternal(pattern, savedCredential, userId); setSeparateProfileChallengeEnabled(userId, true, null); + notifyPasswordChanged(userId); } } @@ -945,6 +948,7 @@ public class LockSettingsService extends ILockSettings.Stub { setKeystorePassword(null, userId); fixateNewestUserKeyAuth(userId); onUserLockChanged(userId); + notifyActivePasswordMetricsAvailable(null, userId); return; } @@ -994,6 +998,7 @@ public class LockSettingsService extends ILockSettings.Stub { synchronized (mSeparateChallengeLock) { setLockPasswordInternal(password, savedCredential, userId); setSeparateProfileChallengeEnabled(userId, true, null); + notifyPasswordChanged(userId); } } @@ -1007,6 +1012,7 @@ public class LockSettingsService extends ILockSettings.Stub { setKeystorePassword(null, userId); fixateNewestUserKeyAuth(userId); onUserLockChanged(userId); + notifyActivePasswordMetricsAvailable(null, userId); return; } @@ -1420,6 +1426,7 @@ public class LockSettingsService extends ILockSettings.Stub { // migrate credential to GateKeeper credentialUtil.setCredential(credential, null, userId); if (!hasChallenge) { + notifyActivePasswordMetricsAvailable(credential, userId); return VerifyCredentialResponse.OK; } // Fall through to get the auth token. Technically this should never happen, @@ -1459,6 +1466,7 @@ public class LockSettingsService extends ILockSettings.Stub { if (progressCallback != null) { progressCallback.onCredentialVerified(); } + notifyActivePasswordMetricsAvailable(credential, userId); unlockKeystore(credential, userId); Slog.i(TAG, "Unlocking user " + userId + @@ -1482,6 +1490,36 @@ public class LockSettingsService extends ILockSettings.Stub { return response; } + private void notifyActivePasswordMetricsAvailable(String password, @UserIdInt int userId) { + final PasswordMetrics metrics; + if (password == null) { + metrics = new PasswordMetrics(); + } else { + metrics = PasswordMetrics.computeForPassword(password); + metrics.quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(userId); + } + + // Asynchronous to avoid dead lock + mHandler.post(() -> { + DevicePolicyManager dpm = (DevicePolicyManager) + mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + dpm.setActivePasswordState(metrics, userId); + }); + } + + /** + * Call after {@link #notifyActivePasswordMetricsAvailable} so metrics are updated before + * reporting the password changed. + */ + private void notifyPasswordChanged(@UserIdInt int userId) { + // Same handler as notifyActivePasswordMetricsAvailable to ensure correct ordering + mHandler.post(() -> { + DevicePolicyManager dpm = (DevicePolicyManager) + mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + dpm.reportPasswordChanged(userId); + }); + } + @Override public boolean checkVoldPassword(int userId) throws RemoteException { if (!mFirstCallToVold) { diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index a1c3564abf8c..f712f121f1d7 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -16,7 +16,11 @@ package com.android.server; +import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT; +import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE; + import android.Manifest.permission; +import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -25,27 +29,29 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.net.INetworkRecommendationProvider; import android.net.INetworkScoreCache; import android.net.INetworkScoreService; import android.net.NetworkKey; -import android.net.NetworkScoreManager; import android.net.NetworkScorerAppManager; import android.net.NetworkScorerAppManager.NetworkScorerAppData; import android.net.RecommendationRequest; import android.net.RecommendationResult; import android.net.ScoredNetwork; +import android.net.Uri; import android.net.wifi.WifiConfiguration; -import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; -import android.provider.Settings; -import android.text.TextUtils; +import android.provider.Settings.Global; import android.util.ArrayMap; import android.util.Log; +import android.util.TimedRemoteCaller; -import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; @@ -59,6 +65,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; /** @@ -67,17 +74,20 @@ import java.util.function.Consumer; */ public class NetworkScoreService extends INetworkScoreService.Stub { private static final String TAG = "NetworkScoreService"; - private static final boolean DBG = false; + private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); private final Context mContext; private final NetworkScorerAppManager mNetworkScorerAppManager; + private final RequestRecommendationCaller mRequestRecommendationCaller; @GuardedBy("mScoreCaches") private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches; /** Lock used to update mPackageMonitor when scorer package changes occur. */ private final Object mPackageMonitorLock = new Object[0]; + private final Object mServiceConnectionLock = new Object[0]; @GuardedBy("mPackageMonitorLock") private NetworkScorerPackageMonitor mPackageMonitor; + @GuardedBy("mServiceConnectionLock") private ScoringServiceConnection mServiceConnection; private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() { @@ -99,10 +109,10 @@ public class NetworkScoreService extends INetworkScoreService.Stub { * manages the service connection. */ private class NetworkScorerPackageMonitor extends PackageMonitor { - final String mRegisteredPackage; + final List<String> mPackagesToWatch; - private NetworkScorerPackageMonitor(String mRegisteredPackage) { - this.mRegisteredPackage = mRegisteredPackage; + private NetworkScorerPackageMonitor(List<String> packagesToWatch) { + mPackagesToWatch = packagesToWatch; } @Override @@ -136,7 +146,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } private void evaluateBinding(String scorerPackageName, boolean forceUnbind) { - if (mRegisteredPackage.equals(scorerPackageName)) { + if (mPackagesToWatch.contains(scorerPackageName)) { if (DBG) { Log.d(TAG, "Evaluating binding for: " + scorerPackageName + ", forceUnbind=" + forceUnbind); @@ -146,13 +156,14 @@ public class NetworkScoreService extends INetworkScoreService.Stub { if (activeScorer == null) { // Package change has invalidated a scorer, this will also unbind any service // connection. - Log.i(TAG, "Package " + mRegisteredPackage + - " is no longer valid, disabling scoring."); - setScorerInternal(null); - } else if (activeScorer.mScoringServiceClassName == null) { - // The scoring service is not available, make sure it's unbound. + if (DBG) Log.d(TAG, "No active scorers available."); unbindFromScoringServiceIfNeeded(); - } else { // The scoring service changed in some way. + } else if (activeScorer.packageName.equals(scorerPackageName)) { + if (DBG) { + Log.d(TAG, "Possible change to the active scorer: " + + activeScorer.packageName); + } + // The scoring service changed in some way. if (forceUnbind) { unbindFromScoringServiceIfNeeded(); } @@ -162,6 +173,27 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } } + /** + * Reevaluates the service binding when the Settings toggle is changed. + */ + private class SettingsObserver extends ContentObserver { + + public SettingsObserver() { + super(null /*handler*/); + } + + @Override + public void onChange(boolean selfChange) { + onChange(selfChange, null); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri)); + bindToScoringServiceIfNeeded(); + } + } + public NetworkScoreService(Context context) { this(context, new NetworkScorerAppManager(context)); } @@ -176,24 +208,16 @@ public class NetworkScoreService extends INetworkScoreService.Stub { mContext.registerReceiverAsUser( mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/, null /* scheduler */); + // TODO(jjoslin): 12/15/16 - Make timeout configurable. + mRequestRecommendationCaller = + new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); } /** Called when the system is ready to run third-party code but before it actually does so. */ void systemReady() { if (DBG) Log.d(TAG, "systemReady"); - ContentResolver cr = mContext.getContentResolver(); - if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) { - // On first run, we try to initialize the scorer to the one configured at build time. - // This will be a no-op if the scorer isn't actually valid. - String defaultPackage = mContext.getResources().getString( - R.string.config_defaultNetworkScorerPackageName); - if (!TextUtils.isEmpty(defaultPackage)) { - mNetworkScorerAppManager.setActiveScorer(defaultPackage); - } - Settings.Global.putInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 1); - } - registerPackageMonitorIfNeeded(); + registerRecommendationSettingObserverIfNeeded(); } /** Called when the system is ready for us to start third-party code. */ @@ -207,29 +231,40 @@ public class NetworkScoreService extends INetworkScoreService.Stub { bindToScoringServiceIfNeeded(); } + private void registerRecommendationSettingObserverIfNeeded() { + final List<String> providerPackages = + mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); + if (!providerPackages.isEmpty()) { + final ContentResolver resolver = mContext.getContentResolver(); + final Uri uri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED); + resolver.registerContentObserver(uri, false, new SettingsObserver()); + } + } + private void registerPackageMonitorIfNeeded() { if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded"); - NetworkScorerAppData scorer = mNetworkScorerAppManager.getActiveScorer(); + final List<String> providerPackages = + mNetworkScorerAppManager.getPotentialRecommendationProviderPackages(); synchronized (mPackageMonitorLock) { // Unregister the current monitor if needed. if (mPackageMonitor != null) { if (DBG) { Log.d(TAG, "Unregistering package monitor for " - + mPackageMonitor.mRegisteredPackage); + + mPackageMonitor.mPackagesToWatch); } mPackageMonitor.unregister(); mPackageMonitor = null; } - // Create and register the monitor if a scorer is active. - if (scorer != null) { - mPackageMonitor = new NetworkScorerPackageMonitor(scorer.mPackageName); + // Create and register the monitor if there are packages that could be providers. + if (!providerPackages.isEmpty()) { + mPackageMonitor = new NetworkScorerPackageMonitor(providerPackages); // TODO: Need to update when we support per-user scorers. http://b/23422763 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM, false /* externalStorage */); if (DBG) { Log.d(TAG, "Registered package monitor for " - + mPackageMonitor.mRegisteredPackage); + + mPackageMonitor.mPackagesToWatch); } } } @@ -243,22 +278,24 @@ public class NetworkScoreService extends INetworkScoreService.Stub { private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) { if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")"); - if (scorerData != null && scorerData.mScoringServiceClassName != null) { - ComponentName componentName = - new ComponentName(scorerData.mPackageName, scorerData.mScoringServiceClassName); - // If we're connected to a different component then drop it. - if (mServiceConnection != null - && !mServiceConnection.mComponentName.equals(componentName)) { - unbindFromScoringServiceIfNeeded(); - } + if (scorerData != null && scorerData.recommendationServiceClassName != null) { + ComponentName componentName = new ComponentName(scorerData.packageName, + scorerData.recommendationServiceClassName); + synchronized (mServiceConnectionLock) { + // If we're connected to a different component then drop it. + if (mServiceConnection != null + && !mServiceConnection.mComponentName.equals(componentName)) { + unbindFromScoringServiceIfNeeded(); + } - // If we're not connected at all then create a new connection. - if (mServiceConnection == null) { - mServiceConnection = new ScoringServiceConnection(componentName); - } + // If we're not connected at all then create a new connection. + if (mServiceConnection == null) { + mServiceConnection = new ScoringServiceConnection(componentName); + } - // Make sure the connection is connected (idempotent) - mServiceConnection.connect(mContext); + // Make sure the connection is connected (idempotent) + mServiceConnection.connect(mContext); + } } else { // otherwise make sure it isn't bound. unbindFromScoringServiceIfNeeded(); } @@ -266,10 +303,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub { private void unbindFromScoringServiceIfNeeded() { if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded"); - if (mServiceConnection != null) { - mServiceConnection.disconnect(mContext); + synchronized (mServiceConnectionLock) { + if (mServiceConnection != null) { + mServiceConnection.disconnect(mContext); + } + mServiceConnection = null; } - mServiceConnection = null; + clearInternal(); } @Override @@ -349,7 +389,8 @@ public class NetworkScoreService extends INetworkScoreService.Stub { // mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG); mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG); - return setScorerInternal(packageName); + // Scorers (recommendation providers) are selected and no longer set. + return false; } @Override @@ -359,56 +400,13 @@ public class NetworkScoreService extends INetworkScoreService.Stub { if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) || mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) == PackageManager.PERMISSION_GRANTED) { - // The return value is discarded here because at this point, the call should always - // succeed. The only reason for failure is if the new package is not a valid scorer, but - // we're disabling scoring altogether here. - setScorerInternal(null /* packageName */); + // no-op for now but we could write to the setting if needed. } else { throw new SecurityException( "Caller is neither the active scorer nor the scorer manager."); } } - /** Set the active scorer. Callers are responsible for checking permissions as appropriate. */ - private boolean setScorerInternal(String packageName) { - if (DBG) Log.d(TAG, "setScorerInternal(" + packageName + ")"); - long token = Binder.clearCallingIdentity(); - try { - unbindFromScoringServiceIfNeeded(); - // Preemptively clear scores even though the set operation could fail. We do this for - // safety as scores should never be compared across apps; in practice, Settings should - // only be allowing valid apps to be set as scorers, so failure here should be rare. - clearInternal(); - // Get the scorer that is about to be replaced, if any, so we can notify it directly. - NetworkScorerAppData prevScorer = mNetworkScorerAppManager.getActiveScorer(); - boolean result = mNetworkScorerAppManager.setActiveScorer(packageName); - // Unconditionally attempt to bind to the current scorer. If setActiveScorer() failed - // then we'll attempt to restore the previous binding (if any), otherwise an attempt - // will be made to bind to the new scorer. - bindToScoringServiceIfNeeded(); - if (result) { // new scorer successfully set - registerPackageMonitorIfNeeded(); - - Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED); - if (prevScorer != null) { // Directly notify the old scorer. - intent.setPackage(prevScorer.mPackageName); - // TODO: Need to update when we support per-user scorers. http://b/23422763 - mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM); - } - - if (packageName != null) { // Then notify the new scorer - intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName); - intent.setPackage(packageName); - // TODO: Need to update when we support per-user scorers. http://b/23422763 - mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM); - } - } - return result; - } finally { - Binder.restoreCallingIdentity(token); - } - } - /** Clear scores. Callers are responsible for checking permissions as appropriate. */ private void clearInternal() { sendCallback(new Consumer<INetworkScoreCache>() { @@ -464,7 +462,22 @@ public class NetworkScoreService extends INetworkScoreService.Stub { @Override public RecommendationResult requestRecommendation(RecommendationRequest request) { - // TODO(jjoslin): 11/25/16 - Update with real impl. + mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG); + throwIfCalledOnMainThread(); + final INetworkRecommendationProvider provider = getRecommendationProvider(); + if (provider != null) { + try { + return mRequestRecommendationCaller.getRecommendationResult(provider, request); + } catch (RemoteException | TimeoutException e) { + Log.w(TAG, "Failed to request a recommendation.", e); + // TODO(jjoslin): 12/15/16 - Keep track of failures. + } + } + + if (DBG) { + Log.d(TAG, "Returning the default network recommendation."); + } + WifiConfiguration selectedConfig = null; if (request != null) { selectedConfig = request.getCurrentSelectedConfig(); @@ -474,7 +487,19 @@ public class NetworkScoreService extends INetworkScoreService.Stub { @Override public boolean requestScores(NetworkKey[] networks) { - // TODO(jjoslin): 12/13/16 - Implement + mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG); + final INetworkRecommendationProvider provider = getRecommendationProvider(); + if (provider != null) { + try { + provider.requestScores(networks); + // TODO(jjoslin): 12/15/16 - Consider pushing null scores into the cache to prevent + // repeated requests for the same scores. + return true; + } catch (RemoteException e) { + Log.w(TAG, "Failed to request scores.", e); + // TODO(jjoslin): 12/15/16 - Keep track of failures. + } + } return false; } @@ -486,7 +511,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { writer.println("Scoring is disabled."); return; } - writer.println("Current scorer: " + currentScorer.mPackageName); + writer.println("Current scorer: " + currentScorer.packageName); sendCallback(new Consumer<INetworkScoreCache>() { @Override @@ -499,10 +524,12 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } }, getScoreCacheLists()); - if (mServiceConnection != null) { - mServiceConnection.dump(fd, writer, args); - } else { - writer.println("ScoringServiceConnection: null"); + synchronized (mServiceConnectionLock) { + if (mServiceConnection != null) { + mServiceConnection.dump(fd, writer, args); + } else { + writer.println("ScoringServiceConnection: null"); + } } writer.flush(); } @@ -535,10 +562,27 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } } + private void throwIfCalledOnMainThread() { + if (Thread.currentThread() == mContext.getMainLooper().getThread()) { + throw new RuntimeException("Cannot invoke on the main thread"); + } + } + + @Nullable + private INetworkRecommendationProvider getRecommendationProvider() { + synchronized (mServiceConnectionLock) { + if (mServiceConnection != null) { + return mServiceConnection.getRecommendationProvider(); + } + } + return null; + } + private static class ScoringServiceConnection implements ServiceConnection { private final ComponentName mComponentName; - private boolean mBound = false; - private boolean mConnected = false; + private volatile boolean mBound = false; + private volatile boolean mConnected = false; + private volatile INetworkRecommendationProvider mRecommendationProvider; ScoringServiceConnection(ComponentName componentName) { mComponentName = componentName; @@ -569,12 +613,19 @@ public class NetworkScoreService extends INetworkScoreService.Stub { } catch (RuntimeException e) { Log.e(TAG, "Unbind failed.", e); } + + mRecommendationProvider = null; + } + + INetworkRecommendationProvider getRecommendationProvider() { + return mRecommendationProvider; } @Override public void onServiceConnected(ComponentName name, IBinder service) { if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString()); mConnected = true; + mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service); } @Override @@ -583,6 +634,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString()); } mConnected = false; + mRecommendationProvider = null; } public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { @@ -590,4 +642,43 @@ public class NetworkScoreService extends INetworkScoreService.Stub { + ", connected: " + mConnected); } } + + /** + * Executes the async requestRecommendation() call with a timeout. + */ + private static final class RequestRecommendationCaller + extends TimedRemoteCaller<RecommendationResult> { + private final IRemoteCallback mCallback; + + RequestRecommendationCaller(long callTimeoutMillis) { + super(callTimeoutMillis); + mCallback = new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) throws RemoteException { + final RecommendationResult result = + data.getParcelable(EXTRA_RECOMMENDATION_RESULT); + final int sequence = data.getInt(EXTRA_SEQUENCE, -1); + onRemoteMethodResult(result, sequence); + } + }; + } + + /** + * Runs the requestRecommendation() call on the given {@link INetworkRecommendationProvider} + * instance. + * + * @param target the {@link INetworkRecommendationProvider} to request a recommendation + * from + * @param request the {@link RecommendationRequest} from the calling client + * @return a {@link RecommendationResult} from the provider + * @throws RemoteException if the call failed + * @throws TimeoutException if the call took longer than the set timeout + */ + RecommendationResult getRecommendationResult(INetworkRecommendationProvider target, + RecommendationRequest request) throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.requestRecommendation(request, mCallback, sequence); + return getResultTimed(sequence); + } + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3ec1a4a59be8..eae4905f27e4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -369,6 +369,7 @@ import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME; import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS; import static com.android.server.am.ActivityStackSupervisor.ON_TOP; import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; +import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS; import static com.android.server.am.ActivityStackSupervisor.RESTORE_FROM_RECENTS; import static com.android.server.am.TaskRecord.INVALID_TASK_ID; import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK; @@ -539,8 +540,6 @@ public class ActivityManagerService extends IActivityManager.Stub private static final String INTENT_REMOTE_BUGREPORT_FINISHED = "android.intent.action.REMOTE_BUGREPORT_FINISHED"; - // Used to indicate that a task is removed it should also be removed from recents. - private static final boolean REMOVE_FROM_RECENTS = true; // Used to indicate that an app transition should be animated. static final boolean ANIMATE = true; @@ -3684,6 +3683,15 @@ public class ActivityManagerService extends IActivityManager.Stub mNativeDebuggingApp = null; } + String invokeWith = null; + if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { + // Debuggable apps may include a wrapper script with their library directory. + String wrapperFileName = app.info.nativeLibraryDir + "/wrap.sh"; + if (new File(wrapperFileName).exists()) { + invokeWith = "/system/bin/logwrapper " + wrapperFileName; + } + } + String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi; if (requiredAbi == null) { requiredAbi = Build.SUPPORTED_ABIS[0]; @@ -3710,12 +3718,12 @@ public class ActivityManagerService extends IActivityManager.Stub startResult = Process.startWebView(entryPoint, app.processName, uid, uid, gids, debugFlags, mountExternal, app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet, - app.info.dataDir, entryPointArgs); + app.info.dataDir, null, entryPointArgs); } else { startResult = Process.start(entryPoint, app.processName, uid, uid, gids, debugFlags, mountExternal, app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet, - app.info.dataDir, entryPointArgs); + app.info.dataDir, invokeWith, entryPointArgs); } checkTime(startTime, "startProcess: returned from zygote!"); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); @@ -4817,7 +4825,7 @@ public class ActivityManagerService extends IActivityManager.Stub // because we don't support returning them across task boundaries. Also, to // keep backwards compatibility we remove the task from recents when finishing // task with root activity. - res = removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity); + res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity); if (!res) { Slog.i(TAG, "Removing task failed to finish activity"); } @@ -5462,7 +5470,7 @@ public class ActivityManagerService extends IActivityManager.Stub tr.getBaseIntent().getComponent().getPackageName(); if (tr.userId != userId) continue; if (!taskPackageName.equals(packageName)) continue; - removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS); + mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS); } } @@ -9576,79 +9584,6 @@ public class ActivityManagerService extends IActivityManager.Stub mWindowManager.executeAppTransition(); } - private void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess, - boolean removeFromRecents) { - if (removeFromRecents) { - mRecentTasks.remove(tr); - tr.removedFromRecents(); - } - ComponentName component = tr.getBaseIntent().getComponent(); - if (component == null) { - Slog.w(TAG, "No component for base intent of task: " + tr); - return; - } - - // Find any running services associated with this app and stop if needed. - mServices.cleanUpRemovedTaskLocked(tr, component, new Intent(tr.getBaseIntent())); - - if (!killProcess) { - return; - } - - // Determine if the process(es) for this task should be killed. - final String pkg = component.getPackageName(); - ArrayList<ProcessRecord> procsToKill = new ArrayList<>(); - ArrayMap<String, SparseArray<ProcessRecord>> pmap = mProcessNames.getMap(); - for (int i = 0; i < pmap.size(); i++) { - - SparseArray<ProcessRecord> uids = pmap.valueAt(i); - for (int j = 0; j < uids.size(); j++) { - ProcessRecord proc = uids.valueAt(j); - if (proc.userId != tr.userId) { - // Don't kill process for a different user. - continue; - } - if (proc == mHomeProcess) { - // Don't kill the home process along with tasks from the same package. - continue; - } - if (!proc.pkgList.containsKey(pkg)) { - // Don't kill process that is not associated with this task. - continue; - } - - for (int k = 0; k < proc.activities.size(); k++) { - TaskRecord otherTask = proc.activities.get(k).task; - if (tr.taskId != otherTask.taskId && otherTask.inRecents) { - // Don't kill process(es) that has an activity in a different task that is - // also in recents. - return; - } - } - - if (proc.foregroundServices) { - // Don't kill process(es) with foreground service. - return; - } - - // Add process to kill list. - procsToKill.add(proc); - } - } - - // Kill the running processes. - for (int i = 0; i < procsToKill.size(); i++) { - ProcessRecord pr = procsToKill.get(i); - if (pr.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND - && pr.curReceivers.isEmpty()) { - pr.kill("remove task", true); - } else { - // We delay killing processes that are not in the background or running a receiver. - pr.waitingToKill = "remove task"; - } - } - } - private void removeTasksByPackageNameLocked(String packageName, int userId) { // Remove all tasks with activities in the specified package from the list of recent tasks for (int i = mRecentTasks.size() - 1; i >= 0; i--) { @@ -9658,7 +9593,7 @@ public class ActivityManagerService extends IActivityManager.Stub ComponentName cn = tr.intent.getComponent(); if (cn != null && cn.getPackageName().equals(packageName)) { // If the package name matches, remove the task. - removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS); + mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS); } } } @@ -9676,33 +9611,9 @@ public class ActivityManagerService extends IActivityManager.Stub final boolean sameComponent = cn != null && cn.getPackageName().equals(packageName) && (filterByClasses == null || filterByClasses.contains(cn.getClassName())); if (sameComponent) { - removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS); - } - } - } - - /** - * Removes the task with the specified task id. - * - * @param taskId Identifier of the task to be removed. - * @param killProcess Kill any process associated with the task if possible. - * @param removeFromRecents Whether to also remove the task from recents. - * @return Returns true if the given task was found and removed. - */ - private boolean removeTaskByIdLocked(int taskId, boolean killProcess, - boolean removeFromRecents) { - final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked( - taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID); - if (tr != null) { - tr.removeTaskActivitiesLocked(); - cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents); - if (tr.isPersistable) { - notifyTaskPersisterLocked(null, true); + mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS); } - return true; } - Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId); - return false; } @Override @@ -9715,15 +9626,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { final long ident = Binder.clearCallingIdentity(); try { - final ActivityStack stack = mStackSupervisor.getStack(stackId); - if (stack == null) { - return; - } - final ArrayList<TaskRecord> tasks = stack.getAllTasks(); - for (int i = tasks.size() - 1; i >= 0; i--) { - removeTaskByIdLocked( - tasks.get(i).taskId, true /* killProcess */, REMOVE_FROM_RECENTS); - } + mStackSupervisor.removeStackLocked(stackId); } finally { Binder.restoreCallingIdentity(ident); } @@ -9752,7 +9655,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { final long ident = Binder.clearCallingIdentity(); try { - return removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS); + return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS); } finally { Binder.restoreCallingIdentity(ident); } @@ -22511,7 +22414,8 @@ public class ActivityManagerService extends IActivityManager.Stub long origId = Binder.clearCallingIdentity(); try { // We remove the task from recents to preserve backwards - if (!removeTaskByIdLocked(mTaskId, false, REMOVE_FROM_RECENTS)) { + if (!mStackSupervisor.removeTaskByIdLocked(mTaskId, false, + REMOVE_FROM_RECENTS)) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } } finally { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index fe261f10885b..b4b346576507 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -249,6 +249,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer // Don't execute any calls to resume. static final boolean DEFER_RESUME = true; + // Used to indicate that a task is removed it should also be removed from recents. + static final boolean REMOVE_FROM_RECENTS = true; + // Activity actions an app cannot start if it uses a permission which is not granted. private static final ArrayMap<String, String> ACTION_TO_RUNTIME_PERMISSION = new ArrayMap<>(); @@ -2183,7 +2186,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer // Update the return-to to reflect where the pinned stack task was moved // from so that we retain the stack that was previously visible if the // pinned stack is recreated. See moveActivityToPinnedStackLocked(). - task.setTaskToReturnTo(getFocusedStack().getStackId() == HOME_STACK_ID + final int focusedStackId = getFocusedStack().getStackId(); + task.setTaskToReturnTo(focusedStackId == HOME_STACK_ID || !onTop ? HOME_ACTIVITY_TYPE : APPLICATION_ACTIVITY_TYPE); } moveTaskToStackLocked(tasks.get(i).taskId, @@ -2374,6 +2378,141 @@ public class ActivityStackSupervisor extends ConfigurationContainer return activityContainer.mStack; } + /** + * Removes the stack associed with the given {@param stackId}. If the {@param stackId} is the + * pinned stack, then its tasks are not explicitly removed when the stack is destroyed, but + * instead moved back onto the fullscreen stack. + */ + void removeStackLocked(int stackId) { + final ActivityStack stack = getStack(stackId); + if (stack == null) { + return; + } + + final ArrayList<TaskRecord> tasks = stack.getAllTasks(); + if (stack.getStackId() == PINNED_STACK_ID) { + final ActivityStack fullscreenStack = getStack(FULLSCREEN_WORKSPACE_STACK_ID); + if (fullscreenStack != null) { + final boolean isFullscreenStackVisible = + fullscreenStack.getStackVisibilityLocked(null) == STACK_VISIBLE; + for (int i = 0; i < tasks.size(); i++) { + // Insert the task either at the top of the fullscreen stack if it is hidden, + // or just under the top task if it is currently visible + final int insertPosition = isFullscreenStackVisible + ? Math.max(0, fullscreenStack.getChildCount() - 1) + : fullscreenStack.getChildCount(); + positionTaskInStackLocked(tasks.get(i).taskId, FULLSCREEN_WORKSPACE_STACK_ID, + insertPosition); + } + ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); + resumeFocusedStackTopActivityLocked(); + } else { + // If there is no fullscreen stack, then create the stack and move all the tasks + // onto the stack + moveTasksToFullscreenStackLocked(PINNED_STACK_ID, false /* onTop */); + } + } else { + for (int i = tasks.size() - 1; i >= 0; i--) { + removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */, + REMOVE_FROM_RECENTS); + } + } + } + + /** + * Removes the task with the specified task id. + * + * @param taskId Identifier of the task to be removed. + * @param killProcess Kill any process associated with the task if possible. + * @param removeFromRecents Whether to also remove the task from recents. + * @return Returns true if the given task was found and removed. + */ + boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents) { + final TaskRecord tr = anyTaskForIdLocked(taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID); + if (tr != null) { + tr.removeTaskActivitiesLocked(); + cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents); + if (tr.isPersistable) { + mService.notifyTaskPersisterLocked(null, true); + } + return true; + } + Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId); + return false; + } + + void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess, boolean removeFromRecents) { + if (removeFromRecents) { + mRecentTasks.remove(tr); + tr.removedFromRecents(); + } + ComponentName component = tr.getBaseIntent().getComponent(); + if (component == null) { + Slog.w(TAG, "No component for base intent of task: " + tr); + return; + } + + // Find any running services associated with this app and stop if needed. + mService.mServices.cleanUpRemovedTaskLocked(tr, component, new Intent(tr.getBaseIntent())); + + if (!killProcess) { + return; + } + + // Determine if the process(es) for this task should be killed. + final String pkg = component.getPackageName(); + ArrayList<ProcessRecord> procsToKill = new ArrayList<>(); + ArrayMap<String, SparseArray<ProcessRecord>> pmap = mService.mProcessNames.getMap(); + for (int i = 0; i < pmap.size(); i++) { + + SparseArray<ProcessRecord> uids = pmap.valueAt(i); + for (int j = 0; j < uids.size(); j++) { + ProcessRecord proc = uids.valueAt(j); + if (proc.userId != tr.userId) { + // Don't kill process for a different user. + continue; + } + if (proc == mService.mHomeProcess) { + // Don't kill the home process along with tasks from the same package. + continue; + } + if (!proc.pkgList.containsKey(pkg)) { + // Don't kill process that is not associated with this task. + continue; + } + + for (int k = 0; k < proc.activities.size(); k++) { + TaskRecord otherTask = proc.activities.get(k).task; + if (tr.taskId != otherTask.taskId && otherTask.inRecents) { + // Don't kill process(es) that has an activity in a different task that is + // also in recents. + return; + } + } + + if (proc.foregroundServices) { + // Don't kill process(es) with foreground service. + return; + } + + // Add process to kill list. + procsToKill.add(proc); + } + } + + // Kill the running processes. + for (int i = 0; i < procsToKill.size(); i++) { + ProcessRecord pr = procsToKill.get(i); + if (pr.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND + && pr.curReceivers.isEmpty()) { + pr.kill("remove task", true); + } else { + // We delay killing processes that are not in the background or running a receiver. + pr.waitingToKill = "remove task"; + } + } + } + int getNextStackId() { while (true) { if (mNextFreeStackId >= FIRST_DYNAMIC_STACK_ID diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index 5e9885914169..9ffe2b78a095 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -433,6 +433,8 @@ public class NetworkMonitor extends StateMachine { })); intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL, mLastPortalProbeResult.detectUrl); + intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT, + getCaptivePortalUserAgent(mContext)); intent.setFlags( Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivityAsUser(intent, UserHandle.CURRENT); diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 63b525009911..c0550c6c7876 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -142,8 +142,9 @@ public class NetworkNotificationManager { if (DBG) { Slog.d(TAG, String.format( - "showNotification tag=%s event=%s transport=%s extraInfo=%d highPrioriy=%s", - tag, nameOf(eventId), getTransportName(transportType), extraInfo, highPriority)); + "showNotification tag=%s event=%s transport=%s extraInfo=%s highPrioriy=%s", + tag, nameOf(eventId), getTransportName(transportType), extraInfo, + highPriority)); } Resources r = Resources.getSystem(); @@ -227,13 +228,14 @@ public class NetworkNotificationManager { } final int eventId = mNotificationTypeMap.get(id); if (DBG) { - Slog.d(TAG, String.format("clearing notification tag=%s event=", tag, nameOf(eventId))); + Slog.d(TAG, String.format("clearing notification tag=%s event=%s", tag, + nameOf(eventId))); } try { mNotificationManager.cancelAsUser(tag, eventId, UserHandle.ALL); } catch (NullPointerException npe) { Slog.d(TAG, String.format( - "failed to clear notification tag=%s event=", tag, nameOf(eventId)), npe); + "failed to clear notification tag=%s event=%s", tag, nameOf(eventId)), npe); } mNotificationTypeMap.delete(id); } diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 132967c6b26a..3f0ebf28bbfd 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -46,7 +46,11 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.service.fingerprint.FingerprintActionStatsProto; +import android.service.fingerprint.FingerprintServiceDumpProto; +import android.service.fingerprint.FingerprintUserStatsProto; import android.util.Slog; +import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; @@ -968,7 +972,11 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe final long ident = Binder.clearCallingIdentity(); try { - dumpInternal(pw); + if (args.length > 0 && "--proto".equals(args[0])) { + dumpProto(fd); + } else { + dumpInternal(pw); + } } finally { Binder.restoreCallingIdentity(ident); } @@ -1027,6 +1035,45 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe pw.println(dump); } + private void dumpProto(FileDescriptor fd) { + final ProtoOutputStream proto = new ProtoOutputStream(fd); + for (UserInfo user : UserManager.get(getContext()).getUsers()) { + final int userId = user.getUserHandle().getIdentifier(); + + final long userToken = proto.start(FingerprintServiceDumpProto.USERS); + + proto.write(FingerprintUserStatsProto.USER_ID, userId); + proto.write(FingerprintUserStatsProto.NUM_FINGERPRINTS, + mFingerprintUtils.getFingerprintsForUser(mContext, userId).size()); + + // Normal fingerprint authentications (e.g. lockscreen) + final PerformanceStats normal = mPerformanceMap.get(userId); + if (normal != null) { + final long countsToken = proto.start(FingerprintUserStatsProto.NORMAL); + proto.write(FingerprintActionStatsProto.ACCEPT, normal.accept); + proto.write(FingerprintActionStatsProto.REJECT, normal.reject); + proto.write(FingerprintActionStatsProto.ACQUIRE, normal.acquire); + proto.write(FingerprintActionStatsProto.LOCKOUT, normal.lockout); + proto.end(countsToken); + } + + // Statistics about secure fingerprint transactions (e.g. to unlock password + // storage, make secure purchases, etc.) + final PerformanceStats crypto = mPerformanceMap.get(userId); + if (crypto != null) { + final long countsToken = proto.start(FingerprintUserStatsProto.CRYPTO); + proto.write(FingerprintActionStatsProto.ACCEPT, crypto.accept); + proto.write(FingerprintActionStatsProto.REJECT, crypto.reject); + proto.write(FingerprintActionStatsProto.ACQUIRE, crypto.acquire); + proto.write(FingerprintActionStatsProto.LOCKOUT, crypto.lockout); + proto.end(countsToken); + } + + proto.end(userToken); + } + proto.flush(); + } + @Override public void onStart() { publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper()); diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index a03c4aa62045..dbd719b9167f 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -25,6 +25,7 @@ import android.app.Notification; import android.app.NotificationChannel; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.Bitmap; @@ -136,15 +137,9 @@ public final class NotificationRecord { private boolean isPreChannelsNotification() { try { if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(getChannel().getId())) { - final boolean isSystemNotification = - NotificationManagerService.isUidSystem(sbn.getUid()) - || ("android".equals(sbn.getPackageName())); - if (isSystemNotification) { - return false; - } - final ApplicationInfo applicationInfo = + final ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfoAsUser(sbn.getPackageName(), - 0, sbn.getUserId()); + 0, UserHandle.getUserId(sbn.getUid())); if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) { return true; } diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 95718de6e2c7..98d4c6902f74 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -784,11 +784,6 @@ public class RankingHelper implements RankingConfig { } } - private static boolean isUidSystem(int uid) { - final int appid = UserHandle.getAppId(uid); - return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0); - } - private static class Record { static int UNKNOWN_UID = UserHandle.USER_NULL; diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 31939749ee66..203f841bb305 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -105,11 +105,11 @@ public class Installer extends SystemService { } } - public void createAppData(String uuid, String packageName, int userId, int flags, int appId, + public long createAppData(String uuid, String packageName, int userId, int flags, int appId, String seInfo, int targetSdkVersion) throws InstallerException { - if (!checkBeforeRemote()) return; + if (!checkBeforeRemote()) return -1; try { - mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo, + return mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo, targetSdkVersion); } catch (Exception e) { throw InstallerException.from(e); @@ -182,16 +182,6 @@ public class Installer extends SystemService { } } - public long getAppDataInode(String uuid, String packageName, int userId, int flags) - throws InstallerException { - if (!checkBeforeRemote()) return -1; - try { - return mInstalld.getAppDataInode(uuid, packageName, userId, flags); - } catch (Exception e) { - throw InstallerException.from(e); - } - } - public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet, int dexoptNeeded, @Nullable String outputPath, int dexFlags, String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index c65514b5772f..4937662b91d4 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -5524,9 +5524,21 @@ public class PackageManagerService extends IPackageManager.Stub { final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); final ActivityInfo ai = getActivityInfo(comp, flags, userId); if (ai != null) { - final ResolveInfo ri = new ResolveInfo(); - ri.activityInfo = ai; - list.add(ri); + // When specifying an explicit component, we prevent the activity from being + // used when either 1) the calling package is normal and the activity is within + // an ephemeral application or 2) the calling package is ephemeral and the + // activity is not visible to ephemeral applications. + boolean blockResolution = + (ephemeralPkgName == null + && (ai.applicationInfo.privateFlags + & ApplicationInfo.PRIVATE_FLAG_EPHEMERAL) != 0) + || (ephemeralPkgName != null + && (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL) == 0); + if (!blockResolution) { + final ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = ai; + list.add(ri); + } } return list; } @@ -5604,10 +5616,10 @@ public class PackageManagerService extends IPackageManager.Stub { } else { final PackageParser.Package pkg = mPackages.get(pkgName); if (pkg != null) { - result = filterIfNotSystemUser( + result = filterForEphemeral(filterIfNotSystemUser( mActivities.queryIntentForPackage( intent, resolvedType, flags, pkg.activities, userId), - userId); + userId), ephemeralPkgName); } else { // the caller wants to resolve for a particular package; however, there // were no installed results, so, try to find an ephemeral result @@ -7627,6 +7639,11 @@ public class PackageManagerService extends IPackageManager.Stub { } } + @Override + public void notifyDexLoad(String loadingPackageName, List<String> dexPaths, String loaderIsa) { + // TODO(calin): b/32871170 + } + // TODO: this is not used nor needed. Delete it. @Override public boolean performDexOptIfNeeded(String packageName) { @@ -20442,8 +20459,9 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); Preconditions.checkNotNull(app.seinfo); + long ceDataInode = -1; try { - mInstaller.createAppData(volumeUuid, packageName, userId, flags, + ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags, appId, app.seinfo, app.targetSdkVersion); } catch (InstallerException e) { if (app.isSystemApp()) { @@ -20451,7 +20469,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); + ", but trying to recover: " + e); destroyAppDataLeafLIF(pkg, userId, flags); try { - mInstaller.createAppData(volumeUuid, packageName, userId, flags, + ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags, appId, app.seinfo, app.targetSdkVersion); logCriticalInfo(Log.DEBUG, "Recovery succeeded!"); } catch (InstallerException e2) { @@ -20462,21 +20480,13 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } } - if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) { - try { - // CE storage is unlocked right now, so read out the inode and - // remember for use later when it's locked - // TODO: mark this structure as dirty so we persist it! - final long ceDataInode = mInstaller.getAppDataInode(volumeUuid, packageName, userId, - StorageManager.FLAG_STORAGE_CE); - synchronized (mPackages) { - final PackageSetting ps = mSettings.mPackages.get(packageName); - if (ps != null) { - ps.setCeDataInode(ceDataInode, userId); - } + if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) { + // TODO: mark this structure as dirty so we persist it! + synchronized (mPackages) { + final PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps != null) { + ps.setCeDataInode(ceDataInode, userId); } - } catch (InstallerException e) { - Slog.e(TAG, "Failed to find inode for " + packageName + ": " + e); } } diff --git a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java index 792825798ce6..cdb69ce813a7 100644 --- a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java +++ b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java @@ -48,7 +48,15 @@ class ShortcutRequestPinProcessor { */ private static class PinShortcutRequestInner extends IPinItemRequest.Stub { private final ShortcutRequestPinProcessor mProcessor; - public final ShortcutInfo shortcut; + /** Original shortcut passed by the app. */ + public final ShortcutInfo shortcutOriginal; + + /** + * Cloned shortcut that's passed to the launcher. The notable difference from + * {@link #shortcutOriginal} is it must not have the intent. + */ + public final ShortcutInfo shortcutForLauncher; + private final IntentSender mResultIntent; public final String launcherPackage; @@ -59,10 +67,12 @@ class ShortcutRequestPinProcessor { private boolean mAccepted; private PinShortcutRequestInner(ShortcutRequestPinProcessor processor, - ShortcutInfo shortcut, IntentSender resultIntent, + ShortcutInfo shortcutOriginal, ShortcutInfo shortcutForLauncher, + IntentSender resultIntent, String launcherPackage, int launcherUserId, boolean preExisting) { mProcessor = processor; - this.shortcut = shortcut; + this.shortcutOriginal = shortcutOriginal; + this.shortcutForLauncher = shortcutForLauncher; mResultIntent = resultIntent; this.launcherPackage = launcherPackage; this.launcherUserId = launcherUserId; @@ -99,8 +109,8 @@ class ShortcutRequestPinProcessor { mAccepted = true; } if (DEBUG) { - Slog.d(TAG, "Launcher accepted shortcut. ID=" + shortcut.getId() - + " package=" + shortcut.getPackage() + Slog.d(TAG, "Launcher accepted shortcut. ID=" + shortcutOriginal.getId() + + " package=" + shortcutOriginal.getPackage() + " options=" + options); } @@ -163,7 +173,7 @@ class ShortcutRequestPinProcessor { } // This is the shortcut that'll be sent to the launcher. - final ShortcutInfo shortcutToSend; + final ShortcutInfo shortcutForLauncher; if (existsAlready) { validateExistingShortcut(existing); @@ -179,8 +189,10 @@ class ShortcutRequestPinProcessor { // Pass a clone, not the original. // Note this will remove the intent and icons. - shortcutToSend = existing.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); - shortcutToSend.clearFlags(ShortcutInfo.FLAG_PINNED); + shortcutForLauncher = existing.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); + + // FLAG_PINNED is still set, if it's pinned by other launchers. + shortcutForLauncher.clearFlags(ShortcutInfo.FLAG_PINNED); } else { // It doesn't exist, so it must have all mandatory fields. mService.validateShortcutForPinRequest(inShortcut); @@ -191,17 +203,18 @@ class ShortcutRequestPinProcessor { if (DEBUG) { Slog.d(TAG, "resolved shortcut=" + inShortcut.toInsecureString()); } - // TODO Remove the intent here -- don't pass shortcut intents to the launcher. - shortcutToSend = inShortcut; + // We should strip out the intent, but should preserve the icon. + shortcutForLauncher = inShortcut.clone( + ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER_APPROVAL); } // Create a request object. final PinShortcutRequestInner inner = - new PinShortcutRequestInner(this, shortcutToSend, resultIntent, + new PinShortcutRequestInner(this, inShortcut, shortcutForLauncher, resultIntent, launcherPackage, launcherUserId, existsAlready); final PinItemRequest outer = new PinItemRequest(PinItemRequest.REQUEST_TYPE_SHORTCUT, - shortcutToSend, inner); + shortcutForLauncher, inner); return startRequestConfirmActivity(launcherComponent, launcherUserId, outer); } @@ -210,7 +223,7 @@ class ShortcutRequestPinProcessor { // Make sure it's enabled. // (Because we can't always force enable it automatically as it may be a stale // manifest shortcut.) - Preconditions.checkState(shortcutInfo.isEnabled(), + Preconditions.checkArgument(shortcutInfo.isEnabled(), "Shortcut ID=" + shortcutInfo + " already exists but disabled."); } @@ -270,7 +283,7 @@ class ShortcutRequestPinProcessor { */ public boolean directPinShortcut(PinShortcutRequestInner request) { - final ShortcutInfo original = request.shortcut; + final ShortcutInfo original = request.shortcutOriginal; final int appUserId = original.getUserId(); final String appPackageName = original.getPackage(); final int launcherUserId = request.launcherUserId; @@ -292,7 +305,7 @@ class ShortcutRequestPinProcessor { try { if (current == null) { // It doesn't exist, so it must have all necessary fields. - mService.validateShortcutForPinRequest(request.shortcut); + mService.validateShortcutForPinRequest(original); } else { validateExistingShortcut(current); } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 424830bd7919..436a53c77580 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -1865,9 +1865,10 @@ public class ShortcutService extends IShortcutService.Stub { synchronized (mLock) { throwIfUserLockedL(userId); - // TODO Make sure the caller is in the foreground. + Preconditions.checkState(isUidForegroundLocked(injectBinderCallingUid()), + "Calling application must have a foreground activity or a foreground service"); - // TODO Cancel all pending request from the same app. + // TODO Cancel all pending requests from the caller. // Send request to the launcher, if supported. ret = mShortcutRequestPinProcessor.requestPinShortcutLocked(shortcut, resultIntent); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 3645c24043ca..8f64353a362d 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1556,6 +1556,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); } final long ident = Binder.clearCallingIdentity(); + + // Live wallpapers can't be specified for keyguard. If we're using a static + // system+lock image currently, migrate the system wallpaper to be a lock-only + // image as part of making a different live component active as the system + // wallpaper. + if (mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + if (mLockWallpaperMap.get(userId) == null) { + // We're using the static imagery and there is no lock-specific image in place, + // therefore it's a shared system+lock image that we need to migrate. + migrateSystemToLockWallpaperLocked(userId); + } + } + try { wallpaper.imageWallpaperPending = false; if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) { diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index 50a6095912e2..3e8e42063bc1 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -74,6 +74,7 @@ LOCAL_SHARED_LIBRARIES += \ libutils \ android.hardware.audio.common@2.0 \ android.hardware.gnss@1.0 \ + android.hardware.ir@1.0 \ android.hardware.light@2.0 \ android.hardware.power@1.0 \ android.hardware.thermal@1.0 \ diff --git a/services/core/jni/com_android_server_ConsumerIrService.cpp b/services/core/jni/com_android_server_ConsumerIrService.cpp index 7104870349b5..1f7bf4a05ad4 100644 --- a/services/core/jni/com_android_server_ConsumerIrService.cpp +++ b/services/core/jni/com_android_server_ConsumerIrService.cpp @@ -23,87 +23,70 @@ #include <stdlib.h> #include <utils/misc.h> #include <utils/Log.h> -#include <hardware/hardware.h> -#include <hardware/consumerir.h> +#include <android/hardware/ir/1.0/IConsumerIr.h> #include <ScopedPrimitiveArray.h> -namespace android { - -static jlong halOpen(JNIEnv* /* env */, jobject /* obj */) { - hw_module_t const* module; - consumerir_device_t *dev; - int err; +using ::android::hardware::ir::V1_0::IConsumerIr; +using ::android::hardware::ir::V1_0::ConsumerIrFreqRange; +using ::android::hardware::hidl_vec; - err = hw_get_module(CONSUMERIR_HARDWARE_MODULE_ID, &module); - if (err != 0) { - ALOGE("Can't open consumer IR HW Module, error: %d", err); - return 0; - } +namespace android { - err = module->methods->open(module, CONSUMERIR_TRANSMITTER, - (hw_device_t **) &dev); - if (err < 0) { - ALOGE("Can't open consumer IR transmitter, error: %d", err); - return 0; - } +static sp<IConsumerIr> mHal; - return reinterpret_cast<jlong>(dev); +static jboolean halOpen(JNIEnv* /* env */, jobject /* obj */) { + // TODO(b/31632518) + mHal = IConsumerIr::getService("consumerir"); + return mHal != nullptr; } -static jint halTransmit(JNIEnv *env, jobject /* obj */, jlong halObject, - jint carrierFrequency, jintArray pattern) { - int ret; - - consumerir_device_t *dev = reinterpret_cast<consumerir_device_t*>(halObject); +static jint halTransmit(JNIEnv *env, jobject /* obj */, jint carrierFrequency, + jintArray pattern) { ScopedIntArrayRO cPattern(env, pattern); if (cPattern.get() == NULL) { return -EINVAL; } - jsize patternLength = cPattern.size(); - - ret = dev->transmit(dev, carrierFrequency, cPattern.get(), patternLength); + hidl_vec<int32_t> patternVec; + patternVec.setToExternal(const_cast<int32_t*>(cPattern.get()), cPattern.size()); - return reinterpret_cast<jint>(ret); + bool success = mHal->transmit(carrierFrequency, patternVec); + return success ? 0 : -1; } -static jintArray halGetCarrierFrequencies(JNIEnv *env, jobject /* obj */, - jlong halObject) { - consumerir_device_t *dev = reinterpret_cast<consumerir_device_t*>(halObject); - consumerir_freq_range_t *ranges; +static jintArray halGetCarrierFrequencies(JNIEnv *env, jobject /* obj */) { int len; + hidl_vec<ConsumerIrFreqRange> ranges; + bool success; - len = dev->get_num_carrier_freqs(dev); - if (len <= 0) - return NULL; - - ranges = new consumerir_freq_range_t[len]; + auto cb = [&](bool s, hidl_vec<ConsumerIrFreqRange> vec) { + ranges = vec; + success = s; + }; + mHal->getCarrierFreqs(cb); - len = dev->get_carrier_freqs(dev, len, ranges); - if (len <= 0) { - delete[] ranges; + if (!success) { return NULL; } + len = ranges.size(); int i; ScopedIntArrayRW freqsOut(env, env->NewIntArray(len*2)); jint *arr = freqsOut.get(); if (arr == NULL) { - delete[] ranges; return NULL; } for (i = 0; i < len; i++) { - arr[i*2] = ranges[i].min; - arr[i*2+1] = ranges[i].max; + arr[i*2] = static_cast<jint>(ranges[i].min); + arr[i*2+1] = static_cast<jint>(ranges[i].max); } - delete[] ranges; return freqsOut.getJavaArray(); } static const JNINativeMethod method_table[] = { - { "halOpen", "()J", (void *)halOpen }, - { "halTransmit", "(JI[I)I", (void *)halTransmit }, - { "halGetCarrierFrequencies", "(J)[I", (void *)halGetCarrierFrequencies}, + { "halOpen", "()Z", (void *)halOpen }, + { "halTransmit", "(I[I)I", (void *)halTransmit }, + { "halGetCarrierFrequencies", "()[I", (void *)halGetCarrierFrequencies}, }; int register_android_server_ConsumerIrService(JNIEnv *env) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 4d0f5c7f750b..050f25d364f5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -19,6 +19,7 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; import static android.app.admin.DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG; import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY; +import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED; import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED; import static android.app.admin.DevicePolicyManager.CODE_HAS_DEVICE_OWNER; @@ -254,7 +255,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String ATTR_DEVICE_PROVISIONING_CONFIG_APPLIED = "device-provisioning-config-applied"; private static final String ATTR_DEVICE_PAIRED = "device-paired"; - private static final String ATTR_DELEGATED_CERT_INSTALLER = "delegated-cert-installer"; private static final String ATTR_APPLICATION_RESTRICTIONS_MANAGER = "application-restrictions-manager"; @@ -1706,9 +1706,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mSecurityLogMonitor = new SecurityLogMonitor(this); - mHasFeature = mContext.getPackageManager() + mHasFeature = mInjector.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN); - mIsWatch = mContext.getPackageManager() + mIsWatch = mInjector.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_WATCH); if (!mHasFeature) { // Skip the rest of the initialization @@ -2358,20 +2358,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.endTag(null, "failed-password-attempts"); } - final PasswordMetrics metrics = policy.mActivePasswordMetrics; - if (!metrics.isDefault()) { - out.startTag(null, "active-password"); - out.attribute(null, "quality", Integer.toString(metrics.quality)); - out.attribute(null, "length", Integer.toString(metrics.length)); - out.attribute(null, "uppercase", Integer.toString(metrics.upperCase)); - out.attribute(null, "lowercase", Integer.toString(metrics.lowerCase)); - out.attribute(null, "letters", Integer.toString(metrics.letters)); - out.attribute(null, "numeric", Integer.toString(metrics.numeric)); - out.attribute(null, "symbols", Integer.toString(metrics.symbols)); - out.attribute(null, "nonletter", Integer.toString(metrics.nonLetter)); - out.endTag(null, "active-password"); - } - for (int i = 0; i < policy.mAcceptedCaCertificates.size(); i++) { out.startTag(null, TAG_ACCEPTED_CA_CERTIFICATES); out.attribute(null, ATTR_NAME, policy.mAcceptedCaCertificates.valueAt(i)); @@ -2472,6 +2458,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { JournaledFile journal = makeJournaledFile(userHandle); FileInputStream stream = null; File file = journal.chooseForRead(); + boolean needsRewrite = false; try { stream = new FileInputStream(file); XmlPullParser parser = Xml.newPullParser(); @@ -2558,16 +2545,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if ("password-owner".equals(tag)) { policy.mPasswordOwner = Integer.parseInt( parser.getAttributeValue(null, "value")); - } else if ("active-password".equals(tag)) { - final PasswordMetrics m = policy.mActivePasswordMetrics; - m.quality = Integer.parseInt(parser.getAttributeValue(null, "quality")); - m.length = Integer.parseInt(parser.getAttributeValue(null, "length")); - m.upperCase = Integer.parseInt(parser.getAttributeValue(null, "uppercase")); - m.lowerCase = Integer.parseInt(parser.getAttributeValue(null, "lowercase")); - m.letters = Integer.parseInt(parser.getAttributeValue(null, "letters")); - m.numeric = Integer.parseInt(parser.getAttributeValue(null, "numeric")); - m.symbols = Integer.parseInt(parser.getAttributeValue(null, "symbols")); - m.nonLetter = Integer.parseInt(parser.getAttributeValue(null, "nonletter")); } else if (TAG_ACCEPTED_CA_CERTIFICATES.equals(tag)) { policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME)); } else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) { @@ -2593,6 +2570,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { policy.mAdminBroadcastPending = Boolean.toString(true).equals(pending); } else if (TAG_INITIALIZATION_BUNDLE.equals(tag)) { policy.mInitBundle = PersistableBundle.restoreFromXml(parser); + } else if ("active-password".equals(tag)) { + needsRewrite = true; } else { Slog.w(LOG_TAG, "Unknown tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -2612,27 +2591,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Ignore } + // Might need to upgrade the file by rewriting it + if (needsRewrite) { + saveSettingsLocked(userHandle); + } + // Generate a list of admins from the admin map policy.mAdminList.addAll(policy.mAdminMap.values()); - // Validate that what we stored for the password quality matches - // sufficiently what is currently set. Note that this is only - // a sanity check in case the two get out of sync; this should - // never normally happen. - final long identity = mInjector.binderClearCallingIdentity(); - try { - int actualPasswordQuality = mLockPatternUtils.getActivePasswordQuality(userHandle); - if (actualPasswordQuality < policy.mActivePasswordMetrics.quality) { - Slog.w(LOG_TAG, "Active password quality 0x" - + Integer.toHexString(policy.mActivePasswordMetrics.quality) - + " does not match actual quality 0x" - + Integer.toHexString(actualPasswordQuality)); - policy.mActivePasswordMetrics = new PasswordMetrics(); - } - } finally { - mInjector.binderRestoreCallingIdentity(identity); - } - validatePasswordOwnerLocked(policy); updateMaximumTimeToLockLocked(userHandle); updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle); @@ -3866,6 +3832,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private boolean isActivePasswordSufficientForUserLocked( DevicePolicyData policy, int userHandle, boolean parent) { + enforceUserUnlocked(userHandle, parent); + final int requiredPasswordQuality = getPasswordQuality(null, userHandle, parent); if (policy.mActivePasswordMetrics.quality < requiredPasswordQuality) { return false; @@ -4477,7 +4445,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } try { - int uid = mContext.getPackageManager().getPackageUidAsUser( + int uid = mInjector.getPackageManager().getPackageUidAsUser( policy.mDelegatedCertInstallerPackage, userHandle); return uid == callingUid; } catch (NameNotFoundException e) { @@ -4940,33 +4908,52 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } enforceFullCrossUsersPermission(userHandle); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BIND_DEVICE_ADMIN, null); + + // If the managed profile doesn't have a separate password, set the metrics to default + if (isManagedProfile(userHandle) && !isSeparateProfileChallengeEnabled(userHandle)) { + metrics = new PasswordMetrics(); + } + + validateQualityConstant(metrics.quality); + DevicePolicyData policy = getUserData(userHandle); + synchronized (this) { + policy.mActivePasswordMetrics = metrics; + } + } + + @Override + public void reportPasswordChanged(@UserIdInt int userId) { + if (!mHasFeature) { + return; + } + enforceFullCrossUsersPermission(userId); // Managed Profile password can only be changed when it has a separate challenge. - if (!isSeparateProfileChallengeEnabled(userHandle)) { - enforceNotManagedProfile(userHandle, "set the active password"); + if (!isSeparateProfileChallengeEnabled(userId)) { + enforceNotManagedProfile(userId, "set the active password"); } mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - validateQualityConstant(metrics.quality); - DevicePolicyData policy = getUserData(userHandle); + DevicePolicyData policy = getUserData(userId); long ident = mInjector.binderClearCallingIdentity(); try { synchronized (this) { - policy.mActivePasswordMetrics = metrics; policy.mFailedPasswordAttempts = 0; - saveSettingsLocked(userHandle); - updatePasswordExpirationsLocked(userHandle); - setExpirationAlarmCheckLocked(mContext, userHandle, /* parent */ false); + saveSettingsLocked(userId); + updatePasswordExpirationsLocked(userId); + setExpirationAlarmCheckLocked(mContext, userId, /* parent */ false); // Send a broadcast to each profile using this password as its primary unlock. sendAdminCommandForLockscreenPoliciesLocked( DeviceAdminReceiver.ACTION_PASSWORD_CHANGED, - DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userHandle); + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userId); } - removeCaApprovalsIfNeeded(userHandle); + removeCaApprovalsIfNeeded(userId); } finally { mInjector.binderRestoreCallingIdentity(ident); } @@ -6038,6 +6025,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private boolean isDeviceOwnerPackage(String packageName, int userId) { + synchronized (this) { + return mOwners.hasDeviceOwner() + && mOwners.getDeviceOwnerUserId() == userId + && mOwners.getDeviceOwnerPackageName().equals(packageName); + } + } + public boolean isProfileOwner(ComponentName who, int userId) { final ComponentName profileOwner = getProfileOwner(userId); return who != null && who.equals(profileOwner); @@ -6120,7 +6115,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkNotNull(packageName, "packageName is null"); final int callingUid = mInjector.binderGetCallingUid(); try { - int uid = mContext.getPackageManager().getPackageUidAsUser(packageName, + int uid = mInjector.getPackageManager().getPackageUidAsUser(packageName, UserHandle.getUserId(callingUid)); if (uid != callingUid) { throw new SecurityException("Invalid packageName"); @@ -6606,6 +6601,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "User must be running and unlocked"); } + private void enforceUserUnlocked(@UserIdInt int userId, boolean parent) { + if (parent) { + enforceUserUnlocked(getProfileParentId(userId)); + } else { + enforceUserUnlocked(userId); + } + } + private void enforceManageUsers() { final int callingUid = mInjector.binderGetCallingUid(); if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) { @@ -6864,7 +6867,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } try { - int uid = mContext.getPackageManager().getPackageUidAsUser( + int uid = mInjector.getPackageManager().getPackageUidAsUser( policy.mApplicationRestrictionsManagingPackage, userHandle); return uid == callingUid; } catch (NameNotFoundException e) { @@ -8652,7 +8655,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final String deviceOwnerPackageName = mOwners.getDeviceOwnerComponent() .getPackageName(); - final String[] pkgs = mContext.getPackageManager().getPackagesForUid(callerUid); + final String[] pkgs = mInjector.getPackageManager().getPackagesForUid(callerUid); for (String pkg : pkgs) { if (deviceOwnerPackageName.equals(pkg)) { @@ -8689,7 +8692,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { ActivityInfo[] receivers = null; try { - receivers = mContext.getPackageManager().getPackageInfo( + receivers = mInjector.getPackageManager().getPackageInfo( deviceOwnerPackage, PackageManager.GET_RECEIVERS).receivers; } catch (NameNotFoundException e) { Log.e(LOG_TAG, "Cannot find device owner package", e); @@ -8745,7 +8748,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { < android.os.Build.VERSION_CODES.M) { return false; } - final PackageManager packageManager = mContext.getPackageManager(); + final PackageManager packageManager = mInjector.getPackageManager(); switch (grantState) { case DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED: { mInjector.getPackageManagerInternal().grantRuntimePermission(packageName, @@ -8780,7 +8783,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public int getPermissionGrantState(ComponentName admin, String packageName, String permission) throws RemoteException { - PackageManager packageManager = mContext.getPackageManager(); + PackageManager packageManager = mInjector.getPackageManager(); UserHandle user = mInjector.binderGetCallingUserHandle(); synchronized (this) { @@ -8817,17 +8820,33 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public boolean isProvisioningAllowed(String action) { - return checkProvisioningPreConditionSkipPermission(action) == CODE_OK; + public boolean isProvisioningAllowed(String action, String packageName) { + Preconditions.checkNotNull(packageName); + + final int callingUid = mInjector.binderGetCallingUid(); + final long ident = mInjector.binderClearCallingIdentity(); + try { + final int uidForPackage = mInjector.getPackageManager().getPackageUidAsUser( + packageName, UserHandle.getUserId(callingUid)); + Preconditions.checkArgument(callingUid == uidForPackage, + "Caller uid doesn't match the one for the provided package."); + } catch (NameNotFoundException e) { + throw new IllegalArgumentException("Invalid package provided " + packageName, e); + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } + + return checkProvisioningPreConditionSkipPermission(action, packageName) == CODE_OK; } @Override - public int checkProvisioningPreCondition(String action) { + public int checkProvisioningPreCondition(String action, String packageName) { + Preconditions.checkNotNull(packageName); enforceCanManageProfileAndDeviceOwners(); - return checkProvisioningPreConditionSkipPermission(action); + return checkProvisioningPreConditionSkipPermission(action, packageName); } - private int checkProvisioningPreConditionSkipPermission(String action) { + private int checkProvisioningPreConditionSkipPermission(String action, String packageName) { if (!mHasFeature) { return CODE_DEVICE_ADMIN_NOT_SUPPORTED; } @@ -8836,7 +8855,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (action != null) { switch (action) { case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE: - return checkManagedProfileProvisioningPreCondition(callingUserId); + return checkManagedProfileProvisioningPreCondition(packageName, callingUserId); case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE: return checkDeviceOwnerProvisioningPreCondition(callingUserId); case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER: @@ -8905,7 +8924,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private int checkManagedProfileProvisioningPreCondition(int callingUserId) { + private int checkManagedProfileProvisioningPreCondition(String packageName, int callingUserId) { if (!hasFeatureManagedUsers()) { return CODE_MANAGED_USERS_NOT_SUPPORTED; } @@ -8918,24 +8937,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Managed user cannot have a managed profile. return CODE_USER_HAS_PROFILE_OWNER; } + final long ident = mInjector.binderClearCallingIdentity(); try { - /* STOPSHIP(b/31952368) Reinstate a check similar to this once ManagedProvisioning - uses checkProvisioningPreCondition (see ag/1607846) and passes the packageName - there. In isProvisioningAllowed we should check isCallerDeviceOwner, but for - managed provisioning we need to check the package that is going to be set as PO - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE)) { - if (!isCallerDeviceOwner(callingUid) - || isAdminAffectedByRestriction(mOwners.getDeviceOwnerComponent(), - UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) { + final UserHandle callingUserHandle = UserHandle.of(callingUserId); + if (mUserManager.hasUserRestriction( + UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle)) { + // The DO can initiate provisioning if the restriction was set by the DO. + if (!isDeviceOwnerPackage(packageName, callingUserId) + || isAdminAffectedByRestriction(mOwners.getDeviceOwnerComponent(), + UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) { // Caller is not DO or the restriction was set by the system. - return false; - } - } */ + return CODE_ADD_MANAGED_PROFILE_DISALLOWED; + } + } + // TODO: Allow it if the caller is the DO? DO could just call removeUser() before // provisioning, so not strictly required... boolean canRemoveProfile = !mUserManager.hasUserRestriction( - UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, UserHandle.of(callingUserId)); + UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, callingUserHandle); if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) { return CODE_CANNOT_ADD_MANAGED_PROFILE; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java index 5f464bded23b..49ae2bc3523a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java @@ -46,11 +46,11 @@ final class NetworkLogger { private final DevicePolicyManagerService mDpm; private final PackageManagerInternal mPm; + private final AtomicBoolean mIsLoggingEnabled = new AtomicBoolean(false); private IIpConnectivityMetrics mIpConnectivityMetrics; private ServiceThread mHandlerThread; private NetworkLoggingHandler mNetworkLoggingHandler; - private AtomicBoolean mIsLoggingEnabled; private final INetdEventCallback mNetdEventCallback = new INetdEventCallback.Stub() { @Override diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 6ec25c53726b..be13499e2c18 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -32,6 +32,7 @@ import android.os.Build; import android.os.Environment; import android.os.FactoryTest; import android.os.FileUtils; +import android.os.IIncidentManager; import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; @@ -1635,6 +1636,19 @@ public final class SystemServer { } traceEnd(); + traceBeginAndSlog("IncidentDaemonReady"); + try { + // TODO: Switch from checkService to getService once it's always + // in the build and should reliably be there. + final IIncidentManager incident = IIncidentManager.Stub.asInterface( + ServiceManager.checkService("incident")); + if (incident != null) incident.systemRunning(); + } catch (Throwable e) { + reportWtf("Notifying incident daemon running", e); + } + traceEnd(); + + traceEnd(); // PhaseActivityManagerReady } }); diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java index fa37576a15f1..fb78457538be 100644 --- a/services/print/java/com/android/server/print/RemotePrintService.java +++ b/services/print/java/com/android/server/print/RemotePrintService.java @@ -132,8 +132,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleDestroy() { - throwIfDestroyed(); - // Stop tracking printers. stopTrackingAllPrinters(); @@ -174,7 +172,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleOnAllPrintJobsHandled() { - throwIfDestroyed(); mHasActivePrintJobs = false; if (!isBound()) { // The service is dead and neither has active jobs nor discovery @@ -208,7 +205,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleRequestCancelPrintJob(final PrintJobInfo printJob) { - throwIfDestroyed(); if (!isBound()) { ensureBound(); mPendingCommands.add(new Runnable() { @@ -235,7 +231,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleOnPrintJobQueued(final PrintJobInfo printJob) { - throwIfDestroyed(); mHasActivePrintJobs = true; if (!isBound()) { ensureBound(); @@ -262,7 +257,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleCreatePrinterDiscoverySession() { - throwIfDestroyed(); mHasPrinterDiscoverySession = true; if (!isBound()) { ensureBound(); @@ -289,7 +283,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleDestroyPrinterDiscoverySession() { - throwIfDestroyed(); mHasPrinterDiscoverySession = false; if (!isBound()) { // The service is dead and neither has active jobs nor discovery @@ -328,7 +321,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleStartPrinterDiscovery(final List<PrinterId> priorityList) { - throwIfDestroyed(); // Take a note that we are doing discovery. mDiscoveryPriorityList = new ArrayList<PrinterId>(); if (priorityList != null) { @@ -359,7 +351,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleStopPrinterDiscovery() { - throwIfDestroyed(); // We are not doing discovery anymore. mDiscoveryPriorityList = null; if (!isBound()) { @@ -392,7 +383,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleValidatePrinters(final List<PrinterId> printerIds) { - throwIfDestroyed(); if (!isBound()) { ensureBound(); mPendingCommands.add(new Runnable() { @@ -419,23 +409,40 @@ final class RemotePrintService implements DeathRecipient { } /** - * Request the custom printer icon for a printer. + * Queue a request for a custom printer icon for a printer. * * @param printerId the id of the printer the icon should be loaded for - * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon() + * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon */ public void requestCustomPrinterIcon(@NonNull PrinterId printerId) { - try { - if (isBound()) { + mHandler.obtainMessage(MyHandler.MSG_REQUEST_CUSTOM_PRINTER_ICON, + printerId).sendToTarget(); + } + + /** + * Request a custom printer icon for a printer. + * + * @param printerId the id of the printer the icon should be loaded for + * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon + */ + private void handleRequestCustomPrinterIcon(@NonNull PrinterId printerId) { + if (!isBound()) { + ensureBound(); + mPendingCommands.add(() -> handleRequestCustomPrinterIcon(printerId)); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] requestCustomPrinterIcon()"); + } + + try { mPrintService.requestCustomPrinterIcon(printerId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error requesting icon for " + printerId, re); } - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error requesting icon for " + printerId, re); } } private void handleStartPrinterStateTracking(final @NonNull PrinterId printerId) { - throwIfDestroyed(); // Take a note we are tracking the printer. if (mTrackedPrinterList == null) { mTrackedPrinterList = new ArrayList<PrinterId>(); @@ -467,7 +474,6 @@ final class RemotePrintService implements DeathRecipient { } private void handleStopPrinterStateTracking(final PrinterId printerId) { - throwIfDestroyed(); // We are no longer tracking the printer. if (mTrackedPrinterList == null || !mTrackedPrinterList.remove(printerId)) { return; @@ -581,12 +587,6 @@ final class RemotePrintService implements DeathRecipient { } } - private void throwIfDestroyed() { - if (mDestroyed) { - throw new IllegalStateException("Cannot interact with a destroyed service"); - } - } - private class RemoteServiceConneciton implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { @@ -657,6 +657,7 @@ final class RemotePrintService implements DeathRecipient { public static final int MSG_ON_PRINT_JOB_QUEUED = 10; public static final int MSG_DESTROY = 11; public static final int MSG_BINDER_DIED = 12; + public static final int MSG_REQUEST_CUSTOM_PRINTER_ICON = 13; public MyHandler(Looper looper) { super(looper, null, false); @@ -665,6 +666,11 @@ final class RemotePrintService implements DeathRecipient { @Override @SuppressWarnings("unchecked") public void handleMessage(Message message) { + if (mDestroyed) { + Slog.w(LOG_TAG, "Not handling " + message + " as service for " + mComponentName + + " is already destroyed"); + return; + } switch (message.what) { case MSG_CREATE_PRINTER_DISCOVERY_SESSION: { handleCreatePrinterDiscoverySession(); @@ -719,6 +725,11 @@ final class RemotePrintService implements DeathRecipient { case MSG_BINDER_DIED: { handleBinderDied(); } break; + + case MSG_REQUEST_CUSTOM_PRINTER_ICON: { + PrinterId printerId = (PrinterId) message.obj; + handleRequestCustomPrinterIcon(printerId); + } break; } } } diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index 50911cb32ddf..c653b8eecffa 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -16,10 +16,13 @@ package com.android.server; +import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT; +import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE; import static android.net.NetworkScoreManager.CACHE_FILTER_NONE; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; @@ -28,12 +31,15 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyListOf; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isA; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.never; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.Manifest.permission; @@ -44,37 +50,42 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.net.INetworkRecommendationProvider; import android.net.INetworkScoreCache; import android.net.NetworkKey; -import android.net.NetworkScoreManager; import android.net.NetworkScorerAppManager; import android.net.NetworkScorerAppManager.NetworkScorerAppData; +import android.net.RecommendationRequest; +import android.net.RecommendationResult; import android.net.ScoredNetwork; import android.net.WifiKey; +import android.net.wifi.WifiConfiguration; +import android.os.Bundle; import android.os.IBinder; +import android.os.IRemoteCallback; +import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; -import android.provider.Settings; -import android.provider.Settings.Global; import android.support.test.InstrumentationRegistry; import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; -import com.android.internal.R; import com.android.server.devicepolicy.MockUtils; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; /** * Tests for {@link NetworkScoreService}. @@ -85,12 +96,8 @@ public class NetworkScoreServiceTest { private static final ScoredNetwork SCORED_NETWORK = new ScoredNetwork(new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")), null /* rssiCurve*/); - private static final NetworkScorerAppData PREV_SCORER = new NetworkScorerAppData( - "prevPackageName", 0, "prevScorerName", null /* configurationActivityClassName */, - "prevScoringServiceClass"); - private static final NetworkScorerAppData NEW_SCORER = new NetworkScorerAppData( - "newPackageName", 1, "newScorerName", null /* configurationActivityClassName */, - "newScoringServiceClass"); + private static final NetworkScorerAppData NEW_SCORER = + new NetworkScorerAppData("newPackageName", 1, "newScoringServiceClass"); @Mock private PackageManager mPackageManager; @Mock private NetworkScorerAppManager mNetworkScorerAppManager; @@ -98,10 +105,12 @@ public class NetworkScoreServiceTest { @Mock private Resources mResources; @Mock private INetworkScoreCache.Stub mNetworkScoreCache, mNetworkScoreCache2; @Mock private IBinder mIBinder, mIBinder2; + @Mock private INetworkRecommendationProvider mRecommendationProvider; @Captor private ArgumentCaptor<List<ScoredNetwork>> mScoredNetworkCaptor; private ContentResolver mContentResolver; private NetworkScoreService mNetworkScoreService; + private RecommendationRequest mRecommendationRequest; @Before public void setUp() throws Exception { @@ -112,57 +121,135 @@ public class NetworkScoreServiceTest { when(mContext.getContentResolver()).thenReturn(mContentResolver); when(mContext.getResources()).thenReturn(mResources); mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager); + WifiConfiguration configuration = new WifiConfiguration(); + mRecommendationRequest = new RecommendationRequest.Builder() + .setCurrentRecommendedWifiConfig(configuration).build(); } @Test - public void testSystemReady_networkScorerProvisioned() throws Exception { - Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 1); + public void testSystemRunning() { + when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER); - mNetworkScoreService.systemReady(); + mNetworkScoreService.systemRunning(); - verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString()); + verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent( + new ComponentName(NEW_SCORER.packageName, + NEW_SCORER.recommendationServiceClassName))), + any(ServiceConnection.class), + eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), + eq(UserHandle.SYSTEM)); } @Test - public void testSystemReady_networkScorerNotProvisioned_defaultScorer() throws Exception { - Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0); - - when(mResources.getString(R.string.config_defaultNetworkScorerPackageName)) - .thenReturn(NEW_SCORER.mPackageName); + public void testRequestScores_noPermission() throws Exception { + doThrow(new SecurityException()).when(mContext) + .enforceCallingOrSelfPermission(eq(permission.BROADCAST_NETWORK_PRIVILEGED), + anyString()); + try { + mNetworkScoreService.requestScores(null); + fail("BROADCAST_NETWORK_PRIVILEGED not enforced."); + } catch (SecurityException e) { + // expected + } + } - mNetworkScoreService.systemReady(); + @Test + public void testRequestScores_providerNotConnected() throws Exception { + assertFalse(mNetworkScoreService.requestScores(new NetworkKey[0])); + verifyZeroInteractions(mRecommendationProvider); + } - verify(mNetworkScorerAppManager).setActiveScorer(NEW_SCORER.mPackageName); - assertEquals(1, - Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED)); + @Test + public void testRequestScores_providerThrowsRemoteException() throws Exception { + injectProvider(); + doThrow(new RemoteException()).when(mRecommendationProvider) + .requestScores(any(NetworkKey[].class)); + assertFalse(mNetworkScoreService.requestScores(new NetworkKey[0])); } @Test - public void testSystemReady_networkScorerNotProvisioned_noDefaultScorer() throws Exception { - Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0); + public void testRequestScores_providerAvailable() throws Exception { + injectProvider(); - when(mResources.getString(R.string.config_defaultNetworkScorerPackageName)) - .thenReturn(null); + final NetworkKey[] networks = new NetworkKey[0]; + assertTrue(mNetworkScoreService.requestScores(networks)); + verify(mRecommendationProvider).requestScores(networks); + } - mNetworkScoreService.systemReady(); + @Test + public void testRequestRecommendation_noPermission() throws Exception { + doThrow(new SecurityException()).when(mContext) + .enforceCallingOrSelfPermission(eq(permission.BROADCAST_NETWORK_PRIVILEGED), + anyString()); + try { + mNetworkScoreService.requestRecommendation(mRecommendationRequest); + fail("BROADCAST_NETWORK_PRIVILEGED not enforced."); + } catch (SecurityException e) { + // expected + } + } - verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString()); - assertEquals(1, - Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED)); + @Test + public void testRequestRecommendation_mainThread() throws Exception { + when(mContext.getMainLooper()).thenReturn(Looper.myLooper()); + try { + mNetworkScoreService.requestRecommendation(mRecommendationRequest); + fail("requestRecommendation run on main thread."); + } catch (RuntimeException e) { + // expected + } } @Test - public void testSystemRunning() { - when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER); + public void testRequestRecommendation_providerNotConnected() throws Exception { + when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); + + final RecommendationResult result = + mNetworkScoreService.requestRecommendation(mRecommendationRequest); + assertNotNull(result); + assertEquals(mRecommendationRequest.getCurrentSelectedConfig(), + result.getWifiConfiguration()); + } - mNetworkScoreService.systemRunning(); + @Test + public void testRequestRecommendation_providerThrowsRemoteException() throws Exception { + injectProvider(); + when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); + doThrow(new RemoteException()).when(mRecommendationProvider) + .requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class), + anyInt()); + + final RecommendationResult result = + mNetworkScoreService.requestRecommendation(mRecommendationRequest); + assertNotNull(result); + assertEquals(mRecommendationRequest.getCurrentSelectedConfig(), + result.getWifiConfiguration()); + } - verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent( - new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))), - any(ServiceConnection.class), - eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), - eq(UserHandle.SYSTEM)); + @Test + public void testRequestRecommendation_resultReturned() throws Exception { + injectProvider(); + when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); + final WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiConfiguration.SSID = "testRequestRecommendation_resultReturned"; + final RecommendationResult providerResult = + new RecommendationResult(wifiConfiguration); + final Bundle bundle = new Bundle(); + bundle.putParcelable(EXTRA_RECOMMENDATION_RESULT, providerResult); + doAnswer(invocation -> { + bundle.putInt(EXTRA_SEQUENCE, invocation.getArgumentAt(2, int.class)); + invocation.getArgumentAt(1, IRemoteCallback.class).sendResult(bundle); + return null; + }).when(mRecommendationProvider) + .requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class), + anyInt()); + + final RecommendationResult result = + mNetworkScoreService.requestRecommendation(mRecommendationRequest); + assertNotNull(result); + assertEquals(providerResult.getWifiConfiguration().SSID, + result.getWifiConfiguration().SSID); } @Test @@ -288,45 +375,6 @@ public class NetworkScoreServiceTest { } @Test - public void testSetActiveScorer_failure() throws RemoteException { - when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER); - when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(false); - mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache, - CACHE_FILTER_NONE); - - boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName); - - assertFalse(success); - verify(mNetworkScoreCache).clearScores(); - verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent( - new ComponentName(PREV_SCORER.mPackageName, PREV_SCORER.mScoringServiceClassName))), - any(ServiceConnection.class), - eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), - eq(UserHandle.SYSTEM)); - } - - @Test - public void testSetActiveScorer_success() throws RemoteException { - when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, NEW_SCORER); - when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(true); - mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache, - CACHE_FILTER_NONE); - - boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName); - - assertTrue(success); - verify(mNetworkScoreCache).clearScores(); - verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent( - new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))), - any(ServiceConnection.class), - eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), - eq(UserHandle.SYSTEM)); - verify(mContext, times(2)).sendBroadcastAsUser( - MockUtils.checkIntentAction(NetworkScoreManager.ACTION_SCORER_CHANGED), - eq(UserHandle.SYSTEM)); - } - - @Test public void testDisableScoring_notActiveScorer_noBroadcastNetworkPermission() { when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false); when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED)) @@ -338,48 +386,6 @@ public class NetworkScoreServiceTest { } catch (SecurityException e) { // expected } - - } - - @Test - public void testDisableScoring_activeScorer() throws RemoteException { - when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true); - when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null); - when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true); - mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache, - CACHE_FILTER_NONE); - - mNetworkScoreService.disableScoring(); - - verify(mNetworkScoreCache).clearScores(); - verify(mContext).sendBroadcastAsUser( - MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED) - .setPackage(PREV_SCORER.mPackageName)), - eq(UserHandle.SYSTEM)); - verify(mContext, never()).bindServiceAsUser(any(Intent.class), - any(ServiceConnection.class), anyInt(), any(UserHandle.class)); - } - - @Test - public void testDisableScoring_notActiveScorer_hasBroadcastNetworkPermission() - throws RemoteException { - when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false); - when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED)) - .thenReturn(PackageManager.PERMISSION_GRANTED); - when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null); - when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true); - mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache, - CACHE_FILTER_NONE); - - mNetworkScoreService.disableScoring(); - - verify(mNetworkScoreCache).clearScores(); - verify(mContext).sendBroadcastAsUser( - MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED) - .setPackage(PREV_SCORER.mPackageName)), - eq(UserHandle.SYSTEM)); - verify(mContext, never()).bindServiceAsUser(any(Intent.class), - any(ServiceConnection.class), anyInt(), any(UserHandle.class)); } @Test @@ -434,4 +440,24 @@ public class NetworkScoreServiceTest { assertFalse(stringWriter.toString().isEmpty()); } + + // "injects" the mock INetworkRecommendationProvider into the NetworkScoreService. + private void injectProvider() { + final ComponentName componentName = new ComponentName(NEW_SCORER.packageName, + NEW_SCORER.recommendationServiceClassName); + when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER); + when(mContext.bindServiceAsUser(isA(Intent.class), isA(ServiceConnection.class), anyInt(), + isA(UserHandle.class))).thenAnswer(new Answer<Boolean>() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + IBinder mockBinder = mock(IBinder.class); + when(mockBinder.queryLocalInterface(anyString())) + .thenReturn(mRecommendationProvider); + invocation.getArgumentAt(1, ServiceConnection.class) + .onServiceConnected(componentName, mockBinder); + return true; + } + }); + mNetworkScoreService.systemRunning(); + } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 48c985342430..c35d11420fbc 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -77,7 +77,7 @@ import static org.mockito.Mockito.when; /** * Tests for DevicePolicyManager( and DevicePolicyManagerService). - * + * You can run them via: m FrameworksServicesTests && adb install \ -r ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk && @@ -85,6 +85,9 @@ import static org.mockito.Mockito.when; -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner (mmma frameworks/base/services/tests/servicestests/ for non-ninja build) + * + * , or: + * runtest -c com.android.server.devicepolicy.DevicePolicyManagerTest frameworks-services */ @SmallTest public class DevicePolicyManagerTest extends DpmTestBase { @@ -2010,7 +2013,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // UnfinishedVerificationException. } - public void setup_DeviceAdminFeatureOff() throws Exception { + private void setup_DeviceAdminFeatureOff() throws Exception { when(mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) .thenReturn(false); when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) @@ -2026,6 +2029,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_DeviceAdminFeatureOff() throws Exception { setup_DeviceAdminFeatureOff(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, @@ -2047,7 +2052,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED); } - public void setup_ManagedProfileFeatureOff() throws Exception { + private void setup_ManagedProfileFeatureOff() throws Exception { when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(false); initializeDpms(); @@ -2061,6 +2066,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_ManagedProfileFeatureOff() throws Exception { setup_ManagedProfileFeatureOff(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, @@ -2102,7 +2109,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED); } - public void setup_nonSplitUser_firstBoot_primaryUser() throws Exception { + private void setup_nonSplitUser_firstBoot_primaryUser() throws Exception { when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(false); @@ -2115,6 +2122,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_nonSplitUser_firstBoot_primaryUser() throws Exception { setup_nonSplitUser_firstBoot_primaryUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, @@ -2138,7 +2147,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_NOT_SYSTEM_USER_SPLIT); } - public void setup_nonSplitUser_afterDeviceSetup_primaryUser() throws Exception { + private void setup_nonSplitUser_afterDeviceSetup_primaryUser() throws Exception { when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(false); @@ -2152,6 +2161,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_nonSplitUser_afterDeviceSetup_primaryUser() throws Exception { setup_nonSplitUser_afterDeviceSetup_primaryUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false/* because of completed device setup */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); @@ -2176,7 +2187,88 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_NOT_SYSTEM_USER_SPLIT); } - public void setup_splitUser_firstBoot_systemUser() throws Exception { + public void testIsProvisioningAllowed_nonSplitUser_withDo_primaryUser() throws Exception { + setDeviceOwner(); + setup_nonSplitUser_afterDeviceSetup_primaryUser(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); + mContext.packageName = admin1.getPackageName(); + + // COMP mode is allowed. + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + + when(mContext.userManager.hasUserRestriction( + eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), + eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) + .thenReturn(true); + + // The DO should be allowed to initiate provisioning if it set the restriction itself. + when(mContext.userManager.getUserRestrictionSource( + eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), + eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) + .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); + + // The DO should not be allowed to initiate provisioning if the restriction is set by + // another entity. + when(mContext.userManager.getUserRestrictionSource( + eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), + eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) + .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM); + assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); + } + + public void + testCheckProvisioningPreCondition_nonSplitUser_withDo_primaryUser() throws Exception { + setDeviceOwner(); + setup_nonSplitUser_afterDeviceSetup_primaryUser(); + mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); + + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, + DevicePolicyManager.CODE_HAS_DEVICE_OWNER); + + // COMP mode is allowed. + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + DevicePolicyManager.CODE_OK); + + // And other DPCs can also provisioning a managed profile (DO + BYOD case). + assertCheckProvisioningPreCondition( + DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + "some.other.dpc.package.name", + DevicePolicyManager.CODE_OK); + + when(mContext.userManager.hasUserRestriction( + eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), + eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) + .thenReturn(true); + + // The DO should be allowed to initiate provisioning if it set the restriction itself, but + // other packages should be forbidden. + when(mContext.userManager.getUserRestrictionSource( + eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), + eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) + .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + DevicePolicyManager.CODE_OK); + assertCheckProvisioningPreCondition( + DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + "some.other.dpc.package.name", + DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + + // The DO should not be allowed to initiate provisioning if the restriction is set by + // another entity. + when(mContext.userManager.getUserRestrictionSource( + eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), + eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) + .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM); + assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + assertCheckProvisioningPreCondition( + DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, + "some.other.dpc.package.name", + DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED); + } + + private void setup_splitUser_firstBoot_systemUser() throws Exception { when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); @@ -2189,6 +2281,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_splitUser_firstBoot_systemUser() throws Exception { setup_splitUser_firstBoot_systemUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false /* because canAddMoreManagedProfiles returns false */); @@ -2213,7 +2307,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_SYSTEM_USER); } - public void setup_splitUser_afterDeviceSetup_systemUser() throws Exception { + private void setup_splitUser_afterDeviceSetup_systemUser() throws Exception { when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); @@ -2226,6 +2320,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_splitUser_afterDeviceSetup_systemUser() throws Exception { setup_splitUser_afterDeviceSetup_systemUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true/* it's undefined behavior. Can be changed into false in the future */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, @@ -2251,7 +2347,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_SYSTEM_USER); } - public void setup_splitUser_firstBoot_primaryUser() throws Exception { + private void setup_splitUser_firstBoot_primaryUser() throws Exception { when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); @@ -2264,6 +2360,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_splitUser_firstBoot_primaryUser() throws Exception { setup_splitUser_firstBoot_primaryUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE, @@ -2286,7 +2384,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_OK); } - public void setup_splitUser_afterDeviceSetup_primaryUser() throws Exception { + private void setup_splitUser_afterDeviceSetup_primaryUser() throws Exception { when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) .thenReturn(true); when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); @@ -2300,6 +2398,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_splitUser_afterDeviceSetup_primaryUser() throws Exception { setup_splitUser_afterDeviceSetup_primaryUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true/* it's undefined behavior. Can be changed into false in the future */); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); @@ -2324,7 +2424,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { DevicePolicyManager.CODE_USER_SETUP_COMPLETED); } - public void setup_provisionManagedProfileWithDeviceOwner_systemUser() throws Exception { + private void setup_provisionManagedProfileWithDeviceOwner_systemUser() throws Exception { setDeviceOwner(); when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0)) @@ -2340,6 +2440,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_provisionManagedProfileWithDeviceOwner_systemUser() throws Exception { setup_provisionManagedProfileWithDeviceOwner_systemUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false /* can't provision managed profile on system user */); } @@ -2368,6 +2470,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_provisionManagedProfileWithDeviceOwner_primaryUser() throws Exception { setup_provisionManagedProfileWithDeviceOwner_primaryUser(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); + mContext.packageName = admin1.getPackageName(); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true); } @@ -2375,6 +2479,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { throws Exception { setup_provisionManagedProfileWithDeviceOwner_primaryUser(); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); + + // COMP mode is allowed. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DevicePolicyManager.CODE_OK); } @@ -2386,8 +2492,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { .thenReturn(true); when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true); when(mContext.userManager.hasUserRestriction( - UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, - UserHandle.of(DpmMockContext.CALLER_USER_HANDLE))) + eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE), + eq(UserHandle.of(DpmMockContext.CALLER_USER_HANDLE)))) .thenReturn(true); when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE, false /* we can't remove a managed profile */)).thenReturn(false); @@ -2401,6 +2507,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testIsProvisioningAllowed_provisionManagedProfileCantRemoveUser_primaryUser() throws Exception { setup_provisionManagedProfileCantRemoveUser_primaryUser(); + mContext.packageName = admin1.getPackageName(); + setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); } @@ -2415,7 +2523,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testCheckProvisioningPreCondition_permission() { // GIVEN the permission MANAGE_PROFILE_AND_DEVICE_OWNERS is not granted try { - dpm.checkProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE); + dpm.checkProvisioningPreCondition( + DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, "some.package"); fail("Didn't throw SecurityException"); } catch (SecurityException expected) { } @@ -2820,8 +2929,14 @@ public class DevicePolicyManagerTest extends DpmTestBase { } private void assertCheckProvisioningPreCondition(String action, int provisioningCondition) { - assertEquals("checkProvisioningPreCondition(" + action + ") returning unexpected result", - provisioningCondition, dpm.checkProvisioningPreCondition(action)); + assertCheckProvisioningPreCondition(action, admin1.getPackageName(), provisioningCondition); + } + + private void assertCheckProvisioningPreCondition( + String action, String packageName, int provisioningCondition) { + assertEquals("checkProvisioningPreCondition(" + + action + ", " + packageName + ") returning unexpected result", + provisioningCondition, dpm.checkProvisioningPreCondition(action, packageName)); } /** diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java index db27f7230db9..8a1197618acd 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java @@ -82,6 +82,10 @@ public abstract class DpmTestBase extends AndroidTestCase { eq(packageName), eq(0), eq(userId)); + + doReturn(ai.uid).when(mMockContext.packageManager).getPackageUidAsUser( + eq(packageName), + eq(userId)); } protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid) diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index cb27af1e3fd6..6bc4c19f6c89 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -71,6 +71,7 @@ import android.os.Handler; import android.os.Looper; import android.os.PersistableBundle; import android.os.Process; +import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.test.InstrumentationTestCase; @@ -1289,6 +1290,13 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } /** + * Make a shortcut with an ID only. + */ + protected ShortcutInfo makeShortcutIdOnly(String id) { + return new ShortcutInfo.Builder(mClientContext, id).build(); + } + + /** * Make a shortcut with an ID. */ protected ShortcutInfo makeShortcut(String id) { @@ -1297,12 +1305,19 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); } + @Deprecated // Title was renamed to short label. protected ShortcutInfo makeShortcutWithTitle(String id, String title) { return makeShortcut( id, title, /* activity =*/ null, /* icon =*/ null, makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); } + protected ShortcutInfo makeShortcutWithShortLabel(String id, String shortLabel) { + return makeShortcut( + id, shortLabel, /* activity =*/ null, /* icon =*/ null, + makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0); + } + /** * Make a shortcut with an ID and timestamp. */ @@ -1695,6 +1710,13 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { return getLauncherShortcuts(launcher, userId, ShortcutQuery.FLAG_GET_PINNED); } + protected List<ShortcutInfo> getShortcutAsLauncher(int targetUserId) { + final ShortcutQuery q = new ShortcutQuery(); + q.setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC | ShortcutQuery.FLAG_MATCH_DYNAMIC + | ShortcutQuery.FLAG_MATCH_PINNED); + return mLauncherApps.getShortcuts(q, UserHandle.of(targetUserId)); + } + protected ShortcutInfo getShortcutInfoAsLauncher(String packageName, String shortcutId, int userId) { final List<ShortcutInfo> infoList = @@ -1968,7 +1990,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { public static List<ShortcutInfo> assertAllHaveIcon( List<ShortcutInfo> actualShortcuts) { for (ShortcutInfo s : actualShortcuts) { - assertTrue("ID " + s.getId() + " has no icon ", s.hasIconFile() || s.hasIconResource()); + assertTrue("ID " + s.getId() + " has no icon ", + s.hasIconFile() || s.hasIconResource() || s.getIcon() != null); } return actualShortcuts; } @@ -2030,4 +2053,31 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { return ri(PACKAGE_FALLBACK_LAUNCHER, PACKAGE_FALLBACK_LAUNCHER_NAME, true, PACKAGE_FALLBACK_LAUNCHER_PRIORITY); } + + protected void makeCallerForeground() { + try { + mService.mUidObserver.onUidStateChanged( + mInjectedCallingUid, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + protected void makeCallerBackground() { + try { + mService.mUidObserver.onUidStateChanged( + mInjectedCallingUid, ActivityManager.PROCESS_STATE_TOP_SLEEPING); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + protected void publishManifestShortcutsAsCaller(int resId) { + addManifestShortcutResource( + new ComponentName(getCallingPackage(), ShortcutActivity.class.getName()), + resId); + updatePackageVersion(getCallingPackage(), 1); + mService.mPackageMonitor.onReceive(getTestContext(), + genPackageAddIntent(getCallingPackage(), getCallingUserId())); + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java index de344c2705b1..fbf0ed2938eb 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java @@ -15,7 +15,10 @@ */ package com.android.server.pm; +import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException; +import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertForLauncherCallbackNoThrow; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith; +import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; @@ -31,10 +34,14 @@ import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.PinItemRequest; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; +import android.graphics.drawable.Icon; import android.os.UserHandle; +import android.test.MoreAsserts; import android.test.suitebuilder.annotation.SmallTest; import android.util.Pair; +import com.android.frameworks.servicestests.R; + import org.mockito.ArgumentCaptor; /** @@ -45,6 +52,11 @@ import org.mockito.ArgumentCaptor; -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk && adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest8 \ -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner + + * TODO for CTS + * - Foreground check. + * - Reading icons from requested shortcuts. + * - Invalid pre-approved token. */ @SmallTest public class ShortcutManagerTest8 extends BaseShortcutManagerTest { @@ -56,6 +68,14 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { mProcessor = mService.getShortcutRequestPinProcessorForTest(); } + @Override + protected void setCaller(String packageName, int userId) { + super.setCaller(packageName, userId); + + // Note during this test, assume all callers are in the foreground by default. + makeCallerForeground(); + } + public void testGetParentOrSelfUserId() { assertEquals(USER_0, mService.getParentOrSelfUserId(USER_0)); assertEquals(USER_10, mService.getParentOrSelfUserId(USER_10)); @@ -188,6 +208,23 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { actualIntent.getFlags()); } + public void testNotForeground() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + makeCallerBackground(); + + assertExpectException(IllegalStateException.class, "foreground activity", () -> { + assertTrue(mManager.requestPinShortcut(makeShortcut("s1"), + /* resultIntent= */ null)); + }); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + verify(mServiceContext, times(0)).startActivityAsUser( + any(Intent.class), any(UserHandle.class)); + }); + } + private void assertPinItemRequest(PinItemRequest actualRequest) { assertNotNull(actualRequest); @@ -203,14 +240,15 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); setDefaultLauncher(USER_10, mMainActivityFetcher.apply(LAUNCHER_2, USER_10)); + final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { - ShortcutInfo s1 = makeShortcut("s1"); + ShortcutInfo s1 = makeShortcutWithIcon("s1", res32x32); assertTrue(mManager.requestPinShortcut(s1, resultIntent == null ? null : resultIntent.getIntentSender())); - verify(mServiceContext, times(0)) - .sendIntentSender(any(IntentSender.class)); + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); // Shortcut shouldn't be registered yet. assertWith(getCallerShortcuts()) @@ -232,16 +270,18 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { assertWith(request.getShortcutInfo()) .haveIds("s1") - .areAllOrphan(); + .areAllOrphan() + .areAllWithNoIntent(); - // Can't test icons; need to test on CTS. + assertAllHaveIcon(list(request.getShortcutInfo())); // Accept the request. - request.accept(); + assertForLauncherCallbackNoThrow(mLauncherApps, + () -> assertTrue(request.accept())) + .assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_P0) + .haveIds("s1"); }); - // Check from the launcher side, including callback - // This method is always called, even with PI == null. if (resultIntent == null) { verify(mServiceContext, times(1)).sendIntentSender(eq(null)); @@ -254,7 +294,8 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { .haveIds("s1") .areAllNotDynamic() .areAllEnabled() - .areAllPinned(); + .areAllPinned() + .areAllWithIntent(); }); } @@ -269,23 +310,919 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { checkRequestPinShortcut(resultIntent); } - // TODO More tests: - // Shortcut exists as a dynamic shortcut. - // Shortcut exists as a manifest shortcut. - // Shortcut exists as a dynamic, already pinned by this launcher - // Shortcut exists as a manifest, already pinned by this launcher - // Shortcut exists as floating, already pinned by this launcher + public void testRequestPinShortcut_dynamicExists() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + // Create dynamic shortcut + ShortcutInfo s1 = makeShortcutWithIcon("s1", res32x32); + assertTrue(mManager.setDynamicShortcuts(list(s1))); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned() + .areAllWithNoIntent(); + + assertAllHaveIcon(list(request.getShortcutInfo())); + + // Accept the request. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDynamic() + .areAllEnabled() + .areAllPinned(); + }); + } + + public void testRequestPinShortcut_manifestExists() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned() + .areAllWithNoIntent(); + + assertAllHaveIcon(list(request.getShortcutInfo())); + + // Accept the request. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllManifest() + .areAllEnabled() + .areAllPinned(); + }); + } + + public void testRequestPinShortcut_dynamicExists_alreadyPinned() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1")))); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDynamic() + .areAllPinned(); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null)); + + // The intent should be sent right away. + verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class)); + }); + } + + public void testRequestPinShortcut_manifestExists_alreadyPinned() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllManifest() + .areAllPinned(); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"), + /* resultIntent=*/ null)); + + // The intent should be sent right away. + verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class)); + }); + } + + public void testRequestPinShortcut_wasDynamic_alreadyPinned() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1")))); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + mManager.removeAllDynamicShortcuts(); + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllNotDynamic() + .areAllEnabled() + .areAllPinned(); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null)); + + // The intent should be sent right away. + verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class)); + }); + } + + public void testRequestPinShortcut_wasDynamic_disabled_alreadyPinned() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1")))); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + mManager.disableShortcuts(list("s1")); + + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllNotDynamic() + .areAllDisabled() + .areAllPinned(); + + assertExpectException(IllegalArgumentException.class, "exists but disabled", () -> { + mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null); + }); + + // Shouldn't be called. + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + }); + } + + public void testRequestPinShortcut_wasManifest_alreadyPinned() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_0); + + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllNotManifest() + .areAllDisabled() + .areAllPinned(); + + assertExpectException(IllegalArgumentException.class, "exists but disabled", () -> { + mManager.requestPinShortcut(makeShortcutIdOnly("ms1"), + /* resultIntent=*/ null); + }); + + // Shouldn't be called. + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + }); + } + + public void testRequestPinShortcut_dynamicExists_alreadyPinnedByAnother() { + // Initially all launchers have the shortcut permission, until we call setDefaultLauncher(). + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1")))); + }); + + runWithCaller(LAUNCHER_2, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0); + }); + + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDynamic() + .areAllPinned(); + + // The shortcut is already pinned, but not by the current launcher, so it'll still + // invoke the whole flow. + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned() // Note it's not pinned by this launcher. + .areAllWithNoIntent(); + + // Accept the request. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDynamic() + .areAllEnabled() + .areAllPinned(); + }); + } + + public void testRequestPinShortcut_manifestExists_alreadyPinnedByAnother() { + // Initially all launchers have the shortcut permission, until we call setDefaultLauncher(). + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + }); + + runWithCaller(LAUNCHER_2, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0); + }); + + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllManifest() + .areAllPinned(); + + // The shortcut is already pinned, but not by the current launcher, so it'll still + // invoke the whole flow. + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned() // Note it's not pinned by this launcher. + .areAllWithNoIntent(); + + // Accept the request. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllManifest() + .areAllEnabled() + .areAllPinned(); + }); + } + + /** + * The launcher already has a pinned shortuct. The new one should be added, not replace + * the existing one. + */ + public void testRequestPinShortcut_launcherAlreadyHasPinned() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), makeShortcut("s2")))); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_P0); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned() + .areAllWithNoIntent(); + + // Accept the request. + assertTrue(request.accept()); + + assertWith(getShortcutAsLauncher(USER_P0)) + .haveIds("s1", "s2") + .areAllDynamic() + .areAllEnabled() + .areAllPinned(); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1", "s2") + .areAllDynamic() + .areAllEnabled() + .areAllPinned(); + }); + } - // Shortcut exists as a dynamic, already pinned by another launcher - // Shortcut exists as a manifest, already pinned by another launcher - // Shortcut exists as floating, already pinned by another launcher + /** + * When trying to pin an existing shortcut, the new fields shouldn't override existing fields. + */ + public void testRequestPinShortcut_dynamicExists_titleWontChange() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + // Create dynamic shortcut + ShortcutInfo s1 = makeShortcutWithIcon("s1", res32x32); + assertTrue(mManager.setDynamicShortcuts(list(s1))); + + assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("s1", "xxx"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned() + .areAllWithNoIntent(); + + assertAllHaveIcon(list(request.getShortcutInfo())); + + // Accept the request. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDynamic() + .areAllEnabled() + .areAllPinned() + .forShortcutWithId("s1", (si) -> { + // Still the original title. + assertEquals("Title-s1", si.getShortLabel()); + }); + }); + } + + /** + * When trying to pin an existing shortcut, the new fields shouldn't override existing fields. + */ + public void testRequestPinShortcut_manifestExists_titleWontChange() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + + assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("ms1", "xxx"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned() + .areAllWithNoIntent(); + + assertAllHaveIcon(list(request.getShortcutInfo())); + + // Accept the request. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllManifest() + .areAllEnabled() + .areAllPinned() + .forShortcutWithId("ms1", (si) -> { + // Still the original title. + // Title should be something like: + // "string-com.android.test.1-user:20-res:2131034112/en" + MoreAsserts.assertContainsRegex("^string-", si.getShortLabel().toString()); + }); + }); + } + + /** + * The dynamic shortcut existed, but before accepting(), it's removed. Because the request + * has a partial shortcut, accept() should fail. + */ + public void testRequestPinShortcut_dynamicExists_thenRemoved_error() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + // Create dynamic shortcut + ShortcutInfo s1 = makeShortcut("s1"); + assertTrue(mManager.setDynamicShortcuts(list(s1))); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + mManager.removeAllDynamicShortcuts(); + + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned() + .areAllWithNoIntent(); + + // Accept the request -> should fail. + assertForLauncherCallbackNoThrow(mLauncherApps, + () -> assertFalse(request.accept())) + .assertNoCallbackCalled(); + }); + + // Intent shouldn't be sent. + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + } + + /** + * The dynamic shortcut existed, but before accepting(), it's removed. Because the request + * has all the mandatory fields, we can go ahead and still publish it. + */ + public void testRequestPinShortcut_dynamicExists_thenRemoved_okay() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + // Create dynamic shortcut + ShortcutInfo s1 = makeShortcut("s1"); + assertTrue(mManager.setDynamicShortcuts(list(s1))); + + assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("s1", "new"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + mManager.removeAllDynamicShortcuts(); + + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); - // Shortcut exists but disabled (both mutable and immutable) + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); - // Shortcut exists but removed before accept(). - // Shortcut exists but disabled before accept(). - // Shortcut exists but pinned before accept(). - // Shortcut exists but unpinned before accept(). + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned() + .areAllWithNoIntent(); + + // Accept the request -> should fail. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllFloating() + .forShortcutWithId("s1", si -> { + assertEquals("new", si.getShortLabel()); + }); + }); + } + + /** + * The manifest shortcut existed, but before accepting(), it's removed. Because the request + * has a partial shortcut, accept() should fail. + */ + public void testRequestPinShortcut_manifestExists_thenRemoved_error() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + publishManifestShortcutsAsCaller(R.xml.shortcut_0); + + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned() + .areAllWithNoIntent(); + + // Accept the request -> should fail. + assertForLauncherCallbackNoThrow(mLauncherApps, + () -> assertFalse(request.accept())) + .assertNoCallbackCalled(); + }); + + // Intent shouldn't be sent. + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + } + + /** + * The manifest shortcut existed, but before accepting(), it's removed. Because the request + * has all the mandatory fields, we can go ahead and still publish it. + */ + public void testRequestPinShortcut_manifestExists_thenRemoved_okay() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + + assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("ms1", "new"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + publishManifestShortcutsAsCaller(R.xml.shortcut_0); + + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned() + .areAllWithNoIntent(); + + + // Accept the request -> should fail. + assertTrue(request.accept()); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllMutable() // Note it's no longer immutable. + .areAllFloating() + .forShortcutWithId("ms1", si -> { + assertEquals("new", si.getShortLabel()); + }); + }); + } + + /** + * The dynamic shortcut existed, but before accepting(), it's removed. Because the request + * has a partial shortcut, accept() should fail. + */ + public void testRequestPinShortcut_dynamicExists_thenDisabled_error() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + ShortcutInfo s1 = makeShortcut("s1"); + assertTrue(mManager.setDynamicShortcuts(list(s1))); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + }); + + // Then, pin by another launcher and disable it. + // We have to pin it here so that disable() won't remove it. + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_2, USER_0)); + runWithCaller(LAUNCHER_2, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + mManager.disableShortcuts(list("s1")); + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDisabled(); + }); + + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("s1") + .areAllDynamic() + .areAllNotPinned() + .areAllWithNoIntent(); + + // Accept the request -> should fail. + assertForLauncherCallbackNoThrow(mLauncherApps, + () -> assertFalse(request.accept())) + .assertNoCallbackCalled(); + + // Note s1 is floating and pinned by another launcher, so it shouldn't be + // visible here. + assertWith(getShortcutAsLauncher(USER_P0)) + .isEmpty(); + }); + + // Intent shouldn't be sent. + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1") + .areAllDisabled(); + }); + } + + /** + * The manifest shortcut existed, but before accepting(), it's removed. Because the request + * has a partial shortcut, accept() should fail. + */ + public void testRequestPinShortcut_manifestExists_thenDisabled_error() { + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_1); + + assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"), + /* resultIntent=*/ null)); + + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + }); + + // Then, pin by another launcher and disable it. + // We have to pin it here so that disable() won't remove it. + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_2, USER_0)); + runWithCaller(LAUNCHER_2, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + publishManifestShortcutsAsCaller(R.xml.shortcut_0); + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllDisabled(); + }); + + setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0)); + runWithCaller(LAUNCHER_1, USER_0, () -> { + // Check the intent passed to startActivityAsUser(). + final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); + + verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); + + assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage); + + // Check the request object. + final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); + + assertPinItemRequest(request); + + assertWith(request.getShortcutInfo()) + .haveIds("ms1") + .areAllManifest() + .areAllNotPinned() + .areAllWithNoIntent(); + + // Accept the request -> should fail. + assertForLauncherCallbackNoThrow(mLauncherApps, + () -> assertFalse(request.accept())) + .assertNoCallbackCalled(); + + // Note ms1 is floating and pinned by another launcher, so it shouldn't be + // visible here. + assertWith(getShortcutAsLauncher(USER_P0)) + .isEmpty(); + }); + + // Intent shouldn't be sent. + verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class)); + + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1") + .areAllDisabled(); + }); + } + + // TODO More tests: // Cancel previous pending request and release memory? + + // Check the launcher callback too. + + // Missing fields -- pre and post, both. } diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java index 8ecea7175ae1..a664f21bc0dc 100644 --- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java +++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java @@ -934,6 +934,16 @@ public class ShortcutManagerTestUtils { return this; } + public ShortcutListAsserter areAllWithIntent() { + forAllShortcuts(s -> assertNotNull("id=" + s.getId(), s.getIntent())); + return this; + } + + public ShortcutListAsserter areAllWithNoIntent() { + forAllShortcuts(s -> assertNull("id=" + s.getId(), s.getIntent())); + return this; + } + public ShortcutListAsserter forAllShortcuts(Consumer<ShortcutInfo> sa) { boolean found = false; for (int i = 0; i < mList.size(); i++) { @@ -1092,6 +1102,16 @@ public class ShortcutManagerTestUtils { return asserter; } + public static LauncherCallbackAsserter assertForLauncherCallbackNoThrow( + LauncherApps launcherApps, Runnable body) { + try { + return assertForLauncherCallback(launcherApps, body); + } catch (InterruptedException e) { + fail("Caught InterruptedException"); + return null; // Never happens. + } + } + public static void retryUntil(BooleanSupplier checker, String message) { retryUntil(checker, message, 30); } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 939a3b8838b6..8f2a44ccd401 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1154,6 +1154,17 @@ public class CarrierConfigManager { public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool"; + /** + * Determine whether user edited tether APN (type dun) has effect + * {@code false} - Default. APN with dun type in telephony database has no effect. + * + * {@code true} - DUN APN added/edited in ApnEditor will be used for tethering data call. + * + * @hide + */ + public static final String KEY_EDITABLE_TETHER_APN_BOOL = + "editable_tether_apn_bool"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -1358,6 +1369,7 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_CARRIER_WIFI_STRING_ARRAY, null); sDefaults.putInt(KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT, -1); sDefaults.putBoolean(KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true); + sDefaults.putBoolean(KEY_EDITABLE_TETHER_APN_BOOL, false); } /** diff --git a/tools/incident_report/Android.mk b/tools/incident_report/Android.mk new file mode 100644 index 000000000000..ed89bd6251e3 --- /dev/null +++ b/tools/incident_report/Android.mk @@ -0,0 +1,39 @@ +# +# Copyright (C) 2015 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +LOCAL_PATH:= $(call my-dir) + +# ========================================================== +# Build the host executable: protoc-gen-javastream +# ========================================================== +include $(CLEAR_VARS) + +LOCAL_MODULE := incident_report + +LOCAL_C_INCLUDES := \ + external/protobuf/src + +LOCAL_SRC_FILES := \ + generic_message.cpp \ + main.cpp \ + printer.cpp + +LOCAL_SHARED_LIBRARIES := \ + libplatformprotos \ + libprotobuf-cpp-full + +include $(BUILD_HOST_EXECUTABLE) + + diff --git a/tools/incident_report/formatter.cpp b/tools/incident_report/formatter.cpp new file mode 100644 index 000000000000..944348f30022 --- /dev/null +++ b/tools/incident_report/formatter.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "proto_format.h" + +#include <string.h> + +extern int const PROTO_FORMAT_STRING_POOL_SIZE; +extern int const PROTO_FORMAT_ENUM_LABELS_LENGTH; +extern int const PROTO_FORMAT_MESSAGES_LENGTH; +extern int const PROTO_FORMAT_FIELDS_LENGTH; + +extern char const PROTO_FORMAT_STRING_POOL[]; +extern ProtoFieldFormat const PROTO_FORMAT_FIELDS[]; +extern ProtoEnumLabel const PROTO_FORMAT_ENUM_LABELS[]; +extern ProtoMessageFormat const PROTO_FORMAT_MESSAGES[]; + +static const char* +get_string(int index) +{ + if (index >= 0 && index < PROTO_FORMAT_STRING_POOL_SIZE) { + return PROTO_FORMAT_STRING_POOL + index; + } else { + // These indices all come from within the generated table, so just crash now. + *(int*)NULL = 42; + return NULL; + } +} + +static ProtoMessageFormat const* +get_message(int index) +{ + if (index >= 0 && index < PROTO_FORMAT_MESSAGES_LENGTH) { + return PROTO_FORMAT_MESSAGES + index; + } else { + // These indices all come from within the generated table, so just crash now. + *(int*)NULL = 42; + return NULL; + } +} + +static int +compare_name(const char* full, const char* package, const char* clazz) +{ + int const packageLen = strlen(package); + int cmp = strncmp(full, package, packageLen); + if (cmp == 0) { + cmp = full[packageLen] - '.'; + if (cmp == 0) { + return strcmp(full + packageLen, clazz); + } + } + return cmp; +} + +int +find_message_index(const char* name) +{ + size_t low = 0; + size_t high = PROTO_FORMAT_FIELDS_LENGTH - 1; + + while (low <= high) { + size_t mid = (low + high) >> 1; + ProtoMessageFormat const* msg = get_message(mid); + + int cmp = compare_name(name, get_string(msg->package_name), get_string(msg->package_name)); + if (cmp < 0) { + low = mid + 1; + } else if (cmp > 0) { + high = mid - 1; + } else { + return mid; + } + } + return -1; +} diff --git a/tools/incident_report/generic_message.cpp b/tools/incident_report/generic_message.cpp new file mode 100644 index 000000000000..84d9d7cfe7c2 --- /dev/null +++ b/tools/incident_report/generic_message.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "generic_message.h" + +GenericMessage::GenericMessage() +{ +} + +GenericMessage::~GenericMessage() +{ +} + +void +GenericMessage::addInt32(int32_t fieldId, uint32_t value) +{ + Node node; + node.type = TYPE_VALUE32; + node.value32 = value; + mNodes.insert(pair<int32_t,Node>(fieldId, node)); +} + +void +GenericMessage::addInt64(int32_t fieldId, uint64_t value) +{ + Node node; + node.type = TYPE_VALUE64; + node.value64 = value; + mNodes.insert(pair<int32_t,Node>(fieldId, node)); +} + +GenericMessage* +GenericMessage::addMessage(int32_t fieldId) +{ + GenericMessage* result = new GenericMessage(); + Node node; + node.type = TYPE_MESSAGE; + node.message = result; + mNodes.insert(pair<int32_t,Node>(fieldId, node)); + return result; +} + +void +GenericMessage::addString(int32_t fieldId, const string& value) +{ + Node node; + node.type = TYPE_STRING; + node.str = new string(value); + mNodes.insert(pair<int32_t,Node>(fieldId, node)); +} + +GenericMessage::const_iterator_pair +GenericMessage::find(int fieldId) const +{ + return mNodes.equal_range(fieldId); +} + diff --git a/tools/incident_report/generic_message.h b/tools/incident_report/generic_message.h new file mode 100644 index 000000000000..df3f7b22dfc7 --- /dev/null +++ b/tools/incident_report/generic_message.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GENERIC_MESSAGE_H +#define GENERIC_MESSAGE_H + +#include <map> +#include <string> + +using namespace std; + +/** + * Class to represent a protobuf Message, where we don't actually + * know what any of the fields are, just their type codes. In other + * words, this loslessly stores a parsed protobuf object without + * having the .proto file that generated it. + */ +class GenericMessage +{ +public: + GenericMessage(); + ~GenericMessage(); + + enum { + TYPE_VALUE32, + TYPE_VALUE64, + TYPE_MESSAGE, + TYPE_STRING, + TYPE_DATA + }; + + struct Node { + uint32_t type; + union { + uint32_t value32; + uint64_t value64; + GenericMessage* message; + string* str; + string* data; + }; + }; + + void addInt32(int32_t fieldId, uint32_t value); + void addInt64(int32_t fieldId, uint64_t value); + GenericMessage* addMessage(int32_t fieldId); + void addString(int32_t fieldId, const string& value); + + typedef multimap<int32_t,Node>::const_iterator const_iterator; + typedef pair<const_iterator,const_iterator> const_iterator_pair; + + const_iterator_pair find(int fieldId) const; + +private: + multimap<int,Node> mNodes; +}; + +#endif // GENERIC_MESSAGE_H + diff --git a/tools/incident_report/main.cpp b/tools/incident_report/main.cpp new file mode 100644 index 000000000000..a814847e41f7 --- /dev/null +++ b/tools/incident_report/main.cpp @@ -0,0 +1,576 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "generic_message.h" +#include "printer.h" + +#include <frameworks/base/core/proto/android/os/incident_proto.pb.h> +#include <google/protobuf/wire_format.h> +#include <google/protobuf/io/coded_stream.h> +#include <google/protobuf/io/zero_copy_stream_impl.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +using namespace android::os; +using namespace google::protobuf; +using namespace google::protobuf::io; +using namespace google::protobuf::internal; + +static bool read_message(CodedInputStream* in, Descriptor const* descriptor, + GenericMessage* message); +static void print_message(Out* out, Descriptor const* descriptor, GenericMessage const* message); + +// ================================================================================ +static bool +read_length_delimited(CodedInputStream* in, uint32 fieldId, Descriptor const* descriptor, + GenericMessage* message) +{ + uint32 size; + if (!in->ReadVarint32(&size)) { + return false; + } + + FieldDescriptor const* field = descriptor->FindFieldByNumber(fieldId); + if (field != NULL) { + int type = field->type(); + if (type == FieldDescriptor::TYPE_MESSAGE) { + GenericMessage* child = message->addMessage(fieldId); + + CodedInputStream::Limit limit = in->PushLimit(size); + bool rv = read_message(in, field->message_type(), child); + in->PopLimit(limit); + return rv; + } else if (type == FieldDescriptor::TYPE_STRING) { + // TODO: do a version of readstring that just pumps the data + // rather than allocating a string which we don't care about. + string str; + if (in->ReadString(&str, size)) { + message->addString(fieldId, str); + return true; + } else { + return false; + } + } else if (type == FieldDescriptor::TYPE_BYTES) { + // TODO: Save bytes field. + return in->Skip(size); + } + } + return in->Skip(size); +} + +// ================================================================================ +static bool +read_message(CodedInputStream* in, Descriptor const* descriptor, GenericMessage* message) +{ + uint32 value32; + uint64 value64; + + while (true) { + uint32 tag = in->ReadTag(); + if (tag == 0) { + return true; + } + int fieldId = WireFormatLite::GetTagFieldNumber(tag); + switch (WireFormatLite::GetTagWireType(tag)) { + case WireFormatLite::WIRETYPE_VARINT: + if (in->ReadVarint64(&value64)) { + message->addInt64(fieldId, value64); + break; + } else { + return false; + } + case WireFormatLite::WIRETYPE_FIXED64: + if (in->ReadLittleEndian64(&value64)) { + message->addInt64(fieldId, value64); + break; + } else { + return false; + } + case WireFormatLite::WIRETYPE_LENGTH_DELIMITED: + if (!read_length_delimited(in, fieldId, descriptor, message)) { + return false; + } + break; + case WireFormatLite::WIRETYPE_FIXED32: + if (in->ReadLittleEndian32(&value32)) { + message->addInt32(fieldId, value32); + break; + } else { + return false; + } + default: + fprintf(stderr, "bad tag: 0x%x (%d) at index %d\n", tag, tag, + in->CurrentPosition()); + return false; + } + } +} + +// ================================================================================ +static void +print_value(Out* out, FieldDescriptor const* field, GenericMessage::Node const& node) +{ + uint32_t val32; + FieldDescriptor::Type type = field->type(); + + switch (node.type) { + case GenericMessage::TYPE_VALUE32: + switch (type) { + case FieldDescriptor::TYPE_FIXED32: + out->printf("%u", node.value32); + break; + case FieldDescriptor::TYPE_SFIXED32: + out->printf("%d", node.value32); + break; + case FieldDescriptor::TYPE_FLOAT: + out->printf("%f", *(float*)&node.value32); + break; + default: + out->printf("(unexpected value %d (0x%x)", node.value32, node.value32); + break; + } + break; + case GenericMessage::TYPE_VALUE64: + switch (type) { + case FieldDescriptor::TYPE_FIXED64: + case FieldDescriptor::TYPE_SFIXED64: + case FieldDescriptor::TYPE_DOUBLE: + out->printf("%f", *(double*)&node.value64); + break; + case FieldDescriptor::TYPE_SINT32: + case FieldDescriptor::TYPE_INT32: + val32 = (uint32_t)node.value32; + out->printf("%d", val32); + break; + case FieldDescriptor::TYPE_INT64: + case FieldDescriptor::TYPE_UINT32: + val32 = (uint32_t)node.value32; + out->printf("%u", val32); + break; + case FieldDescriptor::TYPE_UINT64: + case FieldDescriptor::TYPE_SINT64: + case FieldDescriptor::TYPE_BOOL: + if (node.value64) { + out->printf("true"); + } else { + out->printf("false"); + } + break; + case FieldDescriptor::TYPE_ENUM: + default: + out->printf("(unexpected value %ld (0x%x))", node.value64, node.value64); + break; + } + break; + case GenericMessage::TYPE_MESSAGE: + print_message(out, field->message_type(), node.message); + break; + case GenericMessage::TYPE_STRING: + // TODO: custom format for multi-line strings. + out->printf("%s", node.str->c_str()); + break; + case GenericMessage::TYPE_DATA: + out->printf("<bytes>"); + break; + } +} + +static void +print_message(Out* out, Descriptor const* descriptor, GenericMessage const* message) +{ + out->printf("%s {\n", descriptor->name().c_str()); + out->indent(); + + int const N = descriptor->field_count(); + for (int i=0; i<N; i++) { + FieldDescriptor const* field = descriptor->field(i); + + int fieldId = field->number(); + bool repeated = field->label() == FieldDescriptor::LABEL_REPEATED; + FieldDescriptor::Type type = field->type(); + GenericMessage::const_iterator_pair it = message->find(fieldId); + + out->printf("%s=", field->name().c_str()); + if (repeated) { + if (it.first != it.second) { + out->printf("["); + if (type == FieldDescriptor::TYPE_MESSAGE + || type == FieldDescriptor::TYPE_STRING + || type == FieldDescriptor::TYPE_BYTES) { + out->printf("\n"); + } + out->indent(); + + for (GenericMessage::const_iterator_pair it = message->find(fieldId); + it.first != it.second; it.first++) { + print_value(out, field, it.first->second); + if (type == FieldDescriptor::TYPE_MESSAGE + || type == FieldDescriptor::TYPE_STRING + || type == FieldDescriptor::TYPE_BYTES) { + out->printf("\n"); + } + } + + out->dedent(); + out->printf("]"); + } else { + out->printf("[]"); + } + } else { + if (it.first != it.second) { + print_value(out, field, it.first->second); + } else { + switch (type) { + case FieldDescriptor::TYPE_BOOL: + out->printf("false"); + break; + case FieldDescriptor::TYPE_STRING: + case FieldDescriptor::TYPE_MESSAGE: + out->printf(""); + break; + case FieldDescriptor::TYPE_ENUM: + out->printf("%s", field->default_value_enum()->name().c_str()); + break; + default: + out->printf("0"); + break; + } + } + } + out->printf("\n"); + } + out->dedent(); + out->printf("}"); +} + +// ================================================================================ +static uint8_t* +write_raw_varint(uint8_t* buf, uint32_t val) +{ + uint8_t* p = buf; + while (true) { + if ((val & ~0x7F) == 0) { + *p++ = (uint8_t)val; + return p; + } else { + *p++ = (uint8_t)((val & 0x7F) | 0x80); + val >>= 7; + } + } +} + +static int +write_all(int fd, uint8_t const* buf, size_t size) +{ + while (size > 0) { + ssize_t amt = ::write(fd, buf, size); + if (amt < 0) { + return errno; + } + size -= amt; + buf += amt; + } + return 0; +} + +static int +adb_incident_workaround(const char* adbSerial, const vector<string>& sections) +{ + const int maxAllowedSize = 20 * 1024 * 1024; // 20MB + uint8_t* buffer = (uint8_t*)malloc(maxAllowedSize); + + for (vector<string>::const_iterator it=sections.begin(); it!=sections.end(); it++) { + Descriptor const* descriptor = IncidentProto::descriptor(); + FieldDescriptor const* field; + + // Get the name and field id. + string name = *it; + char* end; + int id = strtol(name.c_str(), &end, 0); + if (*end == '\0') { + // If it's an id, find out the string. + field = descriptor->FindFieldByNumber(id); + if (field == NULL) { + fprintf(stderr, "Unable to find field number: %d\n", id); + return 1; + } + name = field->name(); + } else { + // If it's a string, find out the id. + field = descriptor->FindFieldByName(name); + if (field == NULL) { + fprintf(stderr, "Unable to find field: %s\n", name.c_str()); + return 1; + } + id = field->number(); + } + + int pfd[2]; + if (pipe(pfd) != 0) { + fprintf(stderr, "pipe failed: %s\n", strerror(errno)); + return 1; + } + + pid_t pid = fork(); + if (pid == -1) { + fprintf(stderr, "fork failed: %s\n", strerror(errno)); + return 1; + } else if (pid == 0) { + // child + dup2(pfd[1], STDOUT_FILENO); + close(pfd[0]); + close(pfd[1]); + + char const** args = (char const**)malloc(sizeof(char*) * 8); + int argpos = 0; + args[argpos++] = "adb"; + if (adbSerial != NULL) { + args[argpos++] = "-s"; + args[argpos++] = adbSerial; + } + args[argpos++] = "shell"; + args[argpos++] = "dumpsys"; + args[argpos++] = name.c_str(); + args[argpos++] = "--proto"; + args[argpos++] = NULL; + execvp(args[0], (char*const*)args); + fprintf(stderr, "execvp failed: %s\n", strerror(errno)); + return 1; + } else { + // parent + close(pfd[1]); + + size_t size = 0; + while (size < maxAllowedSize) { + ssize_t amt = read(pfd[0], buffer + size, maxAllowedSize - size); + if (amt == 0) { + break; + } else if (amt == -1) { + fprintf(stderr, "read error: %s\n", strerror(errno)); + return 1; + } + size += amt; + } + + int status; + do { + waitpid(pid, &status, 0); + } while (!WIFEXITED(status)); + if (WEXITSTATUS(status) != 0) { + return WEXITSTATUS(status); + } + + if (size > 0) { + uint8_t header[20]; + uint8_t* p = write_raw_varint(header, (id << 3) | 2); + p = write_raw_varint(p, size); + int err = write_all(STDOUT_FILENO, header, p-header); + if (err != 0) { + fprintf(stderr, "write error: %s\n", strerror(err)); + return 1; + } + err = write_all(STDOUT_FILENO, buffer, size); + if (err != 0) { + fprintf(stderr, "write error: %s\n", strerror(err)); + return 1; + } + } + + close(pfd[0]); + } + } + + return 0; +} + +// ================================================================================ +static void +usage(FILE* out) +{ + fprintf(out, "usage: incident_report -i INPUT [-o OUTPUT]\n"); + fprintf(out, "\n"); + fprintf(out, "Pretty-prints an incident report protobuf file.\n"); + fprintf(out, " -i INPUT the input file. INPUT may be '-' to use stdin\n"); + fprintf(out, " -o OUTPUT the output file. OUTPUT may be '-' or omitted to use stdout\n"); + fprintf(out, "\n"); + fprintf(out, "\n"); + fprintf(out, "usage: incident_report [-o OUTPUT] [-t|b] [-s SERIAL] [SECTION...]\n"); + fprintf(out, "\n"); + fprintf(out, "Take an incident report over adb (which must be in the PATH).\n"); + fprintf(out, " -b output the incident report raw protobuf format\n"); + fprintf(out, " -o OUTPUT the output file. OUTPUT may be '-' or omitted to use stdout\n"); + fprintf(out, " -s SERIAL sent to adb to choose which device, instead of $ANDROID_SERIAL\n"); + fprintf(out, " -t output the incident report in pretty-printed text format\n"); + fprintf(out, "\n"); + fprintf(out, " SECTION which bugreport sections to print, either the int code of the\n"); + fprintf(out, " section in the Incident proto or the field name. If ommited,\n"); + fprintf(out, " the report will contain all fields\n"); + fprintf(out, "\n"); +} + +int +main(int argc, char** argv) +{ + enum { OUTPUT_TEXT, OUTPUT_PROTO } outputFormat = OUTPUT_TEXT; + const char* inFilename = NULL; + const char* outFilename = NULL; + const char* adbSerial = NULL; + bool adbIncidentWorkaround = true; + pid_t childPid = -1; + vector<string> sections; + + int opt; + while ((opt = getopt(argc, argv, "bhi:o:s:tw")) != -1) { + switch (opt) { + case 'b': + outputFormat = OUTPUT_PROTO; + break; + case 'i': + inFilename = optarg; + break; + case 'o': + outFilename = optarg; + break; + case 's': + adbSerial = optarg; + break; + case 't': + outputFormat = OUTPUT_TEXT; + break; + case 'h': + usage(stdout); + return 0; + case 'w': + adbIncidentWorkaround = false; + break; + default: + usage(stderr); + return 1; + } + } + + while (optind < argc) { + sections.push_back(argv[optind++]); + } + + int inFd; + if (inFilename != NULL) { + // translate-only mode - oepn the file or use stdin. + if (strcmp("-", inFilename) == 0) { + inFd = STDIN_FILENO; + } else { + inFd = open(inFilename, O_RDONLY | O_CLOEXEC); + if (inFd < 0) { + fprintf(stderr, "unable to open file for read (%s): %s\n", strerror(errno), + inFilename); + return 1; + } + } + } else { + // pipe mode - run adb shell incident ... + int pfd[2]; + if (pipe(pfd) != 0) { + fprintf(stderr, "pipe failed: %s\n", strerror(errno)); + return 1; + } + + childPid = fork(); + if (childPid == -1) { + fprintf(stderr, "fork failed: %s\n", strerror(errno)); + return 1; + } else if (childPid == 0) { + dup2(pfd[1], STDOUT_FILENO); + close(pfd[0]); + close(pfd[1]); + // child + if (adbIncidentWorkaround) { + // TODO: Until the device side incident command is checked in, + // the incident_report builds the outer Incident proto by hand + // from individual adb shell dumpsys <service> --proto calls, + // with a maximum allowed output size. + return adb_incident_workaround(adbSerial, sections); + } + + // TODO: This is what the real implementation will be... + char const** args = (char const**)malloc(sizeof(char*) * (6 + sections.size())); + int argpos = 0; + args[argpos++] = "adb"; + if (adbSerial != NULL) { + args[argpos++] = "-s"; + args[argpos++] = adbSerial; + } + args[argpos++] = "shell"; + args[argpos++] = "incident"; + for (vector<string>::const_iterator it=sections.begin(); it!=sections.end(); it++) { + args[argpos++] = it->c_str(); + } + args[argpos++] = NULL; + execvp(args[0], (char*const*)args); + fprintf(stderr, "execvp failed: %s\n", strerror(errno)); + return 0; + } else { + // parent + inFd = pfd[0]; + close(pfd[1]); + } + } + + int outFd; + if (outFilename == NULL || strcmp("-", outFilename) == 0) { + outFd = STDOUT_FILENO; + } else { + outFd = open(outFilename, O_CREAT | O_RDWR, 0666); + if (outFd < 0) { + fprintf(stderr, "unable to open file for write: %s\n", outFilename); + return 1; + } + } + + GenericMessage message; + + Descriptor const* descriptor = IncidentProto::descriptor(); + FileInputStream infile(inFd); + CodedInputStream in(&infile); + + if (!read_message(&in, descriptor, &message)) { + fprintf(stderr, "unable to read incident\n"); + return 1; + } + + Out out(outFd); + + print_message(&out, descriptor, &message); + out.printf("\n"); + + if (childPid != -1) { + int status; + do { + waitpid(childPid, &status, 0); + } while (!WIFEXITED(status)); + if (WEXITSTATUS(status) != 0) { + return WEXITSTATUS(status); + } + } + + return 0; +} diff --git a/tools/incident_report/printer.cpp b/tools/incident_report/printer.cpp new file mode 100644 index 000000000000..8111b2786007 --- /dev/null +++ b/tools/incident_report/printer.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "printer.h" + +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + +#define INITIAL_BUF_SIZE (16*1024) + +char const* SPACES = " "; +const int SPACE_COUNT = strlen(SPACES); + +Out::Out(int fd) + :mOut(fd == STDOUT_FILENO ? stdout : fdopen(fd, "w")), + mBufSize(INITIAL_BUF_SIZE), + mBuf((char*)malloc(INITIAL_BUF_SIZE)), + mIndent(0), + mPendingIndent(false) +{ +} + +Out::~Out() +{ + fclose(mOut); +} + +int +Out::reallocate(int size) +{ + if (size > mBufSize) { + char* p = (char*)malloc(size); + if (p != NULL) { + free(mBuf); + mBufSize = size; + mBuf = p; + return size; + } + } + return mBufSize; +} + +void +Out::printf(const char* format, ...) +{ + if (mPendingIndent) { + print_indent(); + mPendingIndent = false; + } + + int len; + + va_list args; + va_start(args, format); + + len = vsnprintf(mBuf, mBufSize, format, args); + bool truncated = (len >= mBufSize) && (reallocate(len) < len); + + len = vsnprintf(mBuf, mBufSize, format, args); + va_end(args); + + if (len > 0) { + if (mIndent == 0) { + fwrite(mBuf, len, 1, mOut); + } else { + char* last = mBuf; + char* p; + do { + p = strchr(last, '\n'); + int size = p != NULL ? p - last + 1 : strlen(last); + fwrite(last, size, 1, mOut); + if (p != NULL) { + if (p[1] == '\0') { + mPendingIndent = true; + } else { + print_indent(); + } + } + last = p+1; + } while (p != NULL); + } + } +} + +void +Out::indent() +{ + mPendingIndent = true; + mIndent += 2; +} + +void +Out::dedent() +{ + if (mIndent > 0) { + mIndent -= 2; + } +} + +void +Out::print_indent() +{ +#if 0 + fprintf(mOut, "[%d]", mIndent); +#else + int indent = mIndent; + while (indent > SPACE_COUNT) { + fwrite(SPACES, SPACE_COUNT, 1, mOut); + indent -= SPACE_COUNT; + } + fwrite(SPACES + SPACE_COUNT - indent, indent, 1, mOut); +#endif +} diff --git a/tools/incident_report/printer.h b/tools/incident_report/printer.h new file mode 100644 index 000000000000..ed93fa19542c --- /dev/null +++ b/tools/incident_report/printer.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PRINTER_H +#define PRINTER_H + +#include <stdio.h> + +class Out +{ +public: + Out(int fd); + ~Out(); + + void printf(const char* format, ...); + + void indent(); + void dedent(); + +private: + FILE* mOut; + int mBufSize; + char* mBuf; + int mIndent; + bool mPendingIndent; + + int reallocate(int size); + void print_indent(); +}; + +#endif // PRINTER_H diff --git a/tools/incident_section_gen/Android.mk b/tools/incident_section_gen/Android.mk new file mode 100644 index 000000000000..acf3f8327b5c --- /dev/null +++ b/tools/incident_section_gen/Android.mk @@ -0,0 +1,35 @@ +# +# Copyright (C) 2015 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +LOCAL_PATH:= $(call my-dir) + +# ========================================================== +# Build the host executable: protoc-gen-javastream +# ========================================================== +include $(CLEAR_VARS) + +LOCAL_MODULE := incident-section-gen +LOCAL_CFLAGS += -g -O0 +LOCAL_C_INCLUDES := \ + external/protobuf/src +LOCAL_SRC_FILES := \ + main.cpp +LOCAL_LDFLAGS := -ldl +LOCAL_SHARED_LIBRARIES := \ + libplatformprotos \ + libprotobuf-cpp-full + +include $(BUILD_HOST_EXECUTABLE) + diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp new file mode 100644 index 000000000000..d0048105d9fe --- /dev/null +++ b/tools/incident_section_gen/main.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <frameworks/base/core/proto/android/os/incident_proto.pb.h> + + +#include <map> + +using namespace android::os; +using namespace google::protobuf; +using namespace google::protobuf::io; +using namespace google::protobuf::internal; +using namespace std; + +int +main(int, const char**) +{ + map<string,FieldDescriptor const*> sections; + int N; + + printf("// Auto generated file. Do not modify\n"); + printf("\n"); + printf("#include \"incident_sections.h\"\n"); + printf("\n"); + + Descriptor const* descriptor = IncidentProto::descriptor(); + N = descriptor->field_count(); + for (int i=0; i<N; i++) { + const FieldDescriptor* field = descriptor->field(i); + if (field->type() == FieldDescriptor::TYPE_MESSAGE) { + sections[field->name()] = field; + } + } + + printf("IncidentSection const INCIDENT_SECTIONS[] = {\n"); + N = sections.size(); + int i = 0; + for (map<string,FieldDescriptor const*>::const_iterator it = sections.begin(); + it != sections.end(); it++, i++) { + const FieldDescriptor* field = it->second; + printf(" { %d, \"%s\" }", field->number(), field->name().c_str()); + if (i != N-1) { + printf(",\n"); + } else { + printf("\n"); + } + } + printf("};\n"); + + printf("const int INCIDENT_SECTION_COUNT = %d;\n", N); + + return 0; +} diff --git a/tools/layoutlib/.idea/modules.xml b/tools/layoutlib/.idea/modules.xml index 9bdc3815da29..6ffc1cc12b88 100644 --- a/tools/layoutlib/.idea/modules.xml +++ b/tools/layoutlib/.idea/modules.xml @@ -4,6 +4,7 @@ <modules> <module fileurl="file://$PROJECT_DIR$/bridge/bridge.iml" filepath="$PROJECT_DIR$/bridge/bridge.iml" /> <module fileurl="file://$PROJECT_DIR$/create/create.iml" filepath="$PROJECT_DIR$/create/create.iml" /> + <module fileurl="file://$PROJECT_DIR$/legacy/legacy.iml" filepath="$PROJECT_DIR$/legacy/legacy.iml" /> <module fileurl="file://$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" filepath="$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" /> </modules> </component> diff --git a/tools/layoutlib/legacy/Android.mk b/tools/layoutlib/legacy/Android.mk new file mode 100644 index 000000000000..5855f894c111 --- /dev/null +++ b/tools/layoutlib/legacy/Android.mk @@ -0,0 +1,30 @@ +# +# Copyright (C) 2008 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under,src) + +LOCAL_JAVA_LIBRARIES := \ + layoutlib_api-prebuilt + +LOCAL_MODULE := layoutlib-legacy + +include $(BUILD_HOST_JAVA_LIBRARY) + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) + diff --git a/tools/layoutlib/legacy/legacy.iml b/tools/layoutlib/legacy/legacy.iml new file mode 100644 index 000000000000..a167a7585eda --- /dev/null +++ b/tools/layoutlib/legacy/legacy.iml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="library" name="layoutlib_api-prebuilt" level="project" /> + </component> +</module>
\ No newline at end of file diff --git a/tools/layoutlib/legacy/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/legacy/src/com/android/layoutlib/bridge/Bridge.java new file mode 100644 index 000000000000..0cfc181116c7 --- /dev/null +++ b/tools/layoutlib/legacy/src/com/android/layoutlib/bridge/Bridge.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge;import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.Result.Status; +import com.android.ide.common.rendering.api.SessionParams; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; + +/** + * Legacy Bridge used in the SDK version of layoutlib + */ +public final class Bridge extends com.android.ide.common.rendering.api.Bridge { + private static final String SDK_NOT_SUPPORTED = "The SDK layoutlib version is not supported"; + private static final Result NOT_SUPPORTED_RESULT = + Status.NOT_IMPLEMENTED.createResult(SDK_NOT_SUPPORTED); + private static BufferedImage sImage; + + private static class BridgeRenderSession extends RenderSession { + + @Override + public synchronized BufferedImage getImage() { + if (sImage == null) { + sImage = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = sImage.createGraphics(); + g.clearRect(0, 0, 500, 500); + g.drawString(SDK_NOT_SUPPORTED, 20, 20); + g.dispose(); + } + + return sImage; + } + + @Override + public Result render(long timeout, boolean forceMeasure) { + return NOT_SUPPORTED_RESULT; + } + + @Override + public Result measure(long timeout) { + return NOT_SUPPORTED_RESULT; + } + + @Override + public Result getResult() { + return NOT_SUPPORTED_RESULT; + } + } + + + @Override + public RenderSession createSession(SessionParams params) { + return new BridgeRenderSession(); + } + + @Override + public int getApiLevel() { + return 0; + } +} diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java index 21b7a04e07dc..84ec8b7d0fdd 100644 --- a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java +++ b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java @@ -171,6 +171,9 @@ public class Hprof implements ClassDataRetriever { arg1.getDevice().getSyncService().pullFile(arg0, target.getAbsoluteFile().toString(), new NullProgressMonitor()); } catch (Exception e) { + if (target != null) { + target.delete(); + } e.printStackTrace(); target = null; } @@ -189,6 +192,9 @@ public class Hprof implements ClassDataRetriever { out.write(arg0); out.close(); } catch (Exception e) { + if (target != null) { + target.delete(); + } e.printStackTrace(); target = null; } @@ -215,6 +221,8 @@ public class Hprof implements ClassDataRetriever { return analyzeHprof(hprofLocalFile); } catch (Exception e) { throw new RuntimeException(e); + } finally { + hprofLocalFile.delete(); } } } |