summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/system-current.txt11
-rw-r--r--core/java/android/bluetooth/BluetoothSocket.java10
-rw-r--r--core/java/android/net/ConnectivityManager.java7
-rw-r--r--core/java/android/net/INetworkScoreService.aidl9
-rw-r--r--core/java/android/net/LocalServerSocket.java2
-rw-r--r--core/java/android/net/LocalSocket.java34
-rw-r--r--core/java/android/net/LocalSocketImpl.java2
-rw-r--r--core/java/android/net/NetworkScoreManager.java15
-rw-r--r--core/java/android/net/NetworkScorerAppManager.java4
-rw-r--r--core/java/android/net/ScoredNetwork.java40
-rw-r--r--core/java/android/view/ThreadedRenderer.java11
-rw-r--r--core/java/com/android/internal/os/WebViewZygoteInit.java3
-rw-r--r--core/java/com/android/internal/os/Zygote.java14
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java5
-rw-r--r--core/jni/Android.mk1
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp48
-rw-r--r--core/jni/fd_utils-inl.h564
-rw-r--r--core/jni/fd_utils.cpp567
-rw-r--r--core/jni/fd_utils.h147
-rw-r--r--core/res/res/values/strings.xml11
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/tests/coretests/src/android/net/IpPrefixTest.java35
-rw-r--r--core/tests/coretests/src/android/net/ScoredNetworkTest.java48
-rw-r--r--libs/hwui/AnimatorManager.cpp2
-rw-r--r--libs/hwui/renderthread/EglManager.cpp7
-rw-r--r--packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java85
-rw-r--r--services/core/Android.mk2
-rw-r--r--services/core/java/com/android/server/BluetoothManagerService.java28
-rw-r--r--services/core/java/com/android/server/NetworkScoreService.java26
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkMonitor.java13
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkNotificationManager.java6
-rw-r--r--services/core/java/com/android/server/connectivity/Tethering.java120
-rw-r--r--services/core/java/com/android/server/pm/AbstractStatsBase.java6
-rw-r--r--services/core/java/com/android/server/pm/BackgroundDexOptService.java203
-rw-r--r--services/core/java/com/android/server/pm/Installer.java36
-rw-r--r--services/core/java/com/android/server/pm/PackageDexOptimizer.java23
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java68
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java15
-rw-r--r--services/core/java/com/android/server/pm/dex/DexManager.java329
-rw-r--r--services/core/java/com/android/server/pm/dex/PackageDexUsage.java512
-rw-r--r--services/core/java/com/android/server/updates/TzDataInstallReceiver.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java60
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/InstallerTest.java123
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java282
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java318
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java4
-rw-r--r--wifi/java/android/net/wifi/WifiConfiguration.java12
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.
*/