diff options
47 files changed, 2983 insertions, 894 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index b62196a4a5a6..6d8b2804c8d9 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -25834,10 +25834,16 @@ package android.net { ctor public ScoredNetwork(android.net.NetworkKey, android.net.RssiCurve); ctor public ScoredNetwork(android.net.NetworkKey, android.net.RssiCurve, boolean); ctor public ScoredNetwork(android.net.NetworkKey, android.net.RssiCurve, boolean, android.os.Bundle); + method public int calculateBadge(int); method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); + field public static final java.lang.String ATTRIBUTES_KEY_BADGING_CURVE = "android.net.attributes.key.BADGING_CURVE"; field public static final java.lang.String ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL = "android.net.attributes.key.HAS_CAPTIVE_PORTAL"; field public static final java.lang.String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET = "android.net.attributes.key.RANKING_SCORE_OFFSET"; + field public static final int BADGING_4K = 30; // 0x1e + field public static final int BADGING_HD = 20; // 0x14 + field public static final int BADGING_NONE = 0; // 0x0 + field public static final int BADGING_SD = 10; // 0xa field public static final android.os.Parcelable.Creator<android.net.ScoredNetwork> CREATOR; field public final android.os.Bundle attributes; field public final boolean meteredHint; @@ -25845,6 +25851,9 @@ package android.net { field public final android.net.RssiCurve rssiCurve; } + public static abstract class ScoredNetwork.Badging implements java.lang.annotation.Annotation { + } + public class TrafficStats { ctor public TrafficStats(); method public static void clearThreadStatsTag(); @@ -26853,6 +26862,8 @@ package android.net.wifi { public class WifiConfiguration implements android.os.Parcelable { ctor public WifiConfiguration(); method public int describeContents(); + method public boolean hasNoInternetAccess(); + method public boolean isNoInternetAccessExpected(); method public boolean isPasspoint(); method public void writeToParcel(android.os.Parcel, int); field public java.lang.String BSSID; diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index b68693880acf..98a5341b3025 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -243,7 +243,7 @@ public final class BluetoothSocket implements Closeable { } as.mPfd = new ParcelFileDescriptor(fds[0]); - as.mSocket = new LocalSocket(fds[0]); + as.mSocket = LocalSocket.createConnectedLocalSocket(fds[0]); as.mSocketIS = as.mSocket.getInputStream(); as.mSocketOS = as.mSocket.getOutputStream(); as.mAddress = RemoteAddr; @@ -367,7 +367,7 @@ public final class BluetoothSocket implements Closeable { if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); if (mPfd == null) throw new IOException("bt socket connect failed"); FileDescriptor fd = mPfd.getFileDescriptor(); - mSocket = new LocalSocket(fd); + mSocket = LocalSocket.createConnectedLocalSocket(fd); mSocketIS = mSocket.getInputStream(); mSocketOS = mSocket.getOutputStream(); } @@ -416,9 +416,9 @@ public final class BluetoothSocket implements Closeable { if(mSocketState != SocketState.INIT) return EBADFD; if(mPfd == null) return -1; FileDescriptor fd = mPfd.getFileDescriptor(); - if (DBG) Log.d(TAG, "bindListen(), new LocalSocket "); - mSocket = new LocalSocket(fd); - if (DBG) Log.d(TAG, "bindListen(), new LocalSocket.getInputStream() "); + if (DBG) Log.d(TAG, "bindListen(), Create LocalSocket"); + mSocket = LocalSocket.createConnectedLocalSocket(fd); + if (DBG) Log.d(TAG, "bindListen(), new LocalSocket.getInputStream()"); mSocketIS = mSocket.getInputStream(); mSocketOS = mSocket.getOutputStream(); } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 51431ebfaf14..042481f1727e 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -223,6 +223,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/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl index 932f03116f15..b097f12d460e 100644 --- a/core/java/android/net/INetworkScoreService.aidl +++ b/core/java/android/net/INetworkScoreService.aidl @@ -100,4 +100,13 @@ interface INetworkScoreService * @hide */ boolean requestScores(in NetworkKey[] networks); + + /** + * Determine whether the application with the given UID is the enabled scorer. + * + * @param callingUid the UID to check + * @return true if the provided UID is the active scorer, false otherwise. + * @hide + */ + boolean isCallerActiveScorer(int callingUid); } diff --git a/core/java/android/net/LocalServerSocket.java b/core/java/android/net/LocalServerSocket.java index e1eaf00d0f20..3fcde33071e9 100644 --- a/core/java/android/net/LocalServerSocket.java +++ b/core/java/android/net/LocalServerSocket.java @@ -89,7 +89,7 @@ public class LocalServerSocket { impl.accept(acceptedImpl); - return LocalSocket.createLocalSocketForAccept(acceptedImpl, LocalSocket.SOCKET_UNKNOWN); + return LocalSocket.createLocalSocketForAccept(acceptedImpl); } /** diff --git a/core/java/android/net/LocalSocket.java b/core/java/android/net/LocalSocket.java index d9ad74beb016..8afa1ed3e4a5 100644 --- a/core/java/android/net/LocalSocket.java +++ b/core/java/android/net/LocalSocket.java @@ -31,6 +31,7 @@ import java.net.SocketOptions; public class LocalSocket implements Closeable { private final LocalSocketImpl impl; + /** false if impl.create() needs to be called */ private volatile boolean implCreated; private LocalSocketAddress localAddress; private boolean isBound; @@ -61,19 +62,6 @@ public class LocalSocket implements Closeable { */ public LocalSocket(int sockType) { this(new LocalSocketImpl(), sockType); - isBound = false; - isConnected = false; - } - - /** - * Creates a AF_LOCAL/UNIX domain stream socket with FileDescriptor. - * @hide - */ - public LocalSocket(FileDescriptor fd) throws IOException { - this(new LocalSocketImpl(fd), SOCKET_UNKNOWN); - isBound = true; - isConnected = true; - implCreated = true; } private LocalSocket(LocalSocketImpl impl, int sockType) { @@ -84,9 +72,27 @@ public class LocalSocket implements Closeable { } /** + * Creates a LocalSocket instances using the FileDescriptor for an already-connected + * AF_LOCAL/UNIX domain stream socket. Note: the FileDescriptor must be closed by the caller: + * closing the LocalSocket will not close it. + * + * @hide - used by BluetoothSocket. + */ + public static LocalSocket createConnectedLocalSocket(FileDescriptor fd) { + return createConnectedLocalSocket(new LocalSocketImpl(fd), SOCKET_UNKNOWN); + } + + /** * for use with LocalServerSocket.accept() */ - static LocalSocket createLocalSocketForAccept(LocalSocketImpl impl, int sockType) { + static LocalSocket createLocalSocketForAccept(LocalSocketImpl impl) { + return createConnectedLocalSocket(impl, SOCKET_UNKNOWN); + } + + /** + * Creates a LocalSocket from an existing LocalSocketImpl that is already connected. + */ + private static LocalSocket createConnectedLocalSocket(LocalSocketImpl impl, int sockType) { LocalSocket socket = new LocalSocket(impl, sockType); socket.isConnected = true; socket.isBound = true; diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java index d8f782115cf8..05c8afb358b0 100644 --- a/core/java/android/net/LocalSocketImpl.java +++ b/core/java/android/net/LocalSocketImpl.java @@ -218,7 +218,7 @@ class LocalSocketImpl * * @param fd non-null; bound file descriptor */ - /*package*/ LocalSocketImpl(FileDescriptor fd) throws IOException + /*package*/ LocalSocketImpl(FileDescriptor fd) { this.fd = fd; } diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index 9a6dca013882..af0306e35aaa 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -341,4 +341,19 @@ public class NetworkScoreManager { throw e.rethrowFromSystemServer(); } } + + /** + * Determine whether the application with the given UID is the enabled scorer. + * + * @param callingUid the UID to check + * @return true if the provided UID is the active scorer, false otherwise. + * @hide + */ + public boolean isCallerActiveScorer(int callingUid) { + try { + return mService.isCallerActiveScorer(callingUid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java index ec0bb25d28eb..23d5af55641c 100644 --- a/core/java/android/net/NetworkScorerAppManager.java +++ b/core/java/android/net/NetworkScorerAppManager.java @@ -16,7 +16,6 @@ package android.net; -import android.Manifest; import android.Manifest.permission; import android.annotation.Nullable; import android.content.ContentResolver; @@ -28,7 +27,9 @@ 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.Collections; import java.util.List; @@ -226,6 +227,7 @@ public class NetworkScorerAppManager { } /** Determine whether the application with the given UID is the enabled scorer. */ + @Deprecated // Use NetworkScoreManager.isCallerActiveScorer() public boolean isCallerActiveScorer(int callingUid) { NetworkScorerAppData defaultApp = getActiveScorer(); if (defaultApp == null) { diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java index 94e518707cf9..7e3dd77cf384 100644 --- a/core/java/android/net/ScoredNetwork.java +++ b/core/java/android/net/ScoredNetwork.java @@ -16,6 +16,7 @@ package android.net; +import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Bundle; @@ -24,6 +25,8 @@ import android.os.Parcelable; import java.lang.Math; import java.lang.UnsupportedOperationException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -33,6 +36,15 @@ import java.util.Objects; */ @SystemApi public class ScoredNetwork implements Parcelable { + + /** + * Key used with the {@link #attributes} bundle to define the badging curve. + * + * <p>The badging curve is a {@link RssiCurve} used to map different RSSI values to {@link + * Badging} enums. + */ + public static final String ATTRIBUTES_KEY_BADGING_CURVE = + "android.net.attributes.key.BADGING_CURVE"; /** * Extra used with {@link #attributes} to specify whether the * network is believed to have a captive portal. @@ -58,6 +70,15 @@ public class ScoredNetwork implements Parcelable { public static final String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET = "android.net.attributes.key.RANKING_SCORE_OFFSET"; + @IntDef({BADGING_NONE, BADGING_SD, BADGING_HD, BADGING_4K}) + @Retention(RetentionPolicy.SOURCE) + public @interface Badging {} + + public static final int BADGING_NONE = 0; + public static final int BADGING_SD = 10; + public static final int BADGING_HD = 20; + public static final int BADGING_4K = 30; + /** A {@link NetworkKey} uniquely identifying this network. */ public final NetworkKey networkKey; @@ -249,6 +270,25 @@ public class ScoredNetwork implements Parcelable { } } + /** + * Return the {@link Badging} enum for this network for the given RSSI, derived from the + * badging curve. + * + * <p>If no badging curve is present, {@link #BADGE_NONE} will be returned. + * + * @param rssi The rssi level for which the badge should be calculated + */ + @Badging + public int calculateBadge(int rssi) { + if (attributes != null && attributes.containsKey(ATTRIBUTES_KEY_BADGING_CURVE)) { + RssiCurve badgingCurve = + attributes.getParcelable(ATTRIBUTES_KEY_BADGING_CURVE); + return badgingCurve.lookupScore(rssi); + } + + return BADGING_NONE; + } + public static final Parcelable.Creator<ScoredNetwork> CREATOR = new Parcelable.Creator<ScoredNetwork>() { @Override diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 2e0729be8b63..67196caa42df 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -75,17 +75,6 @@ public final class ThreadedRenderer { private static final String CACHE_PATH_SHADERS = "com.android.opengl.shaders_cache"; /** - * System property used to enable or disable dirty regions invalidation. - * This property is only queried if {@link #RENDER_DIRTY_REGIONS} is true. - * The default value of this property is assumed to be true. - * - * Possible values: - * "true", to enable partial invalidates - * "false", to disable partial invalidates - */ - static final String RENDER_DIRTY_REGIONS_PROPERTY = "debug.hwui.render_dirty_regions"; - - /** * System property used to enable or disable hardware rendering profiling. * The default value of this property is assumed to be false. * 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..e1e0a21eb7f5 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -85,6 +85,9 @@ public final class Zygote { * file descriptor numbers that are to be closed by the child * (and replaced by /dev/null) after forking. An integer value * of -1 in any entry in the array means "ignore this one". + * @param fdsToIgnore null-ok an array of ints, either null or holding + * one or more POSIX file descriptor numbers that are to be ignored + * in the file descriptor table check. * @param instructionSet null-ok the instruction set to use. * @param appDataDir null-ok the data directory of the app. * @@ -93,11 +96,11 @@ public final class Zygote { */ public static int forkAndSpecialize(int uid, int gid, int[] gids, int debugFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, - String instructionSet, String appDataDir) { + int[] fdsToIgnore, String instructionSet, String appDataDir) { VM_HOOKS.preFork(); int pid = nativeForkAndSpecialize( uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, - instructionSet, appDataDir); + fdsToIgnore, instructionSet, appDataDir); // Enable tracing as soon as possible for the child process. if (pid == 0) { Trace.setTracingEnabled(true); @@ -111,7 +114,7 @@ public final class Zygote { native private static int nativeForkAndSpecialize(int uid, int gid, int[] gids,int debugFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, - String instructionSet, String appDataDir); + int[] fdsToIgnore, String instructionSet, String appDataDir); /** * Special method to start the system server process. In addition to the @@ -153,6 +156,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 44c6e8557c2c..ec80303f714a 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -193,11 +193,14 @@ class ZygoteConnection { rlimits = parsedArgs.rlimits.toArray(intArray2d); } + int[] fdsToIgnore = null; + if (parsedArgs.invokeWith != null) { FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC); childPipeFd = pipeFds[1]; serverPipeFd = pipeFds[0]; Os.fcntlInt(childPipeFd, F_SETFD, 0); + fdsToIgnore = new int[] { childPipeFd.getInt$(), serverPipeFd.getInt$() }; } /** @@ -230,7 +233,7 @@ class ZygoteConnection { pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo, - parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet, + parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet, parsedArgs.appDataDir); } catch (ErrnoException ex) { logAndPrintError(newStderr, "Exception creating pipe", ex); diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 70e90044f3c7..d9d06c5576e5 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -179,6 +179,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/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index fec8f4e39efb..a32dbad7838f 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -43,6 +43,7 @@ #include <sys/wait.h> #include <unistd.h> +#include "android-base/logging.h" #include <cutils/fs.h> #include <cutils/multiuser.h> #include <cutils/sched_policy.h> @@ -56,7 +57,7 @@ #include "ScopedLocalRef.h" #include "ScopedPrimitiveArray.h" #include "ScopedUtfChars.h" -#include "fd_utils-inl.h" +#include "fd_utils.h" #include "nativebridge/native_bridge.h" @@ -440,6 +441,22 @@ void SetThreadName(const char* thread_name) { // The list of open zygote file descriptors. static FileDescriptorTable* gOpenFdTable = NULL; +static void FillFileDescriptorVector(JNIEnv* env, + jintArray java_fds, + std::vector<int>* fds) { + CHECK(fds != nullptr); + if (java_fds != nullptr) { + ScopedIntArrayRO ar(env, java_fds); + if (ar.get() == nullptr) { + RuntimeAbort(env, __LINE__, "Bad fd array"); + } + fds->reserve(ar.size()); + for (size_t i = 0; i < ar.size(); ++i) { + fds->push_back(ar[i]); + } + } +} + // Utility routine to fork zygote and specialize the child process. static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids, jint debug_flags, jobjectArray javaRlimits, @@ -447,6 +464,7 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra jint mount_external, jstring java_se_info, jstring java_se_name, bool is_system_server, jintArray fdsToClose, + jintArray fdsToIgnore, jstring instructionSet, jstring dataDir) { SetSigChldHandler(); @@ -457,12 +475,14 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra // If this is the first fork for this zygote, create the open FD table. // If it isn't, we just need to check whether the list of open files has // changed (and it shouldn't in the normal case). + std::vector<int> fds_to_ignore; + FillFileDescriptorVector(env, fdsToIgnore, &fds_to_ignore); if (gOpenFdTable == NULL) { - gOpenFdTable = FileDescriptorTable::Create(); + gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore); if (gOpenFdTable == NULL) { RuntimeAbort(env, __LINE__, "Unable to construct file descriptor table."); } - } else if (!gOpenFdTable->Restat()) { + } else if (!gOpenFdTable->Restat(fds_to_ignore)) { RuntimeAbort(env, __LINE__, "Unable to restat file descriptor table."); } @@ -621,7 +641,9 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, jint debug_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring se_name, - jintArray fdsToClose, jstring instructionSet, jstring appDataDir) { + jintArray fdsToClose, + jintArray fdsToIgnore, + jstring instructionSet, jstring appDataDir) { jlong capabilities = 0; // Grant CAP_WAKE_ALARM to the Bluetooth process. @@ -656,7 +678,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( return ForkAndSpecializeCommon(env, uid, gid, gids, debug_flags, rlimits, capabilities, capabilities, mount_external, se_info, - se_name, false, fdsToClose, instructionSet, appDataDir); + se_name, false, fdsToClose, fdsToIgnore, instructionSet, appDataDir); } static jint com_android_internal_os_Zygote_nativeForkSystemServer( @@ -667,7 +689,7 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( debug_flags, rlimits, permittedCapabilities, effectiveCapabilities, MOUNT_EXTERNAL_DEFAULT, NULL, NULL, true, NULL, - NULL, NULL); + NULL, NULL, NULL); if (pid > 0) { // The zygote process checks whether the child process has died or not. ALOGI("System server process %d has been created", pid); @@ -684,6 +706,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 @@ -724,10 +756,12 @@ static void com_android_internal_os_Zygote_nativeUnmountStorageOnInit(JNIEnv* en static const JNINativeMethod gMethods[] = { { "nativeForkAndSpecialize", - "(II[II[[IILjava/lang/String;Ljava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I", + "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I", (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 b78b8ffa2d5d..000000000000 --- a/core/jni/fd_utils-inl.h +++ /dev/null @@ -1,564 +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) { - } - - // 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 char* kFrameworksPrefix = "/system/framework/"; - static const char* kJarSuffix = ".jar"; - if (android::base::StartsWith(path, kFrameworksPrefix) - && android::base::EndsWith(path, kJarSuffix)) { - return true; - } - - // Whitelist files needed for Runtime Resource Overlay, like these: - // /system/vendor/overlay/framework-res.apk - // /system/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@PG@framework-res.apk@idmap - static const char* kOverlayDir = "/system/vendor/overlay/"; - static const char* kApkSuffix = ".apk"; - - if (android::base::StartsWith(path, kOverlayDir) - && android::base::EndsWith(path, kApkSuffix) - && path.find("/../") == std::string::npos) { - return true; - } - - static const char* kOverlayIdmapPrefix = "/data/resource-cache/"; - static const char* kOverlayIdmapSuffix = ".apk@idmap"; - if (android::base::StartsWith(path, kOverlayIdmapPrefix) - && android::base::EndsWith(path, kOverlayIdmapSuffix)) { - 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..59a536b1b001 --- /dev/null +++ b/core/jni/fd_utils.cpp @@ -0,0 +1,567 @@ +/* + * 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(const std::vector<int>& fds_to_ignore) { + 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; + } + if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) { + ALOGI("Ignoring open file descriptor %d", fd); + 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(const std::vector<int>& fds_to_ignore) { + 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; + } + if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) { + ALOGI("Ignoring open file descriptor %d", fd); + 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..03298c38dca9 --- /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(const std::vector<int>& fds_to_ignore); + + bool Restat(const std::vector<int>& fds_to_ignore); + + // 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/res/res/values/strings.xml b/core/res/res/values/strings.xml index 5d399c15e89d..38137f8c95a4 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2912,17 +2912,6 @@ <!-- If there is ever a ringtone set for some setting, but that ringtone can no longer be resolved, t his is shown instead. For example, if the ringtone was on a SD card and it had been removed, this woudl be shown for ringtones on that SD card. --> <string name="ringtone_unknown">Unknown ringtone</string> - <!-- A notification is shown when there are open wireless networks nearby. This is the notification's title. --> - <plurals name="wifi_available"> - <item quantity="one">Wi-Fi network available</item> - <item quantity="other">Wi-Fi networks available</item> - </plurals> - <!-- A notification is shown when there are open wireless networks nearby. This is the notification's message. --> - <plurals name="wifi_available_detailed"> - <item quantity="one">Open Wi-Fi network available</item> - <item quantity="other">Open Wi-Fi networks available</item> - </plurals> - <!-- A notification is shown when a wifi captive portal network is detected. This is the notification's title. --> <string name="wifi_available_sign_in">Sign in to Wi-Fi network</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index dadbce8fb6fe..9db131b373a5 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1787,8 +1787,6 @@ <java-symbol type="layout" name="safe_mode" /> <java-symbol type="layout" name="simple_list_item_2_single_choice" /> <java-symbol type="layout" name="app_error_dialog" /> - <java-symbol type="plurals" name="wifi_available" /> - <java-symbol type="plurals" name="wifi_available_detailed" /> <java-symbol type="string" name="accessibility_binding_label" /> <java-symbol type="string" name="adb_active_notification_message" /> <java-symbol type="string" name="adb_active_notification_title" /> diff --git a/core/tests/coretests/src/android/net/IpPrefixTest.java b/core/tests/coretests/src/android/net/IpPrefixTest.java index fcc638960bb8..4f2387dcf5c1 100644 --- a/core/tests/coretests/src/android/net/IpPrefixTest.java +++ b/core/tests/coretests/src/android/net/IpPrefixTest.java @@ -18,14 +18,14 @@ package android.net; import android.net.IpPrefix; import android.os.Parcel; -import static android.test.MoreAsserts.assertNotEqual; import android.test.suitebuilder.annotation.SmallTest; - -import static org.junit.Assert.assertArrayEquals; import java.net.InetAddress; import java.util.Random; import junit.framework.TestCase; +import static android.test.MoreAsserts.assertNotEqual; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; public class IpPrefixTest extends TestCase { @@ -242,25 +242,42 @@ public class IpPrefixTest extends TestCase { @SmallTest public void testHashCode() { - IpPrefix p; - int oldCode = -1; + IpPrefix p = new IpPrefix(new byte[4], 0); Random random = new Random(); for (int i = 0; i < 100; i++) { + final IpPrefix oldP = p; if (random.nextBoolean()) { // IPv4. byte[] b = new byte[4]; random.nextBytes(b); p = new IpPrefix(b, random.nextInt(33)); - assertNotEqual(oldCode, p.hashCode()); - oldCode = p.hashCode(); } else { // IPv6. byte[] b = new byte[16]; random.nextBytes(b); p = new IpPrefix(b, random.nextInt(129)); - assertNotEqual(oldCode, p.hashCode()); - oldCode = p.hashCode(); } + if (p.equals(oldP)) { + assertEquals(p.hashCode(), oldP.hashCode()); + } + if (p.hashCode() != oldP.hashCode()) { + assertNotEqual(p, oldP); + } + } + } + + @SmallTest + public void testHashCodeIsNotConstant() { + IpPrefix[] prefixes = { + new IpPrefix("2001:db8:f00::ace:d00d/127"), + new IpPrefix("192.0.2.0/23"), + new IpPrefix("::/0"), + new IpPrefix("0.0.0.0/0"), + }; + for (int i = 0; i < prefixes.length; i++) { + for (int j = i + 1; j < prefixes.length; j++) { + assertNotEqual(prefixes[i].hashCode(), prefixes[j].hashCode()); + } } } diff --git a/core/tests/coretests/src/android/net/ScoredNetworkTest.java b/core/tests/coretests/src/android/net/ScoredNetworkTest.java index 9c3346e1238b..e818c564a639 100644 --- a/core/tests/coretests/src/android/net/ScoredNetworkTest.java +++ b/core/tests/coretests/src/android/net/ScoredNetworkTest.java @@ -166,4 +166,52 @@ public class ScoredNetworkTest { assertTrue(newNetwork.meteredHint); assertNull(newNetwork.attributes); } + + @Test + public void calculateBadgeShouldReturnNoBadgeWhenNoAttributesBundle() { + ScoredNetwork network = new ScoredNetwork(KEY, CURVE); + assertEquals(ScoredNetwork.BADGING_NONE, network.calculateBadge(TEST_RSSI)); + } + + @Test + public void calculateBadgeShouldReturnNoBadgeWhenNoBadgingCurveInBundle() { + ScoredNetwork network = new ScoredNetwork(KEY, CURVE, false /* meteredHint */, ATTRIBUTES); + assertEquals(ScoredNetwork.BADGING_NONE, network.calculateBadge(TEST_RSSI)); + } + + @Test + public void calculateBadgeShouldReturn4kBadge() { + ScoredNetwork network = + buildScoredNetworkWithGivenBadgeForTestRssi(ScoredNetwork.BADGING_4K); + assertEquals(ScoredNetwork.BADGING_4K, network.calculateBadge(TEST_RSSI)); + } + + @Test + public void calculateBadgeShouldReturnHdBadge() { + ScoredNetwork network = + buildScoredNetworkWithGivenBadgeForTestRssi(ScoredNetwork.BADGING_HD); + assertEquals(ScoredNetwork.BADGING_HD, network.calculateBadge(TEST_RSSI)); + } + + @Test + public void calculateBadgeShouldReturnSdBadge() { + ScoredNetwork network = + buildScoredNetworkWithGivenBadgeForTestRssi(ScoredNetwork.BADGING_SD); + assertEquals(ScoredNetwork.BADGING_SD, network.calculateBadge(TEST_RSSI)); + } + + @Test + public void calculateBadgeShouldReturnNoBadge() { + ScoredNetwork network = + buildScoredNetworkWithGivenBadgeForTestRssi(ScoredNetwork.BADGING_NONE); + assertEquals(ScoredNetwork.BADGING_NONE, network.calculateBadge(TEST_RSSI)); + } + + private ScoredNetwork buildScoredNetworkWithGivenBadgeForTestRssi(int badge) { + RssiCurve badgingCurve = + new RssiCurve(RSSI_START, 10, new byte[] {0, 0, 0, 0, 0, 0, (byte) badge}); + Bundle attr = new Bundle(); + attr.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, badgingCurve); + return new ScoredNetwork(KEY, CURVE, false /* meteredHint */, attr); + } } diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp index 9d5860ca0d1a..f5bb821f4e23 100644 --- a/libs/hwui/AnimatorManager.cpp +++ b/libs/hwui/AnimatorManager.cpp @@ -142,7 +142,7 @@ void AnimatorManager::animateNoDamage(TreeInfo& info) { } uint32_t AnimatorManager::animateCommon(TreeInfo& info) { - uint32_t dirtyMask; + uint32_t dirtyMask = 0; AnimateFunctor functor(info, mAnimationHandle->context(), &dirtyMask); auto newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor); mAnimators.erase(newEnd, mAnimators.end()); diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index ac6a28fe6289..86731c9581be 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -134,7 +134,12 @@ void EglManager::initialize() { void EglManager::initExtensions() { auto extensions = StringUtils::split( eglQueryString(mEglDisplay, EGL_EXTENSIONS)); - EglExtensions.bufferAge = extensions.has("EGL_EXT_buffer_age"); + // For our purposes we don't care if EGL_BUFFER_AGE is a result of + // EGL_EXT_buffer_age or EGL_KHR_partial_update as our usage is covered + // under EGL_KHR_partial_update and we don't need the expanded scope + // that EGL_EXT_buffer_age provides. + EglExtensions.bufferAge = extensions.has("EGL_EXT_buffer_age") + || extensions.has("EGL_KHR_partial_update"); EglExtensions.setDamage = extensions.has("EGL_KHR_partial_update"); LOG_ALWAYS_FATAL_IF(!extensions.has("EGL_KHR_swap_buffers_with_damage"), "Missing required extension EGL_KHR_swap_buffers_with_damage"); diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java index bb8eb2cd0797..23a8655a3bb6 100644 --- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -55,12 +55,15 @@ import java.lang.reflect.Method; import java.util.Random; public class CaptivePortalLoginActivity extends Activity { - private static final String TAG = "CaptivePortalLogin"; + private static final String TAG = CaptivePortalLoginActivity.class.getSimpleName(); + private static final boolean DBG = true; + private static final int SOCKET_TIMEOUT_MS = 10000; private enum Result { DISMISSED, UNWANTED, WANTED_AS_IS }; - private URL mURL; + private URL mUrl; + private String mUserAgent; private Network mNetwork; private CaptivePortal mCaptivePortal; private NetworkCallback mNetworkCallback; @@ -72,17 +75,20 @@ public class CaptivePortalLoginActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mCm = ConnectivityManager.from(this); - String url = getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL); - if (url == null) url = mCm.getCaptivePortalServerUrl(); - try { - mURL = new URL(url); - } catch (MalformedURLException e) { - // System misconfigured, bail out in a way that at least provides network access. - Log.e(TAG, "Invalid captive portal URL, url=" + url); - done(Result.WANTED_AS_IS); - } 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 + // at least provides network access. + done(Result.WANTED_AS_IS); + return; + } + if (DBG) { + Log.d(TAG, String.format("onCreate for %s", mUrl.toString())); + } // Also initializes proxy system properties. mCm.bindProcessToNetwork(mNetwork); @@ -149,6 +155,9 @@ public class CaptivePortalLoginActivity extends Activity { } private void done(Result result) { + if (DBG) { + Log.d(TAG, String.format("Result %s for %s", result.name(), mUrl.toString())); + } if (mNetworkCallback != null) { mCm.unregisterNetworkCallback(mNetworkCallback); mNetworkCallback = null; @@ -185,22 +194,31 @@ public class CaptivePortalLoginActivity extends Activity { @Override public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - if (id == R.id.action_use_network) { - done(Result.WANTED_AS_IS); - return true; + final Result result; + final String action; + final int id = item.getItemId(); + switch (id) { + case R.id.action_use_network: + result = Result.WANTED_AS_IS; + action = "USE_NETWORK"; + break; + case R.id.action_do_not_use_network: + result = Result.UNWANTED; + action = "DO_NOT_USE_NETWORK"; + break; + default: + return super.onOptionsItemSelected(item); } - if (id == R.id.action_do_not_use_network) { - done(Result.UNWANTED); - return true; + if (DBG) { + Log.d(TAG, String.format("onOptionsItemSelect %s for %s", action, mUrl.toString())); } - return super.onOptionsItemSelected(item); + done(result); + return true; } @Override public void onDestroy() { super.onDestroy(); - if (mNetworkCallback != null) { mCm.unregisterNetworkCallback(mNetworkCallback); mNetworkCallback = null; @@ -215,11 +233,29 @@ public class CaptivePortalLoginActivity extends Activity { } catch (InterruptedException e) { } } - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(mURL.toString()))); + final String url = mUrl.toString(); + if (DBG) { + Log.d(TAG, "starting activity with intent ACTION_VIEW for " + url); + } + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); + } + } + + private URL getUrl() { + String url = getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL); + if (url == null) { + url = mCm.getCaptivePortalServerUrl(); + } + try { + return new URL(url); + } catch (MalformedURLException e) { + Log.e(TAG, "Invalid captive portal URL " + url); } + return null; } 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. @@ -230,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) { @@ -292,7 +331,7 @@ public class CaptivePortalLoginActivity extends Activity { // settings. Now prompt the WebView read the Network-specific proxy settings. setWebViewProxy(); // Load the real page. - view.loadUrl(mURL.toString()); + view.loadUrl(mUrl.toString()); return; } else if (mPagesLoaded == 2) { // Prevent going back to empty first page. diff --git a/services/core/Android.mk b/services/core/Android.mk index 9f01c1832ef2..a61743d78981 100644 --- a/services/core/Android.mk +++ b/services/core/Android.mk @@ -19,7 +19,7 @@ LOCAL_AIDL_INCLUDES += \ system/netd/server/binder LOCAL_JAVA_LIBRARIES := services.net telephony-common -LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update +LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update2 LOCAL_PROTOC_OPTIMIZE_TYPE := nano ifneq ($(INCREMENTAL_BUILDS),) diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 4f43eac8b3ca..f2e1fb678189 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -217,6 +217,11 @@ class BluetoothManagerService extends IBluetoothManager.Stub { @Override public void onUserRestrictionsChanged(int userId, Bundle newRestrictions, Bundle prevRestrictions) { + if (!newRestrictions.containsKey(UserManager.DISALLOW_BLUETOOTH) + && !prevRestrictions.containsKey(UserManager.DISALLOW_BLUETOOTH)) { + // The relevant restriction has not changed - do nothing. + return; + } final boolean bluetoothDisallowed = newRestrictions.getBoolean(UserManager.DISALLOW_BLUETOOTH); if ((mEnable || mEnableExternal) && bluetoothDisallowed) { @@ -227,6 +232,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { // when from system. } } + updateOppLauncherComponentState(bluetoothDisallowed); } }; @@ -938,7 +944,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub { UserManagerInternal userManagerInternal = LocalServices.getService(UserManagerInternal.class); userManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener); - if (isBluetoothDisallowed()) { + final boolean isBluetoothDisallowed = isBluetoothDisallowed(); + updateOppLauncherComponentState(isBluetoothDisallowed); + if (isBluetoothDisallowed) { return; } if (mEnableExternal && isBluetoothPersistedStateOnBluetooth()) { @@ -1995,6 +2003,24 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } } + /** + * Disables BluetoothOppLauncherActivity component, so the Bluetooth sharing option is not + * offered to the user if Bluetooth is disallowed. Puts the component to its default state if + * Bluetooth is not disallowed. + * + * @param bluetoothDisallowed whether the {@link UserManager.DISALLOW_BLUETOOTH} user + * restriction was set. + */ + private void updateOppLauncherComponentState(boolean bluetoothDisallowed) { + final ComponentName oppLauncherComponent = new ComponentName("com.android.bluetooth", + "com.android.bluetooth.opp.BluetoothOppLauncherActivity"); + final int newState = bluetoothDisallowed + ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED + : PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + mContext.getPackageManager() + .setComponentEnabledSetting(oppLauncherComponent, newState, 0); + } + @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index cef459a3ae39..2f05d0f45399 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -301,7 +301,8 @@ public class NetworkScoreService extends INetworkScoreService.Stub { // If we're not connected at all then create a new connection. if (mServiceConnection == null) { - mServiceConnection = new ScoringServiceConnection(componentName); + mServiceConnection = new ScoringServiceConnection(componentName, + scorerData.packageUid); } // Make sure the connection is connected (idempotent) @@ -325,7 +326,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { @Override public boolean updateScores(ScoredNetwork[] networks) { - if (!mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid())) { + if (!isCallerActiveScorer(getCallingUid())) { throw new SecurityException("Caller with UID " + getCallingUid() + " is not the active scorer."); } @@ -389,7 +390,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { @Override public boolean clearScores() { // Only the active scorer or the system should be allowed to flush all scores. - if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) || isCallerSystemUid()) { + if (isCallerActiveScorer(getCallingUid()) || isCallerSystemUid()) { final long token = Binder.clearCallingIdentity(); try { clearInternal(); @@ -418,10 +419,23 @@ public class NetworkScoreService extends INetworkScoreService.Stub { return false; } + /** + * Determine whether the application with the given UID is the enabled scorer. + * + * @param callingUid the UID to check + * @return true if the provided UID is the active scorer, false otherwise. + */ + @Override + public boolean isCallerActiveScorer(int callingUid) { + synchronized (mServiceConnectionLock) { + return mServiceConnection != null && mServiceConnection.mScoringAppUid == callingUid; + } + } + @Override public void disableScoring() { // Only the active scorer or the system should be allowed to disable scoring. - if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) || isCallerSystemUid()) { + if (isCallerActiveScorer(getCallingUid()) || isCallerSystemUid()) { // no-op for now but we could write to the setting if needed. } else { throw new SecurityException( @@ -623,12 +637,14 @@ public class NetworkScoreService extends INetworkScoreService.Stub { private static class ScoringServiceConnection implements ServiceConnection { private final ComponentName mComponentName; + private final int mScoringAppUid; private volatile boolean mBound = false; private volatile boolean mConnected = false; private volatile INetworkRecommendationProvider mRecommendationProvider; - ScoringServiceConnection(ComponentName componentName) { + ScoringServiceConnection(ComponentName componentName, int scoringAppUid) { mComponentName = componentName; + mScoringAppUid = scoringAppUid; } void connect(Context context) { diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index ea2cf5fb4a7e..9ffe2b78a095 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -80,7 +80,8 @@ import java.util.concurrent.TimeUnit; */ public class NetworkMonitor extends StateMachine { private static final String TAG = NetworkMonitor.class.getSimpleName(); - private static final boolean DBG = false; + private static final boolean DBG = true; + private static final boolean VDBG = false; // Default configuration values for captive portal detection probes. // TODO: append a random length parameter to the default HTTPS url. @@ -432,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); @@ -954,7 +957,7 @@ public class NetworkMonitor extends StateMachine { latencyBroadcast.putExtra(EXTRA_SSID, currentWifiInfo.getSSID()); latencyBroadcast.putExtra(EXTRA_BSSID, currentWifiInfo.getBSSID()); } else { - if (DBG) logw("network info is TYPE_WIFI but no ConnectionInfo found"); + if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found"); return; } break; @@ -967,8 +970,8 @@ public class NetworkMonitor extends StateMachine { if (cellInfo.isRegistered()) { numRegisteredCellInfo++; if (numRegisteredCellInfo > 1) { - log("more than one registered CellInfo. Can't " + - "tell which is active. Bailing."); + if (VDBG) logw("more than one registered CellInfo." + + " Can't tell which is active. Bailing."); return; } if (cellInfo instanceof CellInfoCdma) { @@ -984,7 +987,7 @@ public class NetworkMonitor extends StateMachine { CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity(); latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId); } else { - if (DBG) logw("Registered cellinfo is unrecognized"); + if (VDBG) logw("Registered cellinfo is unrecognized"); return; } } diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index f7b01be48d88..c6bf4c5fcd6a 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -114,7 +114,7 @@ public class NetworkNotificationManager { } if (DBG) { - Slog.d(TAG, "showNotification " + notifyType + Slog.d(TAG, "showNotification id=" + id + " " + notifyType + " transportType=" + getTransportName(transportType) + " extraInfo=" + extraInfo + " highPriority=" + highPriority); } @@ -187,7 +187,7 @@ public class NetworkNotificationManager { try { mNotificationManager.notifyAsUser(NOTIFICATION_ID, id, notification, UserHandle.ALL); } catch (NullPointerException npe) { - Slog.d(TAG, "setNotificationVisible: visible notificationManager npe=" + npe); + Slog.d(TAG, "setNotificationVisible: visible notificationManager error", npe); } } @@ -198,7 +198,7 @@ public class NetworkNotificationManager { try { mNotificationManager.cancelAsUser(NOTIFICATION_ID, id, UserHandle.ALL); } catch (NullPointerException npe) { - Slog.d(TAG, "setNotificationVisible: cancel notificationManager npe=" + npe); + Slog.d(TAG, "setNotificationVisible: cancel notificationManager error", npe); } } diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 6d96a1015aa2..79567d50c31c 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -199,7 +199,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper); mTetherMasterSM.start(); - mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(); + mUpstreamNetworkMonitor = new UpstreamNetworkMonitor( + mContext, mTetherMasterSM, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); mStateReceiver = new StateReceiver(); IntentFilter filter = new IntentFilter(); @@ -1027,38 +1028,6 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } /** - * A NetworkCallback class that relays information of interest to the - * tethering master state machine thread for subsequent processing. - */ - class UpstreamNetworkCallback extends NetworkCallback { - @Override - public void onAvailable(Network network) { - mTetherMasterSM.sendMessage(TetherMasterSM.EVENT_UPSTREAM_CALLBACK, - UpstreamNetworkMonitor.EVENT_ON_AVAILABLE, 0, network); - } - - @Override - public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) { - mTetherMasterSM.sendMessage(TetherMasterSM.EVENT_UPSTREAM_CALLBACK, - UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES, 0, - new NetworkState(null, null, newNc, network, null, null)); - } - - @Override - public void onLinkPropertiesChanged(Network network, LinkProperties newLp) { - mTetherMasterSM.sendMessage(TetherMasterSM.EVENT_UPSTREAM_CALLBACK, - UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES, 0, - new NetworkState(null, newLp, null, network, null, null)); - } - - @Override - public void onLost(Network network) { - mTetherMasterSM.sendMessage(TetherMasterSM.EVENT_UPSTREAM_CALLBACK, - UpstreamNetworkMonitor.EVENT_ON_LOST, 0, network); - } - } - - /** * A class to centralize all the network and link properties information * pertaining to the current and any potential upstream network. * @@ -1072,21 +1041,31 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering * TODO: Investigate whether more "upstream-specific" logic/functionality * could/should be moved here. */ - class UpstreamNetworkMonitor { - static final int EVENT_ON_AVAILABLE = 1; - static final int EVENT_ON_CAPABILITIES = 2; - static final int EVENT_ON_LINKPROPERTIES = 3; - static final int EVENT_ON_LOST = 4; - - final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>(); - NetworkCallback mDefaultNetworkCallback; - NetworkCallback mDunTetheringCallback; - - void start() { + public class UpstreamNetworkMonitor { + public static final int EVENT_ON_AVAILABLE = 1; + public static final int EVENT_ON_CAPABILITIES = 2; + public static final int EVENT_ON_LINKPROPERTIES = 3; + public static final int EVENT_ON_LOST = 4; + + private final Context mContext; + private final StateMachine mTarget; + private final int mWhat; + private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>(); + private ConnectivityManager mCM; + private NetworkCallback mDefaultNetworkCallback; + private NetworkCallback mDunTetheringCallback; + + public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what) { + mContext = ctx; + mTarget = tgt; + mWhat = what; + } + + public void start() { stop(); mDefaultNetworkCallback = new UpstreamNetworkCallback(); - getConnectivityManager().registerDefaultNetworkCallback(mDefaultNetworkCallback); + cm().registerDefaultNetworkCallback(mDefaultNetworkCallback); final NetworkRequest dunTetheringRequest = new NetworkRequest.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) @@ -1094,29 +1073,28 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN) .build(); mDunTetheringCallback = new UpstreamNetworkCallback(); - getConnectivityManager().registerNetworkCallback( - dunTetheringRequest, mDunTetheringCallback); + cm().registerNetworkCallback(dunTetheringRequest, mDunTetheringCallback); } - void stop() { + public void stop() { if (mDefaultNetworkCallback != null) { - getConnectivityManager().unregisterNetworkCallback(mDefaultNetworkCallback); + cm().unregisterNetworkCallback(mDefaultNetworkCallback); mDefaultNetworkCallback = null; } if (mDunTetheringCallback != null) { - getConnectivityManager().unregisterNetworkCallback(mDunTetheringCallback); + cm().unregisterNetworkCallback(mDunTetheringCallback); mDunTetheringCallback = null; } mNetworkMap.clear(); } - NetworkState lookup(Network network) { + public NetworkState lookup(Network network) { return (network != null) ? mNetworkMap.get(network) : null; } - NetworkState processCallback(int arg1, Object obj) { + public NetworkState processCallback(int arg1, Object obj) { switch (arg1) { case EVENT_ON_AVAILABLE: { final Network network = (Network) obj; @@ -1128,7 +1106,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering new NetworkState(null, null, null, network, null, null)); } - final ConnectivityManager cm = getConnectivityManager(); + final ConnectivityManager cm = cm(); if (mDefaultNetworkCallback != null) { cm.requestNetworkCapabilities(mDefaultNetworkCallback); @@ -1199,6 +1177,42 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering return null; } } + + // Fetch (and cache) a ConnectivityManager only if and when we need one. + private ConnectivityManager cm() { + if (mCM == null) { + mCM = mContext.getSystemService(ConnectivityManager.class); + } + return mCM; + } + + /** + * A NetworkCallback class that relays information of interest to the + * tethering master state machine thread for subsequent processing. + */ + private class UpstreamNetworkCallback extends NetworkCallback { + @Override + public void onAvailable(Network network) { + mTarget.sendMessage(mWhat, EVENT_ON_AVAILABLE, 0, network); + } + + @Override + public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) { + mTarget.sendMessage(mWhat, EVENT_ON_CAPABILITIES, 0, + new NetworkState(null, null, newNc, network, null, null)); + } + + @Override + public void onLinkPropertiesChanged(Network network, LinkProperties newLp) { + mTarget.sendMessage(mWhat, EVENT_ON_LINKPROPERTIES, 0, + new NetworkState(null, newLp, null, network, null, null)); + } + + @Override + public void onLost(Network network) { + mTarget.sendMessage(mWhat, EVENT_ON_LOST, 0, network); + } + } } // Needed because the canonical source of upstream truth is just the diff --git a/services/core/java/com/android/server/pm/AbstractStatsBase.java b/services/core/java/com/android/server/pm/AbstractStatsBase.java index 612c4767ccaa..0053f588621a 100644 --- a/services/core/java/com/android/server/pm/AbstractStatsBase.java +++ b/services/core/java/com/android/server/pm/AbstractStatsBase.java @@ -60,12 +60,12 @@ public abstract class AbstractStatsBase<T> { return new AtomicFile(fname); } - void writeNow(final T data) { + protected void writeNow(final T data) { writeImpl(data); mLastTimeWritten.set(SystemClock.elapsedRealtime()); } - boolean maybeWriteAsync(final T data) { + protected boolean maybeWriteAsync(final T data) { if (SystemClock.elapsedRealtime() - mLastTimeWritten.get() < WRITE_INTERVAL_MS && !PackageManagerService.DEBUG_DEXOPT) { return false; @@ -105,7 +105,7 @@ public abstract class AbstractStatsBase<T> { protected abstract void writeInternal(T data); - void read(T data) { + protected void read(T data) { if (mLock) { synchronized (data) { synchronized (mFileLock) { diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index cec105816b36..601a2194e8f3 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -42,12 +42,18 @@ import java.util.concurrent.TimeUnit; * {@hide} */ public class BackgroundDexOptService extends JobService { - static final String TAG = "BackgroundDexOptService"; + private static final String TAG = "BackgroundDexOptService"; - static final long RETRY_LATENCY = 4 * AlarmManager.INTERVAL_HOUR; + private static final boolean DEBUG = false; - static final int JOB_IDLE_OPTIMIZE = 800; - static final int JOB_POST_BOOT_UPDATE = 801; + private static final long RETRY_LATENCY = 4 * AlarmManager.INTERVAL_HOUR; + + private static final int JOB_IDLE_OPTIMIZE = 800; + private static final int JOB_POST_BOOT_UPDATE = 801; + + private static final long IDLE_OPTIMIZATION_PERIOD = DEBUG + ? TimeUnit.MINUTES.toMillis(1) + : TimeUnit.DAYS.toMillis(1); private static ComponentName sDexoptServiceName = new ComponentName( "android", @@ -69,7 +75,7 @@ public class BackgroundDexOptService extends JobService { */ final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false); - private final File dataDir = Environment.getDataDirectory(); + private final File mDataDir = Environment.getDataDirectory(); public static void schedule(Context context) { JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); @@ -86,7 +92,7 @@ public class BackgroundDexOptService extends JobService { js.schedule(new JobInfo.Builder(JOB_IDLE_OPTIMIZE, sDexoptServiceName) .setRequiresDeviceIdle(true) .setRequiresCharging(true) - .setPeriodic(TimeUnit.DAYS.toMillis(1)) + .setPeriodic(IDLE_OPTIMIZATION_PERIOD) .build()); if (DEBUG_DEXOPT) { @@ -120,7 +126,7 @@ public class BackgroundDexOptService extends JobService { private long getLowStorageThreshold() { @SuppressWarnings("deprecation") - final long lowThreshold = StorageManager.from(this).getStorageLowBytes(dataDir); + final long lowThreshold = StorageManager.from(this).getStorageLowBytes(mDataDir); if (lowThreshold == 0) { Log.e(TAG, "Invalid low storage threshold"); } @@ -134,114 +140,127 @@ public class BackgroundDexOptService extends JobService { // This job has already been superseded. Do not start it. return false; } + new Thread("BackgroundDexOptService_PostBootUpdate") { + @Override + public void run() { + postBootUpdate(jobParams, pm, pkgs); + } + }.start(); + return true; + } + + private void postBootUpdate(JobParameters jobParams, PackageManagerService pm, + ArraySet<String> pkgs) { // Load low battery threshold from the system config. This is a 0-100 integer. final int lowBatteryThreshold = getResources().getInteger( com.android.internal.R.integer.config_lowBatteryWarningLevel); - final long lowThreshold = getLowStorageThreshold(); mAbortPostBootUpdate.set(false); - new Thread("BackgroundDexOptService_PostBootUpdate") { + + for (String pkg : pkgs) { + if (mAbortPostBootUpdate.get()) { + // JobScheduler requested an early abort. + return; + } + if (mExitPostBootUpdate.get()) { + // Different job, which supersedes this one, is running. + break; + } + if (getBatteryLevel() < lowBatteryThreshold) { + // Rather bail than completely drain the battery. + break; + } + long usableSpace = mDataDir.getUsableSpace(); + if (usableSpace < lowThreshold) { + // Rather bail than completely fill up the disk. + Log.w(TAG, "Aborting background dex opt job due to low storage: " + + usableSpace); + break; + } + + if (DEBUG_DEXOPT) { + Log.i(TAG, "Updating package " + pkg); + } + + // Update package if needed. Note that there can be no race between concurrent + // jobs because PackageDexOptimizer.performDexOpt is synchronized. + + // checkProfiles is false to avoid merging profiles during boot which + // might interfere with background compilation (b/28612421). + // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will + // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a + // trade-off worth doing to save boot time work. + pm.performDexOpt(pkg, + /* checkProfiles */ false, + PackageManagerService.REASON_BOOT, + /* force */ false); + } + // Ran to completion, so we abandon our timeslice and do not reschedule. + jobFinished(jobParams, /* reschedule */ false); + } + + private boolean runIdleOptimization(final JobParameters jobParams, + final PackageManagerService pm, final ArraySet<String> pkgs) { + new Thread("BackgroundDexOptService_IdleOptimization") { @Override public void run() { - for (String pkg : pkgs) { - if (mAbortPostBootUpdate.get()) { - // JobScheduler requested an early abort. - return; - } - if (mExitPostBootUpdate.get()) { - // Different job, which supersedes this one, is running. - break; - } - if (getBatteryLevel() < lowBatteryThreshold) { - // Rather bail than completely drain the battery. - break; - } - long usableSpace = dataDir.getUsableSpace(); - if (usableSpace < lowThreshold) { - // Rather bail than completely fill up the disk. - Log.w(TAG, "Aborting background dex opt job due to low storage: " + - usableSpace); - break; - } - - if (DEBUG_DEXOPT) { - Log.i(TAG, "Updating package " + pkg); - } - - // Update package if needed. Note that there can be no race between concurrent - // jobs because PackageDexOptimizer.performDexOpt is synchronized. - - // checkProfiles is false to avoid merging profiles during boot which - // might interfere with background compilation (b/28612421). - // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will - // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a - // trade-off worth doing to save boot time work. - pm.performDexOpt(pkg, - /* checkProfiles */ false, - PackageManagerService.REASON_BOOT, - /* force */ false); - } - // Ran to completion, so we abandon our timeslice and do not reschedule. - jobFinished(jobParams, /* reschedule */ false); + idleOptimization(jobParams, pm, pkgs); } }.start(); return true; } - private boolean runIdleOptimization(final JobParameters jobParams, - final PackageManagerService pm, final ArraySet<String> pkgs) { + private void idleOptimization(JobParameters jobParams, PackageManagerService pm, + ArraySet<String> pkgs) { + Log.i(TAG, "Performing idle optimizations"); // If post-boot update is still running, request that it exits early. mExitPostBootUpdate.set(true); mAbortIdleOptimization.set(false); final long lowThreshold = getLowStorageThreshold(); + for (String pkg : pkgs) { + if (mAbortIdleOptimization.get()) { + // JobScheduler requested an early abort. + return; + } - new Thread("BackgroundDexOptService_IdleOptimization") { - @Override - public void run() { - for (String pkg : pkgs) { - if (mAbortIdleOptimization.get()) { - // JobScheduler requested an early abort. - return; - } - if (sFailedPackageNames.contains(pkg)) { - // Skip previously failing package - continue; - } - - long usableSpace = dataDir.getUsableSpace(); - if (usableSpace < lowThreshold) { - // Rather bail than completely fill up the disk. - Log.w(TAG, "Aborting background dex opt job due to low storage: " + - usableSpace); - break; - } - - // Conservatively add package to the list of failing ones in case performDexOpt - // never returns. - synchronized (sFailedPackageNames) { - sFailedPackageNames.add(pkg); - } - // Optimize package if needed. Note that there can be no race between - // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. - if (pm.performDexOpt(pkg, - /* checkProfiles */ true, - PackageManagerService.REASON_BACKGROUND_DEXOPT, - /* force */ false)) { - // Dexopt succeeded, remove package from the list of failing ones. - synchronized (sFailedPackageNames) { - sFailedPackageNames.remove(pkg); - } - } + synchronized (sFailedPackageNames) { + if (sFailedPackageNames.contains(pkg)) { + // Skip previously failing package + continue; } - // Ran to completion, so we abandon our timeslice and do not reschedule. - jobFinished(jobParams, /* reschedule */ false); } - }.start(); - return true; + + long usableSpace = mDataDir.getUsableSpace(); + if (usableSpace < lowThreshold) { + // Rather bail than completely fill up the disk. + Log.w(TAG, "Aborting background dex opt job due to low storage: " + + usableSpace); + break; + } + + // Conservatively add package to the list of failing ones in case performDexOpt + // never returns. + synchronized (sFailedPackageNames) { + sFailedPackageNames.add(pkg); + } + // Optimize package if needed. Note that there can be no race between + // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. + if (pm.performDexOpt(pkg, + /* checkProfiles */ true, + PackageManagerService.REASON_BACKGROUND_DEXOPT, + /* force */ false)) { + // Dexopt succeeded, remove package from the list of failing ones. + synchronized (sFailedPackageNames) { + sFailedPackageNames.remove(pkg); + } + } + } + // Ran to completion, so we abandon our timeslice and do not reschedule. + jobFinished(jobParams, /* reschedule */ false); } @Override diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 605fa5d8d0c3..98249dd12634 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -199,16 +199,44 @@ public class Installer extends SystemService { } } - public void getAppSize(String uuid, String packageName, int userId, int flags, int appId, - long ceDataInode, String codePath, String externalUuid, PackageStats stats) + public void getAppSize(String uuid, String[] packageNames, int userId, int flags, int appId, + long[] ceDataInodes, String[] codePaths, PackageStats stats) throws InstallerException { if (!checkBeforeRemote()) return; try { - final long[] res = mInstalld.getAppSize(uuid, packageName, userId, flags, appId, - ceDataInode, codePath, externalUuid); + final long[] res = mInstalld.getAppSize(uuid, packageNames, userId, flags, + appId, ceDataInodes, codePaths); stats.codeSize += res[0]; stats.dataSize += res[1]; stats.cacheSize += res[2]; + stats.externalCodeSize += res[3]; + stats.externalDataSize += res[4]; + stats.externalCacheSize += res[5]; + } catch (Exception e) { + throw InstallerException.from(e); + } + } + + public void getUserSize(String uuid, int userId, int flags, int[] appIds, PackageStats stats) + throws InstallerException { + if (!checkBeforeRemote()) return; + try { + final long[] res = mInstalld.getUserSize(uuid, userId, flags, appIds); + stats.codeSize += res[0]; + stats.dataSize += res[1]; + stats.cacheSize += res[2]; + stats.externalCodeSize += res[3]; + stats.externalDataSize += res[4]; + stats.externalCacheSize += res[5]; + } catch (Exception e) { + throw InstallerException.from(e); + } + } + + public long[] getExternalSize(String uuid, int userId, int flags) throws InstallerException { + if (!checkBeforeRemote()) return new long[4]; + try { + return mInstalld.getExternalSize(uuid, userId, flags); } catch (Exception e) { throw InstallerException.from(e); } diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 30ff32bd3ee8..aeac7f69ee4f 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -217,26 +217,11 @@ class PackageDexOptimizer { dexoptNeeded); } - final String dexoptType; - String oatDir = null; - boolean isOdexLocation = (dexoptNeeded < 0); - switch (Math.abs(dexoptNeeded)) { - case DexFile.NO_DEXOPT_NEEDED: - continue; - case DexFile.DEX2OAT_FROM_SCRATCH: - case DexFile.DEX2OAT_FOR_BOOT_IMAGE: - case DexFile.DEX2OAT_FOR_FILTER: - case DexFile.DEX2OAT_FOR_RELOCATION: - dexoptType = "dex2oat"; - oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet); - break; - case DexFile.PATCHOAT_FOR_RELOCATION: - dexoptType = "patchoat"; - break; - default: - throw new IllegalStateException("Invalid dexopt:" + dexoptNeeded); + if (dexoptNeeded == DexFile.NO_DEXOPT_NEEDED) { + continue; } + String oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet); String sharedLibrariesPath = null; if (sharedLibraries != null && sharedLibraries.length != 0) { StringBuilder sb = new StringBuilder(); @@ -248,7 +233,7 @@ class PackageDexOptimizer { } sharedLibrariesPath = sb.toString(); } - Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg=" + Log.i(TAG, "Running dexopt on: " + path + " pkg=" + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable + " target-filter=" + targetCompilerFilter + " oatDir = " + oatDir diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 614230c273dd..7a547f00e607 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -253,6 +253,7 @@ import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.PermissionsState.PermissionState; import com.android.server.pm.Settings.DatabaseVersion; import com.android.server.pm.Settings.VersionInfo; +import com.android.server.pm.dex.DexManager; import com.android.server.storage.DeviceStorageMonitorInternal; import dalvik.system.CloseGuard; @@ -295,6 +296,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashSet; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -712,6 +714,9 @@ public class PackageManagerService extends IPackageManager.Stub { final PackageInstallerService mInstallerService; private final PackageDexOptimizer mPackageDexOptimizer; + // DexManager handles the usage of dex files (e.g. secondary files, whether or not a package + // is used by other apps). + private final DexManager mDexManager; private AtomicInteger mNextMoveId = new AtomicInteger(); private final MoveCallbacks mMoveCallbacks; @@ -2116,6 +2121,7 @@ public class PackageManagerService extends IPackageManager.Stub { mInstaller = installer; mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context, "*dexopt*"); + mDexManager = new DexManager(); mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper()); mOnPermissionChangeListeners = new OnPermissionChangeListeners( @@ -2709,6 +2715,21 @@ public class PackageManagerService extends IPackageManager.Stub { } mEphemeralApplicationRegistry = new EphemeralApplicationRegistry(this); + + // Read and update the usage of dex files. + // Do this at the end of PM init so that all the packages have their + // data directory reconciled. + // At this point we know the code paths of the packages, so we can validate + // the disk file and build the internal cache. + // The usage file is expected to be small so loading and verifying it + // should take a fairly small time compare to the other activities (e.g. package + // scanning). + final Map<Integer, List<PackageInfo>> userPackages = new HashMap<>(); + final int[] currentUserIds = UserManagerService.getInstance().getUserIds(); + for (int userId : currentUserIds) { + userPackages.put(userId, getInstalledPackages(/*flags*/ 0, userId).getList()); + } + mDexManager.load(userPackages); } // synchronized (mPackages) } // synchronized (mInstallLock) @@ -7365,7 +7386,14 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public void notifyDexLoad(String loadingPackageName, List<String> dexPaths, String loaderIsa) { - // TODO(calin): b/32871170 + int userId = UserHandle.getCallingUserId(); + ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId); + if (ai == null) { + Slog.w(TAG, "Loading a package that does not exist for the calling user. package=" + + loadingPackageName + ", user=" + userId); + return; + } + mDexManager.notifyDexLoad(ai, dexPaths, loaderIsa, userId); } // TODO: this is not used nor needed. Delete it. @@ -16872,11 +16900,6 @@ public class PackageManagerService extends IPackageManager.Stub { mHandler.sendMessage(msg); } - private boolean equals(PackageStats a, PackageStats b) { - return (a.codeSize == b.codeSize) && (a.dataSize == b.dataSize) - && (a.cacheSize == b.cacheSize); - } - private boolean getPackageSizeInfoLI(String packageName, int userId, PackageStats stats) { final PackageSetting ps; synchronized (mPackages) { @@ -16887,44 +16910,21 @@ public class PackageManagerService extends IPackageManager.Stub { } } - final long ceDataInode = ps.getCeDataInode(userId); - final PackageStats quotaStats = new PackageStats(stats.packageName, stats.userHandle); + final String[] packageNames = { packageName }; + final long[] ceDataInodes = { ps.getCeDataInode(userId) }; + final String[] codePaths = { ps.codePathString }; - final StorageManager storage = mContext.getSystemService(StorageManager.class); - final String externalUuid = storage.getPrimaryStorageUuid(); try { - final long start = SystemClock.elapsedRealtimeNanos(); - mInstaller.getAppSize(ps.volumeUuid, packageName, userId, 0, - ps.appId, ceDataInode, ps.codePathString, externalUuid, stats); - final long stopManual = SystemClock.elapsedRealtimeNanos(); - if (ENABLE_QUOTA) { - mInstaller.getAppSize(ps.volumeUuid, packageName, userId, Installer.FLAG_USE_QUOTA, - ps.appId, ceDataInode, ps.codePathString, externalUuid, quotaStats); - } - final long stopQuota = SystemClock.elapsedRealtimeNanos(); + mInstaller.getAppSize(ps.volumeUuid, packageNames, userId, 0, + ps.appId, ceDataInodes, codePaths, stats); // For now, ignore code size of packages on system partition if (isSystemApp(ps) && !isUpdatedSystemApp(ps)) { stats.codeSize = 0; - quotaStats.codeSize = 0; - } - - if (ENABLE_QUOTA && Build.IS_ENG && !ps.isSharedUser()) { - if (!equals(stats, quotaStats)) { - Log.w(TAG, "Found discrepancy between statistics:"); - Log.w(TAG, "Manual: " + stats); - Log.w(TAG, "Quota: " + quotaStats); - } - final long manualTime = stopManual - start; - final long quotaTime = stopQuota - stopManual; - EventLogTags.writePmPackageStats(manualTime, quotaTime, - stats.dataSize, quotaStats.dataSize, - stats.cacheSize, quotaStats.cacheSize); } // External clients expect these to be tracked separately stats.dataSize -= stats.cacheSize; - quotaStats.dataSize -= quotaStats.cacheSize; } catch (InstallerException e) { Slog.w(TAG, String.valueOf(e)); diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index cfd0af7635e8..45887e1c8a3f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -24,11 +24,13 @@ import android.app.AppGlobals; import android.content.Intent; import android.content.pm.PackageParser; import android.content.pm.ResolveInfo; +import android.os.Build; import android.os.RemoteException; import android.os.UserHandle; import android.system.ErrnoException; import android.util.ArraySet; import android.util.Log; +import dalvik.system.VMRuntime; import libcore.io.Libcore; import java.io.File; @@ -197,4 +199,17 @@ public class PackageManagerServiceUtils { } return sb.toString(); } + + /** + * Verifies that the given string {@code isa} is a valid supported isa on + * the running device. + */ + public static boolean checkISA(String isa) { + for (String abi : Build.SUPPORTED_ABIS) { + if (VMRuntime.getInstructionSet(abi).equals(isa)) { + return true; + } + } + return false; + } } diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java new file mode 100644 index 000000000000..6d06838cd24f --- /dev/null +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.pm.dex; + +import android.content.pm.PackageInfo; +import android.content.pm.PackageParser; +import android.content.pm.ApplicationInfo; + +import android.util.Slog; + +import com.android.server.pm.PackageManagerServiceUtils; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * This class keeps track of how dex files are used. + * Every time it gets a notification about a dex file being loaded it tracks + * its owning package and records it in PackageDexUsage (package-dex-usage.list). + * + * TODO(calin): Extract related dexopt functionality from PackageManagerService + * into this class. + */ +public class DexManager { + private static final String TAG = "DexManager"; + + private static final boolean DEBUG = false; + + // Maps package name to code locations. + // It caches the code locations for the installed packages. This allows for + // faster lookups (no locks) when finding what package owns the dex file. + private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache; + + // PackageDexUsage handles the actual I/O operations. It is responsible to + // encode and save the dex usage data. + private final PackageDexUsage mPackageDexUsage; + + // Possible outcomes of a dex search. + private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found + private static int DEX_SEARCH_FOUND_PRIMARY = 1; // dex file is the primary/base apk + private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk + private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex + + public DexManager() { + mPackageCodeLocationsCache = new HashMap<>(); + mPackageDexUsage = new PackageDexUsage(); + } + + /** + * Notify about dex files loads. + * Note that this method is invoked when apps load dex files and it should + * return as fast as possible. + * + * @param loadingPackage the package performing the load + * @param dexPaths the list of dex files being loaded + * @param loaderIsa the ISA of the app loading the dex files + * @param loaderUserId the user id which runs the code loading the dex files + */ + public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> dexPaths, + String loaderIsa, int loaderUserId) { + try { + notifyDexLoadInternal(loadingAppInfo, dexPaths, loaderIsa, loaderUserId); + } catch (Exception e) { + Slog.w(TAG, "Exception while notifying dex load for package " + + loadingAppInfo.packageName, e); + } + } + + private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, List<String> dexPaths, + String loaderIsa, int loaderUserId) { + if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { + Slog.w(TAG, "Loading dex files " + dexPaths + " in unsupported ISA: " + + loaderIsa + "?"); + return; + } + + for (String dexPath : dexPaths) { + // Find the owning package name. + DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId); + + if (DEBUG) { + Slog.i(TAG, loadingAppInfo.packageName + + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath); + } + + if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) { + // TODO(calin): extend isUsedByOtherApps check to detect the cases where + // different apps share the same runtime. In that case we should not mark the dex + // file as isUsedByOtherApps. Currently this is a safe approximation. + boolean isUsedByOtherApps = !loadingAppInfo.packageName.equals( + searchResult.mOwningPackageName); + boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || + searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT; + + if (primaryOrSplit && !isUsedByOtherApps) { + // If the dex file is the primary apk (or a split) and not isUsedByOtherApps + // do not record it. This case does not bring any new usable information + // and can be safely skipped. + continue; + } + + // Record dex file usage. If the current usage is a new pattern (e.g. new secondary, + // or UsedBytOtherApps), record will return true and we trigger an async write + // to disk to make sure we don't loose the data in case of a reboot. + if (mPackageDexUsage.record(searchResult.mOwningPackageName, + dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit)) { + mPackageDexUsage.maybeWriteAsync(); + } + } else { + // This can happen in a few situations: + // - bogus dex loads + // - recent installs/uninstalls that we didn't detect. + // - new installed splits + // If we can't find the owner of the dex we simply do not track it. The impact is + // that the dex file will not be considered for offline optimizations. + // TODO(calin): add hooks for install/uninstall notifications to + // capture new or obsolete packages. + if (DEBUG) { + Slog.i(TAG, "Could not find owning package for dex file: " + dexPath); + } + } + } + } + + /** + * Read the dex usage from disk and populate the code cache locations. + * @param existingPackages a map containing information about what packages + * are available to what users. Only packages in this list will be + * recognized during notifyDexLoad(). + */ + public void load(Map<Integer, List<PackageInfo>> existingPackages) { + try { + loadInternal(existingPackages); + } catch (Exception e) { + mPackageDexUsage.clear(); + Slog.w(TAG, "Exception while loading package dex usage. " + + "Starting with a fresh state.", e); + } + } + + private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) { + Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); + // Cache the code locations for the installed packages. This allows for + // faster lookups (no locks) when finding what package owns the dex file. + for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) { + List<PackageInfo> packageInfoList = entry.getValue(); + int userId = entry.getKey(); + for (PackageInfo pi : packageInfoList) { + // Cache the code locations. + PackageCodeLocations pcl = mPackageCodeLocationsCache.get(pi.packageName); + if (pcl != null) { + pcl.mergeAppDataDirs(pi.applicationInfo, userId); + } else { + mPackageCodeLocationsCache.put(pi.packageName, + new PackageCodeLocations(pi.applicationInfo, userId)); + } + // Cache a map from package name to the set of user ids who installed the package. + // We will use it to sync the data and remove obsolete entries from + // mPackageDexUsage. + Set<Integer> users = putIfAbsent( + packageToUsersMap, pi.packageName, new HashSet<>()); + users.add(userId); + } + } + + mPackageDexUsage.read(); + mPackageDexUsage.syncData(packageToUsersMap); + } + + /** + * Get the package dex usage for the given package name. + * @return the package data or null if there is no data available for this package. + */ + public PackageDexUsage.PackageUseInfo getPackageUseInfo(String packageName) { + return mPackageDexUsage.getPackageUseInfo(packageName); + } + + /** + * Retrieves the package which owns the given dexPath. + */ + private DexSearchResult getDexPackage( + ApplicationInfo loadingAppInfo, String dexPath, int userId) { + // Ignore framework code. + // TODO(calin): is there a better way to detect it? + if (dexPath.startsWith("/system/framework/")) { + new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); + } + + // First, check if the package which loads the dex file actually owns it. + // Most of the time this will be true and we can return early. + PackageCodeLocations loadingPackageCodeLocations = + new PackageCodeLocations(loadingAppInfo, userId); + int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId); + if (outcome != DEX_SEARCH_NOT_FOUND) { + // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level. + return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome); + } + + // The loadingPackage does not own the dex file. + // Perform a reverse look-up in the cache to detect if any package has ownership. + // Note that we can have false negatives if the cache falls out of date. + for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) { + outcome = pcl.searchDex(dexPath, userId); + if (outcome != DEX_SEARCH_NOT_FOUND) { + return new DexSearchResult(pcl.mPackageName, outcome); + } + } + + // Cache miss. Return not found for the moment. + // + // TODO(calin): this may be because of a newly installed package, an update + // or a new added user. We can either perform a full look up again or register + // observers to be notified of package/user updates. + return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND); + } + + private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) { + V existingValue = map.putIfAbsent(key, newValue); + return existingValue == null ? newValue : existingValue; + } + + /** + * Convenience class to store the different locations where a package might + * own code. + */ + private static class PackageCodeLocations { + private final String mPackageName; + private final String mBaseCodePath; + private final Set<String> mSplitCodePaths; + // Maps user id to the application private directory. + private final Map<Integer, Set<String>> mAppDataDirs; + + public PackageCodeLocations(ApplicationInfo ai, int userId) { + mPackageName = ai.packageName; + mBaseCodePath = ai.sourceDir; + mSplitCodePaths = new HashSet<>(); + if (ai.splitSourceDirs != null) { + for (String split : ai.splitSourceDirs) { + mSplitCodePaths.add(split); + } + } + mAppDataDirs = new HashMap<>(); + mergeAppDataDirs(ai, userId); + } + + public void mergeAppDataDirs(ApplicationInfo ai, int userId) { + Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>()); + dataDirs.add(ai.dataDir); + } + + public int searchDex(String dexPath, int userId) { + // First check that this package is installed or active for the given user. + // If we don't have a data dir it means this user is trying to load something + // unavailable for them. + Set<String> userDataDirs = mAppDataDirs.get(userId); + if (userDataDirs == null) { + Slog.w(TAG, "Trying to load a dex path which does not exist for the current " + + "user. dexPath=" + dexPath + ", userId=" + userId); + return DEX_SEARCH_NOT_FOUND; + } + + if (mBaseCodePath.equals(dexPath)) { + return DEX_SEARCH_FOUND_PRIMARY; + } + if (mSplitCodePaths.contains(dexPath)) { + return DEX_SEARCH_FOUND_SPLIT; + } + for (String dataDir : userDataDirs) { + if (dexPath.startsWith(dataDir)) { + return DEX_SEARCH_FOUND_SECONDARY; + } + } + + // TODO(calin): What if we get a symlink? e.g. data dir may be a symlink, + // /data/data/ -> /data/user/0/. + if (DEBUG) { + try { + String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath)); + if (dexPathReal != dexPath) { + Slog.d(TAG, "Dex loaded with symlink. dexPath=" + + dexPath + " dexPathReal=" + dexPathReal); + } + } catch (IOException e) { + // Ignore + } + } + return DEX_SEARCH_NOT_FOUND; + } + } + + /** + * Convenience class to store ownership search results. + */ + private class DexSearchResult { + private String mOwningPackageName; + private int mOutcome; + + public DexSearchResult(String owningPackageName, int outcome) { + this.mOwningPackageName = owningPackageName; + this.mOutcome = outcome; + } + + @Override + public String toString() { + return mOwningPackageName + "-" + mOutcome; + } + } + + +} diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java new file mode 100644 index 000000000000..10384a2749e8 --- /dev/null +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -0,0 +1,512 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.pm.dex; + +import android.util.AtomicFile; +import android.util.Slog; +import android.os.Build; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.FastPrintWriter; +import com.android.server.pm.AbstractStatsBase; +import com.android.server.pm.PackageManagerServiceUtils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Iterator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import dalvik.system.VMRuntime; +import libcore.io.IoUtils; + +/** + * Stat file which store usage information about dex files. + */ +public class PackageDexUsage extends AbstractStatsBase<Void> { + private final static String TAG = "PackageDexUsage"; + + private final static int PACKAGE_DEX_USAGE_VERSION = 1; + private final static String PACKAGE_DEX_USAGE_VERSION_HEADER = + "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__"; + + private final static String SPLIT_CHAR = ","; + private final static String DEX_LINE_CHAR = "#"; + + // Map which structures the information we have on a package. + // Maps package name to package data (which stores info about UsedByOtherApps and + // secondary dex files.). + // Access to this map needs synchronized. + @GuardedBy("mPackageUseInfoMap") + private Map<String, PackageUseInfo> mPackageUseInfoMap; + + public PackageDexUsage() { + super("package-dex-usage.list", "PackageDexUsage_DiskWriter", /*lock*/ false); + mPackageUseInfoMap = new HashMap<>(); + } + + /** + * Record a dex file load. + * + * Note this is called when apps load dex files and as such it should return + * as fast as possible. + * + * @param loadingPackage the package performing the load + * @param dexPath the path of the dex files being loaded + * @param ownerUserId the user id which runs the code loading the dex files + * @param loaderIsa the ISA of the app loading the dex files + * @param isUsedByOtherApps whether or not this dex file was not loaded by its owning package + * @param primaryOrSplit whether or not the dex file is a primary/split dex. True indicates + * the file is either primary or a split. False indicates the file is secondary dex. + * @return true if the dex load constitutes new information, or false if this information + * has been seen before. + */ + public boolean record(String owningPackageName, String dexPath, int ownerUserId, + String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit) { + if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { + throw new IllegalArgumentException("loaderIsa " + loaderIsa + " is unsupported"); + } + synchronized (mPackageUseInfoMap) { + PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(owningPackageName); + if (packageUseInfo == null) { + // This is the first time we see the package. + packageUseInfo = new PackageUseInfo(); + if (primaryOrSplit) { + // If we have a primary or a split apk, set isUsedByOtherApps. + // We do not need to record the loaderIsa or the owner because we compile + // primaries for all users and all ISAs. + packageUseInfo.mIsUsedByOtherApps = isUsedByOtherApps; + } else { + // For secondary dex files record the loaderISA and the owner. We'll need + // to know under which user to compile and for what ISA. + packageUseInfo.mDexUseInfoMap.put( + dexPath, new DexUseInfo(isUsedByOtherApps, ownerUserId, loaderIsa)); + } + mPackageUseInfoMap.put(owningPackageName, packageUseInfo); + return true; + } else { + // We already have data on this package. Amend it. + if (primaryOrSplit) { + // We have a possible update on the primary apk usage. Merge + // isUsedByOtherApps information and return if there was an update. + return packageUseInfo.merge(isUsedByOtherApps); + } else { + DexUseInfo newData = new DexUseInfo( + isUsedByOtherApps, ownerUserId, loaderIsa); + DexUseInfo existingData = packageUseInfo.mDexUseInfoMap.get(dexPath); + if (existingData == null) { + // It's the first time we see this dex file. + packageUseInfo.mDexUseInfoMap.put(dexPath, newData); + return true; + } else { + if (ownerUserId != existingData.mOwnerUserId) { + // Oups, this should never happen, the DexManager who calls this should + // do the proper checks and not call record if the user does not own the + // dex path. + // Secondary dex files are stored in the app user directory. A change in + // owningUser for the same path means that something went wrong at some + // higher level, and the loaderUser was allowed to cross + // user-boundaries and access data from what we know to be the owner + // user. + throw new IllegalArgumentException("Trying to change ownerUserId for " + + " dex path " + dexPath + " from " + existingData.mOwnerUserId + + " to " + ownerUserId); + } + // Merge the information into the existing data. + // Returns true if there was an update. + return existingData.merge(newData); + } + } + } + } + } + + /** + * Convenience method for sync reads which does not force the user to pass a useless + * (Void) null. + */ + public void read() { + read((Void) null); + } + + /** + * Convenience method for async writes which does not force the user to pass a useless + * (Void) null. + */ + public void maybeWriteAsync() { + maybeWriteAsync((Void) null); + } + + @Override + protected void writeInternal(Void data) { + AtomicFile file = getFile(); + FileOutputStream f = null; + + try { + f = file.startWrite(); + OutputStreamWriter osw = new OutputStreamWriter(f); + write(osw); + osw.flush(); + file.finishWrite(f); + } catch (IOException e) { + if (f != null) { + file.failWrite(f); + } + Slog.e(TAG, "Failed to write usage for dex files", e); + } + } + + /** + * File format: + * + * file_magic_version + * package_name_1 + * #dex_file_path_1_1 + * user_1_1, used_by_other_app_1_1, user_isa_1_1_1, user_isa_1_1_2 + * #dex_file_path_1_2 + * user_1_2, used_by_other_app_1_2, user_isa_1_2_1, user_isa_1_2_2 + * ... + * package_name_2 + * #dex_file_path_2_1 + * user_2_1, used_by_other_app_2_1, user_isa_2_1_1, user_isa_2_1_2 + * #dex_file_path_2_2, + * user_2_2, used_by_other_app_2_2, user_isa_2_2_1, user_isa_2_2_2 + * ... + */ + /* package */ void write(Writer out) { + // Make a clone to avoid locking while writing to disk. + Map<String, PackageUseInfo> packageUseInfoMapClone = clonePackageUseInfoMap(); + + FastPrintWriter fpw = new FastPrintWriter(out); + + // Write the header. + fpw.print(PACKAGE_DEX_USAGE_VERSION_HEADER); + fpw.println(PACKAGE_DEX_USAGE_VERSION); + + for (Map.Entry<String, PackageUseInfo> pEntry : packageUseInfoMapClone.entrySet()) { + // Write the package line. + String packageName = pEntry.getKey(); + PackageUseInfo packageUseInfo = pEntry.getValue(); + + fpw.println(String.join(SPLIT_CHAR, packageName, + writeBoolean(packageUseInfo.mIsUsedByOtherApps))); + + // Write dex file lines. + for (Map.Entry<String, DexUseInfo> dEntry : packageUseInfo.mDexUseInfoMap.entrySet()) { + String dexPath = dEntry.getKey(); + DexUseInfo dexUseInfo = dEntry.getValue(); + fpw.println(DEX_LINE_CHAR + dexPath); + fpw.print(String.join(SPLIT_CHAR, Integer.toString(dexUseInfo.mOwnerUserId), + writeBoolean(dexUseInfo.mIsUsedByOtherApps))); + for (String isa : dexUseInfo.mLoaderIsas) { + fpw.print(SPLIT_CHAR + isa); + } + fpw.println(); + } + } + fpw.flush(); + } + + @Override + protected void readInternal(Void data) { + AtomicFile file = getFile(); + BufferedReader in = null; + try { + in = new BufferedReader(new InputStreamReader(file.openRead())); + read(in); + } catch (FileNotFoundException expected) { + // The file may not be there. E.g. When we first take the OTA with this feature. + } catch (IOException e) { + Slog.w(TAG, "Failed to parse package dex usage.", e); + } finally { + IoUtils.closeQuietly(in); + } + } + + /* package */ void read(Reader reader) throws IOException { + Map<String, PackageUseInfo> data = new HashMap<>(); + BufferedReader in = new BufferedReader(reader); + // Read header, do version check. + String versionLine = in.readLine(); + if (versionLine == null) { + throw new IllegalStateException("No version line found."); + } else { + if (!versionLine.startsWith(PACKAGE_DEX_USAGE_VERSION_HEADER)) { + // TODO(calin): the caller is responsible to clear the file. + throw new IllegalStateException("Invalid version line: " + versionLine); + } + int version = Integer.parseInt( + versionLine.substring(PACKAGE_DEX_USAGE_VERSION_HEADER.length())); + if (version != PACKAGE_DEX_USAGE_VERSION) { + throw new IllegalStateException("Unexpected version: " + version); + } + } + + String s = null; + String currentPakage = null; + PackageUseInfo currentPakageData = null; + + Set<String> supportedIsas = new HashSet<>(); + for (String abi : Build.SUPPORTED_ABIS) { + supportedIsas.add(VMRuntime.getInstructionSet(abi)); + } + while ((s = in.readLine()) != null) { + if (s.startsWith(DEX_LINE_CHAR)) { + // This is the start of the the dex lines. + // We expect two lines for each dex entry: + // #dexPaths + // onwerUserId,isUsedByOtherApps,isa1,isa2 + if (currentPakage == null) { + throw new IllegalStateException( + "Malformed PackageDexUsage file. Expected package line before dex line."); + } + + // First line is the dex path. + String dexPath = s.substring(DEX_LINE_CHAR.length()); + // Next line is the dex data. + s = in.readLine(); + if (s == null) { + throw new IllegalStateException("Could not fine dexUseInfo for line: " + s); + } + + // We expect at least 3 elements (isUsedByOtherApps, userId, isa). + String[] elems = s.split(SPLIT_CHAR); + if (elems.length < 3) { + throw new IllegalStateException("Invalid PackageDexUsage line: " + s); + } + int ownerUserId = Integer.parseInt(elems[0]); + boolean isUsedByOtherApps = readBoolean(elems[1]); + DexUseInfo dexUseInfo = new DexUseInfo(isUsedByOtherApps, ownerUserId); + for (int i = 2; i < elems.length; i++) { + String isa = elems[i]; + if (supportedIsas.contains(isa)) { + dexUseInfo.mLoaderIsas.add(elems[i]); + } else { + // Should never happen unless someone crafts the file manually. + // In theory it could if we drop a supported ISA after an OTA but we don't + // do that. + Slog.wtf(TAG, "Unsupported ISA when parsing PackageDexUsage: " + isa); + } + } + if (supportedIsas.isEmpty()) { + Slog.wtf(TAG, "Ignore dexPath when parsing PackageDexUsage because of " + + "unsupported isas. dexPath=" + dexPath); + continue; + } + currentPakageData.mDexUseInfoMap.put(dexPath, dexUseInfo); + } else { + // This is a package line. + // We expect it to be: `packageName,isUsedByOtherApps`. + String[] elems = s.split(SPLIT_CHAR); + if (elems.length != 2) { + throw new IllegalStateException("Invalid PackageDexUsage line: " + s); + } + currentPakage = elems[0]; + currentPakageData = new PackageUseInfo(); + currentPakageData.mIsUsedByOtherApps = readBoolean(elems[1]); + data.put(currentPakage, currentPakageData); + } + } + + synchronized (mPackageUseInfoMap) { + mPackageUseInfoMap.clear(); + mPackageUseInfoMap.putAll(data); + } + } + + /** + * Syncs the existing data with the set of available packages by removing obsolete entries. + */ + public void syncData(Map<String, Set<Integer>> packageToUsersMap) { + synchronized (mPackageUseInfoMap) { + Iterator<Map.Entry<String, PackageUseInfo>> pIt = + mPackageUseInfoMap.entrySet().iterator(); + while (pIt.hasNext()) { + Map.Entry<String, PackageUseInfo> pEntry = pIt.next(); + String packageName = pEntry.getKey(); + PackageUseInfo packageUseInfo = pEntry.getValue(); + Set<Integer> users = packageToUsersMap.get(packageName); + if (users == null) { + // The package doesn't exist anymore, remove the record. + pIt.remove(); + } else { + // The package exists but we can prune the entries associated with non existing + // users. + Iterator<Map.Entry<String, DexUseInfo>> dIt = + packageUseInfo.mDexUseInfoMap.entrySet().iterator(); + while (dIt.hasNext()) { + DexUseInfo dexUseInfo = dIt.next().getValue(); + if (!users.contains(dexUseInfo.mOwnerUserId)) { + // User was probably removed. Delete its dex usage info. + dIt.remove(); + } + } + if (!packageUseInfo.mIsUsedByOtherApps + && packageUseInfo.mDexUseInfoMap.isEmpty()) { + // The package is not used by other apps and we removed all its dex files + // records. Remove the entire package record as well. + pIt.remove(); + } + } + } + } + } + + public PackageUseInfo getPackageUseInfo(String packageName) { + synchronized (mPackageUseInfoMap) { + return mPackageUseInfoMap.get(packageName); + } + } + + public void clear() { + synchronized (mPackageUseInfoMap) { + mPackageUseInfoMap.clear(); + } + } + // Creates a deep copy of the class' mPackageUseInfoMap. + private Map<String, PackageUseInfo> clonePackageUseInfoMap() { + Map<String, PackageUseInfo> clone = new HashMap<>(); + synchronized (mPackageUseInfoMap) { + for (Map.Entry<String, PackageUseInfo> e : mPackageUseInfoMap.entrySet()) { + clone.put(e.getKey(), new PackageUseInfo(e.getValue())); + } + } + return clone; + } + + private String writeBoolean(boolean bool) { + return bool ? "1" : "0"; + } + + private boolean readBoolean(String bool) { + if ("0".equals(bool)) return false; + if ("1".equals(bool)) return true; + throw new IllegalArgumentException("Unknown bool encoding: " + bool); + } + + private boolean contains(int[] array, int elem) { + for (int i = 0; i < array.length; i++) { + if (elem == array[i]) { + return true; + } + } + return false; + } + + public String dump() { + StringWriter sw = new StringWriter(); + write(sw); + return sw.toString(); + } + + /** + * Stores data on how a package and its dex files are used. + */ + public static class PackageUseInfo { + // This flag is for the primary and split apks. It is set to true whenever one of them + // is loaded by another app. + private boolean mIsUsedByOtherApps; + // Map dex paths to their data (isUsedByOtherApps, owner id, loader isa). + private final Map<String, DexUseInfo> mDexUseInfoMap; + + public PackageUseInfo() { + mIsUsedByOtherApps = false; + mDexUseInfoMap = new HashMap<>(); + } + + // Creates a deep copy of the `other`. + public PackageUseInfo(PackageUseInfo other) { + mIsUsedByOtherApps = other.mIsUsedByOtherApps; + mDexUseInfoMap = new HashMap<>(); + for (Map.Entry<String, DexUseInfo> e : other.mDexUseInfoMap.entrySet()) { + mDexUseInfoMap.put(e.getKey(), new DexUseInfo(e.getValue())); + } + } + + private boolean merge(boolean isUsedByOtherApps) { + boolean oldIsUsedByOtherApps = mIsUsedByOtherApps; + mIsUsedByOtherApps = mIsUsedByOtherApps || isUsedByOtherApps; + return oldIsUsedByOtherApps != this.mIsUsedByOtherApps; + } + + public boolean isUsedByOtherApps() { + return mIsUsedByOtherApps; + } + + public Map<String, DexUseInfo> getDexUseInfoMap() { + return mDexUseInfoMap; + } + } + + /** + * Stores data about a loaded dex files. + */ + public static class DexUseInfo { + private boolean mIsUsedByOtherApps; + private final int mOwnerUserId; + private final Set<String> mLoaderIsas; + + public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId) { + this(isUsedByOtherApps, ownerUserId, null); + } + + public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId, String loaderIsa) { + mIsUsedByOtherApps = isUsedByOtherApps; + mOwnerUserId = ownerUserId; + mLoaderIsas = new HashSet<>(); + if (loaderIsa != null) { + mLoaderIsas.add(loaderIsa); + } + } + + // Creates a deep copy of the `other`. + public DexUseInfo(DexUseInfo other) { + mIsUsedByOtherApps = other.mIsUsedByOtherApps; + mOwnerUserId = other.mOwnerUserId; + mLoaderIsas = new HashSet<>(other.mLoaderIsas); + } + + private boolean merge(DexUseInfo dexUseInfo) { + boolean oldIsUsedByOtherApps = mIsUsedByOtherApps; + mIsUsedByOtherApps = mIsUsedByOtherApps || dexUseInfo.mIsUsedByOtherApps; + boolean updateIsas = mLoaderIsas.addAll(dexUseInfo.mLoaderIsas); + return updateIsas || (oldIsUsedByOtherApps != mIsUsedByOtherApps); + } + + public boolean isUsedByOtherApps() { + return mIsUsedByOtherApps; + } + + public int getOwnerUserId() { + return mOwnerUserId; + } + + public Set<String> getLoaderIsas() { + return mLoaderIsas; + } + } +} diff --git a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java index b260e4e2fc34..b704eb1d991c 100644 --- a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java +++ b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java @@ -20,7 +20,7 @@ import android.util.Slog; import java.io.File; import java.io.IOException; -import libcore.tzdata.update.TzDataBundleInstaller; +import libcore.tzdata.update2.TimeZoneBundleInstaller; /** * An install receiver responsible for installing timezone data updates. @@ -29,18 +29,19 @@ public class TzDataInstallReceiver extends ConfigUpdateInstallReceiver { private static final String TAG = "TZDataInstallReceiver"; + private static final File SYSTEM_TZ_DATA_FILE = new File("/system/usr/share/zoneinfo/tzdata"); private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo"); private static final String UPDATE_DIR_NAME = TZ_DATA_DIR.getPath() + "/updates/"; private static final String UPDATE_METADATA_DIR_NAME = "metadata/"; private static final String UPDATE_VERSION_FILE_NAME = "version"; private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_bundle.zip"; - private final TzDataBundleInstaller installer; + private final TimeZoneBundleInstaller installer; public TzDataInstallReceiver() { super(UPDATE_DIR_NAME, UPDATE_CONTENT_FILE_NAME, UPDATE_METADATA_DIR_NAME, UPDATE_VERSION_FILE_NAME); - installer = new TzDataBundleInstaller(TAG, TZ_DATA_DIR); + installer = new TimeZoneBundleInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR); } @Override diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index 9c5c67237fd2..9bb7bd1c5fd6 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -61,6 +61,7 @@ import android.net.RecommendationResult; import android.net.ScoredNetwork; import android.net.WifiKey; import android.net.wifi.WifiConfiguration; +import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; @@ -261,7 +262,7 @@ public class NetworkScoreServiceTest { @Test public void testUpdateScores_notActiveScorer() { - when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false); + bindToScorer(false /*callerIsScorer*/); try { mNetworkScoreService.updateScores(new ScoredNetwork[0]); @@ -273,7 +274,7 @@ public class NetworkScoreServiceTest { @Test public void testUpdateScores_oneRegisteredCache() throws RemoteException { - when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true); + bindToScorer(true /*callerIsScorer*/); mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache, CACHE_FILTER_NONE); @@ -288,7 +289,7 @@ public class NetworkScoreServiceTest { @Test public void testUpdateScores_twoRegisteredCaches() throws RemoteException { - when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true); + bindToScorer(true /*callerIsScorer*/); mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache, CACHE_FILTER_NONE); @@ -322,8 +323,8 @@ public class NetworkScoreServiceTest { } @Test - public void testClearScores_notActiveScorer_noBroadcastNetworkPermission() { - when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false); + public void testClearScores_notActiveScorer_noRequestNetworkScoresPermission() { + bindToScorer(false /*callerIsScorer*/); when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES)) .thenReturn(PackageManager.PERMISSION_DENIED); try { @@ -335,8 +336,8 @@ public class NetworkScoreServiceTest { } @Test - public void testClearScores_activeScorer_noBroadcastNetworkPermission() { - when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true); + public void testClearScores_activeScorer_noRequestNetworkScoresPermission() { + bindToScorer(true /*callerIsScorer*/); when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES)) .thenReturn(PackageManager.PERMISSION_DENIED); @@ -345,7 +346,7 @@ public class NetworkScoreServiceTest { @Test public void testClearScores_activeScorer() throws RemoteException { - when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true); + bindToScorer(true /*callerIsScorer*/); mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache, CACHE_FILTER_NONE); @@ -355,9 +356,9 @@ public class NetworkScoreServiceTest { } @Test - public void testClearScores_notActiveScorer_hasBroadcastNetworkPermission() + public void testClearScores_notActiveScorer_hasRequestNetworkScoresPermission() throws RemoteException { - when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false); + bindToScorer(false /*callerIsScorer*/); when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES)) .thenReturn(PackageManager.PERMISSION_GRANTED); @@ -382,8 +383,8 @@ public class NetworkScoreServiceTest { } @Test - public void testDisableScoring_notActiveScorer_noBroadcastNetworkPermission() { - when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false); + public void testDisableScoring_notActiveScorer_noRequestNetworkScoresPermission() { + bindToScorer(false /*callerIsScorer*/); when(mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES)) .thenReturn(PackageManager.PERMISSION_DENIED); @@ -396,7 +397,7 @@ public class NetworkScoreServiceTest { } @Test - public void testRegisterNetworkScoreCache_noBroadcastNetworkPermission() { + public void testRegisterNetworkScoreCache_noRequestNetworkScoresPermission() { doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( eq(permission.REQUEST_NETWORK_SCORES), anyString()); @@ -410,7 +411,7 @@ public class NetworkScoreServiceTest { } @Test - public void testUnregisterNetworkScoreCache_noBroadcastNetworkPermission() { + public void testUnregisterNetworkScoreCache_noRequestNetworkScoresPermission() { doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( eq(permission.REQUEST_NETWORK_SCORES), anyString()); @@ -448,6 +449,27 @@ public class NetworkScoreServiceTest { assertFalse(stringWriter.toString().isEmpty()); } + @Test + public void testIsCallerActiveScorer_noBoundService() throws Exception { + mNetworkScoreService.systemRunning(); + + assertFalse(mNetworkScoreService.isCallerActiveScorer(Binder.getCallingUid())); + } + + @Test + public void testIsCallerActiveScorer_boundServiceIsNotCaller() throws Exception { + bindToScorer(false /*callerIsScorer*/); + + assertFalse(mNetworkScoreService.isCallerActiveScorer(Binder.getCallingUid())); + } + + @Test + public void testIsCallerActiveScorer_boundServiceIsCaller() throws Exception { + bindToScorer(true /*callerIsScorer*/); + + assertTrue(mNetworkScoreService.isCallerActiveScorer(Binder.getCallingUid())); + } + // "injects" the mock INetworkRecommendationProvider into the NetworkScoreService. private void injectProvider() { final ComponentName componentName = new ComponentName(NEW_SCORER.packageName, @@ -467,4 +489,14 @@ public class NetworkScoreServiceTest { }); mNetworkScoreService.systemRunning(); } + + private void bindToScorer(boolean callerIsScorer) { + final int callingUid = callerIsScorer ? Binder.getCallingUid() : 0; + NetworkScorerAppData appData = new NetworkScorerAppData(NEW_SCORER.packageName, + callingUid, NEW_SCORER.recommendationServiceClassName); + when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(appData); + when(mContext.bindServiceAsUser(isA(Intent.class), isA(ServiceConnection.class), anyInt(), + isA(UserHandle.class))).thenReturn(true); + mNetworkScoreService.systemRunning(); + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java new file mode 100644 index 000000000000..2a7cbc21137a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageStats; +import android.os.UserHandle; +import android.test.AndroidTestCase; +import android.util.Log; + +import com.android.internal.util.ArrayUtils; + +import java.util.Arrays; + +public class InstallerTest extends AndroidTestCase { + private static final String TAG = "InstallerTest"; + + private Installer mInstaller; + + @Override + public void setUp() throws Exception { + mInstaller = new Installer(getContext()); + mInstaller.onStart(); + } + + @Override + public void tearDown() throws Exception { + mInstaller = null; + } + + public void testGetAppSize() throws Exception { + final PackageManager pm = getContext().getPackageManager(); + for (ApplicationInfo app : pm.getInstalledApplications(0)) { + final int userId = UserHandle.getUserId(app.uid); + final int appId = UserHandle.getAppId(app.uid); + + final String[] packageNames = pm.getPackagesForUid(app.uid); + final long[] ceDataInodes = new long[packageNames.length]; + final String[] codePaths = new String[packageNames.length]; + + for (int i = 0; i < packageNames.length; i++) { + final ApplicationInfo info = pm.getApplicationInfo(packageNames[i], 0); + codePaths[i] = info.getCodePath(); + } + + final PackageStats stats = new PackageStats(app.packageName); + final PackageStats quotaStats = new PackageStats(app.packageName); + + mInstaller.getAppSize(app.volumeUuid, packageNames, userId, 0, + appId, ceDataInodes, codePaths, stats); + + mInstaller.getAppSize(app.volumeUuid, packageNames, userId, Installer.FLAG_USE_QUOTA, + appId, ceDataInodes, codePaths, quotaStats); + + checkEquals(Arrays.toString(packageNames) + " UID=" + app.uid, stats, quotaStats); + } + } + + public void testGetUserSize() throws Exception { + int[] appIds = null; + + final PackageManager pm = getContext().getPackageManager(); + for (ApplicationInfo app : pm.getInstalledApplications(0)) { + final int appId = UserHandle.getAppId(app.uid); + if (!ArrayUtils.contains(appIds, appId)) { + appIds = ArrayUtils.appendInt(appIds, appId); + } + } + + final PackageStats stats = new PackageStats("android"); + final PackageStats quotaStats = new PackageStats("android"); + + mInstaller.getUserSize(null, UserHandle.USER_SYSTEM, 0, + appIds, stats); + + mInstaller.getUserSize(null, UserHandle.USER_SYSTEM, Installer.FLAG_USE_QUOTA, + appIds, quotaStats); + + checkEquals(Arrays.toString(appIds), stats, quotaStats); + } + + public void testGetExternalSize() throws Exception { + + final long[] stats = mInstaller.getExternalSize(null, UserHandle.USER_SYSTEM, 0); + + final long[] quotaStats = mInstaller.getExternalSize(null, UserHandle.USER_SYSTEM, + Installer.FLAG_USE_QUOTA); + + for (int i = 0; i < stats.length; i++) { + checkEquals("#" + i, stats[i], quotaStats[i]); + } + } + + private static void checkEquals(String msg, PackageStats a, PackageStats b) { + checkEquals(msg + " codeSize", a.codeSize, b.codeSize); + checkEquals(msg + " dataSize", a.dataSize, b.dataSize); + checkEquals(msg + " cacheSize", a.cacheSize, b.cacheSize); + checkEquals(msg + " externalCodeSize", a.externalCodeSize, b.externalCodeSize); + checkEquals(msg + " externalDataSize", a.externalDataSize, b.externalDataSize); + checkEquals(msg + " externalCacheSize", a.externalCacheSize, b.externalCacheSize); + } + + private static void checkEquals(String msg, long expected, long actual) { + if (expected != actual) { + Log.e(TAG, msg + " expected " + expected + " actual " + actual); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java new file mode 100644 index 000000000000..b655f3af3a24 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.pm.dex; + +import android.os.Build; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import dalvik.system.VMRuntime; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; +import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DexManagerTests { + private DexManager mDexManager; + + private TestData mFooUser0; + private TestData mBarUser0; + private TestData mBarUser1; + private TestData mInvalidIsa; + private TestData mDoesNotExist; + + private int mUser0; + private int mUser1; + @Before + public void setup() { + + mUser0 = 0; + mUser1 = 1; + + String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); + String foo = "foo"; + String bar = "bar"; + + mFooUser0 = new TestData(foo, isa, mUser0); + mBarUser0 = new TestData(bar, isa, mUser0); + mBarUser1 = new TestData(bar, isa, mUser1); + mInvalidIsa = new TestData("INVALID", "INVALID_ISA", mUser0); + mDoesNotExist = new TestData("DOES.NOT.EXIST", isa, mUser1); + + + mDexManager = new DexManager(); + + // Foo and Bar are available to user0. + // Only Bar is available to user1; + Map<Integer, List<PackageInfo>> existingPackages = new HashMap<>(); + existingPackages.put(mUser0, Arrays.asList(mFooUser0.mPackageInfo, mBarUser0.mPackageInfo)); + existingPackages.put(mUser1, Arrays.asList(mBarUser1.mPackageInfo)); + mDexManager.load(existingPackages); + } + + @Test + public void testNotifyPrimaryUse() { + // The main dex file and splits are re-loaded by the app. + notifyDexLoad(mFooUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser0); + + // Package is not used by others, so we should get nothing back. + assertNull(getPackageUseInfo(mFooUser0)); + } + + @Test + public void testNotifyPrimaryForeignUse() { + // Foo loads Bar main apks. + notifyDexLoad(mFooUser0, mBarUser0.getBaseAndSplitDexPaths(), mUser0); + + // Bar is used by others now and should be in our records + PackageUseInfo pui = getPackageUseInfo(mBarUser0); + assertNotNull(pui); + assertTrue(pui.isUsedByOtherApps()); + assertTrue(pui.getDexUseInfoMap().isEmpty()); + } + + @Test + public void testNotifySecondary() { + // Foo loads its own secondary files. + List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths(); + notifyDexLoad(mFooUser0, fooSecondaries, mUser0); + + PackageUseInfo pui = getPackageUseInfo(mFooUser0); + assertNotNull(pui); + assertFalse(pui.isUsedByOtherApps()); + assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); + assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); + } + + @Test + public void testNotifySecondaryForeign() { + // Foo loads bar secondary files. + List<String> barSecondaries = mBarUser0.getSecondaryDexPaths(); + notifyDexLoad(mFooUser0, barSecondaries, mUser0); + + PackageUseInfo pui = getPackageUseInfo(mBarUser0); + assertNotNull(pui); + assertFalse(pui.isUsedByOtherApps()); + assertEquals(barSecondaries.size(), pui.getDexUseInfoMap().size()); + assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0); + } + + @Test + public void testNotifySequence() { + // Foo loads its own secondary files. + List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths(); + notifyDexLoad(mFooUser0, fooSecondaries, mUser0); + // Foo loads Bar own secondary files. + List<String> barSecondaries = mBarUser0.getSecondaryDexPaths(); + notifyDexLoad(mFooUser0, barSecondaries, mUser0); + // Foo loads Bar primary files. + notifyDexLoad(mFooUser0, mBarUser0.getBaseAndSplitDexPaths(), mUser0); + // Bar loads its own secondary files. + notifyDexLoad(mBarUser0, barSecondaries, mUser0); + // Bar loads some own secondary files which foo didn't load. + List<String> barSecondariesForOwnUse = mBarUser0.getSecondaryDexPathsForOwnUse(); + notifyDexLoad(mBarUser0, barSecondariesForOwnUse, mUser0); + + // Check bar usage. Should be used by other app (for primary and barSecondaries). + PackageUseInfo pui = getPackageUseInfo(mBarUser0); + assertNotNull(pui); + assertTrue(pui.isUsedByOtherApps()); + assertEquals(barSecondaries.size() + barSecondariesForOwnUse.size(), + pui.getDexUseInfoMap().size()); + + assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0); + assertSecondaryUse(mFooUser0, pui, barSecondariesForOwnUse, + /*isUsedByOtherApps*/false, mUser0); + + // Check foo usage. Should not be used by other app. + pui = getPackageUseInfo(mFooUser0); + assertNotNull(pui); + assertFalse(pui.isUsedByOtherApps()); + assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); + assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); + } + + @Test + public void testPackageUseInfoNotFound() { + // Assert we don't get back data we did not previously record. + assertNull(getPackageUseInfo(mFooUser0)); + } + + @Test + public void testInvalidIsa() { + // Notifying with an invalid ISA should be ignored. + notifyDexLoad(mInvalidIsa, mInvalidIsa.getSecondaryDexPaths(), mUser0); + assertNull(getPackageUseInfo(mInvalidIsa)); + } + + @Test + public void testNotExistingPackate() { + // Notifying about the load of a package which was previously not + // register in DexManager#load should be ignored. + notifyDexLoad(mDoesNotExist, mDoesNotExist.getBaseAndSplitDexPaths(), mUser0); + assertNull(getPackageUseInfo(mDoesNotExist)); + } + + @Test + public void testCrossUserAttempt() { + // Bar from User1 tries to load secondary dex files from User0 Bar. + // Request should be ignored. + notifyDexLoad(mBarUser1, mBarUser0.getSecondaryDexPaths(), mUser1); + assertNull(getPackageUseInfo(mBarUser1)); + } + + @Test + public void testPackageNotInstalledForUser() { + // User1 tries to load Foo which is installed for User0 but not for User1. + // Note that the PackageManagerService already filters this out but we + // still check that nothing goes unexpected in DexManager. + notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser1); + assertNull(getPackageUseInfo(mBarUser1)); + } + + private void assertSecondaryUse(TestData testData, PackageUseInfo pui, + List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) { + for (String dex : secondaries) { + DexUseInfo dui = pui.getDexUseInfoMap().get(dex); + assertNotNull(dui); + assertEquals(isUsedByOtherApps, dui.isUsedByOtherApps()); + assertEquals(ownerUserId, dui.getOwnerUserId()); + assertEquals(1, dui.getLoaderIsas().size()); + assertTrue(dui.getLoaderIsas().contains(testData.mLoaderIsa)); + } + } + + private void notifyDexLoad(TestData testData, List<String> dexPaths, int loaderUserId) { + mDexManager.notifyDexLoad(testData.mPackageInfo.applicationInfo, dexPaths, + testData.mLoaderIsa, loaderUserId); + } + + private PackageUseInfo getPackageUseInfo(TestData testData) { + return mDexManager.getPackageUseInfo(testData.mPackageInfo.packageName); + } + + private static PackageInfo getMockPackageInfo(String packageName, int userId) { + PackageInfo pi = new PackageInfo(); + pi.packageName = packageName; + pi.applicationInfo = getMockApplicationInfo(packageName, userId); + return pi; + } + + private static ApplicationInfo getMockApplicationInfo(String packageName, int userId) { + ApplicationInfo ai = new ApplicationInfo(); + String codeDir = "/data/app/" + packageName; + ai.setBaseCodePath(codeDir + "/base.dex"); + ai.setSplitCodePaths(new String[] {codeDir + "/split-1.dex", codeDir + "/split-2.dex"}); + ai.dataDir = "/data/user/" + userId + "/" + packageName; + ai.packageName = packageName; + return ai; + } + + private static class TestData { + private final PackageInfo mPackageInfo; + private final String mLoaderIsa; + + private TestData(String packageName, String loaderIsa, int userId) { + mPackageInfo = getMockPackageInfo(packageName, userId); + mLoaderIsa = loaderIsa; + } + + private String getPackageName() { + return mPackageInfo.packageName; + } + + List<String> getSecondaryDexPaths() { + List<String> paths = new ArrayList<>(); + paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary1.dex"); + paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary2.dex"); + paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary3.dex"); + return paths; + } + + List<String> getSecondaryDexPathsForOwnUse() { + List<String> paths = new ArrayList<>(); + paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary4.dex"); + paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary5.dex"); + return paths; + } + + List<String> getBaseAndSplitDexPaths() { + List<String> paths = new ArrayList<>(); + paths.add(mPackageInfo.applicationInfo.sourceDir); + for (String split : mPackageInfo.applicationInfo.splitSourceDirs) { + paths.add(split); + } + return paths; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java new file mode 100644 index 000000000000..5a428414a970 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.dex; + +import android.os.Build; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import dalvik.system.VMRuntime; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; +import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class PackageDexUsageTests { + private PackageDexUsage mPackageDexUsage; + + private TestData mFooBaseUser0; + private TestData mFooSplit1User0; + private TestData mFooSplit2UsedByOtherApps0; + private TestData mFooSecondary1User0; + private TestData mFooSecondary1User1; + private TestData mFooSecondary2UsedByOtherApps0; + private TestData mInvalidIsa; + + private TestData mBarBaseUser0; + private TestData mBarSecondary1User0; + private TestData mBarSecondary2User1; + + @Before + public void setup() { + mPackageDexUsage = new PackageDexUsage(); + + String fooPackageName = "com.google.foo"; + String fooCodeDir = "/data/app/com.google.foo/"; + String fooDataDir = "/data/user/0/com.google.foo/"; + + String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); + + mFooBaseUser0 = new TestData(fooPackageName, + fooCodeDir + "base.apk", 0, isa, false, true); + + mFooSplit1User0 = new TestData(fooPackageName, + fooCodeDir + "split-1.apk", 0, isa, false, true); + + mFooSplit2UsedByOtherApps0 = new TestData(fooPackageName, + fooCodeDir + "split-2.apk", 0, isa, true, true); + + mFooSecondary1User0 = new TestData(fooPackageName, + fooDataDir + "sec-1.dex", 0, isa, false, false); + + mFooSecondary1User1 = new TestData(fooPackageName, + fooDataDir + "sec-1.dex", 1, isa, false, false); + + mFooSecondary2UsedByOtherApps0 = new TestData(fooPackageName, + fooDataDir + "sec-2.dex", 0, isa, true, false); + + mInvalidIsa = new TestData(fooPackageName, + fooCodeDir + "base.apk", 0, "INVALID_ISA", false, true); + + String barPackageName = "com.google.bar"; + String barCodeDir = "/data/app/com.google.bar/"; + String barDataDir = "/data/user/0/com.google.bar/"; + String barDataDir1 = "/data/user/1/com.google.bar/"; + + mBarBaseUser0 = new TestData(barPackageName, + barCodeDir + "base.apk", 0, isa, false, true); + mBarSecondary1User0 = new TestData(barPackageName, + barDataDir + "sec-1.dex", 0, isa, false, false); + mBarSecondary2User1 = new TestData(barPackageName, + barDataDir1 + "sec-2.dex", 1, isa, false, false); + } + + @Test + public void testRecordPrimary() { + // Assert new information. + assertTrue(record(mFooBaseUser0)); + + assertPackageDexUsage(mFooBaseUser0); + writeAndReadBack(); + assertPackageDexUsage(mFooBaseUser0); + } + + @Test + public void testRecordSplit() { + // Assert new information. + assertTrue(record(mFooSplit1User0)); + + assertPackageDexUsage(mFooSplit1User0); + writeAndReadBack(); + assertPackageDexUsage(mFooSplit1User0); + } + + @Test + public void testRecordSplitPrimarySequence() { + // Assert new information. + assertTrue(record(mFooBaseUser0)); + // Assert no new information. + assertFalse(record(mFooSplit1User0)); + + assertPackageDexUsage(mFooBaseUser0); + writeAndReadBack(); + assertPackageDexUsage(mFooBaseUser0); + + // Write Split2 which is used by other apps. + // Assert new information. + assertTrue(record(mFooSplit2UsedByOtherApps0)); + assertPackageDexUsage(mFooSplit2UsedByOtherApps0); + writeAndReadBack(); + assertPackageDexUsage(mFooSplit2UsedByOtherApps0); + } + + @Test + public void testRecordSecondary() { + assertTrue(record(mFooSecondary1User0)); + + assertPackageDexUsage(null, mFooSecondary1User0); + writeAndReadBack(); + assertPackageDexUsage(null, mFooSecondary1User0); + + // Recording again does not add more data. + assertFalse(record(mFooSecondary1User0)); + assertPackageDexUsage(null, mFooSecondary1User0); + } + + @Test + public void testRecordBaseAndSecondarySequence() { + // Write split. + assertTrue(record(mFooSplit2UsedByOtherApps0)); + // Write secondary. + assertTrue(record(mFooSecondary1User0)); + + // Check. + assertPackageDexUsage(mFooSplit2UsedByOtherApps0, mFooSecondary1User0); + writeAndReadBack(); + assertPackageDexUsage(mFooSplit2UsedByOtherApps0, mFooSecondary1User0); + + // Write another secondary. + assertTrue(record(mFooSecondary2UsedByOtherApps0)); + + // Check. + assertPackageDexUsage( + mFooSplit2UsedByOtherApps0, mFooSecondary1User0, mFooSecondary2UsedByOtherApps0); + writeAndReadBack(); + assertPackageDexUsage( + mFooSplit2UsedByOtherApps0, mFooSecondary1User0, mFooSecondary2UsedByOtherApps0); + } + + @Test + public void testMultiplePackages() { + assertTrue(record(mFooBaseUser0)); + assertTrue(record(mFooSecondary1User0)); + assertTrue(record(mFooSecondary2UsedByOtherApps0)); + assertTrue(record(mBarBaseUser0)); + assertTrue(record(mBarSecondary1User0)); + assertTrue(record(mBarSecondary2User1)); + + assertPackageDexUsage(mFooBaseUser0, mFooSecondary1User0, mFooSecondary2UsedByOtherApps0); + assertPackageDexUsage(mBarBaseUser0, mBarSecondary1User0, mBarSecondary2User1); + writeAndReadBack(); + assertPackageDexUsage(mFooBaseUser0, mFooSecondary1User0, mFooSecondary2UsedByOtherApps0); + assertPackageDexUsage(mBarBaseUser0, mBarSecondary1User0, mBarSecondary2User1); + } + + @Test + public void testPackageNotFound() { + assertNull(mPackageDexUsage.getPackageUseInfo("missing.package")); + } + + @Test + public void testAttemptToChangeOwner() { + assertTrue(record(mFooSecondary1User0)); + try { + record(mFooSecondary1User1); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + // expected + } + } + + @Test + public void testInvalidIsa() { + try { + record(mInvalidIsa); + fail("Expected exception"); + } catch (IllegalArgumentException e) { + // expected + } + } + + @Test + public void testReadWriteEmtpy() { + // Expect no exceptions when writing/reading without data. + writeAndReadBack(); + } + + @Test + public void testSyncData() { + // Write some records. + assertTrue(record(mFooBaseUser0)); + assertTrue(record(mFooSecondary1User0)); + assertTrue(record(mFooSecondary2UsedByOtherApps0)); + assertTrue(record(mBarBaseUser0)); + assertTrue(record(mBarSecondary1User0)); + assertTrue(record(mBarSecondary2User1)); + + // Verify all is good. + assertPackageDexUsage(mFooBaseUser0, mFooSecondary1User0, mFooSecondary2UsedByOtherApps0); + assertPackageDexUsage(mBarBaseUser0, mBarSecondary1User0, mBarSecondary2User1); + writeAndReadBack(); + assertPackageDexUsage(mFooBaseUser0, mFooSecondary1User0, mFooSecondary2UsedByOtherApps0); + assertPackageDexUsage(mBarBaseUser0, mBarSecondary1User0, mBarSecondary2User1); + + // Simulate that only user 1 is available. + Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); + packageToUsersMap.put(mBarSecondary2User1.mPackageName, + new HashSet<>(Arrays.asList(mBarSecondary2User1.mOwnerUserId))); + mPackageDexUsage.syncData(packageToUsersMap); + + // Assert that only user 1 files are there. + assertPackageDexUsage(mBarBaseUser0, mBarSecondary2User1); + assertNull(mPackageDexUsage.getPackageUseInfo(mFooBaseUser0.mPackageName)); + } + + private void assertPackageDexUsage(TestData primary, TestData... secondaries) { + String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName; + boolean primaryUsedByOtherApps = primary == null ? false : primary.mUsedByOtherApps; + PackageUseInfo pInfo = mPackageDexUsage.getPackageUseInfo(packageName); + + // Check package use info + assertNotNull(pInfo); + assertEquals(primaryUsedByOtherApps, pInfo.isUsedByOtherApps()); + Map<String, DexUseInfo> dexUseInfoMap = pInfo.getDexUseInfoMap(); + assertEquals(secondaries.length, dexUseInfoMap.size()); + + // Check dex use info + for (TestData testData : secondaries) { + DexUseInfo dInfo = dexUseInfoMap.get(testData.mDexFile); + assertNotNull(dInfo); + assertEquals(testData.mUsedByOtherApps, dInfo.isUsedByOtherApps()); + assertEquals(testData.mOwnerUserId, dInfo.getOwnerUserId()); + assertEquals(1, dInfo.getLoaderIsas().size()); + assertTrue(dInfo.getLoaderIsas().contains(testData.mLoaderIsa)); + } + } + + private boolean record(TestData testData) { + return mPackageDexUsage.record(testData.mPackageName, testData.mDexFile, + testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps, + testData.mPrimaryOrSplit); + } + + private void writeAndReadBack() { + try { + StringWriter writer = new StringWriter(); + mPackageDexUsage.write(writer); + + mPackageDexUsage = new PackageDexUsage(); + mPackageDexUsage.read(new StringReader(writer.toString())); + } catch (IOException e) { + fail("Unexpected IOException: " + e.getMessage()); + } + } + + private static class TestData { + private final String mPackageName; + private final String mDexFile; + private final int mOwnerUserId; + private final String mLoaderIsa; + private final boolean mUsedByOtherApps; + private final boolean mPrimaryOrSplit; + + private TestData(String packageName, String dexFile, int ownerUserId, + String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit) { + mPackageName = packageName; + mDexFile = dexFile; + mOwnerUserId = ownerUserId; + mLoaderIsa = loaderIsa; + mUsedByOtherApps = isUsedByOtherApps; + mPrimaryOrSplit = primaryOrSplit; + } + + } +} diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 2c16ca082747..4565dbdea746 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1034,7 +1034,7 @@ public class CarrierConfigManager { * is returned. * @hide */ - public static final String FILTERED_CNAP_NAMES_STRING_ARRAY = "filtered_cnap_names_string_array"; + public static final String KEY_FILTERED_CNAP_NAMES_STRING_ARRAY = "filtered_cnap_names_string_array"; /** * Determine whether user can change Wi-Fi Calling preference in roaming. @@ -1236,7 +1236,7 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY, null); sDefaults.putBoolean(KEY_ENHANCED_4G_LTE_TITLE_VARIANT_BOOL, false); sDefaults.putBoolean(KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL, false); - sDefaults.putStringArray(FILTERED_CNAP_NAMES_STRING_ARRAY, null); + sDefaults.putStringArray(KEY_FILTERED_CNAP_NAMES_STRING_ARRAY, null); sDefaults.putBoolean(KEY_EDITABLE_WFC_ROAMING_MODE_BOOL, false); } diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 3b7f7216c147..958279badf75 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -608,6 +608,7 @@ public class WifiConfiguration implements Parcelable { * if there has been a report of it having no internet access, and, it never have had * internet access in the past. */ + @SystemApi public boolean hasNoInternetAccess() { return numNoInternetAccessReports > 0 && !validatedInternetAccess; } @@ -621,6 +622,17 @@ public class WifiConfiguration implements Parcelable { public boolean noInternetAccessExpected; /** + * The WiFi configuration is expected not to have Internet access (e.g., a wireless printer, a + * Chromecast hotspot, etc.). This will be set if the user explicitly confirms a connection to + * this configuration and selects "don't ask again". + * @hide + */ + @SystemApi + public boolean isNoInternetAccessExpected() { + return noInternetAccessExpected; + } + + /** * @hide * Last time the system was connected to this configuration. */ |