Merge "[Magnifier-53] Add API around outofbounds behavior"
diff --git a/api/current.txt b/api/current.txt
index 0b91b0c..ea6f190 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -9475,6 +9475,7 @@
public abstract class Context {
ctor public Context();
+ method public abstract boolean bindIsolatedService(android.content.Intent, android.content.ServiceConnection, int, java.lang.String);
method public abstract boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
method public abstract int checkCallingOrSelfPermission(java.lang.String);
method public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
@@ -9686,6 +9687,7 @@
public class ContextWrapper extends android.content.Context {
ctor public ContextWrapper(android.content.Context);
method protected void attachBaseContext(android.content.Context);
+ method public boolean bindIsolatedService(android.content.Intent, android.content.ServiceConnection, int, java.lang.String);
method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
method public int checkCallingOrSelfPermission(java.lang.String);
method public int checkCallingOrSelfUriPermission(android.net.Uri, int);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 21e7203..53d9673 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -147,6 +147,7 @@
PhoneStateChanged phone_state_changed = 95;
UserRestrictionChanged user_restriction_changed = 96;
SettingsUIChanged settings_ui_changed = 97;
+ ConnectivityStateChanged connectivity_state_changed = 98;
}
// Pulled events will start at field 10000.
@@ -2129,6 +2130,22 @@
optional int64 visible_millis = 16;
}
+/*
+ * Logs when a connection becomes available and lost.
+ * Logged in StatsCompanionService.java
+ */
+message ConnectivityStateChanged {
+ // Id of the network.
+ optional int32 net_id = 1;
+
+ enum State {
+ UNKNOWN = 0;
+ CONNECTED = 1;
+ DISCONNECTED = 2;
+ }
+ // Connected state of a network.
+ optional State state = 2;
+}
//////////////////////////////////////////////////////////////////////
// Pulled atoms below this line //
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 05103a9..461ad28 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -140,7 +140,7 @@
// Adjust start for partial bucket
mCurrentBucketStartTimeNs = startTimeNs;
- if (mIsPulled) {
+ if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
pullAndMatchEventsLocked(startTimeNs);
}
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 5866139..6e3530b 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -80,7 +80,7 @@
}
flushCurrentBucketLocked(eventTimeNs);
mCurrentBucketStartTimeNs = eventTimeNs;
- if (mIsPulled) {
+ if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
pullAndMatchEventsLocked(eventTimeNs);
}
};
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index dc707e8..9837deb 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -140,6 +140,13 @@
throw new ReceiverCallNotAllowedException(
"BroadcastReceiver components are not allowed to bind to services");
}
+
+ @Override
+ public boolean bindIsolatedService(Intent service, ServiceConnection conn, int flags,
+ String instanceName) {
+ throw new ReceiverCallNotAllowedException(
+ "BroadcastReceiver components are not allowed to bind to services");
+ }
}
/**
@@ -1630,14 +1637,25 @@
public boolean bindService(Intent service, ServiceConnection conn,
int flags) {
warnIfCallingFromSystemProcess();
- return bindServiceCommon(service, conn, flags, mMainThread.getHandler(), getUser());
+ return bindServiceCommon(service, conn, flags, null, mMainThread.getHandler(), getUser());
+ }
+
+ @Override
+ public boolean bindIsolatedService(Intent service, ServiceConnection conn,
+ int flags, String instanceName) {
+ warnIfCallingFromSystemProcess();
+ if (instanceName == null) {
+ throw new NullPointerException("null instanceName");
+ }
+ return bindServiceCommon(service, conn, flags, instanceName, mMainThread.getHandler(),
+ getUser());
}
/** @hide */
@Override
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
UserHandle user) {
- return bindServiceCommon(service, conn, flags, mMainThread.getHandler(), user);
+ return bindServiceCommon(service, conn, flags, null, mMainThread.getHandler(), user);
}
/** @hide */
@@ -1647,7 +1665,7 @@
if (handler == null) {
throw new IllegalArgumentException("handler must not be null.");
}
- return bindServiceCommon(service, conn, flags, handler, user);
+ return bindServiceCommon(service, conn, flags, null, handler, user);
}
/** @hide */
@@ -1669,7 +1687,8 @@
return mMainThread.getHandler();
}
- private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handler
+ private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
+ String instanceName, Handler
handler, UserHandle user) {
// Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser.
IServiceConnection sd;
@@ -1690,10 +1709,10 @@
flags |= BIND_WAIVE_PRIORITY;
}
service.prepareToLeaveProcess(this);
- int res = ActivityManager.getService().bindService(
+ int res = ActivityManager.getService().bindIsolatedService(
mMainThread.getApplicationThread(), getActivityToken(), service,
service.resolveTypeIfNeeded(getContentResolver()),
- sd, flags, getOpPackageName(), user.getIdentifier());
+ sd, flags, instanceName, getOpPackageName(), user.getIdentifier());
if (res < 0) {
throw new SecurityException(
"Not allowed to bind to service " + service);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index e2312a5..f27c667 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -133,9 +133,13 @@
in String resolvedType, boolean requireForeground, in String callingPackage, int userId);
int stopService(in IApplicationThread caller, in Intent service,
in String resolvedType, int userId);
+ // Currently keeping old bindService because it is on the greylist
int bindService(in IApplicationThread caller, in IBinder token, in Intent service,
in String resolvedType, in IServiceConnection connection, int flags,
in String callingPackage, int userId);
+ int bindIsolatedService(in IApplicationThread caller, in IBinder token, in Intent service,
+ in String resolvedType, in IServiceConnection connection, int flags,
+ in String instanceName, in String callingPackage, int userId);
boolean unbindService(in IServiceConnection connection);
void publishService(in IBinder token, in Intent intent, in IBinder service);
void setDebugApp(in String packageName, boolean waitForDebugger, boolean persistent);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9f8ae0b..03eba7e 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2906,8 +2906,9 @@
* @param flags Operation options for the binding. May be 0,
* {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND},
* {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT},
- * {@link #BIND_ALLOW_OOM_MANAGEMENT}, or
- * {@link #BIND_WAIVE_PRIORITY}.
+ * {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}.
+ * {@link #BIND_IMPORTANT}, or
+ * {@link #BIND_ADJUST_WITH_ACTIVITY}.
* @return {@code true} if the system is in the process of bringing up a
* service that your client has permission to bind to; {@code false}
* if the system couldn't find the service or if your client doesn't
@@ -2923,11 +2924,38 @@
* @see #BIND_AUTO_CREATE
* @see #BIND_DEBUG_UNBIND
* @see #BIND_NOT_FOREGROUND
+ * @see #BIND_ABOVE_CLIENT
+ * @see #BIND_ALLOW_OOM_MANAGEMENT
+ * @see #BIND_WAIVE_PRIORITY
+ * @see #BIND_IMPORTANT
+ * @see #BIND_ADJUST_WITH_ACTIVITY
*/
public abstract boolean bindService(@RequiresPermission Intent service,
@NonNull ServiceConnection conn, @BindServiceFlags int flags);
/**
+ * Variation of {@link #bindService} that, in the specific case of isolated
+ * services, allows the caller to generate multiple instances of a service
+ * from a single component declaration.
+ *
+ * @param service Identifies the service to connect to. The Intent must
+ * specify an explicit component name.
+ * @param conn Receives information as the service is started and stopped.
+ * This must be a valid ServiceConnection object; it must not be null.
+ * @param flags Operation options for the binding as per {@link #bindService}.
+ * @param instanceName Unique identifier for the service instance. Each unique
+ * name here will result in a different service instance being created.
+ * @return Returns success of binding as per {@link #bindService}.
+ *
+ * @throws SecurityException If the caller does not have permission to access the service
+ *
+ * @see #bindService
+ */
+ public abstract boolean bindIsolatedService(@RequiresPermission Intent service,
+ @NonNull ServiceConnection conn, @BindServiceFlags int flags,
+ @NonNull String instanceName);
+
+ /**
* Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle
* argument for use by system server and other multi-user aware code.
* @hide
@@ -2941,7 +2969,7 @@
}
/**
- * Same as {@link #bindService(Intent, ServiceConnection, int, UserHandle)}, but with an
+ * Same as {@link #bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)}, but with an
* explicit non-null Handler to run the ServiceConnection callbacks on.
*
* @hide
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index bfad2b4..88696b0e 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -705,6 +705,12 @@
return mBase.bindService(service, conn, flags);
}
+ @Override
+ public boolean bindIsolatedService(Intent service, ServiceConnection conn,
+ int flags, String instanceName) {
+ return mBase.bindIsolatedService(service, conn, flags, instanceName);
+ }
+
/** @hide */
@Override
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index d679fc7..423ce77 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -268,6 +268,9 @@
public static final int ENCRYPTION_STATE_ERROR_CORRUPT =
IVold.ENCRYPTION_STATE_ERROR_CORRUPT;
+ /** @hide Prefix used in sandboxIds for apps with sharedUserIds */
+ public static final String SHARED_SANDBOX_PREFIX = "shared-";
+
private static volatile IStorageManager sStorageManager = null;
private final Context mContext;
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 4a7e783..a8debbd 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -77,11 +77,17 @@
CONSUMED = new WindowInsets((Insets) null, null, null, false, false, null);
}
- /** @hide */
+ /**
+ * Construct a new WindowInsets from individual insets.
+ *
+ * A {@code null} inset indicates that the respective inset is consumed.
+ *
+ * @hide
+ */
public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets, Rect stableInsets,
boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) {
- this(Insets.of(systemWindowInsets), Insets.of(windowDecorInsets), Insets.of(stableInsets),
- isRound, alwaysConsumeNavBar, displayCutout);
+ this(insetsOrNull(systemWindowInsets), insetsOrNull(windowDecorInsets),
+ insetsOrNull(stableInsets), isRound, alwaysConsumeNavBar, displayCutout);
}
private WindowInsets(Insets systemWindowInsets, Insets windowDecorInsets,
@@ -673,6 +679,10 @@
return Insets.of(newLeft, newTop, newRight, newBottom);
}
+ private static Insets insetsOrNull(Rect insets) {
+ return insets != null ? Insets.of(insets) : null;
+ }
+
/**
* @return whether system window insets have been consumed.
*/
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
new file mode 100644
index 0000000..1c2df2c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WindowInsetsTest {
+
+ @Test
+ public void systemWindowInsets_afterConsuming_isConsumed() {
+ assertTrue(new WindowInsets(new Rect(1, 2, 3, 4), null, null, false, false, null)
+ .consumeSystemWindowInsets().isConsumed());
+ }
+
+ @Test
+ public void multiNullConstructor_isConsumed() {
+ assertTrue(new WindowInsets(null, null, null, false, false, null).isConsumed());
+ }
+
+ @Test
+ public void singleNullConstructor_isConsumed() {
+ assertTrue(new WindowInsets((Rect) null).isConsumed());
+ }
+
+}
diff --git a/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java b/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java
index 01382aa..4e0f2a8 100644
--- a/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java
+++ b/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java
@@ -81,7 +81,7 @@
@Test
public void testSharedInstalledPrimary() throws Exception {
- assertEquals("1001", getContent(getKernelPackageFile("shared:android.uid.phone", "appid")));
+ assertEquals("1001", getContent(getKernelPackageFile("shared-android.uid.phone", "appid")));
}
@Test
@@ -92,7 +92,7 @@
@Test
public void testSharedInstalledAll() throws Exception {
- assertEquals("", getContent(getKernelPackageFile("shared:android.uid.phone",
+ assertEquals("", getContent(getKernelPackageFile("shared-android.uid.phone",
"excluded_userids")));
}
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index 1136684..60153fc 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -78,8 +78,6 @@
public class Assistant extends NotificationAssistantService {
private static final String TAG = "ExtAssistant";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- public static final boolean AUTO_DEMOTE_NOTIFICATIONS = SystemProperties.getBoolean(
- "debug.demote_notifs", false);
public static final boolean AGE_NOTIFICATIONS = SystemProperties.getBoolean(
"debug.age_notifs", false);
@@ -242,7 +240,8 @@
if (!smartReplies.isEmpty()) {
signals.putCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES, smartReplies);
}
- if (AUTO_DEMOTE_NOTIFICATIONS) {
+ if (Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 0) == 1) {
if (mNotificationCategorizer.shouldSilence(entry)) {
final int importance = entry.getImportance() < IMPORTANCE_LOW
? entry.getImportance() : IMPORTANCE_LOW;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 12fbf2d..a6a9d74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -342,7 +342,8 @@
private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
protected StatusBarWindowController mStatusBarWindowController;
protected UnlockMethodCache mUnlockMethodCache;
- private DozeServiceHost mDozeServiceHost = new DozeServiceHost();
+ @VisibleForTesting
+ DozeServiceHost mDozeServiceHost = new DozeServiceHost();
private boolean mWakeUpComingFromTouch;
private PointF mWakeUpTouchLocation;
@@ -479,7 +480,7 @@
private boolean mLaunchCameraOnScreenTurningOn;
private boolean mLaunchCameraOnFinishedGoingToSleep;
private int mLastCameraLaunchSource;
- private PowerManager.WakeLock mGestureWakeLock;
+ protected PowerManager.WakeLock mGestureWakeLock;
private Vibrator mVibrator;
private long[] mCameraLaunchGestureVibePattern;
@@ -3609,6 +3610,7 @@
}
}
+ @VisibleForTesting
final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedGoingToSleep() {
@@ -3650,6 +3652,7 @@
mNotificationPanel.setTouchAndAnimationDisabled(false);
updateVisibleToUser();
updateIsKeyguard();
+ mDozeServiceHost.stopDozing();
}
};
@@ -3856,7 +3859,8 @@
return mStatusBarKeyguardViewManager.isShowing();
}
- private final class DozeServiceHost implements DozeHost {
+ @VisibleForTesting
+ final class DozeServiceHost implements DozeHost {
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
private boolean mAnimateWakeup;
private boolean mAnimateScreenOff;
@@ -3944,7 +3948,6 @@
mDozingRequested = false;
DozeLog.traceDozing(mContext, mDozing);
updateDozing();
- mWakefulnessLifecycle.dispatchStartedWakingUp();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 882f261..d442de2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -593,6 +593,30 @@
verify(mStatusBarStateController).setState(eq(StatusBarState.FULLSCREEN_USER_SWITCHER));
}
+ @Test
+ public void testStartStopDozing() {
+ mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+ when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
+
+ mStatusBar.mDozeServiceHost.startDozing();
+ verify(mStatusBarStateController).setIsDozing(eq(true));
+
+ mStatusBar.mDozeServiceHost.stopDozing();
+ verify(mStatusBarStateController).setIsDozing(eq(false));
+ }
+
+ @Test
+ public void testOnStartedWakingUp_isNotDozing() {
+ mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+ when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
+
+ mStatusBar.mDozeServiceHost.startDozing();
+ verify(mStatusBarStateController).setIsDozing(eq(true));
+
+ mStatusBar.mWakefulnessObserver.onStartedWakingUp();
+ verify(mStatusBarStateController).setIsDozing(eq(false));
+ }
+
static class TestableStatusBar extends StatusBar {
public TestableStatusBar(StatusBarKeyguardViewManager man,
UnlockMethodCache unlock, KeyguardIndicationController key,
@@ -642,6 +666,7 @@
mLockscreenUserManager = notificationLockscreenUserManager;
mCommandQueue = commandQueue;
mPresenter = notificationPresenter;
+ mGestureWakeLock = mock(PowerManager.WakeLock.class);
}
private WakefulnessLifecycle createAwakeWakefulnessLifecycle() {
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java
new file mode 100644
index 0000000..18011f6
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2018 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.backup.encryption.chunking.cdc;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import com.android.server.backup.encryption.chunking.Chunker;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+
+/** Splits a stream of bytes into variable-sized chunks, using content-defined chunking. */
+public class ContentDefinedChunker implements Chunker {
+ private static final int WINDOW_SIZE = 31;
+ private static final byte DEFAULT_OUT_BYTE = (byte) 0;
+
+ private final byte[] mChunkBuffer;
+ private final RabinFingerprint64 mRabinFingerprint64;
+ private final FingerprintMixer mFingerprintMixer;
+ private final BreakpointPredicate mBreakpointPredicate;
+ private final int mMinChunkSize;
+ private final int mMaxChunkSize;
+
+ /**
+ * Constructor.
+ *
+ * @param minChunkSize The minimum size of a chunk. No chunk will be produced of a size smaller
+ * than this except possibly at the very end of the stream.
+ * @param maxChunkSize The maximum size of a chunk. No chunk will be produced of a larger size.
+ * @param rabinFingerprint64 Calculates fingerprints, with which to determine breakpoints.
+ * @param breakpointPredicate Given a Rabin fingerprint, returns whether this ought to be a
+ * breakpoint.
+ */
+ public ContentDefinedChunker(
+ int minChunkSize,
+ int maxChunkSize,
+ RabinFingerprint64 rabinFingerprint64,
+ FingerprintMixer fingerprintMixer,
+ BreakpointPredicate breakpointPredicate) {
+ checkArgument(
+ minChunkSize >= WINDOW_SIZE,
+ "Minimum chunk size must be greater than window size.");
+ checkArgument(
+ maxChunkSize >= minChunkSize,
+ "Maximum chunk size cannot be smaller than minimum chunk size.");
+ mChunkBuffer = new byte[maxChunkSize];
+ mRabinFingerprint64 = rabinFingerprint64;
+ mBreakpointPredicate = breakpointPredicate;
+ mFingerprintMixer = fingerprintMixer;
+ mMinChunkSize = minChunkSize;
+ mMaxChunkSize = maxChunkSize;
+ }
+
+ /**
+ * Breaks the input stream into variable-sized chunks.
+ *
+ * @param inputStream The input bytes to break into chunks.
+ * @param chunkConsumer A function to process each chunk as it's generated.
+ * @throws IOException Thrown if there is an issue reading from the input stream.
+ * @throws GeneralSecurityException Thrown if the {@link ChunkConsumer} throws it.
+ */
+ @Override
+ public void chunkify(InputStream inputStream, ChunkConsumer chunkConsumer)
+ throws IOException, GeneralSecurityException {
+ int chunkLength;
+ int initialReadLength = mMinChunkSize - WINDOW_SIZE;
+
+ // Performance optimization - there is no reason to calculate fingerprints for windows
+ // ending before the minimum chunk size.
+ while ((chunkLength =
+ inputStream.read(mChunkBuffer, /*off=*/ 0, /*len=*/ initialReadLength))
+ != -1) {
+ int b;
+ long fingerprint = 0L;
+
+ while ((b = inputStream.read()) != -1) {
+ byte inByte = (byte) b;
+ byte outByte = getCurrentWindowStartByte(chunkLength);
+ mChunkBuffer[chunkLength++] = inByte;
+
+ fingerprint =
+ mRabinFingerprint64.computeFingerprint64(inByte, outByte, fingerprint);
+
+ if (chunkLength >= mMaxChunkSize
+ || (chunkLength >= mMinChunkSize
+ && mBreakpointPredicate.isBreakpoint(
+ mFingerprintMixer.mix(fingerprint)))) {
+ chunkConsumer.accept(Arrays.copyOf(mChunkBuffer, chunkLength));
+ chunkLength = 0;
+ break;
+ }
+ }
+
+ if (chunkLength > 0) {
+ chunkConsumer.accept(Arrays.copyOf(mChunkBuffer, chunkLength));
+ }
+ }
+ }
+
+ private byte getCurrentWindowStartByte(int chunkLength) {
+ if (chunkLength < mMinChunkSize) {
+ return DEFAULT_OUT_BYTE;
+ } else {
+ return mChunkBuffer[chunkLength - WINDOW_SIZE];
+ }
+ }
+
+ /** Whether the current fingerprint indicates the end of a chunk. */
+ public interface BreakpointPredicate {
+
+ /**
+ * Returns {@code true} if the fingerprint of the last {@code WINDOW_SIZE} bytes indicates
+ * the chunk ought to end at this position.
+ *
+ * @param fingerprint Fingerprint of the last {@code WINDOW_SIZE} bytes.
+ * @return Whether this ought to be a chunk breakpoint.
+ */
+ boolean isBreakpoint(long fingerprint);
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java
new file mode 100644
index 0000000..e9f3050
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 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.backup.encryption.chunking.cdc;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+
+import javax.crypto.SecretKey;
+
+/**
+ * Helper for mixing fingerprint with key material.
+ *
+ * <p>We do this as otherwise the Rabin fingerprint leaks information about the plaintext. i.e., if
+ * two users have the same file, it will be partitioned by Rabin in the same way, allowing us to
+ * infer that it is the same as another user's file.
+ *
+ * <p>By mixing the fingerprint with the user's secret key, the chunking method is different on a
+ * per key basis. Each application has its own {@link SecretKey}, so we cannot infer that a file is
+ * the same even across multiple applications owned by the same user, never mind across multiple
+ * users.
+ *
+ * <p>Instead of directly mixing the fingerprint with the user's secret, we first securely and
+ * deterministically derive a secondary chunking key. As Rabin is not a cryptographically secure
+ * hash, it might otherwise leak information about the user's secret. This prevents that from
+ * happening.
+ */
+public class FingerprintMixer {
+ public static final int SALT_LENGTH_BYTES = 256 / Byte.SIZE;
+ private static final String DERIVED_KEY_NAME = "RabinFingerprint64Mixer";
+
+ private final long mAddend;
+ private final long mMultiplicand;
+
+ /**
+ * A new instance from a given secret key and salt. Salt must be the same across incremental
+ * backups, or a different chunking strategy will be used each time, defeating the dedup.
+ *
+ * @param secretKey The application-specific secret.
+ * @param salt The salt.
+ * @throws InvalidKeyException If the encoded form of {@code secretKey} is inaccessible.
+ */
+ public FingerprintMixer(SecretKey secretKey, byte[] salt) throws InvalidKeyException {
+ checkArgument(salt.length == SALT_LENGTH_BYTES, "Requires a 256-bit salt.");
+ byte[] keyBytes = secretKey.getEncoded();
+ if (keyBytes == null) {
+ throw new InvalidKeyException("SecretKey must support encoding for FingerprintMixer.");
+ }
+ byte[] derivedKey =
+ Hkdf.hkdf(keyBytes, salt, DERIVED_KEY_NAME.getBytes(StandardCharsets.UTF_8));
+ ByteBuffer buffer = ByteBuffer.wrap(derivedKey);
+ mAddend = buffer.getLong();
+ // Multiplicand must be odd - otherwise we lose some bits of the Rabin fingerprint when
+ // mixing
+ mMultiplicand = buffer.getLong() | 1;
+ }
+
+ /**
+ * Mixes the fingerprint with the derived key material. This is performed by adding part of the
+ * derived key and multiplying by another part of the derived key (which is forced to be odd, so
+ * that the operation is reversible).
+ *
+ * @param fingerprint A 64-bit Rabin fingerprint.
+ * @return The mixed fingerprint.
+ */
+ long mix(long fingerprint) {
+ return ((fingerprint + mAddend) * mMultiplicand);
+ }
+
+ /** The addend part of the derived key. */
+ long getAddend() {
+ return mAddend;
+ }
+
+ /** The multiplicand part of the derived key. */
+ long getMultiplicand() {
+ return mMultiplicand;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/Hkdf.java b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/Hkdf.java
new file mode 100644
index 0000000..6f4f549
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/Hkdf.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 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.backup.encryption.chunking.cdc;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Secure HKDF utils. Allows client to deterministically derive additional key material from a base
+ * secret. If the derived key material is compromised, this does not in of itself compromise the
+ * root secret.
+ *
+ * <p>TODO(b/116575321): After all code is ported, rename this class to HkdfUtils.
+ */
+public final class Hkdf {
+ private static final byte[] CONSTANT_01 = {0x01};
+ private static final String HmacSHA256 = "HmacSHA256";
+ private static final String AES = "AES";
+
+ /**
+ * Implements HKDF (RFC 5869) with the SHA-256 hash and a 256-bit output key length.
+ *
+ * <p>IMPORTANT: The use or edit of this method requires a security review.
+ *
+ * @param masterKey Master key from which to derive sub-keys.
+ * @param salt A randomly generated 256-bit byte string.
+ * @param data Arbitrary information that is bound to the derived key (i.e., used in its
+ * creation).
+ * @return Raw derived key bytes = HKDF-SHA256(masterKey, salt, data).
+ * @throws InvalidKeyException If the salt can not be used as a valid key.
+ */
+ static byte[] hkdf(byte[] masterKey, byte[] salt, byte[] data) throws InvalidKeyException {
+ checkNotNull(masterKey, "HKDF requires master key to be set.");
+ checkNotNull(salt, "HKDF requires a salt.");
+ checkNotNull(data, "No data provided to HKDF.");
+ return hkdfSha256Expand(hkdfSha256Extract(masterKey, salt), data);
+ }
+
+ private Hkdf() {}
+
+ /**
+ * The HKDF (RFC 5869) extraction function, using the SHA-256 hash function. This function is
+ * used to pre-process the {@code inputKeyMaterial} and mix it with the {@code salt}, producing
+ * output suitable for use with HKDF expansion function (which produces the actual derived key).
+ *
+ * <p>IMPORTANT: The use or edit of this method requires a security review.
+ *
+ * @see #hkdfSha256Expand(byte[], byte[])
+ * @return HMAC-SHA256(salt, inputKeyMaterial) (salt is the "key" for the HMAC)
+ * @throws InvalidKeyException If the salt can not be used as a valid key.
+ */
+ private static byte[] hkdfSha256Extract(byte[] inputKeyMaterial, byte[] salt)
+ throws InvalidKeyException {
+ // Note that the SecretKey encoding format is defined to be RAW, so the encoded form should
+ // be consistent across implementations.
+ Mac sha256;
+ try {
+ sha256 = Mac.getInstance(HmacSHA256);
+ } catch (NoSuchAlgorithmException e) {
+ // This can not happen - HmacSHA256 is supported by the platform.
+ throw new AssertionError(e);
+ }
+ sha256.init(new SecretKeySpec(salt, AES));
+
+ return sha256.doFinal(inputKeyMaterial);
+ }
+
+ /**
+ * Special case of HKDF (RFC 5869) expansion function, using the SHA-256 hash function and
+ * allowing for a maximum output length of 256 bits.
+ *
+ * <p>IMPORTANT: The use or edit of this method requires a security review.
+ *
+ * @param pseudoRandomKey Generated by {@link #hkdfSha256Extract(byte[], byte[])}.
+ * @param info Arbitrary information the derived key should be bound to.
+ * @return Raw derived key bytes = HMAC-SHA256(pseudoRandomKey, info | 0x01).
+ * @throws InvalidKeyException If the salt can not be used as a valid key.
+ */
+ private static byte[] hkdfSha256Expand(byte[] pseudoRandomKey, byte[] info)
+ throws InvalidKeyException {
+ // Note that RFC 5869 computes number of blocks N = ceil(hash length / output length), but
+ // here we only deal with a 256 bit hash up to a 256 bit output, yielding N=1.
+ Mac sha256;
+ try {
+ sha256 = Mac.getInstance(HmacSHA256);
+ } catch (NoSuchAlgorithmException e) {
+ // This can not happen - HmacSHA256 is supported by the platform.
+ throw new AssertionError(e);
+ }
+ sha256.init(new SecretKeySpec(pseudoRandomKey, AES));
+
+ sha256.update(info);
+ sha256.update(CONSTANT_01);
+ return sha256.doFinal();
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java
new file mode 100644
index 0000000..e867e7c
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 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.backup.encryption.chunking.cdc;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import com.android.server.backup.encryption.chunking.cdc.ContentDefinedChunker.BreakpointPredicate;
+
+/**
+ * Function to determine whether a 64-bit fingerprint ought to be a chunk breakpoint.
+ *
+ * <p>This works by checking whether there are at least n leading zeros in the fingerprint. n is
+ * calculated to on average cause a breakpoint after a given number of trials (provided in the
+ * constructor). This allows us to choose a number of trials that gives a desired average chunk
+ * size. This works because the fingerprint is pseudo-randomly distributed.
+ */
+public class IsChunkBreakpoint implements BreakpointPredicate {
+ private final int mLeadingZeros;
+ private final long mBitmask;
+
+ /**
+ * A new instance that causes a breakpoint after a given number of trials on average.
+ *
+ * @param averageNumberOfTrialsUntilBreakpoint The number of trials after which on average to
+ * create a new chunk. If this is not a power of 2, some precision is sacrificed (i.e., on
+ * average, breaks will actually happen after the nearest power of 2 to the average number
+ * of trials passed in).
+ */
+ public IsChunkBreakpoint(long averageNumberOfTrialsUntilBreakpoint) {
+ checkArgument(
+ averageNumberOfTrialsUntilBreakpoint >= 0,
+ "Average number of trials must be non-negative");
+
+ // Want n leading zeros after t trials.
+ // P(leading zeros = n) = 1/2^n
+ // Expected num trials to get n leading zeros = 1/2^-n
+ // t = 1/2^-n
+ // n = log2(t)
+ mLeadingZeros = (int) Math.round(log2(averageNumberOfTrialsUntilBreakpoint));
+ mBitmask = ~(~0L >>> mLeadingZeros);
+ }
+
+ /**
+ * Returns {@code true} if {@code fingerprint} indicates that there should be a chunk
+ * breakpoint.
+ */
+ @Override
+ public boolean isBreakpoint(long fingerprint) {
+ return (fingerprint & mBitmask) == 0;
+ }
+
+ /** Returns the number of leading zeros in the fingerprint that causes a breakpoint. */
+ public int getLeadingZeros() {
+ return mLeadingZeros;
+ }
+
+ /**
+ * Calculates log base 2 of x. Not the most efficient possible implementation, but it's simple,
+ * obviously correct, and is only invoked on object construction.
+ */
+ private static double log2(double x) {
+ return Math.log(x) / Math.log(2);
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java
new file mode 100644
index 0000000..1e14ffa
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 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.backup.encryption.chunking.cdc;
+
+/** Helper to calculate a 64-bit Rabin fingerprint over a 31-byte window. */
+public class RabinFingerprint64 {
+ private static final long DEFAULT_IRREDUCIBLE_POLYNOMIAL_64 = 0x000000000000001BL;
+ private static final int POLYNOMIAL_DEGREE = 64;
+ private static final int SLIDING_WINDOW_SIZE_BYTES = 31;
+
+ private final long mPoly64;
+ // Auxiliary tables to speed up the computation of Rabin fingerprints.
+ private final long[] mTableFP64 = new long[256];
+ private final long[] mTableOutByte = new long[256];
+
+ /**
+ * Constructs a new instance over the given irreducible 64-degree polynomial. It is up to the
+ * caller to determine that the polynomial is irreducible. If it is not the fingerprinting will
+ * not behave as expected.
+ *
+ * @param poly64 The polynomial.
+ */
+ public RabinFingerprint64(long poly64) {
+ mPoly64 = poly64;
+ }
+
+ /** Constructs a new instance using {@code x^64 + x^4 + x + 1} as the irreducible polynomial. */
+ public RabinFingerprint64() {
+ this(DEFAULT_IRREDUCIBLE_POLYNOMIAL_64);
+ computeFingerprintTables64();
+ computeFingerprintTables64Windowed();
+ }
+
+ /**
+ * Computes the fingerprint for the new sliding window given the fingerprint of the previous
+ * sliding window, the byte sliding in, and the byte sliding out.
+ *
+ * @param inChar The new char coming into the sliding window.
+ * @param outChar The left most char sliding out of the window.
+ * @param fingerPrint Fingerprint for previous window.
+ * @return New fingerprint for the new sliding window.
+ */
+ public long computeFingerprint64(byte inChar, byte outChar, long fingerPrint) {
+ return (fingerPrint << 8)
+ ^ (inChar & 0xFF)
+ ^ mTableFP64[(int) (fingerPrint >>> 56)]
+ ^ mTableOutByte[outChar & 0xFF];
+ }
+
+ /** Compute auxiliary tables to speed up the fingerprint computation. */
+ private void computeFingerprintTables64() {
+ long[] degreesRes64 = new long[POLYNOMIAL_DEGREE];
+ degreesRes64[0] = mPoly64;
+ for (int i = 1; i < POLYNOMIAL_DEGREE; i++) {
+ if ((degreesRes64[i - 1] & (1L << 63)) == 0) {
+ degreesRes64[i] = degreesRes64[i - 1] << 1;
+ } else {
+ degreesRes64[i] = (degreesRes64[i - 1] << 1) ^ mPoly64;
+ }
+ }
+ for (int i = 0; i < 256; i++) {
+ int currIndex = i;
+ for (int j = 0; (currIndex > 0) && (j < 8); j++) {
+ if ((currIndex & 0x1) == 1) {
+ mTableFP64[i] ^= degreesRes64[j];
+ }
+ currIndex >>>= 1;
+ }
+ }
+ }
+
+ /**
+ * Compute auxiliary table {@code mTableOutByte} to facilitate the computing of fingerprints for
+ * sliding windows. This table is to take care of the effect on the fingerprint when the
+ * leftmost byte in the window slides out.
+ */
+ private void computeFingerprintTables64Windowed() {
+ // Auxiliary array degsRes64[8] defined by: <code>degsRes64[i] = x^(8 *
+ // SLIDING_WINDOW_SIZE_BYTES + i) mod this.mPoly64.</code>
+ long[] degsRes64 = new long[8];
+ degsRes64[0] = mPoly64;
+ for (int i = 65; i < 8 * (SLIDING_WINDOW_SIZE_BYTES + 1); i++) {
+ if ((degsRes64[(i - 1) % 8] & (1L << 63)) == 0) {
+ degsRes64[i % 8] = degsRes64[(i - 1) % 8] << 1;
+ } else {
+ degsRes64[i % 8] = (degsRes64[(i - 1) % 8] << 1) ^ mPoly64;
+ }
+ }
+ for (int i = 0; i < 256; i++) {
+ int currIndex = i;
+ for (int j = 0; (currIndex > 0) && (j < 8); j++) {
+ if ((currIndex & 0x1) == 1) {
+ mTableOutByte[i] ^= degsRes64[j];
+ }
+ currIndex >>>= 1;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 78e82b6..923ac00 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -186,8 +186,6 @@
private static final boolean ENABLE_ISOLATED_STORAGE = SystemProperties
.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false);
- private static final String SHARED_SANDBOX_ID_PREFIX = "shared:";
-
public static class Lifecycle extends SystemService {
private StorageManagerService mStorageManagerService;
@@ -1501,7 +1499,8 @@
}
private static String getSandboxId(String packageName, String sharedUserId) {
- return sharedUserId == null ? packageName : SHARED_SANDBOX_ID_PREFIX + sharedUserId;
+ return sharedUserId == null
+ ? packageName : StorageManager.SHARED_SANDBOX_PREFIX + sharedUserId;
}
private void connect() {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a33ac70..b5217ad 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -192,10 +192,10 @@
public void stopForegroundServicesForUidPackage(final int uid, final String packageName) {
synchronized (mAm) {
final ServiceMap smap = getServiceMapLocked(UserHandle.getUserId(uid));
- final int N = smap.mServicesByName.size();
+ final int N = smap.mServicesByInstanceName.size();
final ArrayList<ServiceRecord> toStop = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
- final ServiceRecord r = smap.mServicesByName.valueAt(i);
+ final ServiceRecord r = smap.mServicesByInstanceName.valueAt(i);
if (uid == r.serviceInfo.applicationInfo.uid
|| packageName.equals(r.serviceInfo.packageName)) {
if (r.isForeground) {
@@ -246,7 +246,7 @@
*/
final class ServiceMap extends Handler {
final int mUserId;
- final ArrayMap<ComponentName, ServiceRecord> mServicesByName = new ArrayMap<>();
+ final ArrayMap<ComponentName, ServiceRecord> mServicesByInstanceName = new ArrayMap<>();
final ArrayMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent = new ArrayMap<>();
final ArrayList<ServiceRecord> mDelayedStartList = new ArrayList<>();
@@ -368,7 +368,7 @@
// TODO: Deal with global services
if (DEBUG_MU)
Slog.v(TAG_MU, "getServiceByNameLocked(" + name + "), callingUser = " + callingUser);
- return getServiceMapLocked(callingUser).mServicesByName.get(name);
+ return getServiceMapLocked(callingUser).mServicesByInstanceName.get(name);
}
boolean hasBackgroundServicesLocked(int callingUser) {
@@ -386,7 +386,7 @@
}
ArrayMap<ComponentName, ServiceRecord> getServicesLocked(int callingUser) {
- return getServiceMapLocked(callingUser).mServicesByName;
+ return getServiceMapLocked(callingUser).mServicesByInstanceName;
}
private boolean appRestrictedAnyInBackground(final int uid, final String packageName) {
@@ -416,7 +416,7 @@
}
ServiceLookupResult res =
- retrieveServiceLocked(service, resolvedType, callingPackage,
+ retrieveServiceLocked(service, null, resolvedType, callingPackage,
callingPid, callingUid, userId, true, callerFg, false, false);
if (res == null) {
return null;
@@ -444,7 +444,7 @@
boolean forcedStandby = false;
if (bgLaunch && appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) {
if (DEBUG_FOREGROUND_SERVICE) {
- Slog.d(TAG, "Forcing bg-only service start only for " + r.shortName
+ Slog.d(TAG, "Forcing bg-only service start only for " + r.shortInstanceName
+ " : bgLaunch=" + bgLaunch + " callerFg=" + callerFg);
}
forcedStandby = true;
@@ -464,7 +464,7 @@
// Not allowed, fall back to normal start service, failing siliently
// if background check restricts that.
Slog.w(TAG, "startForegroundService not allowed due to app op: service "
- + service + " to " + r.name.flattenToShortString()
+ + service + " to " + r.shortInstanceName
+ " from pid=" + callingPid + " uid=" + callingUid
+ " pkg=" + callingPackage);
fgRequired = false;
@@ -484,7 +484,7 @@
r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
Slog.w(TAG, "Background start not allowed: service "
- + service + " to " + r.name.flattenToShortString()
+ + service + " to " + r.shortInstanceName
+ " from pid=" + callingPid + " uid=" + callingUid
+ " pkg=" + callingPackage + " startFg?=" + fgRequired);
if (allowed == ActivityManager.APP_START_MODE_DELAYED || forceSilentAbort) {
@@ -741,7 +741,7 @@
}
// If this service is active, make sure it is stopped.
- ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, null,
+ ServiceLookupResult r = retrieveServiceLocked(service, null, resolvedType, null,
Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false, false);
if (r != null) {
if (r.record != null) {
@@ -765,8 +765,8 @@
ServiceMap services = mServiceMap.get(UserHandle.getUserId(uid));
ArrayList<ServiceRecord> stopping = null;
if (services != null) {
- for (int i=services.mServicesByName.size()-1; i>=0; i--) {
- ServiceRecord service = services.mServicesByName.valueAt(i);
+ for (int i = services.mServicesByInstanceName.size() - 1; i >= 0; i--) {
+ ServiceRecord service = services.mServicesByInstanceName.valueAt(i);
if (service.appInfo.uid == uid && service.startRequested) {
if (mAm.getAppStartModeLocked(service.appInfo.uid, service.packageName,
service.appInfo.targetSdkVersion, -1, false, false, false)
@@ -774,7 +774,7 @@
if (stopping == null) {
stopping = new ArrayList<>();
}
- String compName = service.name.flattenToShortString();
+ String compName = service.shortInstanceName;
EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName);
StringBuilder sb = new StringBuilder(64);
sb.append("Stopping service due to app idle: ");
@@ -801,7 +801,7 @@
}
IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
- ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, callingPackage,
+ ServiceLookupResult r = retrieveServiceLocked(service, null, resolvedType, callingPackage,
Binder.getCallingPid(), Binder.getCallingUid(),
UserHandle.getCallingUserId(), false, false, false, false);
@@ -1228,7 +1228,7 @@
case AppOpsManager.MODE_IGNORED:
// Whoops, silently ignore this.
Slog.w(TAG, "Service.startForeground() not allowed due to app op: service "
- + r.shortName);
+ + r.shortInstanceName);
ignoreForeground = true;
break;
default:
@@ -1239,7 +1239,7 @@
appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) {
Slog.w(TAG,
"Service.startForeground() not allowed due to bg restriction: service "
- + r.shortName);
+ + r.shortInstanceName);
// Back off of any foreground expectations around this service, since we've
// just turned down its fg request.
updateServiceForegroundLocked(r.app, false);
@@ -1293,7 +1293,7 @@
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName,
true);
StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
- r.appInfo.uid, r.shortName,
+ r.appInfo.uid, r.shortInstanceName,
StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER);
mAm.updateForegroundServiceUsageStats(r.name, r.userId, true);
}
@@ -1343,7 +1343,7 @@
AppOpsManager.getToken(mAm.mAppOpsService),
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
- r.appInfo.uid, r.shortName,
+ r.appInfo.uid, r.shortInstanceName,
StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT);
mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
if (r.app != null) {
@@ -1373,8 +1373,8 @@
// due the other service.
ServiceMap sm = getServiceMapLocked(r.userId);
if (sm != null) {
- for (int i = sm.mServicesByName.size()-1; i >= 0; i--) {
- ServiceRecord other = sm.mServicesByName.valueAt(i);
+ for (int i = sm.mServicesByInstanceName.size() - 1; i >= 0; i--) {
+ ServiceRecord other = sm.mServicesByInstanceName.valueAt(i);
if (other != r && other.foregroundId == r.foregroundId
&& other.packageName.equals(r.packageName)) {
// Found one! Abort the cancel.
@@ -1466,7 +1466,8 @@
int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, final IServiceConnection connection, int flags,
- String callingPackage, final int userId) throws TransactionTooLargeException {
+ String instanceName, String callingPackage, final int userId)
+ throws TransactionTooLargeException {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
+ " type=" + resolvedType + " conn=" + connection.asBinder()
+ " flags=0x" + Integer.toHexString(flags));
@@ -1530,8 +1531,9 @@
final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
ServiceLookupResult res =
- retrieveServiceLocked(service, resolvedType, callingPackage, Binder.getCallingPid(),
- Binder.getCallingUid(), userId, true, callerFg, isBindExternal, allowInstant);
+ retrieveServiceLocked(service, instanceName, resolvedType, callingPackage,
+ Binder.getCallingPid(), Binder.getCallingUid(), userId, true,
+ callerFg, isBindExternal, allowInstant);
if (res == null) {
return 0;
}
@@ -1637,7 +1639,7 @@
mAm.startAssociationLocked(callerApp.uid, callerApp.processName,
callerApp.getCurProcState(), s.appInfo.uid, s.appInfo.longVersionCode,
- s.name, s.processName);
+ s.instanceName, s.processName);
// Once the apps have become associated, if one of them is caller is ephemeral
// the target app should now be able to see the calling app
mAm.grantEphemeralAccessLocked(callerApp.userId, service,
@@ -1709,7 +1711,7 @@
try {
c.conn.connected(s.name, b.intent.binder, false);
} catch (Exception e) {
- Slog.w(TAG, "Failure sending service " + s.shortName
+ Slog.w(TAG, "Failure sending service " + s.shortInstanceName
+ " to connection " + c.conn.asBinder()
+ " (in " + c.binding.client.processName + ")", e);
}
@@ -1763,9 +1765,9 @@
try {
c.conn.connected(r.name, service, false);
} catch (Exception e) {
- Slog.w(TAG, "Failure sending service " + r.name +
- " to connection " + c.conn.asBinder() +
- " (in " + c.binding.client.processName + ")", e);
+ Slog.w(TAG, "Failure sending service " + r.shortInstanceName
+ + " to connection " + c.conn.asBinder()
+ + " (in " + c.binding.client.processName + ")", e);
}
}
}
@@ -1898,7 +1900,8 @@
}
private ServiceLookupResult retrieveServiceLocked(Intent service,
- String resolvedType, String callingPackage, int callingPid, int callingUid, int userId,
+ String instanceName, String resolvedType, String callingPackage,
+ int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
boolean allowInstant) {
ServiceRecord r = null;
@@ -1909,12 +1912,23 @@
ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service", null);
ServiceMap smap = getServiceMapLocked(userId);
- final ComponentName comp = service.getComponent();
+ final ComponentName comp;
+ if (instanceName == null) {
+ comp = service.getComponent();
+ } else {
+ final ComponentName realComp = service.getComponent();
+ if (realComp == null) {
+ throw new IllegalArgumentException("Can't use custom instance name '" + instanceName
+ + "' without expicit component in Intent");
+ }
+ comp = new ComponentName(realComp.getPackageName(),
+ realComp.getClassName() + ":" + instanceName);
+ }
if (comp != null) {
- r = smap.mServicesByName.get(comp);
+ r = smap.mServicesByInstanceName.get(comp);
if (DEBUG_SERVICE && r != null) Slog.v(TAG_SERVICE, "Retrieved by component: " + r);
}
- if (r == null && !isBindExternal) {
+ if (r == null && !isBindExternal && instanceName == null) {
Intent.FilterComparison filter = new Intent.FilterComparison(service);
r = smap.mServicesByIntent.get(filter);
if (DEBUG_SERVICE && r != null) Slog.v(TAG_SERVICE, "Retrieved by intent: " + r);
@@ -1936,24 +1950,29 @@
// TODO: come back and remove this assumption to triage all services
ResolveInfo rInfo = mAm.getPackageManagerInternalLocked().resolveService(service,
resolvedType, flags, userId, callingUid);
- ServiceInfo sInfo =
- rInfo != null ? rInfo.serviceInfo : null;
+ ServiceInfo sInfo = rInfo != null ? rInfo.serviceInfo : null;
if (sInfo == null) {
Slog.w(TAG_SERVICE, "Unable to start service " + service + " U=" + userId +
": not found");
return null;
}
- ComponentName name = new ComponentName(
+ if (instanceName != null
+ && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
+ throw new IllegalArgumentException("Can't use instance name '" + instanceName
+ + "' with non-isolated service '" + sInfo.name + "'");
+ }
+ ComponentName className = new ComponentName(
sInfo.applicationInfo.packageName, sInfo.name);
+ ComponentName name = comp != null ? comp : className;
if ((sInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0) {
if (isBindExternal) {
if (!sInfo.exported) {
- throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + name +
- " is not exported");
+ throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
+ + className + " is not exported");
}
if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
- throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + name +
- " is not an isolatedProcess");
+ throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
+ + className + " is not an isolatedProcess");
}
// Run the service under the calling package's application.
ApplicationInfo aInfo = AppGlobals.getPackageManager().getApplicationInfo(
@@ -1967,6 +1986,9 @@
sInfo.applicationInfo.packageName = aInfo.packageName;
sInfo.applicationInfo.uid = aInfo.uid;
name = new ComponentName(aInfo.packageName, name.getClassName());
+ className = new ComponentName(aInfo.packageName,
+ instanceName == null ? className.getClassName()
+ : (className.getClassName() + ":" + instanceName));
service.setComponent(name);
} else {
throw new SecurityException("BIND_EXTERNAL_SERVICE required for " +
@@ -1986,7 +2008,7 @@
sInfo = new ServiceInfo(sInfo);
sInfo.applicationInfo = mAm.getAppInfoForUser(sInfo.applicationInfo, userId);
}
- r = smap.mServicesByName.get(name);
+ r = smap.mServicesByInstanceName.get(name);
if (DEBUG_SERVICE && r != null) Slog.v(TAG_SERVICE,
"Retrieved via pm by intent: " + r);
if (r == null && createIfNeeded) {
@@ -1997,19 +2019,20 @@
final BatteryStatsImpl stats = mAm.mBatteryStatsService.getActiveStatistics();
synchronized (stats) {
ss = stats.getServiceStatsLocked(
- sInfo.applicationInfo.uid, sInfo.packageName,
- sInfo.name);
+ sInfo.applicationInfo.uid, name.getPackageName(),
+ name.getClassName());
}
- r = new ServiceRecord(mAm, ss, name, filter, sInfo, callingFromFg, res);
+ r = new ServiceRecord(mAm, ss, className, name, filter, sInfo, callingFromFg,
+ res);
res.setService(r);
- smap.mServicesByName.put(name, r);
+ smap.mServicesByInstanceName.put(name, r);
smap.mServicesByIntent.put(filter, r);
// Make sure this component isn't in the pending list.
for (int i=mPendingServices.size()-1; i>=0; i--) {
final ServiceRecord pr = mPendingServices.get(i);
if (pr.serviceInfo.applicationInfo.uid == sInfo.applicationInfo.uid
- && pr.name.equals(name)) {
+ && pr.instanceName.equals(name)) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Remove pending: " + pr);
mPendingServices.remove(i);
}
@@ -2024,14 +2047,14 @@
if (mAm.checkComponentPermission(r.permission,
callingPid, callingUid, r.appInfo.uid, r.exported) != PERMISSION_GRANTED) {
if (!r.exported) {
- Slog.w(TAG, "Permission Denial: Accessing service " + r.name
+ Slog.w(TAG, "Permission Denial: Accessing service " + r.shortInstanceName
+ " from pid=" + callingPid
+ ", uid=" + callingUid
+ " that is not exported from uid " + r.appInfo.uid);
return new ServiceLookupResult(null, "not exported from uid "
+ r.appInfo.uid);
}
- Slog.w(TAG, "Permission Denial: Accessing service " + r.name
+ Slog.w(TAG, "Permission Denial: Accessing service " + r.shortInstanceName
+ " from pid=" + callingPid
+ ", uid=" + callingUid
+ " requires " + r.permission);
@@ -2040,7 +2063,7 @@
final int opCode = AppOpsManager.permissionToOpCode(r.permission);
if (opCode != AppOpsManager.OP_NONE && mAm.mAppOpsService.noteOperation(
opCode, callingUid, callingPackage) != AppOpsManager.MODE_ALLOWED) {
- Slog.w(TAG, "Appop Denial: Accessing service " + r.name
+ Slog.w(TAG, "Appop Denial: Accessing service " + r.shortInstanceName
+ " from pid=" + callingPid
+ ", uid=" + callingUid
+ " requires appop " + AppOpsManager.opToName(opCode));
@@ -2061,7 +2084,7 @@
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
+ why + " of " + r + " in app " + r.app);
else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING "
- + why + " of " + r.shortName);
+ + why + " of " + r.shortInstanceName);
// For b/34123235: Services within the system server won't start until SystemServer
// does Looper.loop(), so we shouldn't try to start/bind to them too early in the boot
@@ -2146,14 +2169,14 @@
boolean canceled = false;
if (mAm.mAtmInternal.isShuttingDown()) {
- Slog.w(TAG, "Not scheduling restart of crashed service " + r.shortName
+ Slog.w(TAG, "Not scheduling restart of crashed service " + r.shortInstanceName
+ " - system is shutting down");
return false;
}
ServiceMap smap = getServiceMapLocked(r.userId);
- if (smap.mServicesByName.get(r.name) != r) {
- ServiceRecord cur = smap.mServicesByName.get(r.name);
+ if (smap.mServicesByInstanceName.get(r.instanceName) != r) {
+ ServiceRecord cur = smap.mServicesByInstanceName.get(r.instanceName);
Slog.wtf(TAG, "Attempting to schedule restart of " + r
+ " when found in map: " + cur);
return false;
@@ -2184,7 +2207,7 @@
if (resetTime < dur) resetTime = dur;
} else {
Slog.w(TAG, "Canceling start item " + si.intent + " in service "
- + r.name);
+ + r.shortInstanceName);
canceled = true;
}
}
@@ -2256,9 +2279,9 @@
mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime);
r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay;
Slog.w(TAG, "Scheduling restart of crashed service "
- + r.shortName + " in " + r.restartDelay + "ms");
+ + r.shortInstanceName + " in " + r.restartDelay + "ms");
EventLog.writeEvent(EventLogTags.AM_SCHEDULE_SERVICE_RESTART,
- r.userId, r.shortName, r.restartDelay);
+ r.userId, r.shortInstanceName, r.restartDelay);
return canceled;
}
@@ -2393,7 +2416,7 @@
} catch (TransactionTooLargeException e) {
throw e;
} catch (RemoteException e) {
- Slog.w(TAG, "Exception when starting service " + r.shortName, e);
+ Slog.w(TAG, "Exception when starting service " + r.shortInstanceName, e);
}
// If a dead object exception was thrown -- fall through to
@@ -2417,7 +2440,7 @@
// to be executed when the app comes up.
if (app == null && !permissionsReviewRequired) {
if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
- hostingType, r.name, false, isolated, false)) == null) {
+ hostingType, r.instanceName, false, isolated, false)) == null) {
String msg = "Unable to launch app "
+ r.appInfo.packageName + "/"
+ r.appInfo.uid + " for service "
@@ -2488,8 +2511,9 @@
try {
if (LOG_SERVICE_START_STOP) {
String nameTerm;
- int lastPeriod = r.shortName.lastIndexOf('.');
- nameTerm = lastPeriod >= 0 ? r.shortName.substring(lastPeriod) : r.shortName;
+ int lastPeriod = r.shortInstanceName.lastIndexOf('.');
+ nameTerm = lastPeriod >= 0 ? r.shortInstanceName.substring(lastPeriod)
+ : r.shortInstanceName;
EventLogTags.writeAmCreateService(
r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid);
}
@@ -2705,9 +2729,9 @@
try {
cr.conn.connected(r.name, null, true);
} catch (Exception e) {
- Slog.w(TAG, "Failure disconnecting service " + r.name +
- " to connection " + c.get(i).conn.asBinder() +
- " (in " + c.get(i).binding.client.processName + ")", e);
+ Slog.w(TAG, "Failure disconnecting service " + r.shortInstanceName
+ + " to connection " + c.get(i).conn.asBinder()
+ + " (in " + c.get(i).binding.client.processName + ")", e);
}
}
}
@@ -2728,7 +2752,7 @@
ibr.intent.getIntent());
} catch (Exception e) {
Slog.w(TAG, "Exception when unbinding service "
- + r.shortName, e);
+ + r.shortInstanceName, e);
serviceProcessGoneLocked(r);
}
}
@@ -2773,14 +2797,14 @@
}
final ServiceMap smap = getServiceMapLocked(r.userId);
- ServiceRecord found = smap.mServicesByName.remove(r.name);
+ ServiceRecord found = smap.mServicesByInstanceName.remove(r.instanceName);
// Note when this method is called by bringUpServiceLocked(), the service is not found
- // in mServicesByName and found will be null.
+ // in mServicesByInstanceName and found will be null.
if (found != null && found != r) {
// This is not actually the service we think is running... this should not happen,
// but if it does, fail hard.
- smap.mServicesByName.put(r.name, found);
+ smap.mServicesByInstanceName.put(r.instanceName, found);
throw new IllegalStateException("Bringing down " + r + " but actually running "
+ found);
}
@@ -2807,8 +2831,8 @@
mAm.mAppOpsService.finishOperation(
AppOpsManager.getToken(mAm.mAppOpsService),
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
- StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.appInfo.uid, r.shortName,
- StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT);
+ StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.appInfo.uid,
+ r.shortInstanceName, StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT);
mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
}
@@ -2838,7 +2862,7 @@
r.app.thread.scheduleStopService(r);
} catch (Exception e) {
Slog.w(TAG, "Exception when destroying service "
- + r.shortName, e);
+ + r.shortInstanceName, e);
serviceProcessGoneLocked(r);
}
} else {
@@ -2915,7 +2939,7 @@
}
mAm.stopAssociationLocked(b.client.uid, b.client.processName, s.appInfo.uid,
- s.appInfo.longVersionCode, s.name, s.processName);
+ s.appInfo.longVersionCode, s.instanceName, s.processName);
if (b.connections.size() == 0) {
b.intent.apps.remove(b.client);
@@ -2942,7 +2966,7 @@
b.intent.doRebind = false;
s.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent());
} catch (Exception e) {
- Slog.w(TAG, "Exception when unbinding service " + s.shortName, e);
+ Slog.w(TAG, "Exception when unbinding service " + s.shortInstanceName, e);
serviceProcessGoneLocked(s);
}
}
@@ -3061,17 +3085,17 @@
+ ": nesting=" + r.executeNesting
+ ", inDestroying=" + inDestroying + ", app=" + r.app);
else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
- "<<< DONE EXECUTING " + r.shortName);
+ "<<< DONE EXECUTING " + r.shortInstanceName);
r.executeNesting--;
if (r.executeNesting <= 0) {
if (r.app != null) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
- "Nesting at 0 of " + r.shortName);
+ "Nesting at 0 of " + r.shortInstanceName);
r.app.execServicesFg = false;
r.app.executingServices.remove(r);
if (r.app.executingServices.size() == 0) {
if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
- "No more executingServices of " + r.shortName);
+ "No more executingServices of " + r.shortInstanceName);
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
} else if (r.executeFg) {
// Need to re-evaluate whether the app still needs to be in the foreground.
@@ -3141,7 +3165,7 @@
}
} catch (RemoteException e) {
Slog.w(TAG, "Exception in new application when starting service "
- + sr.shortName, e);
+ + sr.shortInstanceName, e);
throw e;
}
}
@@ -3227,7 +3251,8 @@
if (userId == UserHandle.USER_ALL) {
for (int i = mServiceMap.size() - 1; i >= 0; i--) {
didSomething |= collectPackageServicesLocked(packageName, filterByClasses,
- evenPersistent, doit, killProcess, mServiceMap.valueAt(i).mServicesByName);
+ evenPersistent, doit, killProcess,
+ mServiceMap.valueAt(i).mServicesByInstanceName);
if (!doit && didSomething) {
return true;
}
@@ -3238,7 +3263,7 @@
} else {
ServiceMap smap = mServiceMap.get(userId);
if (smap != null) {
- ArrayMap<ComponentName, ServiceRecord> items = smap.mServicesByName;
+ ArrayMap<ComponentName, ServiceRecord> items = smap.mServicesByInstanceName;
didSomething = collectPackageServicesLocked(packageName, filterByClasses,
evenPersistent, doit, killProcess, items);
}
@@ -3288,7 +3313,7 @@
ServiceRecord sr = services.get(i);
if (sr.startRequested) {
if ((sr.serviceInfo.flags&ServiceInfo.FLAG_STOP_WITH_TASK) != 0) {
- Slog.i(TAG, "Stopping service " + sr.shortName + ": remove task");
+ Slog.i(TAG, "Stopping service " + sr.shortInstanceName + ": remove task");
stopServiceLocked(sr);
} else {
sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true,
@@ -3326,7 +3351,7 @@
} catch (Exception e) {
// todo: this should be asynchronous!
Slog.w(TAG, "Exception thrown disconnected servce "
- + r.shortName
+ + r.shortInstanceName
+ " from app " + app.processName, e);
}
}
@@ -3400,7 +3425,7 @@
if (false && proc != null && !proc.isPersistent() && proc.thread != null
&& proc.pid != 0 && proc.pid != ActivityManagerService.MY_PID
&& proc.setProcState >= ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
- proc.kill("bound to service " + sr.name.flattenToShortString()
+ proc.kill("bound to service " + sr.shortInstanceName
+ " in dying proc " + (app != null ? app.processName : "??"), true);
}
}
@@ -3421,7 +3446,7 @@
// Sanity check: if the service listed for the app is not one
// we actually are maintaining, just let it drop.
- final ServiceRecord curRec = smap.mServicesByName.get(sr.name);
+ final ServiceRecord curRec = smap.mServicesByInstanceName.get(sr.instanceName);
if (curRec != sr) {
if (curRec != null) {
Slog.wtf(TAG, "Service " + sr + " in process " + app
@@ -3438,7 +3463,7 @@
Slog.w(TAG, "Service crashed " + sr.crashCount
+ " times, stopping: " + sr);
EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH,
- sr.userId, sr.crashCount, sr.shortName, app.pid);
+ sr.userId, sr.crashCount, sr.shortInstanceName, app.pid);
bringDownServiceLocked(sr);
} else if (!allowRestart
|| !mAm.mUserController.isUserRunning(sr.userId, 0)) {
@@ -3644,7 +3669,7 @@
mLastAnrDump = sw.toString();
mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
- anrMessage = "executing service " + timeout.shortName;
+ anrMessage = "executing service " + timeout.shortInstanceName;
} else {
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
@@ -3690,7 +3715,8 @@
final int userId = UserHandle.getUserId(applicationInfo.uid);
ServiceMap serviceMap = mServiceMap.get(userId);
if (serviceMap != null) {
- ArrayMap<ComponentName, ServiceRecord> servicesByName = serviceMap.mServicesByName;
+ ArrayMap<ComponentName, ServiceRecord> servicesByName
+ = serviceMap.mServicesByInstanceName;
for (int j = servicesByName.size() - 1; j >= 0; j--) {
ServiceRecord serviceRecord = servicesByName.valueAt(j);
if (applicationInfo.packageName.equals(serviceRecord.appInfo.packageName)) {
@@ -3761,9 +3787,9 @@
final int[] users = mAm.mUserController.getUsers();
for (int user : users) {
ServiceMap smap = getServiceMapLocked(user);
- if (smap.mServicesByName.size() > 0) {
- for (int si=0; si<smap.mServicesByName.size(); si++) {
- ServiceRecord r = smap.mServicesByName.valueAt(si);
+ if (smap.mServicesByInstanceName.size() > 0) {
+ for (int si=0; si<smap.mServicesByInstanceName.size(); si++) {
+ ServiceRecord r = smap.mServicesByInstanceName.valueAt(si);
if (!matcher.match(r, r.name)) {
continue;
}
@@ -4164,7 +4190,7 @@
}
long token = proto.start(ActiveServicesProto.SERVICES_BY_USERS);
proto.write(ActiveServicesProto.ServicesByUser.USER_ID, user);
- ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
+ ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByInstanceName;
for (int i=0; i<alls.size(); i++) {
alls.valueAt(i).writeToProto(proto,
ActiveServicesProto.ServicesByUser.SERVICE_RECORDS);
@@ -4197,7 +4223,7 @@
if (smap == null) {
continue;
}
- ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
+ ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByInstanceName;
for (int i=0; i<alls.size(); i++) {
ServiceRecord r1 = alls.valueAt(i);
@@ -4235,7 +4261,7 @@
String innerPrefix = prefix + " ";
synchronized (mAm) {
pw.print(prefix); pw.print("SERVICE ");
- pw.print(r.shortName); pw.print(" ");
+ pw.print(r.shortInstanceName); pw.print(" ");
pw.print(Integer.toHexString(System.identityHashCode(r)));
pw.print(" pid=");
if (r.app != null) pw.println(r.app.pid);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d1392d0..771d376 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13183,8 +13183,15 @@
}
public int bindService(IApplicationThread caller, IBinder token, Intent service,
- String resolvedType, IServiceConnection connection, int flags, String callingPackage,
- int userId) throws TransactionTooLargeException {
+ String resolvedType, IServiceConnection connection, int flags,
+ String callingPackage, int userId) throws TransactionTooLargeException {
+ return bindIsolatedService(caller, token, service, resolvedType, connection, flags,
+ null, callingPackage, userId);
+ }
+
+ public int bindIsolatedService(IApplicationThread caller, IBinder token, Intent service,
+ String resolvedType, IServiceConnection connection, int flags, String instanceName,
+ String callingPackage, int userId) throws TransactionTooLargeException {
enforceNotIsolatedCaller("bindService");
// Refuse possible leaked file descriptors
@@ -13198,7 +13205,7 @@
synchronized(this) {
return mServices.bindServiceLocked(caller, token, service,
- resolvedType, connection, flags, callingPackage, userId);
+ resolvedType, connection, flags, instanceName, callingPackage, userId);
}
}
@@ -15831,7 +15838,7 @@
mayBeTop = true;
mayBeTopType = "service";
mayBeTopSource = cr.binding.client;
- mayBeTopTarget = s.name;
+ mayBeTopTarget = s.instanceName;
clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
} else {
// Special handling for above-top states (persistent
@@ -15885,7 +15892,7 @@
.REASON_SERVICE_IN_USE;
app.adjSource = cr.binding.client;
app.adjSourceProcState = clientProcState;
- app.adjTarget = s.name;
+ app.adjTarget = s.instanceName;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
+ ": " + app + ", due to " + cr.binding.client
@@ -15915,7 +15922,7 @@
.REASON_SERVICE_IN_USE;
app.adjSource = a;
app.adjSourceProcState = procState;
- app.adjTarget = s.name;
+ app.adjTarget = s.instanceName;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise to service w/activity: " + app);
diff --git a/services/core/java/com/android/server/am/AppBindRecord.java b/services/core/java/com/android/server/am/AppBindRecord.java
index 4eaebd0..9870420 100644
--- a/services/core/java/com/android/server/am/AppBindRecord.java
+++ b/services/core/java/com/android/server/am/AppBindRecord.java
@@ -59,12 +59,12 @@
public String toString() {
return "AppBindRecord{"
+ Integer.toHexString(System.identityHashCode(this))
- + " " + service.shortName + ":" + client.processName + "}";
+ + " " + service.shortInstanceName + ":" + client.processName + "}";
}
void writeToProto(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
- proto.write(AppBindRecordProto.SERVICE_NAME, service.shortName);
+ proto.write(AppBindRecordProto.SERVICE_NAME, service.shortInstanceName);
proto.write(AppBindRecordProto.CLIENT_PROC_NAME, client.processName);
final int N = connections.size();
for (int i=0; i<N; i++) {
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index 37d07bb..bfa3f66 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -115,16 +115,16 @@
&& (binding.service.appInfo.uid != clientUid
|| !binding.service.processName.equals(clientProcessName))) {
ProcessStats.ProcessStateHolder holder = binding.service.app.pkgList.get(
- binding.service.name.getPackageName());
+ binding.service.instanceName.getPackageName());
if (holder == null) {
Slog.wtf(TAG_AM, "No package in referenced service "
- + binding.service.name.toShortString() + ": proc=" + binding.service.app);
+ + binding.service.shortInstanceName + ": proc=" + binding.service.app);
} else if (holder.pkg == null) {
Slog.wtf(TAG_AM, "Inactive holder in referenced service "
- + binding.service.name.toShortString() + ": proc=" + binding.service.app);
+ + binding.service.shortInstanceName + ": proc=" + binding.service.app);
} else {
association = holder.pkg.getAssociationStateLocked(holder.state,
- binding.service.name.getClassName()).startSource(clientUid,
+ binding.service.instanceName.getClassName()).startSource(clientUid,
clientProcessName);
}
@@ -202,7 +202,7 @@
if (serviceDead) {
sb.append("DEAD ");
}
- sb.append(binding.service.shortName);
+ sb.append(binding.service.shortInstanceName);
sb.append(":@");
sb.append(Integer.toHexString(System.identityHashCode(conn.asBinder())));
sb.append('}');
@@ -223,7 +223,7 @@
proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.DEAD);
}
if (binding.service != null) {
- proto.write(ConnectionRecordProto.SERVICE_NAME, binding.service.shortName);
+ proto.write(ConnectionRecordProto.SERVICE_NAME, binding.service.shortInstanceName);
}
proto.end(token);
}
diff --git a/services/core/java/com/android/server/am/IntentBindRecord.java b/services/core/java/com/android/server/am/IntentBindRecord.java
index 839b6e1..90aef3e 100644
--- a/services/core/java/com/android/server/am/IntentBindRecord.java
+++ b/services/core/java/com/android/server/am/IntentBindRecord.java
@@ -99,7 +99,7 @@
if ((collectFlags()&Context.BIND_AUTO_CREATE) != 0) {
sb.append("CR ");
}
- sb.append(service.shortName);
+ sb.append(service.shortInstanceName);
sb.append(':');
if (intent != null) {
intent.getIntent().toShortString(sb, false, false, false, false);
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index d8f94c9..09f8c3e 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -70,7 +70,8 @@
final ActivityManagerService ams;
final BatteryStatsImpl.Uid.Pkg.Serv stats;
final ComponentName name; // service component.
- final String shortName; // name.flattenToShortString().
+ final ComponentName instanceName; // service component's per-instance name.
+ final String shortInstanceName; // instanceName.flattenToShortString().
final Intent.FilterComparison intent;
// original intent used to find service.
final ServiceInfo serviceInfo;
@@ -190,7 +191,7 @@
StringBuilder sb = new StringBuilder(128);
sb.append("ServiceRecord{")
.append(Integer.toHexString(System.identityHashCode(sr)))
- .append(' ').append(sr.shortName)
+ .append(' ').append(sr.shortInstanceName)
.append(" StartItem ")
.append(Integer.toHexString(System.identityHashCode(this)))
.append(" id=").append(id).append('}');
@@ -235,7 +236,7 @@
void writeToProto(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
- proto.write(ServiceRecordProto.SHORT_NAME, this.shortName);
+ proto.write(ServiceRecordProto.SHORT_NAME, this.shortInstanceName);
proto.write(ServiceRecordProto.IS_RUNNING, app != null);
if (app != null) {
proto.write(ServiceRecordProto.PID, app.pid);
@@ -448,12 +449,14 @@
ServiceRecord(ActivityManagerService ams,
BatteryStatsImpl.Uid.Pkg.Serv servStats, ComponentName name,
+ ComponentName instanceName,
Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
Runnable restarter) {
this.ams = ams;
this.stats = servStats;
this.name = name;
- shortName = name.flattenToShortString();
+ this.instanceName = instanceName;
+ shortInstanceName = instanceName.flattenToShortString();
this.intent = intent;
serviceInfo = sInfo;
appInfo = sInfo.applicationInfo;
@@ -618,7 +621,7 @@
// those dirty apps we will create a notification clearly
// blaming the app.
Slog.v(TAG, "Attempted to start a foreground service ("
- + name
+ + shortInstanceName
+ ") with a broken notification (no icon: "
+ localForegroundNoti
+ ")");
@@ -701,7 +704,7 @@
Slog.w(TAG, "Error showing notification for service", e);
// If it gave us a garbage notification, it doesn't
// get to be foreground.
- ams.setServiceForeground(name, ServiceRecord.this,
+ ams.setServiceForeground(instanceName, ServiceRecord.this,
0, null, 0);
ams.crashApplication(appUid, appPid, localPackageName, -1,
"Bad notification for startForeground: " + e);
@@ -773,7 +776,7 @@
sb.append("ServiceRecord{")
.append(Integer.toHexString(System.identityHashCode(this)))
.append(" u").append(userId)
- .append(' ').append(shortName).append('}');
+ .append(' ').append(shortInstanceName).append('}');
return stringName = sb.toString();
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4c93441..6009bd3 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2637,7 +2637,7 @@
}
for (final SharedUserSetting sus : mSharedUsers.values()) {
- knownSet.remove(sus.getSandboxName());
+ knownSet.remove(sus.getStorageSandboxName());
}
// Remove any unclaimed mappings
@@ -2653,7 +2653,8 @@
void writeKernelMappingLPr(SharedUserSetting sus) {
if (mKernelMappingFilename == null || sus == null || sus.name == null) return;
- writeKernelMappingLPr(sus.getSandboxName(), sus.userId, sus.getNotInstalledUserIds());
+ writeKernelMappingLPr(sus.getStorageSandboxName(),
+ sus.userId, sus.getNotInstalledUserIds());
}
void writeKernelMappingLPr(PackageSetting ps) {
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index 32826e5..d67144e 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageParser;
+import android.os.storage.StorageManager;
import android.service.pm.PackageServiceDumpProto;
import android.util.ArraySet;
import android.util.proto.ProtoOutputStream;
@@ -166,8 +167,8 @@
return excludedUserIds == null ? EmptyArray.INT : excludedUserIds;
}
- public String getSandboxName() {
- return "shared:" + name;
+ public String getStorageSandboxName() {
+ return StorageManager.SHARED_SANDBOX_PREFIX + name;
}
/** Updates all fields in this shared user setting from another. */
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index cef484f..01d02d6 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -41,6 +41,9 @@
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.hardware.fingerprint.FingerprintManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkRequest;
import android.net.NetworkStats;
import android.net.wifi.IWifiManager;
import android.net.wifi.WifiActivityEnergyInfo;
@@ -271,6 +274,12 @@
Slog.e(TAG, "cannot find thermalservice, no throttling push notifications");
}
+ // Default NetworkRequest should cover all transport types.
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ final ConnectivityManager connectivityManager =
+ (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ connectivityManager.registerNetworkCallback(request, new ConnectivityStatsCallback());
+
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
mHandler = new CompanionHandler(handlerThread.getLooper());
@@ -1875,4 +1884,19 @@
temp.getValue());
}
}
+
+ private static final class ConnectivityStatsCallback extends
+ ConnectivityManager.NetworkCallback {
+ @Override
+ public void onAvailable(Network network) {
+ StatsLog.write(StatsLog.CONNECTIVITY_STATE_CHANGED, network.netId,
+ StatsLog.CONNECTIVITY_STATE_CHANGED__STATE__CONNECTED);
+ }
+
+ @Override
+ public void onLost(Network network) {
+ StatsLog.write(StatsLog.CONNECTIVITY_STATE_CHANGED, network.netId,
+ StatsLog.CONNECTIVITY_STATE_CHANGED__STATE__DISCONNECTED);
+ }
+ }
}
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java
new file mode 100644
index 0000000..77b7347
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2018 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.backup.encryption.chunking.cdc;
+
+import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Random;
+
+import javax.crypto.SecretKey;
+
+/** Tests for {@link ContentDefinedChunker}. */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class ContentDefinedChunkerTest {
+ private static final int WINDOW_SIZE_BYTES = 31;
+ private static final int MIN_SIZE_BYTES = 40;
+ private static final int MAX_SIZE_BYTES = 300;
+ private static final String CHUNK_BOUNDARY = "<----------BOUNDARY----------->";
+ private static final byte[] CHUNK_BOUNDARY_BYTES = CHUNK_BOUNDARY.getBytes(UTF_8);
+ private static final String CHUNK_1 = "This is the first chunk";
+ private static final String CHUNK_2 = "And this is the second chunk";
+ private static final String CHUNK_3 = "And finally here is the third chunk";
+ private static final String SMALL_CHUNK = "12345678";
+
+ private FingerprintMixer mFingerprintMixer;
+ private RabinFingerprint64 mRabinFingerprint64;
+ private ContentDefinedChunker mChunker;
+
+ /** Set up a {@link ContentDefinedChunker} and dependencies for use in the tests. */
+ @Before
+ public void setUp() throws Exception {
+ SecretKey secretKey = generateAesKey();
+ byte[] salt = new byte[FingerprintMixer.SALT_LENGTH_BYTES];
+ Random random = new Random();
+ random.nextBytes(salt);
+ mFingerprintMixer = new FingerprintMixer(secretKey, salt);
+
+ mRabinFingerprint64 = new RabinFingerprint64();
+ long chunkBoundaryFingerprint = calculateFingerprint(CHUNK_BOUNDARY_BYTES);
+ mChunker =
+ new ContentDefinedChunker(
+ MIN_SIZE_BYTES,
+ MAX_SIZE_BYTES,
+ mRabinFingerprint64,
+ mFingerprintMixer,
+ (fingerprint) -> fingerprint == chunkBoundaryFingerprint);
+ }
+
+ /**
+ * Creating a {@link ContentDefinedChunker} with a minimum chunk size that is smaller than the
+ * window size should throw an {@link IllegalArgumentException}.
+ */
+ @Test
+ public void create_withMinChunkSizeSmallerThanWindowSize_throwsIllegalArgumentException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ new ContentDefinedChunker(
+ WINDOW_SIZE_BYTES - 1,
+ MAX_SIZE_BYTES,
+ mRabinFingerprint64,
+ mFingerprintMixer,
+ null));
+ }
+
+ /**
+ * Creating a {@link ContentDefinedChunker} with a maximum chunk size that is smaller than the
+ * minimum chunk size should throw an {@link IllegalArgumentException}.
+ */
+ @Test
+ public void create_withMaxChunkSizeSmallerThanMinChunkSize_throwsIllegalArgumentException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ new ContentDefinedChunker(
+ MIN_SIZE_BYTES,
+ MIN_SIZE_BYTES - 1,
+ mRabinFingerprint64,
+ mFingerprintMixer,
+ null));
+ }
+
+ /**
+ * {@link ContentDefinedChunker#chunkify(InputStream, Chunker.ChunkConsumer)} should split the
+ * input stream across chunk boundaries by default.
+ */
+ @Test
+ public void chunkify_withLargeChunks_splitsIntoChunksAcrossBoundaries() throws Exception {
+ byte[] input =
+ (CHUNK_1 + CHUNK_BOUNDARY + CHUNK_2 + CHUNK_BOUNDARY + CHUNK_3).getBytes(UTF_8);
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(input);
+ ArrayList<String> result = new ArrayList<>();
+
+ mChunker.chunkify(inputStream, (chunk) -> result.add(new String(chunk, UTF_8)));
+
+ assertThat(result)
+ .containsExactly(CHUNK_1 + CHUNK_BOUNDARY, CHUNK_2 + CHUNK_BOUNDARY, CHUNK_3)
+ .inOrder();
+ }
+
+ /** Chunks should be combined across boundaries until they reach the minimum chunk size. */
+ @Test
+ public void chunkify_withSmallChunks_combinesChunksUntilMinSize() throws Exception {
+ byte[] input =
+ (SMALL_CHUNK + CHUNK_BOUNDARY + CHUNK_2 + CHUNK_BOUNDARY + CHUNK_3).getBytes(UTF_8);
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(input);
+ ArrayList<String> result = new ArrayList<>();
+
+ mChunker.chunkify(inputStream, (chunk) -> result.add(new String(chunk, UTF_8)));
+
+ assertThat(result)
+ .containsExactly(SMALL_CHUNK + CHUNK_BOUNDARY + CHUNK_2 + CHUNK_BOUNDARY, CHUNK_3)
+ .inOrder();
+ assertThat(result.get(0).length()).isAtLeast(MIN_SIZE_BYTES);
+ }
+
+ /** Chunks can not be larger than the maximum chunk size. */
+ @Test
+ public void chunkify_doesNotProduceChunksLargerThanMaxSize() throws Exception {
+ byte[] largeInput = new byte[MAX_SIZE_BYTES * 10];
+ Arrays.fill(largeInput, "a".getBytes(UTF_8)[0]);
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(largeInput);
+ ArrayList<String> result = new ArrayList<>();
+
+ mChunker.chunkify(inputStream, (chunk) -> result.add(new String(chunk, UTF_8)));
+
+ byte[] expectedChunkBytes = new byte[MAX_SIZE_BYTES];
+ Arrays.fill(expectedChunkBytes, "a".getBytes(UTF_8)[0]);
+ String expectedChunk = new String(expectedChunkBytes, UTF_8);
+ assertThat(result)
+ .containsExactly(
+ expectedChunk,
+ expectedChunk,
+ expectedChunk,
+ expectedChunk,
+ expectedChunk,
+ expectedChunk,
+ expectedChunk,
+ expectedChunk,
+ expectedChunk,
+ expectedChunk)
+ .inOrder();
+ }
+
+ /**
+ * If the input stream signals zero availablility, {@link
+ * ContentDefinedChunker#chunkify(InputStream, Chunker.ChunkConsumer)} should still work.
+ */
+ @Test
+ public void chunkify_withInputStreamReturningZeroAvailability_returnsChunks() throws Exception {
+ byte[] input = (SMALL_CHUNK + CHUNK_BOUNDARY + CHUNK_2).getBytes(UTF_8);
+ ZeroAvailabilityInputStream zeroAvailabilityInputStream =
+ new ZeroAvailabilityInputStream(input);
+ ArrayList<String> result = new ArrayList<>();
+
+ mChunker.chunkify(
+ zeroAvailabilityInputStream, (chunk) -> result.add(new String(chunk, UTF_8)));
+
+ assertThat(result).containsExactly(SMALL_CHUNK + CHUNK_BOUNDARY + CHUNK_2).inOrder();
+ }
+
+ /**
+ * {@link ContentDefinedChunker#chunkify(InputStream, Chunker.ChunkConsumer)} should rethrow any
+ * exception thrown by its consumer.
+ */
+ @Test
+ public void chunkify_whenConsumerThrowsException_rethrowsException() throws Exception {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] {1});
+
+ assertThrows(
+ GeneralSecurityException.class,
+ () ->
+ mChunker.chunkify(
+ inputStream,
+ (chunk) -> {
+ throw new GeneralSecurityException();
+ }));
+ }
+
+ private long calculateFingerprint(byte[] bytes) {
+ long fingerprint = 0;
+ for (byte inByte : bytes) {
+ fingerprint =
+ mRabinFingerprint64.computeFingerprint64(
+ /*inChar=*/ inByte, /*outChar=*/ (byte) 0, fingerprint);
+ }
+ return mFingerprintMixer.mix(fingerprint);
+ }
+
+ private static class ZeroAvailabilityInputStream extends ByteArrayInputStream {
+ ZeroAvailabilityInputStream(byte[] wrapped) {
+ super(wrapped);
+ }
+
+ @Override
+ public synchronized int available() {
+ return 0;
+ }
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java
new file mode 100644
index 0000000..936b5dc
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2018 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.backup.encryption.chunking.cdc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.util.HashSet;
+import java.util.Random;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+/** Tests for {@link FingerprintMixer}. */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class FingerprintMixerTest {
+ private static final String KEY_ALGORITHM = "AES";
+ private static final int SEED = 42;
+ private static final int SALT_LENGTH_BYTES = 256 / 8;
+ private static final int KEY_SIZE_BITS = 256;
+
+ private Random mSeededRandom;
+ private FingerprintMixer mFingerprintMixer;
+
+ /** Set up a {@link FingerprintMixer} with deterministic key and salt generation. */
+ @Before
+ public void setUp() throws Exception {
+ // Seed so that the tests are deterministic.
+ mSeededRandom = new Random(SEED);
+ mFingerprintMixer = new FingerprintMixer(randomKey(), randomSalt());
+ }
+
+ /**
+ * Construcing a {@link FingerprintMixer} with a salt that is too small should throw an {@link
+ * IllegalArgumentException}.
+ */
+ @Test
+ public void create_withIncorrectSaltSize_throwsIllegalArgumentException() {
+ byte[] tooSmallSalt = new byte[SALT_LENGTH_BYTES - 1];
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new FingerprintMixer(randomKey(), tooSmallSalt));
+ }
+
+ /**
+ * Constructing a {@link FingerprintMixer} with a secret key that can't be encoded should throw
+ * an {@link InvalidKeyException}.
+ */
+ @Test
+ public void create_withUnencodableSecretKey_throwsInvalidKeyException() {
+ byte[] keyBytes = new byte[KEY_SIZE_BITS / 8];
+ UnencodableSecretKeySpec keySpec =
+ new UnencodableSecretKeySpec(keyBytes, 0, keyBytes.length, KEY_ALGORITHM);
+
+ assertThrows(InvalidKeyException.class, () -> new FingerprintMixer(keySpec, randomSalt()));
+ }
+
+ /**
+ * {@link FingerprintMixer#getAddend()} should not return the same addend for two different
+ * keys.
+ */
+ @Test
+ public void getAddend_withDifferentKey_returnsDifferentResult() throws Exception {
+ int iterations = 100_000;
+ HashSet<Long> returnedAddends = new HashSet<>();
+ byte[] salt = randomSalt();
+
+ for (int i = 0; i < iterations; i++) {
+ FingerprintMixer fingerprintMixer = new FingerprintMixer(randomKey(), salt);
+ long addend = fingerprintMixer.getAddend();
+ returnedAddends.add(addend);
+ }
+
+ assertThat(returnedAddends).containsNoDuplicates();
+ }
+
+ /**
+ * {@link FingerprintMixer#getMultiplicand()} should not return the same multiplicand for two
+ * different keys.
+ */
+ @Test
+ public void getMultiplicand_withDifferentKey_returnsDifferentResult() throws Exception {
+ int iterations = 100_000;
+ HashSet<Long> returnedMultiplicands = new HashSet<>();
+ byte[] salt = randomSalt();
+
+ for (int i = 0; i < iterations; i++) {
+ FingerprintMixer fingerprintMixer = new FingerprintMixer(randomKey(), salt);
+ long multiplicand = fingerprintMixer.getMultiplicand();
+ returnedMultiplicands.add(multiplicand);
+ }
+
+ assertThat(returnedMultiplicands).containsNoDuplicates();
+ }
+
+ /** The multiplicant returned by {@link FingerprintMixer} should always be odd. */
+ @Test
+ public void getMultiplicand_isOdd() throws Exception {
+ int iterations = 100_000;
+
+ for (int i = 0; i < iterations; i++) {
+ FingerprintMixer fingerprintMixer = new FingerprintMixer(randomKey(), randomSalt());
+
+ long multiplicand = fingerprintMixer.getMultiplicand();
+
+ assertThat(isOdd(multiplicand)).isTrue();
+ }
+ }
+
+ /** {@link FingerprintMixer#mix(long)} should have a random distribution. */
+ @Test
+ public void mix_randomlyDistributesBits() throws Exception {
+ int iterations = 100_000;
+ float tolerance = 0.1f;
+ int[] totals = new int[64];
+
+ for (int i = 0; i < iterations; i++) {
+ long n = mFingerprintMixer.mix(mSeededRandom.nextLong());
+ for (int j = 0; j < 64; j++) {
+ int bit = (int) (n >> j & 1);
+ totals[j] += bit;
+ }
+ }
+
+ for (int i = 0; i < 64; i++) {
+ float mean = ((float) totals[i]) / iterations;
+ float diff = Math.abs(mean - 0.5f);
+ assertThat(diff).isLessThan(tolerance);
+ }
+ }
+
+ /**
+ * {@link FingerprintMixer#mix(long)} should always produce a number that's different from the
+ * input.
+ */
+ @Test
+ public void mix_doesNotProduceSameNumberAsInput() {
+ int iterations = 100_000;
+
+ for (int i = 0; i < iterations; i++) {
+ assertThat(mFingerprintMixer.mix(i)).isNotEqualTo(i);
+ }
+ }
+
+ private byte[] randomSalt() {
+ byte[] salt = new byte[SALT_LENGTH_BYTES];
+ mSeededRandom.nextBytes(salt);
+ return salt;
+ }
+
+ /**
+ * Not a secure way of generating keys. We want to deterministically generate the same keys for
+ * each test run, though, to ensure the test is deterministic.
+ */
+ private SecretKey randomKey() {
+ byte[] keyBytes = new byte[KEY_SIZE_BITS / 8];
+ mSeededRandom.nextBytes(keyBytes);
+ return new SecretKeySpec(keyBytes, 0, keyBytes.length, KEY_ALGORITHM);
+ }
+
+ private static boolean isOdd(long n) {
+ return Math.abs(n % 2) == 1;
+ }
+
+ /**
+ * Subclass of {@link SecretKeySpec} that does not provide an encoded version. As per its
+ * contract in {@link Key}, that means {@code getEncoded()} always returns null.
+ */
+ private class UnencodableSecretKeySpec extends SecretKeySpec {
+ UnencodableSecretKeySpec(byte[] key, int offset, int len, String algorithm) {
+ super(key, offset, len, algorithm);
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ return null;
+ }
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java
new file mode 100644
index 0000000..5494374
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 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.backup.encryption.chunking.cdc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link Hkdf}. */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class HkdfTest {
+ /** HKDF Test Case 1 IKM from RFC 5869 */
+ private static final byte[] HKDF_CASE1_IKM = {
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b
+ };
+
+ /** HKDF Test Case 1 salt from RFC 5869 */
+ private static final byte[] HKDF_CASE1_SALT = {
+ 0x00, 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x0a, 0x0b, 0x0c
+ };
+
+ /** HKDF Test Case 1 info from RFC 5869 */
+ private static final byte[] HKDF_CASE1_INFO = {
+ (byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, (byte) 0xf4,
+ (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, (byte) 0xf8, (byte) 0xf9
+ };
+
+ /** First 32 bytes of HKDF Test Case 1 OKM (output) from RFC 5869 */
+ private static final byte[] HKDF_CASE1_OKM = {
+ (byte) 0x3c, (byte) 0xb2, (byte) 0x5f, (byte) 0x25, (byte) 0xfa,
+ (byte) 0xac, (byte) 0xd5, (byte) 0x7a, (byte) 0x90, (byte) 0x43,
+ (byte) 0x4f, (byte) 0x64, (byte) 0xd0, (byte) 0x36, (byte) 0x2f,
+ (byte) 0x2a, (byte) 0x2d, (byte) 0x2d, (byte) 0x0a, (byte) 0x90,
+ (byte) 0xcf, (byte) 0x1a, (byte) 0x5a, (byte) 0x4c, (byte) 0x5d,
+ (byte) 0xb0, (byte) 0x2d, (byte) 0x56, (byte) 0xec, (byte) 0xc4,
+ (byte) 0xc5, (byte) 0xbf
+ };
+
+ /** Test the example from RFC 5869. */
+ @Test
+ public void hkdf_derivesKeyMaterial() throws Exception {
+ byte[] result = Hkdf.hkdf(HKDF_CASE1_IKM, HKDF_CASE1_SALT, HKDF_CASE1_INFO);
+
+ assertThat(result).isEqualTo(HKDF_CASE1_OKM);
+ }
+
+ /** Providing a key that is null should throw a {@link java.lang.NullPointerException}. */
+ @Test
+ public void hkdf_withNullKey_throwsNullPointerException() throws Exception {
+ assertThrows(
+ NullPointerException.class,
+ () -> Hkdf.hkdf(null, HKDF_CASE1_SALT, HKDF_CASE1_INFO));
+ }
+
+ /** Providing a salt that is null should throw a {@link java.lang.NullPointerException}. */
+ @Test
+ public void hkdf_withNullSalt_throwsNullPointerException() throws Exception {
+ assertThrows(
+ NullPointerException.class, () -> Hkdf.hkdf(HKDF_CASE1_IKM, null, HKDF_CASE1_INFO));
+ }
+
+ /** Providing data that is null should throw a {@link java.lang.NullPointerException}. */
+ @Test
+ public void hkdf_withNullData_throwsNullPointerException() throws Exception {
+ assertThrows(
+ NullPointerException.class, () -> Hkdf.hkdf(HKDF_CASE1_IKM, HKDF_CASE1_SALT, null));
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java
new file mode 100644
index 0000000..277dc37
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2018 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.backup.encryption.chunking.cdc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Random;
+
+/** Tests for {@link IsChunkBreakpoint}. */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class IsChunkBreakpointTest {
+ private static final int RANDOM_SEED = 42;
+ private static final double TOLERANCE = 0.01;
+ private static final int NUMBER_OF_TESTS = 10000;
+ private static final int BITS_PER_LONG = 64;
+
+ private Random mRandom;
+
+ /** Make sure that tests are deterministic. */
+ @Before
+ public void setUp() {
+ mRandom = new Random(RANDOM_SEED);
+ }
+
+ /**
+ * Providing a negative average number of trials should throw an {@link
+ * IllegalArgumentException}.
+ */
+ @Test
+ public void create_withNegativeAverageNumberOfTrials_throwsIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () -> new IsChunkBreakpoint(-1));
+ }
+
+ // Note: the following three tests are compute-intensive, so be cautious adding more.
+
+ /**
+ * If the provided average number of trials is zero, a breakpoint should be expected after one
+ * trial on average.
+ */
+ @Test
+ public void
+ isBreakpoint_withZeroAverageNumberOfTrials_isTrueOnAverageAfterOneTrial() {
+ assertExpectedTrials(new IsChunkBreakpoint(0), /*expectedTrials=*/ 1);
+ }
+
+ /**
+ * If the provided average number of trials is 512, a breakpoint should be expected after 512
+ * trials on average.
+ */
+ @Test
+ public void
+ isBreakpoint_with512AverageNumberOfTrials_isTrueOnAverageAfter512Trials() {
+ assertExpectedTrials(new IsChunkBreakpoint(512), /*expectedTrials=*/ 512);
+ }
+
+ /**
+ * If the provided average number of trials is 1024, a breakpoint should be expected after 1024
+ * trials on average.
+ */
+ @Test
+ public void
+ isBreakpoint_with1024AverageNumberOfTrials_isTrueOnAverageAfter1024Trials() {
+ assertExpectedTrials(new IsChunkBreakpoint(1024), /*expectedTrials=*/ 1024);
+ }
+
+ /** The number of leading zeros should be the logarithm of the average number of trials. */
+ @Test
+ public void getLeadingZeros_squaredIsAverageNumberOfTrials() {
+ for (int i = 0; i < BITS_PER_LONG; i++) {
+ long averageNumberOfTrials = (long) Math.pow(2, i);
+
+ int leadingZeros = new IsChunkBreakpoint(averageNumberOfTrials).getLeadingZeros();
+
+ assertThat(leadingZeros).isEqualTo(i);
+ }
+ }
+
+ private void assertExpectedTrials(IsChunkBreakpoint isChunkBreakpoint, long expectedTrials) {
+ long sum = 0;
+ for (int i = 0; i < NUMBER_OF_TESTS; i++) {
+ sum += numberOfTrialsTillBreakpoint(isChunkBreakpoint);
+ }
+ long averageTrials = sum / NUMBER_OF_TESTS;
+ assertThat((double) Math.abs(averageTrials - expectedTrials))
+ .isLessThan(TOLERANCE * expectedTrials);
+ }
+
+ private int numberOfTrialsTillBreakpoint(IsChunkBreakpoint isChunkBreakpoint) {
+ int trials = 0;
+
+ while (true) {
+ trials++;
+ if (isChunkBreakpoint.isBreakpoint(mRandom.nextLong())) {
+ return trials;
+ }
+ }
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java
new file mode 100644
index 0000000..729580c
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2018 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.backup.encryption.chunking.cdc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link RabinFingerprint64}. */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class RabinFingerprint64Test {
+ private static final int WINDOW_SIZE = 31;
+ private static final ImmutableList<String> TEST_STRINGS =
+ ImmutableList.of(
+ "ervHTtChYXO6eXivYqThlyyzqkbRaOR",
+ "IxaVunH9ZC3qneWfhj1GkBH4ys9CYqz",
+ "wZRVjlE1p976icCFPX9pibk4PEBvjSH",
+ "pHIVaT8x8If9D6s9croksgNmJpmGYWI");
+
+ private final RabinFingerprint64 mRabinFingerprint64 = new RabinFingerprint64();
+
+ /**
+ * No matter where in the input buffer a string occurs, {@link
+ * RabinFingerprint64#computeFingerprint64(byte, byte, long)} should return the same
+ * fingerprint.
+ */
+ @Test
+ public void computeFingerprint64_forSameWindow_returnsSameFingerprint() {
+ long fingerprint1 =
+ computeFingerprintAtPosition(getBytes(TEST_STRINGS.get(0)), WINDOW_SIZE - 1);
+ long fingerprint2 =
+ computeFingerprintAtPosition(
+ getBytes(TEST_STRINGS.get(1), TEST_STRINGS.get(0)), WINDOW_SIZE * 2 - 1);
+ long fingerprint3 =
+ computeFingerprintAtPosition(
+ getBytes(TEST_STRINGS.get(2), TEST_STRINGS.get(3), TEST_STRINGS.get(0)),
+ WINDOW_SIZE * 3 - 1);
+ String stub = "abc";
+ long fingerprint4 =
+ computeFingerprintAtPosition(
+ getBytes(stub, TEST_STRINGS.get(0)), WINDOW_SIZE + stub.length() - 1);
+
+ // Assert that all fingerprints are exactly the same
+ assertThat(ImmutableSet.of(fingerprint1, fingerprint2, fingerprint3, fingerprint4))
+ .hasSize(1);
+ }
+
+ /** The computed fingerprint should be different for different inputs. */
+ @Test
+ public void computeFingerprint64_withDifferentInput_returnsDifferentFingerprint() {
+ long fingerprint1 = computeFingerprintOf(TEST_STRINGS.get(0));
+ long fingerprint2 = computeFingerprintOf(TEST_STRINGS.get(1));
+ long fingerprint3 = computeFingerprintOf(TEST_STRINGS.get(2));
+ long fingerprint4 = computeFingerprintOf(TEST_STRINGS.get(3));
+
+ assertThat(ImmutableList.of(fingerprint1, fingerprint2, fingerprint3, fingerprint4))
+ .containsNoDuplicates();
+ }
+
+ /**
+ * An input with the same characters in a different order should return a different fingerprint.
+ */
+ @Test
+ public void computeFingerprint64_withSameInputInDifferentOrder_returnsDifferentFingerprint() {
+ long fingerprint1 = computeFingerprintOf("abcdefghijklmnopqrstuvwxyz12345");
+ long fingerprint2 = computeFingerprintOf("54321zyxwvutsrqponmlkjihgfedcba");
+ long fingerprint3 = computeFingerprintOf("4bcdefghijklmnopqrstuvwxyz123a5");
+ long fingerprint4 = computeFingerprintOf("bacdefghijklmnopqrstuvwxyz12345");
+
+ assertThat(ImmutableList.of(fingerprint1, fingerprint2, fingerprint3, fingerprint4))
+ .containsNoDuplicates();
+ }
+
+ /** UTF-8 bytes of all the given strings in order. */
+ private byte[] getBytes(String... strings) {
+ StringBuilder sb = new StringBuilder();
+ for (String s : strings) {
+ sb.append(s);
+ }
+ return sb.toString().getBytes(UTF_8);
+ }
+
+ /**
+ * The Rabin fingerprint of a window of bytes ending at {@code position} in the {@code bytes}
+ * array.
+ */
+ private long computeFingerprintAtPosition(byte[] bytes, int position) {
+ assertThat(position).isAtMost(bytes.length - 1);
+ long fingerprint = 0;
+ for (int i = 0; i <= position; i++) {
+ byte outChar;
+ if (i >= WINDOW_SIZE) {
+ outChar = bytes[i - WINDOW_SIZE];
+ } else {
+ outChar = (byte) 0;
+ }
+ fingerprint =
+ mRabinFingerprint64.computeFingerprint64(
+ /*inChar=*/ bytes[i], outChar, fingerprint);
+ }
+ return fingerprint;
+ }
+
+ private long computeFingerprintOf(String s) {
+ assertThat(s.length()).isEqualTo(WINDOW_SIZE);
+ return computeFingerprintAtPosition(s.getBytes(UTF_8), WINDOW_SIZE - 1);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java
index ee42ce8..e6b328a 100644
--- a/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java
@@ -125,7 +125,7 @@
"/storage/emulated/0/foo.jpg",
PID_GREY, UID_GREY);
assertTranslation(
- "/storage/emulated/0/Android/sandbox/shared:colors/foo.jpg",
+ "/storage/emulated/0/Android/sandbox/shared-colors/foo.jpg",
"/storage/emulated/0/foo.jpg",
PID_RED, UID_COLORS);
}
@@ -137,7 +137,7 @@
"/storage/0000-0000/foo/bar.jpg",
PID_GREY, UID_GREY);
assertTranslation(
- "/storage/0000-0000/Android/sandbox/shared:colors/foo/bar.jpg",
+ "/storage/0000-0000/Android/sandbox/shared-colors/foo/bar.jpg",
"/storage/0000-0000/foo/bar.jpg",
PID_RED, UID_COLORS);
}
@@ -152,7 +152,7 @@
// Accessing other package paths goes into sandbox
assertTranslation(
- "/storage/emulated/0/Android/sandbox/shared:colors/"
+ "/storage/emulated/0/Android/sandbox/shared-colors/"
+ "Android/data/com.grey/foo.jpg",
"/storage/emulated/0/Android/data/com.grey/foo.jpg",
PID_RED, UID_COLORS);
@@ -201,7 +201,7 @@
// Sandboxes can't see paths in other sandboxes
try {
mService.translateSystemToApp(
- "/storage/emulated/0/Android/sandbox/shared:colors/foo.jpg",
+ "/storage/emulated/0/Android/sandbox/shared-colors/foo.jpg",
PID_GREY, UID_GREY);
fail();
} catch (SecurityException expected) {
diff --git a/telephony/java/android/telephony/CellConfigLte.java b/telephony/java/android/telephony/CellConfigLte.java
new file mode 100644
index 0000000..35769f0
--- /dev/null
+++ b/telephony/java/android/telephony/CellConfigLte.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * The container of LTE cell related configs.
+ * @hide
+ */
+public class CellConfigLte implements Parcelable {
+ private final boolean mIsEndcAvailable;
+
+ /** @hide */
+ public CellConfigLte() {
+ mIsEndcAvailable = false;
+ }
+
+ /** @hide */
+ public CellConfigLte(boolean isEndcAvailable) {
+ mIsEndcAvailable = isEndcAvailable;
+ }
+
+ /** @hide */
+ public CellConfigLte(CellConfigLte config) {
+ mIsEndcAvailable = config.mIsEndcAvailable;
+ }
+
+ /**
+ * Indicates that if E-UTRA-NR Dual Connectivity (EN-DC) is supported by the LTE cell.
+ *
+ * Reference: 3GPP TS 36.331 v15.2.2 6.3.1 System information blocks.
+ *
+ * @return {@code true} if E-UTRA-NR Dual Connectivity (EN-DC) is supported by the LTE cell.
+ *
+ */
+ boolean isEndcAvailable() {
+ return mIsEndcAvailable;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIsEndcAvailable);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof CellConfigLte)) return false;
+
+ CellConfigLte o = (CellConfigLte) other;
+ return mIsEndcAvailable == o.mIsEndcAvailable;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mIsEndcAvailable);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder().append(this.getClass().getName())
+ .append(" :{")
+ .append(" isEndcAvailable = " + mIsEndcAvailable)
+ .append(" }")
+ .toString();
+ }
+
+ private CellConfigLte(Parcel in) {
+ mIsEndcAvailable = in.readBoolean();
+ }
+
+ public static final Creator<CellConfigLte> CREATOR = new Creator<CellConfigLte>() {
+ @Override
+ public CellConfigLte createFromParcel(Parcel in) {
+ return new CellConfigLte(in);
+ }
+
+ @Override
+ public CellConfigLte[] newArray(int size) {
+ return new CellConfigLte[0];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java
index 389f643..7d5388b 100644
--- a/telephony/java/android/telephony/CellInfoLte.java
+++ b/telephony/java/android/telephony/CellInfoLte.java
@@ -19,7 +19,8 @@
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.Rlog;
+
+import java.util.Objects;
/**
* A {@link CellInfo} representing an LTE cell that provides identity and measurement info.
@@ -31,6 +32,7 @@
private CellIdentityLte mCellIdentityLte;
private CellSignalStrengthLte mCellSignalStrengthLte;
+ private CellConfigLte mCellConfig;
/** @hide */
@UnsupportedAppUsage
@@ -38,6 +40,7 @@
super();
mCellIdentityLte = new CellIdentityLte();
mCellSignalStrengthLte = new CellSignalStrengthLte();
+ mCellConfig = new CellConfigLte();
}
/** @hide */
@@ -45,6 +48,7 @@
super(ci);
this.mCellIdentityLte = ci.mCellIdentityLte.copy();
this.mCellSignalStrengthLte = ci.mCellSignalStrengthLte.copy();
+ this.mCellConfig = new CellConfigLte(ci.mCellConfig);
}
@Override
@@ -71,26 +75,37 @@
mCellSignalStrengthLte = css;
}
+ /** @hide */
+ public void setCellConfig(CellConfigLte cellConfig) {
+ if (DBG) log("setCellConfig: " + cellConfig);
+ mCellConfig = cellConfig;
+ }
+
+ /** @hide */
+ public CellConfigLte getCellConfig() {
+ if (DBG) log("getCellConfig: " + mCellConfig);
+ return mCellConfig;
+ }
+
/**
* @return hash code
*/
@Override
public int hashCode() {
- return super.hashCode() + mCellIdentityLte.hashCode() + mCellSignalStrengthLte.hashCode();
+ return Objects.hash(
+ super.hashCode(),
+ mCellIdentityLte.hashCode(),
+ mCellSignalStrengthLte.hashCode(),
+ mCellConfig.hashCode());
}
@Override
public boolean equals(Object other) {
- if (!super.equals(other)) {
- return false;
- }
- try {
- CellInfoLte o = (CellInfoLte) other;
- return mCellIdentityLte.equals(o.mCellIdentityLte)
- && mCellSignalStrengthLte.equals(o.mCellSignalStrengthLte);
- } catch (ClassCastException e) {
- return false;
- }
+ if (!(other instanceof CellInfoLte)) return false;
+ CellInfoLte o = (CellInfoLte) other;
+ return super.equals(o) && mCellIdentityLte.equals(o.mCellIdentityLte)
+ && mCellSignalStrengthLte.equals(o.mCellSignalStrengthLte)
+ && mCellConfig.equals(o.mCellConfig);
}
@Override
@@ -101,6 +116,7 @@
sb.append(super.toString());
sb.append(" ").append(mCellIdentityLte);
sb.append(" ").append(mCellSignalStrengthLte);
+ sb.append(" ").append(mCellConfig);
sb.append("}");
return sb.toString();
@@ -119,6 +135,7 @@
super.writeToParcel(dest, flags, TYPE_LTE);
mCellIdentityLte.writeToParcel(dest, flags);
mCellSignalStrengthLte.writeToParcel(dest, flags);
+ mCellConfig.writeToParcel(dest, flags);
}
/**
@@ -129,6 +146,7 @@
super(in);
mCellIdentityLte = CellIdentityLte.CREATOR.createFromParcel(in);
mCellSignalStrengthLte = CellSignalStrengthLte.CREATOR.createFromParcel(in);
+ mCellConfig = CellConfigLte.CREATOR.createFromParcel(in);
if (DBG) log("CellInfoLte(Parcel): " + toString());
}
diff --git a/telephony/java/android/telephony/DataSpecificRegistrationStates.java b/telephony/java/android/telephony/DataSpecificRegistrationStates.java
index 97e3037..b6e6cba 100644
--- a/telephony/java/android/telephony/DataSpecificRegistrationStates.java
+++ b/telephony/java/android/telephony/DataSpecificRegistrationStates.java
@@ -17,17 +17,40 @@
*/
public final int maxDataCalls;
- DataSpecificRegistrationStates(int maxDataCalls) {
+ /**
+ * Indicates if the use of dual connectivity with NR is restricted.
+ * Reference: 3GPP TS 24.301 v15.03 section 9.3.3.12A.
+ */
+ public final boolean isDcNrRestricted;
+
+ /**
+ * Indicates if NR is supported by the selected PLMN.
+ *
+ * {@code true} if the bit N is in the PLMN-InfoList-r15 is true and the selected PLMN is
+ * present in plmn-IdentityList at position N.
+ * Reference: 3GPP TS 36.331 v15.2.2 section 6.3.1 PLMN-InfoList-r15.
+ * 3GPP TS 36.331 v15.2.2 section 6.2.2 SystemInformationBlockType1 message.
+ */
+ public final boolean isNrAvailable;
+
+ DataSpecificRegistrationStates(
+ int maxDataCalls, boolean isDcNrRestricted, boolean isNrAvailable) {
this.maxDataCalls = maxDataCalls;
+ this.isDcNrRestricted = isDcNrRestricted;
+ this.isNrAvailable = isNrAvailable;
}
private DataSpecificRegistrationStates(Parcel source) {
maxDataCalls = source.readInt();
+ isDcNrRestricted = source.readBoolean();
+ isNrAvailable = source.readBoolean();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(maxDataCalls);
+ dest.writeBoolean(isDcNrRestricted);
+ dest.writeBoolean(isNrAvailable);
}
@Override
@@ -37,24 +60,30 @@
@Override
public String toString() {
- return "DataSpecificRegistrationStates {" + " mMaxDataCalls=" + maxDataCalls + "}";
+ return new StringBuilder().append(this.getClass().getName())
+ .append(" :{")
+ .append(" maxDataCalls = " + maxDataCalls)
+ .append(" isDcNrRestricted = " + isDcNrRestricted)
+ .append(" isNrAvailable = " + isNrAvailable)
+ .append(" }")
+ .toString();
}
@Override
public int hashCode() {
- return Objects.hash(maxDataCalls);
+ return Objects.hash(maxDataCalls, isDcNrRestricted, isNrAvailable);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || !(o instanceof DataSpecificRegistrationStates)) {
- return false;
- }
+ if (!(o instanceof DataSpecificRegistrationStates)) return false;
DataSpecificRegistrationStates other = (DataSpecificRegistrationStates) o;
- return this.maxDataCalls == other.maxDataCalls;
+ return this.maxDataCalls == other.maxDataCalls
+ && this.isDcNrRestricted == other.isDcNrRestricted
+ && this.isNrAvailable == other.isNrAvailable;
}
public static final Parcelable.Creator<DataSpecificRegistrationStates> CREATOR =
diff --git a/telephony/java/android/telephony/NetworkRegistrationState.java b/telephony/java/android/telephony/NetworkRegistrationState.java
index 68e512e..75e8eda 100644
--- a/telephony/java/android/telephony/NetworkRegistrationState.java
+++ b/telephony/java/android/telephony/NetworkRegistrationState.java
@@ -161,11 +161,9 @@
* @hide
*/
public NetworkRegistrationState(int domain, int transportType, int regState,
- int accessNetworkTechnology, int rejectCause,
- boolean emergencyOnly, int[] availableServices,
- @Nullable CellIdentity cellIdentity, boolean cssSupported,
- int roamingIndicator, int systemIsInPrl,
- int defaultRoamingIndicator) {
+ int accessNetworkTechnology, int rejectCause, boolean emergencyOnly,
+ int[] availableServices, @Nullable CellIdentity cellIdentity, boolean cssSupported,
+ int roamingIndicator, int systemIsInPrl, int defaultRoamingIndicator) {
this(domain, transportType, regState, accessNetworkTechnology, rejectCause, emergencyOnly,
availableServices, cellIdentity);
@@ -178,13 +176,14 @@
* @hide
*/
public NetworkRegistrationState(int domain, int transportType, int regState,
- int accessNetworkTechnology, int rejectCause,
- boolean emergencyOnly, int[] availableServices,
- @Nullable CellIdentity cellIdentity, int maxDataCalls) {
+ int accessNetworkTechnology, int rejectCause, boolean emergencyOnly,
+ int[] availableServices, @Nullable CellIdentity cellIdentity, int maxDataCalls,
+ boolean isDcNrRestricted, boolean isNrAvailable) {
this(domain, transportType, regState, accessNetworkTechnology, rejectCause, emergencyOnly,
availableServices, cellIdentity);
- mDataSpecificStates = new DataSpecificRegistrationStates(maxDataCalls);
+ mDataSpecificStates = new DataSpecificRegistrationStates(
+ maxDataCalls, isDcNrRestricted, isNrAvailable);
}
protected NetworkRegistrationState(Parcel source) {
@@ -345,7 +344,7 @@
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || !(o instanceof NetworkRegistrationState)) {
+ if (!(o instanceof NetworkRegistrationState)) {
return false;
}
@@ -357,11 +356,10 @@
&& mAccessNetworkTechnology == other.mAccessNetworkTechnology
&& mRejectCause == other.mRejectCause
&& mEmergencyOnly == other.mEmergencyOnly
- && (mAvailableServices == other.mAvailableServices
- || Arrays.equals(mAvailableServices, other.mAvailableServices))
- && equals(mCellIdentity, other.mCellIdentity)
- && equals(mVoiceSpecificStates, other.mVoiceSpecificStates)
- && equals(mDataSpecificStates, other.mDataSpecificStates);
+ && Arrays.equals(mAvailableServices, other.mAvailableServices)
+ && Objects.equals(mCellIdentity, other.mCellIdentity)
+ && Objects.equals(mVoiceSpecificStates, other.mVoiceSpecificStates)
+ && Objects.equals(mDataSpecificStates, other.mDataSpecificStates);
}
@Override
@@ -391,14 +389,4 @@
return new NetworkRegistrationState[size];
}
};
-
- private static boolean equals(Object o1, Object o2) {
- if (o1 == o2) {
- return true;
- } else if (o1 == null) {
- return false;
- } else {
- return o1.equals(o2);
- }
- }
}
diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt
index fc9b4c6..f91d74a 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -31,6 +31,7 @@
public class MockContext extends android.content.Context {
ctor public MockContext();
+ method public boolean bindIsolatedService(android.content.Intent, android.content.ServiceConnection, int, java.lang.String);
method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
method public int checkCallingOrSelfPermission(java.lang.String);
method public int checkCallingOrSelfUriPermission(android.net.Uri, int);
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index fa5b896..66be6d9 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -576,6 +576,13 @@
throw new UnsupportedOperationException();
}
+ @Override
+ public boolean bindIsolatedService(Intent service,
+ ServiceConnection conn, int flags,
+ String instanceName) {
+ throw new UnsupportedOperationException();
+ }
+
/** @hide */
@Override
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
diff --git a/test-runner/src/android/test/IsolatedContext.java b/test-runner/src/android/test/IsolatedContext.java
index 6e4c41e..73db451 100644
--- a/test-runner/src/android/test/IsolatedContext.java
+++ b/test-runner/src/android/test/IsolatedContext.java
@@ -75,6 +75,12 @@
}
@Override
+ public boolean bindIsolatedService(Intent service, ServiceConnection conn, int flags,
+ String instanceName) {
+ return false;
+ }
+
+ @Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
return null;
}