diff options
22 files changed, 884 insertions, 73 deletions
diff --git a/Android.bp b/Android.bp index dd12b0a39ab7..0fb90ce289f6 100644 --- a/Android.bp +++ b/Android.bp @@ -176,6 +176,7 @@ java_defaults { jarjar_rules: ":framework-jarjar-rules", static_libs: [ + "mimemap", "apex_aidl_interface-java", "suspend_control_aidl_interface-java", "framework-protos", @@ -244,6 +245,7 @@ java_library { required: [ "framework-platform-compat-config", "libcore-platform-compat-config", + "services-platform-compat-config", ], } diff --git a/api/current.txt b/api/current.txt index 682e0fa40191..0e4530065712 100755 --- a/api/current.txt +++ b/api/current.txt @@ -37415,6 +37415,7 @@ package android.provider { } public static final class Telephony.Mms.Addr implements android.provider.BaseColumns { + method @NonNull public static android.net.Uri getAddrUriForMessage(@NonNull String); field public static final String ADDRESS = "address"; field public static final String CHARSET = "charset"; field public static final String CONTACT_ID = "contact_id"; @@ -37443,6 +37444,7 @@ package android.provider { } public static final class Telephony.Mms.Part implements android.provider.BaseColumns { + method @NonNull public static android.net.Uri getPartUriForMessage(@NonNull String); field public static final String CHARSET = "chset"; field public static final String CONTENT_DISPOSITION = "cd"; field public static final String CONTENT_ID = "cid"; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 3c5cfbe0a743..7baca9bc2ffd 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -37,7 +37,6 @@ import android.app.ActivityManager; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.VrManager; -import android.compat.IPlatformCompat; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; @@ -72,6 +71,8 @@ import android.view.WindowManager; import android.view.autofill.AutofillManager.AutofillClient; import android.view.textclassifier.TextClassificationManager; +import com.android.internal.compat.IPlatformCompat; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java index c2b7d2c71b32..52d485de5a8b 100644 --- a/core/java/android/net/MacAddress.java +++ b/core/java/android/net/MacAddress.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; +import android.net.wifi.WifiInfo; import android.os.Parcel; import android.os.Parcelable; @@ -364,7 +365,12 @@ public final class MacAddress implements Parcelable { long addr = r.nextLong() & VALID_LONG_MASK; addr |= LOCALLY_ASSIGNED_MASK; addr &= ~MULTICAST_MASK; - return new MacAddress(addr); + MacAddress mac = new MacAddress(addr); + // WifiInfo.DEFAULT_MAC_ADDRESS is being used for another purpose, so do not use it here. + if (mac.toString().equals(WifiInfo.DEFAULT_MAC_ADDRESS)) { + return createRandomUnicastAddress(); + } + return mac; } /** @@ -383,7 +389,12 @@ public final class MacAddress implements Parcelable { long addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong()); addr |= LOCALLY_ASSIGNED_MASK; addr &= ~MULTICAST_MASK; - return new MacAddress(addr); + MacAddress mac = new MacAddress(addr); + // WifiInfo.DEFAULT_MAC_ADDRESS is being used for another purpose, so do not use it here. + if (mac.toString().equals(WifiInfo.DEFAULT_MAC_ADDRESS)) { + return createRandomUnicastAddress(base, r); + } + return mac; } // Convenience function for working around the lack of byte literals. diff --git a/core/java/android/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl index 3d8a9d5c5e86..9049c3aea7ec 100644 --- a/core/java/android/compat/IPlatformCompat.aidl +++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.compat; +package com.android.internal.compat; import android.content.pm.ApplicationInfo; diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 274c44446a8c..a47beb5b324b 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -4938,11 +4938,11 @@ public class BatteryStatsImpl extends BatteryStats { final long uptime = mClocks.uptimeMillis(); boolean updateHistory = false; - if (isScreenDoze(state)) { + if (isScreenDoze(state) && !isScreenDoze(oldState)) { mHistoryCur.states |= HistoryItem.STATE_SCREEN_DOZE_FLAG; mScreenDozeTimer.startRunningLocked(elapsedRealtime); updateHistory = true; - } else if (isScreenDoze(oldState)) { + } else if (isScreenDoze(oldState) && !isScreenDoze(state)) { mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_DOZE_FLAG; mScreenDozeTimer.stopRunningLocked(elapsedRealtime); updateHistory = true; diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index 1de2e7272f4d..5b129f4c6e52 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -20,6 +20,7 @@ import android.annotation.UnsupportedAppUsage; import android.app.ActivityManager; import android.app.ActivityThread; import android.app.ApplicationErrorReport; +import android.content.type.MimeMapImpl; import android.os.Build; import android.os.DeadObjectException; import android.os.Debug; @@ -33,6 +34,9 @@ import com.android.internal.logging.AndroidConfig; import com.android.server.NetworkManagementSocketTagger; import dalvik.system.RuntimeHooks; import dalvik.system.VMRuntime; + +import libcore.net.MimeMap; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -210,6 +214,14 @@ public class RuntimeInit { RuntimeHooks.setTimeZoneIdSupplier(() -> SystemProperties.get("persist.sys.timezone")); /* + * Replace libcore's minimal default mapping between MIME types and file + * extensions with a mapping that's suitable for Android. Android's mapping + * contains many more entries that are derived from IANA registrations but + * with several customizations (extensions, overrides). + */ + MimeMap.setDefault(MimeMapImpl.createDefaultInstance()); + + /* * Sets handler for java.util.logging to use Android log facilities. * The odd "new instance-and-then-throw-away" is a mirror of how * the "java.util.logging.config.class" system property works. We diff --git a/mime/Android.bp b/mime/Android.bp new file mode 100644 index 000000000000..9303755ba73d --- /dev/null +++ b/mime/Android.bp @@ -0,0 +1,43 @@ +// Copyright (C) 2019 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. + +java_library { + name: "mimemap", + visibility: [ + "//cts/tests/tests/mimemap:__subpackages__", + "//frameworks/base:__subpackages__", + ], + + srcs: [ + "java/android/content/type/MimeMapImpl.java", + ], + + java_resources: [ + ":debian.mime.types", + ":android.mime.types", + ], + + sdk_version: "core_platform", +} + +filegroup { + name: "android.mime.types", + visibility: [ + "//visibility:private", + ], + path: "java-res/", + srcs: [ + "java-res/android.mime.types", + ], +} diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types new file mode 100644 index 000000000000..1ca912e8510b --- /dev/null +++ b/mime/java-res/android.mime.types @@ -0,0 +1,146 @@ + +############################################################################### +# +# Android-specific MIME type <-> extension mappings +# +# Each line below defines an mapping from one MIME type to the first of the +# listed extensions, and from listed extension back to the MIME type. +# A mapping overrides any previous mapping _from_ that same MIME type or +# extension (put() semantics), unless that MIME type / extension is prefixed with '?' +# (putIfAbsent() semantics). +# +# +############################################################################### +# +# EXAMPLES +# +# A line of the form: +# +# ?mime ext1 ?ext2 ext3 +# +# affects the current mappings along the lines of the following pseudo code: +# +# mimeToExt.putIfAbsent("mime", "ext1"); +# extToMime.put("ext1", "mime"); +# extToMime.putIfAbsent("ext2", "mime"); +# extToMime.put("ext3", "mime"); +# +# The line: +# +# ?text/plain txt +# +# leaves any earlier mapping for "text/plain" untouched, or maps that MIME type +# to the file extension ".txt" if there is no earlier mapping. The line also +# sets the mapping from file extension ".txt" to be the MIME type "text/plain", +# regardless of whether a previous mapping existed. +# +############################################################################### + + +# File extensions that Android wants to override to point to the given MIME type. +# +# After processing a line of the form: +# ?<mimeType> <extension1> <extension2> +# If <mimeType> was not already mapped to an extension then it will be +# mapped to <extension1>. +# <extension1> and <extension2> are mapped (or remapped) to <mimeType>. + +?application/epub+zip epub +?application/pkix-cert cer +?application/rss+xml rss +?application/vnd.android.ota ota +?application/vnd.apple.mpegurl m3u8 +?application/vnd.ms-pki.stl stl +?application/vnd.ms-powerpoint pot +?application/vnd.ms-wpl wpl +?application/vnd.stardivision.impress sdp +?application/vnd.stardivision.writer vor +?application/vnd.youtube.yt yt +?application/x-android-drm-fl fl +?application/x-flac flac +?application/x-font pcf +?application/x-mpegurl m3u m3u8 +?application/x-pem-file pem +?application/x-pkcs12 p12 pfx +?application/x-webarchive webarchive +?application/x-webarchive-xml webarchivexml +?application/x-x509-server-cert crt +?application/x-x509-user-cert crt + +?audio/3gpp 3gpp +?audio/aac-adts aac +?audio/imelody imy +?audio/midi rtttl xmf +?audio/mobile-xmf mxmf +?audio/mp4 m4a +?audio/mpegurl m3u +?audio/sp-midi smf +?audio/x-matroska mka +?audio/x-pn-realaudio ra + +?image/bmp bmp +?image/heic heic +?image/heic-sequence heics +?image/heif heif hif +?image/heif-sequence heifs +?image/ico cur +?image/webp webp +?image/x-adobe-dng dng +?image/x-fuji-raf raf +?image/x-icon ico +?image/x-nikon-nrw nrw +?image/x-panasonic-rw2 rw2 +?image/x-pentax-pef pef +?image/x-samsung-srw srw +?image/x-sony-arw arw + +?text/comma-separated-values csv +?text/plain diff po +?text/rtf rtf +?text/text phps +?text/xml xml +?text/x-vcard vcf + +?video/3gpp2 3gpp2 3g2 +?video/3gpp 3gpp +?video/avi avi +?video/m4v m4v +?video/mp2p mpeg +?video/mp2t m2ts mts +?video/mp2ts ts +?video/vnd.youtube.yt yt +?video/x-webex wrf + +# Optional additions that should not override any previous mapping. + +?application/x-wifi-config ?xml + +# Special cases where Android has a strong opinion about mappings, so we +# define them very last and make them override in both directions (no "?"). +# +# Lines here are of the form: +# <mimeType> <extension1> <extension2> ... +# +# After processing each line, +# <mimeType> is mapped to <extension1> +# <extension1>, <extension2>, ... are all mapped to <mimeType> +# This overrides any mappings for this <mimeType> / for these extensions +# that may have been defined earlier. + +application/pgp-signature pgp +application/x-x509-ca-cert crt +audio/aac aac +audio/basic snd +audio/flac flac +audio/midi rtx +audio/mpeg mp3 m4a m4r +audio/x-mpegurl m3u m3u8 +image/jpeg jpg +image/x-ms-bmp bmp +text/plain txt +text/x-c++hdr hpp +text/x-c++src cpp +video/3gpp 3gpp +video/mpeg mpeg +video/quicktime mov +video/x-matroska mkv diff --git a/mime/java/android/content/type/MimeMapImpl.java b/mime/java/android/content/type/MimeMapImpl.java new file mode 100644 index 000000000000..c904ea3f9b60 --- /dev/null +++ b/mime/java/android/content/type/MimeMapImpl.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2019 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.type; + +import libcore.net.MimeMap; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Default implementation of {@link MimeMap}, a bidirectional mapping between + * MIME types and file extensions. + * + * This default mapping is loaded from data files that start with some mappings + * recognized by IANA plus some custom extensions and overrides. + * + * @hide + */ +public class MimeMapImpl extends MimeMap { + + /** + * Creates and returns a new {@link MimeMapImpl} instance that implements. + * Android's default mapping between MIME types and extensions. + */ + public static MimeMapImpl createDefaultInstance() { + return parseFromResources("/mime.types", "/android.mime.types"); + } + + private static final Pattern SPLIT_PATTERN = Pattern.compile("\\s+"); + + /** + * Note: These maps only contain lowercase keys/values, regarded as the + * {@link #toLowerCase(String) canonical form}. + * + * <p>This is the case for both extensions and MIME types. The mime.types + * data file contains examples of mixed-case MIME types, but some applications + * use the lowercase version of these same types. RFC 2045 section 2 states + * that MIME types are case insensitive. + */ + private final Map<String, String> mMimeTypeToExtension; + private final Map<String, String> mExtensionToMimeType; + + public MimeMapImpl(Map<String, String> mimeTypeToExtension, + Map<String, String> extensionToMimeType) { + this.mMimeTypeToExtension = new HashMap<>(mimeTypeToExtension); + for (Map.Entry<String, String> entry : mimeTypeToExtension.entrySet()) { + checkValidMimeType(entry.getKey()); + checkValidExtension(entry.getValue()); + } + this.mExtensionToMimeType = new HashMap<>(extensionToMimeType); + for (Map.Entry<String, String> entry : extensionToMimeType.entrySet()) { + checkValidExtension(entry.getKey()); + checkValidMimeType(entry.getValue()); + } + } + + private static void checkValidMimeType(String s) { + if (MimeMap.isNullOrEmpty(s) || !s.equals(MimeMap.toLowerCase(s))) { + throw new IllegalArgumentException("Invalid MIME type: " + s); + } + } + + private static void checkValidExtension(String s) { + if (MimeMap.isNullOrEmpty(s) || !s.equals(MimeMap.toLowerCase(s))) { + throw new IllegalArgumentException("Invalid extension: " + s); + } + } + + static MimeMapImpl parseFromResources(String... resourceNames) { + Map<String, String> mimeTypeToExtension = new HashMap<>(); + Map<String, String> extensionToMimeType = new HashMap<>(); + for (String resourceName : resourceNames) { + parseTypes(mimeTypeToExtension, extensionToMimeType, resourceName); + } + return new MimeMapImpl(mimeTypeToExtension, extensionToMimeType); + } + + /** + * An element of a *mime.types file: A MIME type or an extension, with an optional + * prefix of "?" (if not overriding an earlier value). + */ + private static class Element { + public final boolean keepExisting; + public final String s; + + Element(boolean keepExisting, String value) { + this.keepExisting = keepExisting; + this.s = toLowerCase(value); + if (value.isEmpty()) { + throw new IllegalArgumentException(); + } + } + + public String toString() { + return keepExisting ? ("?" + s) : s; + } + } + + private static String maybePut(Map<String, String> map, Element keyElement, String value) { + if (keyElement.keepExisting) { + return map.putIfAbsent(keyElement.s, value); + } else { + return map.put(keyElement.s, value); + } + } + + private static void parseTypes(Map<String, String> mimeTypeToExtension, + Map<String, String> extensionToMimeType, String resource) { + try (BufferedReader r = new BufferedReader( + new InputStreamReader(MimeMapImpl.class.getResourceAsStream(resource)))) { + String line; + while ((line = r.readLine()) != null) { + int commentPos = line.indexOf('#'); + if (commentPos >= 0) { + line = line.substring(0, commentPos); + } + line = line.trim(); + // The first time a MIME type is encountered it is mapped to the first extension + // listed in its line. The first time an extension is encountered it is mapped + // to the MIME type. + // + // When encountering a previously seen MIME type or extension, then by default + // the later ones override earlier mappings (put() semantics); however if a MIME + // type or extension is prefixed with '?' then any earlier mapping _from_ that + // MIME type / extension is kept (putIfAbsent() semantics). + final String[] split = SPLIT_PATTERN.split(line); + if (split.length <= 1) { + // Need mimeType + at least one extension to make a mapping. + // "mime.types" files may also contain lines with just a mimeType without + // an extension but we skip them as they provide no mapping info. + continue; + } + List<Element> lineElements = new ArrayList<>(split.length); + for (String s : split) { + boolean keepExisting = s.startsWith("?"); + if (keepExisting) { + s = s.substring(1); + } + if (s.isEmpty()) { + throw new IllegalArgumentException("Invalid entry in '" + line + "'"); + } + lineElements.add(new Element(keepExisting, s)); + } + + // MIME type -> first extension (one mapping) + // This will override any earlier mapping from this MIME type to another + // extension, unless this MIME type was prefixed with '?'. + Element mimeElement = lineElements.get(0); + List<Element> extensionElements = lineElements.subList(1, lineElements.size()); + String firstExtension = extensionElements.get(0).s; + maybePut(mimeTypeToExtension, mimeElement, firstExtension); + + // extension -> MIME type (one or more mappings). + // This will override any earlier mapping from this extension to another + // MIME type, unless this extension was prefixed with '?'. + for (Element extensionElement : extensionElements) { + maybePut(extensionToMimeType, extensionElement, mimeElement.s); + } + } + } catch (IOException | RuntimeException e) { + throw new RuntimeException("Failed to parse " + resource, e); + } + } + + @Override + protected String guessExtensionFromLowerCaseMimeType(String mimeType) { + return mMimeTypeToExtension.get(mimeType); + } + + @Override + protected String guessMimeTypeFromLowerCaseExtension(String extension) { + return mExtensionToMimeType.get(extension); + } +} diff --git a/services/Android.bp b/services/Android.bp index bea51be321c9..75fd0129d27a 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -37,6 +37,10 @@ java_library { "android.hidl.manager-V1.0-java", ], + plugins: [ + "compat-changeid-annotation-processor", + ], + // Uncomment to enable output of certain warnings (deprecated, unchecked) //javacflags: ["-Xlint"], @@ -50,3 +54,9 @@ cc_library_shared { defaults: ["libservices.core-libs"], whole_static_libs: ["libservices.core"], } + +platform_compat_config { + name: "services-platform-compat-config", + prefix: "services", + src: ":services", +} diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index 6f32beea66d3..bc5973d1b270 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -118,12 +118,6 @@ public final class CompatChange { * @return {@code true} if the change should be enabled for the package. */ boolean isEnabled(ApplicationInfo app) { - if (app.isSystemApp()) { - // All changes are enabled for system apps, and we do not support overrides. - // Compatibility issues for system apps should be addressed in the app itself when - // the compatibility change is made. - return true; - } if (mPackageOverrides != null && mPackageOverrides.containsKey(app.packageName)) { return mPackageOverrides.get(app.packageName); } diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 27050faef712..fc38735509f0 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -16,11 +16,11 @@ package com.android.server.compat; -import android.compat.IPlatformCompat; import android.content.Context; import android.content.pm.ApplicationInfo; import android.util.Slog; +import com.android.internal.compat.IPlatformCompat; import com.android.internal.util.DumpUtils; import java.io.FileDescriptor; diff --git a/services/core/java/com/android/server/display/OWNERS b/services/core/java/com/android/server/display/OWNERS index 98e32997e587..e2f06bf9fd76 100644 --- a/services/core/java/com/android/server/display/OWNERS +++ b/services/core/java/com/android/server/display/OWNERS @@ -1,5 +1,6 @@ michaelwr@google.com hackbod@google.com ogunwale@google.com +santoscordon@google.com per-file ColorDisplayService.java=christyfranks@google.com diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 25ca27836aa7..3a6987fc352f 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -850,7 +850,11 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mSystemAudioActivated = on; mService.announceSystemAudioModeChange(on); } - startArcAction(on); + if (on && !mArcEstablished) { + startArcAction(true); + } else if (!on) { + startArcAction(false); + } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 0188f7c8bfaa..b241a67a6b2d 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -795,8 +795,22 @@ public class NotificationManagerService extends SystemService { @Override public void onNotificationError(int callingUid, int callingPid, String pkg, String tag, int id, int uid, int initialPid, String message, int userId) { - cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, false, userId, - REASON_ERROR, null); + final boolean fgService; + synchronized (mNotificationLock) { + NotificationRecord r = findNotificationLocked(pkg, tag, id, userId); + fgService = r != null && (r.getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0; + } + cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, + false, userId, REASON_ERROR, null); + if (fgService) { + // Still crash for foreground services, preventing the not-crash behaviour abused + // by apps to give us a garbage notification and silently start a fg service. + Binder.withCleanCallingIdentity( + () -> mAm.crashApplication(uid, initialPid, pkg, -1, + "Bad notification(tag=" + tag + ", id=" + id + ") posted from package " + + pkg + ", crashing app(uid=" + uid + ", pid=" + initialPid + "): " + + message)); + } } @Override diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index b2677cb77d9c..ef57ca1cf5cb 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -840,6 +840,10 @@ public final class TvInputManagerService extends SystemService { private void setStateLocked(String inputId, int state, int userId) { UserState userState = getOrCreateUserStateLocked(userId); TvInputState inputState = userState.inputMap.get(inputId); + if (inputState == null) { + Slog.e(TAG, "failed to setStateLocked - unknown input id " + inputId); + return; + } ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent()); int oldState = inputState.state; inputState.state = state; diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index f3c5e99f5f90..f8c87fcb4ef6 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -173,34 +173,6 @@ public class CompatConfigTest { } @Test - public void testSystemAppDisabledChangeEnabled() { - CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true)); // disabled - ApplicationInfo sysApp = makeAppInfo("system.app", 1); - sysApp.flags |= ApplicationInfo.FLAG_SYSTEM; - assertThat(pc.isChangeEnabled(1234L, sysApp)).isTrue(); - } - - @Test - public void testSystemAppOverrideIgnored() { - CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false)); - pc.addOverride(1234L, "system.app", false); - ApplicationInfo sysApp = makeAppInfo("system.app", 1); - sysApp.flags |= ApplicationInfo.FLAG_SYSTEM; - assertThat(pc.isChangeEnabled(1234L, sysApp)).isTrue(); - } - - @Test - public void testSystemAppTargetSdkIgnored() { - CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false)); - ApplicationInfo sysApp = makeAppInfo("system.app", 1); - sysApp.flags |= ApplicationInfo.FLAG_SYSTEM; - assertThat(pc.isChangeEnabled(1234L, sysApp)).isTrue(); - } - - @Test public void testReadConfig() { Change[] changes = {new Change(1234L, "MY_CHANGE1", false, 2), new Change(1235L, "MY_CHANGE2", true, null), new Change(1236L, "MY_CHANGE3", false, null)}; diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java index 0e31aabe84d9..c25918f54c60 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -2962,6 +2962,20 @@ public final class Telephony { * <P>Type: INTEGER</P> */ public static final String CHARSET = "charset"; + + /** + * Generates a Addr {@link Uri} for message, used to perform Addr table operation + * for mms. + * + * @param messageId the messageId used to generate Addr {@link Uri} dynamically + * @return the addrUri used to perform Addr table operation for mms + */ + @NonNull + public static Uri getAddrUriForMessage(@NonNull String messageId) { + Uri addrUri = Mms.CONTENT_URI.buildUpon() + .appendPath(String.valueOf(messageId)).appendPath("addr").build(); + return addrUri; + } } /** @@ -2980,11 +2994,16 @@ public final class Telephony { } /** + * The name of part table. + */ + private static final String TABLE_PART = "part"; + + /** * The {@code content://} style URL for this table. Can be appended with a part ID to * address individual parts. */ @NonNull - public static final Uri CONTENT_URI = Uri.withAppendedPath(Mms.CONTENT_URI, "part"); + public static final Uri CONTENT_URI = Uri.withAppendedPath(Mms.CONTENT_URI, TABLE_PART); /** * The identifier of the message which this part belongs to. @@ -3063,6 +3082,21 @@ public final class Telephony { * <P>Type: TEXT</P> */ public static final String TEXT = "text"; + + /** + * Generates a Part {@link Uri} for message, used to perform Part table operation + * for mms. + * + * @param messageId the messageId used to generate Part {@link Uri} dynamically + * @return the partUri used to perform Part table operation for mms + */ + @NonNull + public static Uri getPartUriForMessage(@NonNull String messageId) { + Uri partUri = Mms.CONTENT_URI.buildUpon() + .appendPath(String.valueOf(messageId)).appendPath( + TABLE_PART).build(); + return partUri; + } } /** diff --git a/telephony/java/android/telephony/NetworkServiceCallback.java b/telephony/java/android/telephony/NetworkServiceCallback.java index 1c64bcd28966..89b96654451e 100644 --- a/telephony/java/android/telephony/NetworkServiceCallback.java +++ b/telephony/java/android/telephony/NetworkServiceCallback.java @@ -24,7 +24,6 @@ import android.telephony.NetworkService.NetworkServiceProvider; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.lang.ref.WeakReference; /** * Network service callback. Object of this class is passed to NetworkServiceProvider upon @@ -61,11 +60,11 @@ public class NetworkServiceCallback { /** Request failed */ public static final int RESULT_ERROR_FAILED = 5; - private final WeakReference<INetworkServiceCallback> mCallback; + private final INetworkServiceCallback mCallback; /** @hide */ public NetworkServiceCallback(INetworkServiceCallback callback) { - mCallback = new WeakReference<>(callback); + mCallback = callback; } /** @@ -78,15 +77,14 @@ public class NetworkServiceCallback { */ public void onRequestNetworkRegistrationInfoComplete(int result, @Nullable NetworkRegistrationInfo state) { - INetworkServiceCallback callback = mCallback.get(); - if (callback != null) { + if (mCallback != null) { try { - callback.onRequestNetworkRegistrationInfoComplete(result, state); + mCallback.onRequestNetworkRegistrationInfoComplete(result, state); } catch (RemoteException e) { Rlog.e(mTag, "Failed to onRequestNetworkRegistrationInfoComplete on the remote"); } } else { - Rlog.e(mTag, "Weak reference of callback is null."); + Rlog.e(mTag, "onRequestNetworkRegistrationInfoComplete callback is null."); } } }
\ No newline at end of file diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java index 5d8d79367ba4..11dc78a611ff 100644 --- a/telephony/java/android/telephony/data/DataServiceCallback.java +++ b/telephony/java/android/telephony/data/DataServiceCallback.java @@ -27,7 +27,6 @@ import android.telephony.data.DataService.DataServiceProvider; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.lang.ref.WeakReference; import java.util.List; /** @@ -42,6 +41,8 @@ public class DataServiceCallback { private static final String TAG = DataServiceCallback.class.getSimpleName(); + private static final boolean DBG = true; + /** * Result of data requests * @hide @@ -62,11 +63,11 @@ public class DataServiceCallback { /** Request sent in illegal state */ public static final int RESULT_ERROR_ILLEGAL_STATE = 4; - private final WeakReference<IDataServiceCallback> mCallback; + private final IDataServiceCallback mCallback; /** @hide */ public DataServiceCallback(IDataServiceCallback callback) { - mCallback = new WeakReference<>(callback); + mCallback = callback; } /** @@ -78,13 +79,15 @@ public class DataServiceCallback { */ public void onSetupDataCallComplete(@ResultCode int result, @Nullable DataCallResponse response) { - IDataServiceCallback callback = mCallback.get(); - if (callback != null) { + if (mCallback != null) { try { - callback.onSetupDataCallComplete(result, response); + if (DBG) Rlog.d(TAG, "onSetupDataCallComplete"); + mCallback.onSetupDataCallComplete(result, response); } catch (RemoteException e) { Rlog.e(TAG, "Failed to onSetupDataCallComplete on the remote"); } + } else { + Rlog.e(TAG, "onSetupDataCallComplete: callback is null!"); } } @@ -95,13 +98,15 @@ public class DataServiceCallback { * @param result The result code. Must be one of the {@link ResultCode}. */ public void onDeactivateDataCallComplete(@ResultCode int result) { - IDataServiceCallback callback = mCallback.get(); - if (callback != null) { + if (mCallback != null) { try { - callback.onDeactivateDataCallComplete(result); + if (DBG) Rlog.d(TAG, "onDeactivateDataCallComplete"); + mCallback.onDeactivateDataCallComplete(result); } catch (RemoteException e) { Rlog.e(TAG, "Failed to onDeactivateDataCallComplete on the remote"); } + } else { + Rlog.e(TAG, "onDeactivateDataCallComplete: callback is null!"); } } @@ -112,13 +117,14 @@ public class DataServiceCallback { * @param result The result code. Must be one of the {@link ResultCode}. */ public void onSetInitialAttachApnComplete(@ResultCode int result) { - IDataServiceCallback callback = mCallback.get(); - if (callback != null) { + if (mCallback != null) { try { - callback.onSetInitialAttachApnComplete(result); + mCallback.onSetInitialAttachApnComplete(result); } catch (RemoteException e) { Rlog.e(TAG, "Failed to onSetInitialAttachApnComplete on the remote"); } + } else { + Rlog.e(TAG, "onSetInitialAttachApnComplete: callback is null!"); } } @@ -129,13 +135,14 @@ public class DataServiceCallback { * @param result The result code. Must be one of the {@link ResultCode}. */ public void onSetDataProfileComplete(@ResultCode int result) { - IDataServiceCallback callback = mCallback.get(); - if (callback != null) { + if (mCallback != null) { try { - callback.onSetDataProfileComplete(result); + mCallback.onSetDataProfileComplete(result); } catch (RemoteException e) { Rlog.e(TAG, "Failed to onSetDataProfileComplete on the remote"); } + } else { + Rlog.e(TAG, "onSetDataProfileComplete: callback is null!"); } } @@ -149,13 +156,14 @@ public class DataServiceCallback { */ public void onRequestDataCallListComplete(@ResultCode int result, @NonNull List<DataCallResponse> dataCallList) { - IDataServiceCallback callback = mCallback.get(); - if (callback != null) { + if (mCallback != null) { try { - callback.onRequestDataCallListComplete(result, dataCallList); + mCallback.onRequestDataCallListComplete(result, dataCallList); } catch (RemoteException e) { Rlog.e(TAG, "Failed to onRequestDataCallListComplete on the remote"); } + } else { + Rlog.e(TAG, "onRequestDataCallListComplete: callback is null!"); } } @@ -166,13 +174,15 @@ public class DataServiceCallback { * @param dataCallList List of the current active data connection. */ public void onDataCallListChanged(@NonNull List<DataCallResponse> dataCallList) { - IDataServiceCallback callback = mCallback.get(); - if (callback != null) { + if (mCallback != null) { try { - callback.onDataCallListChanged(dataCallList); + if (DBG) Rlog.d(TAG, "onDataCallListChanged"); + mCallback.onDataCallListChanged(dataCallList); } catch (RemoteException e) { Rlog.e(TAG, "Failed to onDataCallListChanged on the remote"); } + } else { + Rlog.e(TAG, "onDataCallListChanged: callback is null!"); } } } diff --git a/telephony/java/com/android/internal/telephony/CbGeoUtils.java b/telephony/java/com/android/internal/telephony/CbGeoUtils.java new file mode 100644 index 000000000000..c973b672ae40 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/CbGeoUtils.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2019 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.internal.telephony; + +import android.annotation.NonNull; +import android.telephony.Rlog; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + + +/** + * This utils class is specifically used for geo-targeting of CellBroadcast messages. + * The coordinates used by this utils class are latitude and longitude, but some algorithms in this + * class only use them as coordinates on plane, so the calculation will be inaccurate. So don't use + * this class for anything other then geo-targeting of cellbroadcast messages. + */ +public class CbGeoUtils { + /** Geometric interface. */ + public interface Geometry { + /** + * Determines if the given point {@code p} is inside the geometry. + * @param p point in latitude, longitude format. + * @return {@code True} if the given point is inside the geometry. + */ + boolean contains(LatLng p); + } + + /** + * Tolerance for determining if the value is 0. If the absolute value of a value is less than + * this tolerance, it will be treated as 0. + */ + public static final double EPS = 1e-7; + + /** The radius of earth. */ + public static final int EARTH_RADIUS_METER = 6371 * 1000; + + private static final String TAG = "CbGeoUtils"; + + /** The identifier of geometry in the encoded string. */ + private static final String CIRCLE_SYMBOL = "circle"; + private static final String POLYGON_SYMBOL = "polygon"; + + /** Point represent by (latitude, longitude). */ + public static class LatLng { + public final double lat; + public final double lng; + + /** + * Constructor. + * @param lat latitude, range [-90, 90] + * @param lng longitude, range [-180, 180] + */ + public LatLng(double lat, double lng) { + this.lat = lat; + this.lng = lng; + } + + /** + * @param p the point use to calculate the subtraction result. + * @return the result of this point subtract the given point {@code p}. + */ + public LatLng subtract(LatLng p) { + return new LatLng(lat - p.lat, lng - p.lng); + } + + /** + * Calculate the distance in meter between this point and the given point {@code p}. + * @param p the point use to calculate the distance. + * @return the distance in meter. + */ + public double distance(LatLng p) { + double dlat = Math.sin(0.5 * Math.toRadians(lat - p.lat)); + double dlng = Math.sin(0.5 * Math.toRadians(lng - p.lng)); + double x = dlat * dlat + + dlng * dlng * Math.cos(Math.toRadians(lat)) * Math.cos(Math.toRadians(p.lat)); + return 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1 - x)) * EARTH_RADIUS_METER; + } + } + + /** + * The class represents a simple polygon with at least 3 points. + */ + public static class Polygon implements Geometry { + /** + * In order to reduce the loss of precision in floating point calculations, all vertices + * of the polygon are scaled. Set the value of scale to 1000 can take into account the + * actual distance accuracy of 1 meter if the EPS is 1e-7 during the calculation. + */ + private static final double SCALE = 1000.0; + + private final List<LatLng> mVertices; + private final List<Point> mScaledVertices; + private final LatLng mOrigin; + + /** + * Constructs a simple polygon from the given vertices. The adjacent two vertices are + * connected to form an edge of the polygon. The polygon has at least 3 vertices, and the + * last vertices and the first vertices must be adjacent. + * + * The longitude difference in the vertices should be less than 180 degree. + */ + public Polygon(@NonNull List<LatLng> vertices) { + mVertices = vertices; + + // Find the point with smallest longitude as the mOrigin point. + int idx = 0; + for (int i = 1; i < vertices.size(); i++) { + if (vertices.get(i).lng < vertices.get(idx).lng) { + idx = i; + } + } + mOrigin = vertices.get(idx); + + mScaledVertices = vertices.stream() + .map(latLng -> convertAndScaleLatLng(latLng)) + .collect(Collectors.toList()); + } + + public List<LatLng> getVertices() { + return mVertices; + } + + /** + * Check if the given point {@code p} is inside the polygon. This method counts the number + * of times the polygon winds around the point P, A.K.A "winding number". The point is + * outside only when this "winding number" is 0. + * + * If a point is on the edge of the polygon, it is also considered to be inside the polygon. + */ + @Override + public boolean contains(LatLng latLng) { + Point p = convertAndScaleLatLng(latLng); + + int n = mScaledVertices.size(); + int windingNumber = 0; + for (int i = 0; i < n; i++) { + Point a = mScaledVertices.get(i); + Point b = mScaledVertices.get((i + 1) % n); + + // CCW is counterclockwise + // CCW = ab x ap + // CCW > 0 -> ap is on the left side of ab + // CCW == 0 -> ap is on the same line of ab + // CCW < 0 -> ap is on the right side of ab + int ccw = sign(crossProduct(b.subtract(a), p.subtract(a))); + + if (ccw == 0) { + if (Math.min(a.x, b.x) <= p.x && p.x <= Math.max(a.x, b.x) + && Math.min(a.y, b.y) <= p.y && p.y <= Math.max(a.y, b.y)) { + return true; + } + } else { + if (sign(a.y - p.y) <= 0) { + // upward crossing + if (ccw > 0 && sign(b.y - p.y) > 0) { + ++windingNumber; + } + } else { + // downward crossing + if (ccw < 0 && sign(b.y - p.y) <= 0) { + --windingNumber; + } + } + } + } + return windingNumber != 0; + } + + /** + * Move the given point {@code latLng} to the coordinate system with {@code mOrigin} as the + * origin and scale it. {@code mOrigin} is selected from the vertices of a polygon, it has + * the smallest longitude value among all of the polygon vertices. + * + * @param latLng the point need to be converted and scaled. + * @Return a {@link Point} object. + */ + private Point convertAndScaleLatLng(LatLng latLng) { + double x = latLng.lat - mOrigin.lat; + double y = latLng.lng - mOrigin.lng; + + // If the point is in different hemispheres(western/eastern) than the mOrigin, and the + // edge between them cross the 180th meridian, then its relative coordinates will be + // extended. + // For example, suppose the longitude of the mOrigin is -178, and the longitude of the + // point to be converted is 175, then the longitude after the conversion is -8. + // calculation: (-178 - 8) - (-178). + if (sign(mOrigin.lng) != 0 && sign(mOrigin.lng) != sign(latLng.lng)) { + double distCross0thMeridian = Math.abs(mOrigin.lng) + Math.abs(latLng.lng); + if (sign(distCross0thMeridian * 2 - 360) > 0) { + y = sign(mOrigin.lng) * (360 - distCross0thMeridian); + } + } + return new Point(x * SCALE, y * SCALE); + } + + private static double crossProduct(Point a, Point b) { + return a.x * b.y - a.y * b.x; + } + + static final class Point { + public final double x; + public final double y; + + Point(double x, double y) { + this.x = x; + this.y = y; + } + + public Point subtract(Point p) { + return new Point(x - p.x, y - p.y); + } + } + } + + /** The class represents a circle. */ + public static class Circle implements Geometry { + private final LatLng mCenter; + private final double mRadiusMeter; + + public Circle(LatLng center, double radiusMeter) { + this.mCenter = center; + this.mRadiusMeter = radiusMeter; + } + + public LatLng getCenter() { + return mCenter; + } + + public double getRadius() { + return mRadiusMeter; + } + + @Override + public boolean contains(LatLng p) { + return mCenter.distance(p) <= mRadiusMeter; + } + } + + /** + * Parse the geometries from the encoded string {@code str}. The string must follow the + * geometry encoding specified by {@link android.provider.Telephony.CellBroadcasts#GEOMETRIES}. + */ + @NonNull + public static List<Geometry> parseGeometriesFromString(@NonNull String str) { + List<Geometry> geometries = new ArrayList<>(); + for (String geometryStr : str.split("\\s*;\\s*")) { + String[] geoParameters = geometryStr.split("\\s*\\|\\s*"); + switch (geoParameters[0]) { + case CIRCLE_SYMBOL: + geometries.add(new Circle(parseLatLngFromString(geoParameters[1]), + Double.parseDouble(geoParameters[2]))); + break; + case POLYGON_SYMBOL: + List<LatLng> vertices = new ArrayList<>(geoParameters.length - 1); + for (int i = 1; i < geoParameters.length; i++) { + vertices.add(parseLatLngFromString(geoParameters[i])); + } + geometries.add(new Polygon(vertices)); + break; + default: + Rlog.e(TAG, "Invalid geometry format " + geometryStr); + } + } + return geometries; + } + + /** + * Encode a list of geometry objects to string. The encoding format is specified by + * {@link android.provider.Telephony.CellBroadcasts#GEOMETRIES}. + * + * @param geometries the list of geometry objects need to be encoded. + * @return the encoded string. + */ + @NonNull + public static String encodeGeometriesToString(@NonNull List<Geometry> geometries) { + return geometries.stream() + .map(geometry -> encodeGeometryToString(geometry)) + .filter(encodedStr -> !TextUtils.isEmpty(encodedStr)) + .collect(Collectors.joining(";")); + } + + + /** + * Encode the geometry object to string. The encoding format is specified by + * {@link android.provider.Telephony.CellBroadcasts#GEOMETRIES}. + * @param geometry the geometry object need to be encoded. + * @return the encoded string. + */ + @NonNull + private static String encodeGeometryToString(@NonNull Geometry geometry) { + StringBuilder sb = new StringBuilder(); + if (geometry instanceof Polygon) { + sb.append(POLYGON_SYMBOL); + for (LatLng latLng : ((Polygon) geometry).getVertices()) { + sb.append("|"); + sb.append(latLng.lat); + sb.append(","); + sb.append(latLng.lng); + } + } else if (geometry instanceof Circle) { + sb.append(CIRCLE_SYMBOL); + Circle circle = (Circle) geometry; + + // Center + sb.append("|"); + sb.append(circle.getCenter().lat); + sb.append(","); + sb.append(circle.getCenter().lng); + + // Radius + sb.append("|"); + sb.append(circle.getRadius()); + } else { + Rlog.e(TAG, "Unsupported geometry object " + geometry); + return null; + } + return sb.toString(); + } + + /** + * Parse {@link LatLng} from {@link String}. Latitude and longitude are separated by ",". + * Example: "13.56,-55.447". + * + * @param str encoded lat/lng string. + * @Return {@link LatLng} object. + */ + @NonNull + public static LatLng parseLatLngFromString(@NonNull String str) { + String[] latLng = str.split("\\s*,\\s*"); + return new LatLng(Double.parseDouble(latLng[0]), Double.parseDouble(latLng[1])); + } + + /** + * @Return the sign of the given value {@code value} with the specified tolerance. Return 1 + * means the sign is positive, -1 means negative, 0 means the value will be treated as 0. + */ + public static int sign(double value) { + if (value > EPS) return 1; + if (value < -EPS) return -1; + return 0; + } +} |