Merge "Update shared sandbox prefix to "shared-"."
diff --git a/api/current.txt b/api/current.txt
index 28863b8..72c71c6 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -43682,6 +43682,7 @@
method public int getNetworkType();
method public int getPhoneCount();
method public int getPhoneType();
+ method public int getPreferredOpportunisticDataSubscription();
method public android.telephony.ServiceState getServiceState();
method public android.telephony.SignalStrength getSignalStrength();
method public int getSimCarrierId();
@@ -43731,6 +43732,7 @@
method public boolean setNetworkSelectionModeManual(java.lang.String, boolean);
method public boolean setOperatorBrandOverride(java.lang.String);
method public boolean setPreferredNetworkTypeToGlobal();
+ method public boolean setPreferredOpportunisticDataSubscription(int);
method public void setVisualVoicemailSmsFilterSettings(android.telephony.VisualVoicemailSmsFilterSettings);
method public boolean setVoiceMailNumber(java.lang.String, java.lang.String);
method public deprecated void setVoicemailRingtoneUri(android.telecom.PhoneAccountHandle, android.net.Uri);
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/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 0d33bbd..3d16eb8 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -572,6 +572,10 @@
* Good practice is to first create the surface with the {@link #HIDDEN} flag
* specified, open a transaction, set the surface layer, layer stack, alpha,
* and position, call {@link #show} if appropriate, and close the transaction.
+ * <p>
+ * Bounds of the surface is determined by its crop and its buffer size. If the
+ * surface has no buffer or crop, the surface is boundless and only constrained
+ * by the size of its parent bounds.
*
* @param session The surface session, must not be null.
* @param name The surface name, must not be null.
@@ -959,6 +963,14 @@
}
}
+ /**
+ * Bounds the surface and its children to the bounds specified. Size of the surface will be
+ * ignored and only the crop and buffer size will be used to determine the bounds of the
+ * surface. If no crop is specified and the surface has no buffer, the surface bounds is only
+ * constrained by the size of its parent bounds.
+ *
+ * @param crop Bounds of the crop to apply.
+ */
public void setWindowCrop(Rect crop) {
checkNotReleased();
synchronized (SurfaceControl.class) {
@@ -966,6 +978,19 @@
}
}
+ /**
+ * Same as {@link SurfaceControl#setWindowCrop(Rect)} but sets the crop rect top left at 0, 0.
+ *
+ * @param width width of crop rect
+ * @param height height of crop rect
+ */
+ public void setWindowCrop(int width, int height) {
+ checkNotReleased();
+ synchronized (SurfaceControl.class) {
+ sGlobalTransaction.setWindowCrop(this, width, height);
+ }
+ }
+
public void setLayerStack(int layerStack) {
checkNotReleased();
synchronized(SurfaceControl.class) {
@@ -1477,6 +1502,12 @@
return this;
}
+ public Transaction setWindowCrop(SurfaceControl sc, int width, int height) {
+ sc.checkNotReleased();
+ nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, width, height);
+ return this;
+ }
+
@UnsupportedAppUsage
public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
sc.checkNotReleased();
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 67f9399..2b68ec0 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -598,6 +598,7 @@
}
if (sizeChanged && !creating) {
mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
+ mSurfaceControl.setWindowCrop(mSurfaceWidth, mSurfaceHeight);
}
} finally {
SurfaceControl.closeTransaction();
@@ -1169,6 +1170,12 @@
}
@Override
+ public void setWindowCrop(int width, int height) {
+ super.setWindowCrop(width, height);
+ mBackgroundControl.setWindowCrop(width, height);
+ }
+
+ @Override
public void setLayerStack(int layerStack) {
super.setLayerStack(layerStack);
mBackgroundControl.setLayerStack(layerStack);
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/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java
index 3534636..4ac0188 100644
--- a/media/java/android/media/MediaPlayer2Impl.java
+++ b/media/java/android/media/MediaPlayer2Impl.java
@@ -56,15 +56,20 @@
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Queue;
import java.util.UUID;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
/**
* @hide
@@ -94,17 +99,11 @@
private final Object mSrcLock = new Object();
//--- guarded by |mSrcLock| start
- private long mSrcIdGenerator = 0;
- private DataSourceDesc mCurrentDSD;
- private long mCurrentSrcId = mSrcIdGenerator++;
- private List<DataSourceDesc> mNextDSDs;
- private long mNextSrcId = mSrcIdGenerator++;
- private int mNextSourceState = NEXT_SOURCE_STATE_INIT;
- private boolean mNextSourcePlayPending = false;
+ private SourceInfo mCurrentSourceInfo;
+ private final Queue<SourceInfo> mNextSourceInfos = new ConcurrentLinkedQueue<>();
//--- guarded by |mSrcLock| end
+ private final AtomicLong mSrcIdGenerator = new AtomicLong(0);
- private AtomicInteger mBufferedPercentageCurrent = new AtomicInteger(0);
- private AtomicInteger mBufferedPercentageNext = new AtomicInteger(0);
private volatile float mVolume = 1.0f;
private VideoSize mVideoSize = new VideoSize(0, 0);
@@ -227,7 +226,15 @@
@Override
public long getBufferedPosition() {
// Use cached buffered percent for now.
- return getDuration() * mBufferedPercentageCurrent.get() / 100;
+ int bufferedPercentage;
+ synchronized (mSrcLock) {
+ if (mCurrentSourceInfo == null) {
+ bufferedPercentage = 0;
+ } else {
+ bufferedPercentage = mCurrentSourceInfo.mBufferedPercentage.get();
+ }
+ }
+ return getDuration() * bufferedPercentage / 100;
}
@Override
@@ -268,9 +275,8 @@
}
synchronized (mSrcLock) {
- mCurrentDSD = dsd;
- mCurrentSrcId = mSrcIdGenerator++;
- handleDataSource(true /* isCurrent */, dsd, mCurrentSrcId);
+ mCurrentSourceInfo = new SourceInfo(dsd);
+ handleDataSource(true /* isCurrent */, dsd, mCurrentSourceInfo.mId);
}
}
});
@@ -283,10 +289,8 @@
void process() {
checkArgument(dsd != null, "the DataSourceDesc cannot be null");
synchronized (mSrcLock) {
- mNextDSDs = new ArrayList<DataSourceDesc>(1);
- mNextDSDs.add(dsd);
- mNextSrcId = mSrcIdGenerator++;
- mNextSourceState = NEXT_SOURCE_STATE_INIT;
+ mNextSourceInfos.clear();
+ mNextSourceInfos.add(new SourceInfo(dsd));
}
prepareNextDataSource();
}
@@ -309,9 +313,10 @@
}
synchronized (mSrcLock) {
- mNextDSDs = new ArrayList(dsds);
- mNextSrcId = mSrcIdGenerator++;
- mNextSourceState = NEXT_SOURCE_STATE_INIT;
+ mNextSourceInfos.clear();
+ for (DataSourceDesc dsd : dsds) {
+ mNextSourceInfos.add(new SourceInfo(dsd));
+ }
}
prepareNextDataSource();
}
@@ -323,22 +328,15 @@
return addTask(new Task(CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, false) {
@Override
void process() {
- synchronized (mSrcLock) {
- if (mNextDSDs != null) {
- mNextDSDs.clear();
- mNextDSDs = null;
- }
- mNextSrcId = mSrcIdGenerator++;
- mNextSourceState = NEXT_SOURCE_STATE_INIT;
- }
+ mNextSourceInfos.clear();
}
});
}
@Override
- public @NonNull DataSourceDesc getCurrentDataSource() {
+ public DataSourceDesc getCurrentDataSource() {
synchronized (mSrcLock) {
- return mCurrentDSD;
+ return mCurrentSourceInfo == null ? null : mCurrentSourceInfo.mDSD;
}
}
@@ -707,34 +705,29 @@
}
boolean hasNextDSD;
- synchronized (mSrcLock) {
- hasNextDSD = (mNextDSDs != null && !mNextDSDs.isEmpty());
- }
-
int state = getState();
- if (state == PLAYER_STATE_ERROR || state == PLAYER_STATE_IDLE) {
- // Current source has not been prepared yet.
- return hasNextDSD;
- }
-
synchronized (mSrcLock) {
- if (!hasNextDSD || mNextSourceState != NEXT_SOURCE_STATE_INIT) {
+ hasNextDSD = !mNextSourceInfos.isEmpty();
+ if (state == PLAYER_STATE_ERROR || state == PLAYER_STATE_IDLE) {
+ // Current source has not been prepared yet.
+ return hasNextDSD;
+ }
+
+ SourceInfo nextSource = mNextSourceInfos.peek();
+ if (!hasNextDSD || nextSource.mStateAsNextSource != NEXT_SOURCE_STATE_INIT) {
// There is no next source or it's in preparing or prepared state.
return hasNextDSD;
}
try {
- mNextSourceState = NEXT_SOURCE_STATE_PREPARING;
- handleDataSource(false /* isCurrent */, mNextDSDs.get(0), mNextSrcId);
+ nextSource.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARING;
+ handleDataSource(false /* isCurrent */, nextSource.mDSD, nextSource.mId);
} catch (Exception e) {
Message msg = mTaskHandler.obtainMessage(
MEDIA_ERROR, MEDIA_ERROR_IO, MEDIA_ERROR_UNKNOWN, null);
- mTaskHandler.handleMessage(msg, mNextSrcId);
+ mTaskHandler.handleMessage(msg, nextSource.mId);
- mNextDSDs.remove(0);
- // make a new SrcId to obsolete notification for previous one.
- mNextSrcId = mSrcIdGenerator++;
- mNextSourceState = NEXT_SOURCE_STATE_INIT;
+ mNextSourceInfos.poll();
return prepareNextDataSource();
}
}
@@ -749,19 +742,14 @@
boolean hasNextDSD = false;
synchronized (mSrcLock) {
- if (mNextDSDs != null && !mNextDSDs.isEmpty()) {
+ if (!mNextSourceInfos.isEmpty()) {
hasNextDSD = true;
- if (mNextSourceState == NEXT_SOURCE_STATE_PREPARED) {
+ SourceInfo nextSourceInfo = mNextSourceInfos.peek();
+ if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_PREPARED) {
// Switch to next source only when it has been prepared.
- mCurrentDSD = mNextDSDs.get(0);
- mCurrentSrcId = mNextSrcId;
- mBufferedPercentageCurrent.set(mBufferedPercentageNext.get());
- mNextDSDs.remove(0);
- mNextSrcId = mSrcIdGenerator++; // make it different from |mCurrentSrcId|
- mBufferedPercentageNext.set(0);
- mNextSourceState = NEXT_SOURCE_STATE_INIT;
+ mCurrentSourceInfo = mNextSourceInfos.poll();
- long srcId = mCurrentSrcId;
+ long srcId = mCurrentSourceInfo.mId;
try {
nativePlayNextDataSource(srcId);
} catch (Exception e) {
@@ -776,9 +764,8 @@
// Now a new current src is playing.
// Wait for MEDIA_INFO_DATA_SOURCE_START to prepare next source.
- mNextSourcePlayPending = false;
}
- } else if (mNextSourceState == NEXT_SOURCE_STATE_INIT) {
+ } else if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_INIT) {
hasNextDSD = prepareNextDataSource();
}
}
@@ -1073,12 +1060,8 @@
mDrmEventCallbackRecords.clear();
}
synchronized (mSrcLock) {
- if (mNextDSDs != null) {
- mNextDSDs.clear();
- mNextDSDs = null;
- }
- mNextSrcId = mSrcIdGenerator++;
- mNextSourceState = NEXT_SOURCE_STATE_INIT;
+ mCurrentSourceInfo = null;
+ mNextSourceInfos.clear();
}
synchronized (mTaskLock) {
@@ -1532,20 +1515,11 @@
final int what = msg.arg1;
final int extra = msg.arg2;
- final DataSourceDesc dsd;
- boolean isCurrentSrcId = false;
- boolean isNextSrcId = false;
- synchronized (mSrcLock) {
- if (srcId == mCurrentSrcId) {
- dsd = mCurrentDSD;
- isCurrentSrcId = true;
- } else if (mNextDSDs != null && !mNextDSDs.isEmpty() && srcId == mNextSrcId) {
- dsd = mNextDSDs.get(0);
- isNextSrcId = true;
- } else {
- return;
- }
+ final SourceInfo sourceInfo = getSourceInfoById(srcId);
+ if (sourceInfo == null) {
+ return;
}
+ final DataSourceDesc dsd = sourceInfo.mDSD;
switch(msg.what) {
case MEDIA_PREPARED:
@@ -1561,14 +1535,16 @@
}
synchronized (mSrcLock) {
+ SourceInfo nextSourceInfo = mNextSourceInfos.peek();
Log.i(TAG, "MEDIA_PREPARED: srcId=" + srcId
- + ", currentSrcId=" + mCurrentSrcId + ", nextSrcId=" + mNextSrcId);
+ + ", curSrc=" + mCurrentSourceInfo
+ + ", nextSrc=" + nextSourceInfo);
- if (isCurrentSrcId) {
+ if (isCurrentSource(srcId)) {
prepareNextDataSource();
- } else if (isNextSrcId) {
- mNextSourceState = NEXT_SOURCE_STATE_PREPARED;
- if (mNextSourcePlayPending) {
+ } else if (isNextSource(srcId)) {
+ nextSourceInfo.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARED;
+ if (nextSourceInfo.mPlayPendingAsNextSource) {
playNextDataSource();
}
}
@@ -1621,7 +1597,7 @@
case MEDIA_PLAYBACK_COMPLETE:
{
- if (isCurrentSrcId) {
+ if (isCurrentSource(srcId)) {
sendEvent(new EventNotifier() {
@Override
public void notify(EventCallback callback) {
@@ -1632,11 +1608,13 @@
stayAwake(false);
synchronized (mSrcLock) {
- mNextSourcePlayPending = true;
-
+ SourceInfo nextSourceInfo = mNextSourceInfos.peek();
+ if (nextSourceInfo != null) {
+ nextSourceInfo.mPlayPendingAsNextSource = true;
+ }
Log.i(TAG, "MEDIA_PLAYBACK_COMPLETE: srcId=" + srcId
- + ", currentSrcId=" + mCurrentSrcId
- + ", nextSrcId=" + mNextSrcId);
+ + ", curSrc=" + mCurrentSourceInfo
+ + ", nextSrc=" + nextSourceInfo);
}
playNextDataSource();
@@ -1667,13 +1645,11 @@
}
});
- synchronized (mSrcLock) {
- if (isCurrentSrcId) {
- mBufferedPercentageCurrent.set(percent);
- } else if (isNextSrcId) {
- mBufferedPercentageNext.set(percent);
- }
+ SourceInfo src = getSourceInfoById(srcId);
+ if (src != null) {
+ src.mBufferedPercentage.set(percent);
}
+
return;
}
@@ -1751,7 +1727,7 @@
});
if (msg.arg1 == MEDIA_INFO_DATA_SOURCE_START) {
- if (isCurrentSrcId) {
+ if (isCurrentSource(srcId)) {
prepareNextDataSource();
}
}
@@ -1854,6 +1830,7 @@
}
}
}
+
}
/*
@@ -2130,7 +2107,7 @@
@Override
public void notify(DrmEventCallback callback) {
callback.onDrmPrepared(
- MediaPlayer2Impl.this, mCurrentDSD, prepareDrmStatus);
+ MediaPlayer2Impl.this, getCurrentDataSource(), prepareDrmStatus);
}
});
@@ -2196,7 +2173,7 @@
// call the callback outside the lock
if (mOnDrmConfigHelper != null) {
- mOnDrmConfigHelper.onDrmConfig(this, mCurrentDSD);
+ mOnDrmConfigHelper.onDrmConfig(this, getCurrentDataSource());
}
synchronized (mDrmLock) {
@@ -2817,7 +2794,7 @@
@Override
public void notify(DrmEventCallback callback) {
callback.onDrmPrepared(
- mediaPlayer, mCurrentDSD, status);
+ mediaPlayer, getCurrentDataSource(), status);
}
});
@@ -3084,9 +3061,7 @@
} catch (Exception e) {
status = CALL_STATUS_ERROR_UNKNOWN;
}
- synchronized (mSrcLock) {
- mDSD = mCurrentDSD;
- }
+ mDSD = getCurrentDataSource();
if (mMediaCallType != CALL_COMPLETED_SEEK_TO) {
synchronized (mTaskLock) {
@@ -3129,4 +3104,50 @@
super(detailMessage);
}
};
+
+ private final class SourceInfo {
+ final DataSourceDesc mDSD;
+ final long mId = mSrcIdGenerator.getAndIncrement();
+ AtomicInteger mBufferedPercentage = new AtomicInteger(0);
+
+ // m*AsNextSource (below) only applies to pending data sources in the playlist;
+ // the meanings of mCurrentSourceInfo.{mStateAsNextSource,mPlayPendingAsNextSource}
+ // are undefined.
+ int mStateAsNextSource = NEXT_SOURCE_STATE_INIT;
+ boolean mPlayPendingAsNextSource = false;
+
+ SourceInfo(DataSourceDesc dsd) {
+ this.mDSD = dsd;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s(%d)", SourceInfo.class.getName(), mId);
+ }
+
+ }
+
+ private SourceInfo getSourceInfoById(long srcId) {
+ synchronized (mSrcLock) {
+ if (isCurrentSource(srcId)) {
+ return mCurrentSourceInfo;
+ }
+ if (isNextSource(srcId)) {
+ return mNextSourceInfos.peek();
+ }
+ }
+ return null;
+ }
+
+ private boolean isCurrentSource(long srcId) {
+ synchronized (mSrcLock) {
+ return mCurrentSourceInfo != null && mCurrentSourceInfo.mId == srcId;
+ }
+ }
+
+ private boolean isNextSource(long srcId) {
+ SourceInfo nextSourceInfo = mNextSourceInfos.peek();
+ return nextSourceInfo != null && nextSourceInfo.mId == srcId;
+ }
+
}
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/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
index 1aeb075..e824508 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
@@ -44,7 +44,14 @@
@Override
public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
- action(context, category, "", taggedData);
+ final LogMaker logMaker = new LogMaker(category)
+ .setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+ if (taggedData != null) {
+ for (Pair<Integer, Object> pair : taggedData) {
+ logMaker.addTaggedData(pair.first, pair.second);
+ }
+ }
+ MetricsLogger.action(logMaker);
}
@Override
@@ -58,19 +65,12 @@
}
@Override
- public void action(Context context, int category, String pkg,
- Pair<Integer, Object>... taggedData) {
- if (taggedData == null || taggedData.length == 0) {
- MetricsLogger.action(context, category, pkg);
- } else {
- final LogMaker logMaker = new LogMaker(category)
- .setType(MetricsProto.MetricsEvent.TYPE_ACTION)
- .setPackageName(pkg);
- for (Pair<Integer, Object> pair : taggedData) {
- logMaker.addTaggedData(pair.first, pair.second);
- }
- MetricsLogger.action(logMaker);
- }
+ public void action(Context context, int category, String pkg) {
+ final LogMaker logMaker = new LogMaker(category)
+ .setType(MetricsProto.MetricsEvent.TYPE_ACTION)
+ .setPackageName(pkg);
+
+ MetricsLogger.action(logMaker);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
index b60364e..f187688 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
@@ -51,7 +51,7 @@
/**
* Logs an user action.
*/
- void action(Context context, int category, String pkg, Pair<Integer, Object>... taggedData);
+ void action(Context context, int category, String pkg);
/**
* Generically log action.
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 188204e..8cc3b5a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -79,7 +79,10 @@
}
}
- public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
+ /**
+ * Logs a simple action without page id or attribution
+ */
+ public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
for (LogWriter writer : mLoggerWriters) {
writer.action(context, category, taggedData);
}
@@ -88,10 +91,9 @@
/**
* Logs a generic Settings event.
*/
- public void action(Context context, int category, String pkg,
- Pair<Integer, Object>... taggedData) {
+ public void action(Context context, int category, String pkg) {
for (LogWriter writer : mLoggerWriters) {
- writer.action(context, category, pkg, taggedData);
+ writer.action(context, category, pkg);
}
}
@@ -135,16 +137,22 @@
// Not loggable
return;
}
- action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, action,
- Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory));
+ action(sourceMetricsCategory,
+ MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
+ SettingsEnums.PAGE_UNKNOWN,
+ action,
+ 0);
return;
} else if (TextUtils.equals(cn.getPackageName(), context.getPackageName())) {
// Going to a Setting internal page, skip click logging in favor of page's own
// visibility logging.
return;
}
- action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, cn.flattenToString(),
- Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory));
+ action(sourceMetricsCategory,
+ MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
+ SettingsEnums.PAGE_UNKNOWN,
+ cn.flattenToString(),
+ 0);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
index 71f3789..320380f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
@@ -15,6 +15,7 @@
package com.android.settingslib.core.instrumentation;
import android.annotation.Nullable;
+import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
@@ -22,12 +23,9 @@
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;
-import android.util.Pair;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
@@ -117,10 +115,9 @@
return;
}
- final Pair<Integer, Object> valueData;
+ final int intVal;
if (value instanceof Long) {
final Long longVal = (Long) value;
- final int intVal;
if (longVal > Integer.MAX_VALUE) {
intVal = Integer.MAX_VALUE;
} else if (longVal < Integer.MIN_VALUE) {
@@ -128,47 +125,45 @@
} else {
intVal = longVal.intValue();
}
- valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
- intVal);
} else if (value instanceof Integer) {
- valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
- value);
+ intVal = (int) value;
} else if (value instanceof Boolean) {
- valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
- (Boolean) value ? 1 : 0);
+ intVal = (Boolean) value ? 1 : 0;
} else if (value instanceof Float) {
- valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE,
- value);
- } else if (value instanceof String) {
- Log.d(LOG_TAG, "Tried to log string preference " + prefKey + " = " + value);
- valueData = null;
+ final float floatValue = (float) value;
+ if (floatValue > Integer.MAX_VALUE) {
+ intVal = Integer.MAX_VALUE;
+ } else if (floatValue < Integer.MIN_VALUE) {
+ intVal = Integer.MIN_VALUE;
+ } else {
+ intVal = (int) floatValue;
+ }
} else {
Log.w(LOG_TAG, "Tried to log unloggable object" + value);
- valueData = null;
+ return;
}
- if (valueData != null) {
- // Pref key exists in set, log it's change in metrics.
- mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE,
- Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey),
- valueData);
- }
+ // Pref key exists in set, log it's change in metrics.
+ mMetricsFeature.action(SettingsEnums.PAGE_UNKNOWN,
+ SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
+ SettingsEnums.PAGE_UNKNOWN,
+ prefKey,
+ intVal);
}
@VisibleForTesting
void logPackageName(String key, String value) {
final String prefKey = mTag + "/" + key;
- mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE, value,
- Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey));
+ mMetricsFeature.action(SettingsEnums.PAGE_UNKNOWN,
+ SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
+ SettingsEnums.PAGE_UNKNOWN,
+ prefKey + ":" + value,
+ 0);
}
private void safeLogValue(String key, String value) {
new AsyncPackageCheck().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, key, value);
}
- public static String buildCountName(String prefKey, Object value) {
- return prefKey + "|" + value;
- }
-
public static String buildPrefKey(String tag, String key) {
return tag + "/" + key;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
index 603f838..4ec6fb2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -17,8 +17,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -27,7 +25,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.util.Pair;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.SettingsLibRobolectricTestRunner;
@@ -77,10 +74,11 @@
mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES);
verify(mLogWriter).action(
- eq(mContext),
- eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK),
- anyString(),
- eq(Pair.create(MetricsEvent.FIELD_CONTEXT, MetricsEvent.SETTINGS_GESTURES)));
+ MetricsEvent.SETTINGS_GESTURES,
+ MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
+ SettingsEnums.PAGE_UNKNOWN,
+ Intent.ACTION_ASSIST,
+ 0);
}
@Test
@@ -90,10 +88,11 @@
mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES);
verify(mLogWriter).action(
- eq(mContext),
- eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK),
- anyString(),
- eq(Pair.create(MetricsEvent.FIELD_CONTEXT, MetricsEvent.SETTINGS_GESTURES)));
+ MetricsEvent.SETTINGS_GESTURES,
+ MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
+ SettingsEnums.PAGE_UNKNOWN,
+ "pkg/cls",
+ 0);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
index be671e6..6285fcd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
@@ -15,25 +15,16 @@
*/
package com.android.settingslib.core.instrumentation;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent
- .ACTION_SETTINGS_PREFERENCE_CHANGE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent
- .FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent
- .FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent
- .FIELD_SETTINGS_PREFERENCE_CHANGE_NAME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE;
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.SharedPreferences;
-import android.util.Pair;
import com.android.settingslib.SettingsLibRobolectricTestRunner;
@@ -41,7 +32,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
-import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -50,11 +40,11 @@
private static final String TEST_TAG = "tag";
private static final String TEST_KEY = "key";
+ private static final String TEST_TAGGED_KEY = TEST_TAG + "/" + TEST_KEY;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
- private ArgumentMatcher<Pair<Integer, Object>> mNamePairMatcher;
@Mock
private MetricsFeatureProvider mMetricsFeature;
private SharedPreferencesLogger mSharedPrefLogger;
@@ -63,7 +53,6 @@
public void init() {
MockitoAnnotations.initMocks(this);
mSharedPrefLogger = new SharedPreferencesLogger(mContext, TEST_TAG, mMetricsFeature);
- mNamePairMatcher = pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, String.class);
}
@Test
@@ -77,9 +66,11 @@
editor.putInt(TEST_KEY, 2);
editor.putInt(TEST_KEY, 2);
- verify(mMetricsFeature, times(6)).action(any(Context.class), anyInt(),
- argThat(mNamePairMatcher),
- argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.class)));
+ verify(mMetricsFeature, times(6)).action(eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
+ eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(TEST_TAGGED_KEY),
+ anyInt());
}
@Test
@@ -92,12 +83,16 @@
editor.putBoolean(TEST_KEY, false);
- verify(mMetricsFeature).action(any(Context.class), anyInt(),
- argThat(mNamePairMatcher),
- argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, true)));
- verify(mMetricsFeature, times(3)).action(any(Context.class), anyInt(),
- argThat(mNamePairMatcher),
- argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, false)));
+ verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
+ SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
+ SettingsEnums.PAGE_UNKNOWN,
+ TEST_TAGGED_KEY,
+ 1);
+ verify(mMetricsFeature, times(3)).action(SettingsEnums.PAGE_UNKNOWN,
+ SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
+ SettingsEnums.PAGE_UNKNOWN,
+ TEST_TAGGED_KEY,
+ 0);
}
@Test
@@ -109,9 +104,11 @@
editor.putLong(TEST_KEY, 1);
editor.putLong(TEST_KEY, 2);
- verify(mMetricsFeature, times(4)).action(any(Context.class), anyInt(),
- argThat(mNamePairMatcher),
- argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.class)));
+ verify(mMetricsFeature, times(4)).action(eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
+ eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(TEST_TAGGED_KEY),
+ anyInt());
}
@Test
@@ -121,10 +118,11 @@
editor.putLong(TEST_KEY, 1);
editor.putLong(TEST_KEY, veryBigNumber);
- verify(mMetricsFeature).action(any(Context.class), anyInt(),
- argThat(mNamePairMatcher),
- argThat(pairMatches(
- FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.MAX_VALUE)));
+ verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
+ SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
+ SettingsEnums.PAGE_UNKNOWN,
+ TEST_TAGGED_KEY,
+ Integer.MAX_VALUE);
}
@Test
@@ -134,10 +132,10 @@
editor.putLong(TEST_KEY, 1);
editor.putLong(TEST_KEY, veryNegativeNumber);
- verify(mMetricsFeature).action(any(Context.class), anyInt(),
- argThat(mNamePairMatcher),
- argThat(pairMatches(
- FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.MIN_VALUE)));
+ verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
+ SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE,
+ SettingsEnums.PAGE_UNKNOWN,
+ TEST_TAGGED_KEY, Integer.MIN_VALUE);
}
@Test
@@ -149,38 +147,20 @@
editor.putFloat(TEST_KEY, 1);
editor.putFloat(TEST_KEY, 2);
- verify(mMetricsFeature, times(4)).action(any(Context.class), anyInt(),
- argThat(mNamePairMatcher),
- argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE, Float.class)));
+ verify(mMetricsFeature, times(4)).action(eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
+ eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(TEST_TAGGED_KEY),
+ anyInt());
}
@Test
public void logPackage_shouldUseLogPackageApi() {
mSharedPrefLogger.logPackageName("key", "com.android.settings");
- verify(mMetricsFeature).action(any(Context.class),
- eq(ACTION_SETTINGS_PREFERENCE_CHANGE),
- eq("com.android.settings"),
- any(Pair.class));
- }
-
- private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, Class clazz) {
- return pair -> pair.first == tag && isInstanceOfType(pair.second, clazz);
- }
-
- private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, boolean bool) {
- return pair -> pair.first == tag
- && isInstanceOfType(pair.second, Integer.class)
- && pair.second.equals((bool ? 1 : 0));
- }
-
- private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, int val) {
- return pair -> pair.first == tag
- && isInstanceOfType(pair.second, Integer.class)
- && pair.second.equals(val);
- }
-
- /** Returns true if the instance is assignable to the type Clazz. */
- private static boolean isInstanceOfType(Object instance, Class<?> clazz) {
- return clazz.isInstance(instance);
+ verify(mMetricsFeature).action(SettingsEnums.PAGE_UNKNOWN,
+ ACTION_SETTINGS_PREFERENCE_CHANGE,
+ SettingsEnums.PAGE_UNKNOWN,
+ "tag/key:com.android.settings",
+ 0);
}
}
diff --git a/packages/SystemUI/res/layout/navigation_bar_window.xml b/packages/SystemUI/res/layout/navigation_bar_window.xml
index 6fa46d4..f98cbd8 100644
--- a/packages/SystemUI/res/layout/navigation_bar_window.xml
+++ b/packages/SystemUI/res/layout/navigation_bar_window.xml
@@ -20,6 +20,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/navigation_bar_frame"
+ android:theme="@style/Theme.SystemUI"
android:layout_height="match_parent"
android:layout_width="match_parent">
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index fbcf068..5e6d272 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -296,7 +296,7 @@
new WakefulnessLifecycle());
mProviders.put(FragmentService.class, () ->
- new FragmentService(mContext));
+ new FragmentService());
mProviders.put(ExtensionController.class, () ->
new ExtensionControllerImpl(mContext));
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index 0ed1cd1..779a86c 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -59,11 +59,11 @@
private FragmentController mFragments;
private FragmentLifecycleCallbacks mLifecycleCallbacks;
- FragmentHostManager(Context context, FragmentService manager, View rootView) {
- mContext = context;
+ FragmentHostManager(FragmentService manager, View rootView) {
+ mContext = rootView.getContext();
mManager = manager;
mRootView = rootView;
- mConfigChanges.applyNewConfig(context.getResources());
+ mConfigChanges.applyNewConfig(mContext.getResources());
createFragmentHost(null);
}
@@ -203,6 +203,10 @@
}
}
+ public static void removeAndDestroy(View view) {
+ Dependency.get(FragmentService.class).removeAndDestroy(view);
+ }
+
class HostCallbacks extends FragmentHostCallback<FragmentHostManager> {
public HostCallbacks() {
super(mContext, FragmentHostManager.this.mHandler, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
index f9bf4f5..bf7d629 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -14,18 +14,13 @@
package com.android.systemui.fragments;
-import android.content.Context;
import android.content.res.Configuration;
-import android.os.Bundle;
import android.os.Handler;
import android.util.ArrayMap;
-import android.util.Log;
import android.view.View;
import com.android.systemui.ConfigurationChangedReceiver;
import com.android.systemui.Dumpable;
-import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIApplication;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -40,11 +35,6 @@
private final ArrayMap<View, FragmentHostState> mHosts = new ArrayMap<>();
private final Handler mHandler = new Handler();
- private final Context mContext;
-
- public FragmentService(Context context) {
- mContext = context;
- }
public FragmentHostManager getFragmentHostManager(View view) {
View root = view.getRootView();
@@ -56,6 +46,13 @@
return state.getFragmentHostManager();
}
+ public void removeAndDestroy(View view) {
+ final FragmentHostState state = mHosts.remove(view.getRootView());
+ if (state != null) {
+ state.mFragmentHostManager.destroy();
+ }
+ }
+
public void destroyAll() {
for (FragmentHostState state : mHosts.values()) {
state.mFragmentHostManager.destroy();
@@ -84,7 +81,7 @@
public FragmentHostState(View view) {
mView = view;
- mFragmentHostManager = new FragmentHostManager(mContext, FragmentService.this, mView);
+ mFragmentHostManager = new FragmentHostManager(FragmentService.this, mView);
}
public void sendConfigurationChange(Configuration newConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 4406b14..0cf1b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -144,6 +144,7 @@
private OverviewProxyService mOverviewProxyService;
+ private boolean mIsOnDefaultDisplay = true;
public boolean mHomeBlockedThisTouch;
private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() {
@@ -241,6 +242,11 @@
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mNavigationBarView = (NavigationBarView) view;
+ final Display display = view.getDisplay();
+ // It may not have display when running unit test.
+ if (display != null) {
+ mIsOnDefaultDisplay = display.getDisplayId() == Display.DEFAULT_DISPLAY;
+ }
mNavigationBarView.setComponents(mStatusBar.getPanel());
mNavigationBarView.setDisabledFlags(mDisabledFlags1);
@@ -253,8 +259,6 @@
prepareNavigationBarView();
checkNavBarModes();
- setDisabled2Flags(mDisabledFlags2);
-
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_USER_SWITCHED);
@@ -262,16 +266,23 @@
notifyNavigationBarScreenOn();
mOverviewProxyService.addCallback(mOverviewProxyListener);
- RotationContextButton rotationButton = mNavigationBarView.getRotateSuggestionButton();
- rotationButton.setListener(mRotationButtonListener);
- rotationButton.addRotationCallback(mRotationWatcher);
+ // Currently there is no accelerometer sensor on non-default display.
+ if (mIsOnDefaultDisplay) {
+ final RotationContextButton rotationButton =
+ mNavigationBarView.getRotateSuggestionButton();
+ rotationButton.setListener(mRotationButtonListener);
+ rotationButton.addRotationCallback(mRotationWatcher);
- // Reset user rotation pref to match that of the WindowManager if starting in locked mode
- // This will automatically happen when switching from auto-rotate to locked mode
- if (rotationButton.isRotationLocked()) {
- final int winRotation = mWindowManager.getDefaultDisplay().getRotation();
- rotationButton.setRotationLockedAtAngle(winRotation);
+ // Reset user rotation pref to match that of the WindowManager if starting in locked
+ // mode. This will automatically happen when switching from auto-rotate to locked mode.
+ if (display != null && rotationButton.isRotationLocked()) {
+ final int winRotation = display.getRotation();
+ rotationButton.setRotationLockedAtAngle(winRotation);
+ }
+ } else {
+ mDisabledFlags2 |= StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
}
+ setDisabled2Flags(mDisabledFlags2);
}
@Override
@@ -389,7 +400,7 @@
@Override
public void onRotationProposal(final int rotation, boolean isValid) {
- final int winRotation = mWindowManager.getDefaultDisplay().getRotation();
+ final int winRotation = mNavigationBarView.getDisplay().getRotation();
final boolean rotateSuggestionsDisabled = RotationContextButton
.hasDisable2RotateSuggestionFlag(mDisabledFlags2);
if (RotationContextButton.DEBUG_ROTATION) {
@@ -477,10 +488,13 @@
updateScreenPinningGestures();
}
- final int masked2 = state2 & (StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS);
- if (masked2 != mDisabledFlags2) {
- mDisabledFlags2 = masked2;
- setDisabled2Flags(masked2);
+ // Only default display supports rotation suggestions.
+ if (mIsOnDefaultDisplay) {
+ final int masked2 = state2 & (StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS);
+ if (masked2 != mDisabledFlags2) {
+ mDisabledFlags2 = masked2;
+ setDisabled2Flags(masked2);
+ }
}
}
@@ -881,13 +895,23 @@
if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
if (navigationBarView == null) return null;
+ final NavigationBarFragment fragment = new NavigationBarFragment();
+ navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ final FragmentHostManager fragmentHost = FragmentHostManager.get(v);
+ fragmentHost.getFragmentManager().beginTransaction()
+ .replace(R.id.navigation_bar_frame, fragment, TAG)
+ .commit();
+ fragmentHost.addTagListener(TAG, listener);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ FragmentHostManager.removeAndDestroy(v);
+ }
+ });
context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
- FragmentHostManager fragmentHost = FragmentHostManager.get(navigationBarView);
- NavigationBarFragment fragment = new NavigationBarFragment();
- fragmentHost.getFragmentManager().beginTransaction()
- .replace(R.id.navigation_bar_frame, fragment, TAG)
- .commit();
- fragmentHost.addTagListener(TAG, listener);
return navigationBarView;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 2c3c27f..b43bbdc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -264,8 +264,7 @@
public NavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
- mDisplay = ((WindowManager) context.getSystemService(
- Context.WINDOW_SERVICE)).getDefaultDisplay();
+ mDisplay = context.getDisplay();
mVertical = false;
mLongClickableAccessibilityButton = false;
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/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index 3a5232a..d6f2a87 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -260,6 +260,10 @@
@Nullable private ParcelFileDescriptor mSavedState;
@Nullable private ParcelFileDescriptor mBackupData;
@Nullable private ParcelFileDescriptor mNewState;
+ // Indicates whether there was any data to be backed up, i.e. the queue was not empty
+ // and at least one of the packages had data. Used to avoid updating current token for
+ // empty backups.
+ private boolean mHasDataToBackup;
/**
* This {@link ConditionVariable} is used to signal that the cancel operation has been
@@ -332,6 +336,8 @@
public void run() {
Process.setThreadPriority(THREAD_PRIORITY);
+ mHasDataToBackup = false;
+
int status = BackupTransport.TRANSPORT_OK;
try {
startTask();
@@ -529,10 +535,10 @@
String callerLogString = "KVBT.finishTask()";
- // If we succeeded and this is the first time we've done a backup, we can record the current
- // backup dataset token.
+ // If the backup data was not empty, we succeeded and this is the first time
+ // we've done a backup, we can record the current backup dataset token.
long currentToken = mBackupManagerService.getCurrentToken();
- if ((status == BackupTransport.TRANSPORT_OK) && (currentToken == 0)) {
+ if (mHasDataToBackup && (status == BackupTransport.TRANSPORT_OK) && (currentToken == 0)) {
try {
IBackupTransport transport = mTransportClient.connectOrThrow(callerLogString);
mBackupManagerService.setCurrentToken(transport.getCurrentRestoreSet());
@@ -838,6 +844,8 @@
return BackupTransport.TRANSPORT_OK;
}
+ mHasDataToBackup = true;
+
int status;
try (ParcelFileDescriptor backupData =
ParcelFileDescriptor.open(backupDataFile, MODE_READ_ONLY)) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0bae1f3..d1392d0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19170,6 +19170,9 @@
if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
return false;
}
+ if (uid == SHELL_UID || uid == ROOT_UID) {
+ return false;
+ }
synchronized (mPidsSelfLocked) {
final ProcessRecord pr = mPidsSelfLocked.get(pid);
return pr == null || pr.mountMode != Zygote.MOUNT_EXTERNAL_FULL;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b404c41..1c7572e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3610,7 +3610,7 @@
NotificationRecord r = mNotificationsByKey.get(adjustment.getKey());
if (r != null && mAssistants.isSameUser(token, r.getUserId())) {
applyAdjustment(r, adjustment);
- r.applyAdjustments();
+ r.applyImportanceFromAdjustments();
if (r.getImportance() == IMPORTANCE_NONE) {
cancelNotificationsFromListener(token, new String[]{r.getKey()});
} else {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 84d0c01..a11b03f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -664,6 +664,18 @@
.addTaggedData(MetricsEvent.ADJUSTMENT_KEY_SMART_REPLIES,
getSmartReplies().size()));
}
+ }
+ applyImportanceFromAdjustments();
+ }
+ }
+
+ /**
+ * Update importance from the adjustment.
+ */
+ public void applyImportanceFromAdjustments() {
+ synchronized (mAdjustments) {
+ for (Adjustment adjustment : mAdjustments) {
+ Bundle signals = adjustment.getSignals();
if (signals.containsKey(Adjustment.KEY_IMPORTANCE)) {
int importance = signals.getInt(Adjustment.KEY_IMPORTANCE);
importance = Math.max(IMPORTANCE_UNSPECIFIED, importance);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 26f6e96..ea190a7 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1126,101 +1126,108 @@
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
}
- if (!params.isMultiPackage) {
- Preconditions.checkNotNull(mPackageName);
- Preconditions.checkNotNull(mSigningDetails);
- Preconditions.checkNotNull(mResolvedBaseFile);
+ final IPackageInstallObserver2 localObserver;
+ if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
+ localObserver = null;
+ } else {
+ if (!params.isMultiPackage) {
+ Preconditions.checkNotNull(mPackageName);
+ Preconditions.checkNotNull(mSigningDetails);
+ Preconditions.checkNotNull(mResolvedBaseFile);
- if (needToAskForPermissionsLocked()) {
- // User needs to confirm installation;
- // give installer an intent they can use to involve
- // user.
- final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL);
- intent.setPackage(mPm.getPackageInstallerPackageName());
- intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
- try {
- mRemoteObserver.onUserActionRequired(intent);
- } catch (RemoteException ignored) {
- }
-
- // Commit was keeping session marked as active until now; release
- // that extra refcount so session appears idle.
- closeInternal(false);
- return null;
- }
-
- // Inherit any packages and native libraries from existing install that
- // haven't been overridden.
- if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
- try {
- final List<File> fromFiles = mResolvedInheritedFiles;
- final File toDir = resolveStageDirLocked();
-
- if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
- if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
- throw new IllegalStateException("mInheritedFilesBase == null");
+ if (needToAskForPermissionsLocked()) {
+ // User needs to confirm installation;
+ // give installer an intent they can use to involve
+ // user.
+ final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL);
+ intent.setPackage(mPm.getPackageInstallerPackageName());
+ intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
+ try {
+ mRemoteObserver.onUserActionRequired(intent);
+ } catch (RemoteException ignored) {
}
- if (isLinkPossible(fromFiles, toDir)) {
- if (!mResolvedInstructionSets.isEmpty()) {
- final File oatDir = new File(toDir, "oat");
- createOatDirs(mResolvedInstructionSets, oatDir);
+ // Commit was keeping session marked as active until now; release
+ // that extra refcount so session appears idle.
+ closeInternal(false);
+ return null;
+ }
+
+ // Inherit any packages and native libraries from existing install that
+ // haven't been overridden.
+ if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
+ try {
+ final List<File> fromFiles = mResolvedInheritedFiles;
+ final File toDir = resolveStageDirLocked();
+
+ if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
+ if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
+ throw new IllegalStateException("mInheritedFilesBase == null");
}
- // pre-create lib dirs for linking if necessary
- if (!mResolvedNativeLibPaths.isEmpty()) {
- for (String libPath : mResolvedNativeLibPaths) {
- // "/lib/arm64" -> ["lib", "arm64"]
- final int splitIndex = libPath.lastIndexOf('/');
- if (splitIndex < 0 || splitIndex >= libPath.length() - 1) {
- Slog.e(TAG,
- "Skipping native library creation for linking due to "
- + "invalid path: " + libPath);
- continue;
- }
- final String libDirPath = libPath.substring(1, splitIndex);
- final File libDir = new File(toDir, libDirPath);
- if (!libDir.exists()) {
- NativeLibraryHelper.createNativeLibrarySubdir(libDir);
- }
- final String archDirPath = libPath.substring(splitIndex + 1);
- NativeLibraryHelper.createNativeLibrarySubdir(
- new File(libDir, archDirPath));
+
+ if (isLinkPossible(fromFiles, toDir)) {
+ if (!mResolvedInstructionSets.isEmpty()) {
+ final File oatDir = new File(toDir, "oat");
+ createOatDirs(mResolvedInstructionSets, oatDir);
}
+ // pre-create lib dirs for linking if necessary
+ if (!mResolvedNativeLibPaths.isEmpty()) {
+ for (String libPath : mResolvedNativeLibPaths) {
+ // "/lib/arm64" -> ["lib", "arm64"]
+ final int splitIndex = libPath.lastIndexOf('/');
+ if (splitIndex < 0 || splitIndex >= libPath.length() - 1) {
+ Slog.e(TAG,
+ "Skipping native library creation for linking due"
+ + " to invalid path: " + libPath);
+ continue;
+ }
+ final String libDirPath = libPath.substring(1, splitIndex);
+ final File libDir = new File(toDir, libDirPath);
+ if (!libDir.exists()) {
+ NativeLibraryHelper.createNativeLibrarySubdir(libDir);
+ }
+ final String archDirPath = libPath.substring(splitIndex + 1);
+ NativeLibraryHelper.createNativeLibrarySubdir(
+ new File(libDir, archDirPath));
+ }
+ }
+ linkFiles(fromFiles, toDir, mInheritedFilesBase);
+ } else {
+ // TODO: this should delegate to DCS so the system process
+ // avoids holding open FDs into containers.
+ copyFiles(fromFiles, toDir);
}
- linkFiles(fromFiles, toDir, mInheritedFilesBase);
- } else {
- // TODO: this should delegate to DCS so the system process
- // avoids holding open FDs into containers.
- copyFiles(fromFiles, toDir);
+ } catch (IOException e) {
+ throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+ "Failed to inherit existing install", e);
}
- } catch (IOException e) {
- throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
- "Failed to inherit existing install", e);
}
+
+ // TODO: surface more granular state from dexopt
+ mInternalProgress = 0.5f;
+ computeProgressLocked(true);
+
+ // Unpack native libraries
+ extractNativeLibraries(mResolvedStageDir, params.abiOverride,
+ mayInheritNativeLibs());
}
- // TODO: surface more granular state from dexopt
- mInternalProgress = 0.5f;
- computeProgressLocked(true);
+ // We've reached point of no return; call into PMS to install the stage.
+ // Regardless of success or failure we always destroy session.
+ localObserver = new IPackageInstallObserver2.Stub() {
+ @Override
+ public void onUserActionRequired(Intent intent) {
+ throw new IllegalStateException();
+ }
- // Unpack native libraries
- extractNativeLibraries(mResolvedStageDir, params.abiOverride, mayInheritNativeLibs());
+ @Override
+ public void onPackageInstalled(String basePackageName, int returnCode, String msg,
+ Bundle extras) {
+ destroyInternal();
+ dispatchSessionFinished(returnCode, msg, extras);
+ }
+ };
}
- // We've reached point of no return; call into PMS to install the stage.
- // Regardless of success or failure we always destroy session.
- final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
- @Override
- public void onUserActionRequired(Intent intent) {
- throw new IllegalStateException();
- }
-
- @Override
- public void onPackageInstalled(String basePackageName, int returnCode, String msg,
- Bundle extras) {
- destroyInternal();
- dispatchSessionFinished(returnCode, msg, extras);
- }
- };
final UserHandle user;
if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
@@ -1230,11 +1237,9 @@
}
mRelinquished = true;
- final PackageManagerService.ActiveInstallSession activeInstallSession =
- new PackageManagerService.ActiveInstallSession(mPackageName, stageDir,
- localObserver, params, mInstallerPackageName, mInstallerUid, user,
- mSigningDetails);
- return activeInstallSession;
+ return new PackageManagerService.ActiveInstallSession(mPackageName, stageDir,
+ localObserver, params, mInstallerPackageName, mInstallerUid, user,
+ mSigningDetails);
}
private static void maybeRenameFile(File from, File to) throws PackageManagerException {
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/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index d30cd19..3cece11 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -1749,6 +1749,7 @@
.setName(getSurfaceControl() + " - animation-bounds")
.setSize(getSurfaceWidth(), getSurfaceHeight());
final SurfaceControl boundsLayer = builder.build();
+ t.setWindowCrop(boundsLayer, getSurfaceWidth(), getSurfaceHeight());
t.show(boundsLayer);
return boundsLayer;
}
diff --git a/services/core/java/com/android/server/wm/BlackFrame.java b/services/core/java/com/android/server/wm/BlackFrame.java
index e358ad5..9633864 100644
--- a/services/core/java/com/android/server/wm/BlackFrame.java
+++ b/services/core/java/com/android/server/wm/BlackFrame.java
@@ -52,7 +52,7 @@
.setColorLayer(true)
.setParent(null) // TODO: Work-around for b/69259549
.build();
-
+ transaction.setWindowCrop(surface, w, h);
transaction.setLayerStack(surface, dc.getDisplayId());
transaction.setAlpha(surface, 1);
transaction.setLayer(surface, layer);
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 65c8e96..3accaf8 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -309,6 +309,7 @@
// TODO: Once we use geometry from hierarchy this falls away.
t.setSize(mDimState.mDimLayer, bounds.width(), bounds.height());
t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
+ t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
if (!mDimState.isVisible) {
mDimState.isVisible = true;
t.show(mDimState.mDimLayer);
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 4eb021c..b49d304 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -187,6 +187,7 @@
}
t.setPosition(mSurface, mSurfaceFrame.left, mSurfaceFrame.top);
t.setSize(mSurface, mSurfaceFrame.width(), mSurfaceFrame.height());
+ t.setWindowCrop(mSurface, mSurfaceFrame.width(), mSurfaceFrame.height());
t.show(mSurface);
} else if (mSurface != null) {
t.hide(mSurface);
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 3ea615a..66063c40 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -16,12 +16,12 @@
package com.android.server.wm;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.SurfaceAnimatorProto.ANIMATION_ADAPTER;
import static com.android.server.wm.SurfaceAnimatorProto.ANIMATION_START_DELAYED;
import static com.android.server.wm.SurfaceAnimatorProto.LEASH;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -305,6 +305,7 @@
.setName(surface + " - animation-leash")
.setSize(width, height);
final SurfaceControl leash = builder.build();
+ t.setWindowCrop(surface, width, height);
if (!hidden) {
t.show(leash);
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 3493111..073601d 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -259,6 +259,7 @@
final Rect stackBounds = getBounds();
getPendingTransaction()
.setSize(mAnimationBackgroundSurface, mTmpRect.width(), mTmpRect.height())
+ .setWindowCrop(mAnimationBackgroundSurface, mTmpRect.width(), mTmpRect.height())
.setPosition(mAnimationBackgroundSurface, mTmpRect.left - stackBounds.left,
mTmpRect.top - stackBounds.top);
scheduleAnimation();
@@ -789,6 +790,7 @@
return;
}
transaction.setSize(mSurfaceControl, width, height);
+ transaction.setWindowCrop(mSurfaceControl, width, height);
mLastSurfaceSize.set(width, height);
}
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/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index bd6ede2..a69f007 100644
--- a/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -25,9 +25,12 @@
import static android.app.backup.ForwardingBackupAgent.forward;
import static com.android.server.backup.testing.BackupManagerServiceTestUtils.createBackupWakeLock;
-import static com.android.server.backup.testing.BackupManagerServiceTestUtils.createInitializedBackupManagerService;
-import static com.android.server.backup.testing.BackupManagerServiceTestUtils.setUpBackupManagerServiceBasics;
-import static com.android.server.backup.testing.BackupManagerServiceTestUtils.setUpBinderCallerAndApplicationAsSystem;
+import static com.android.server.backup.testing.BackupManagerServiceTestUtils
+ .createInitializedBackupManagerService;
+import static com.android.server.backup.testing.BackupManagerServiceTestUtils
+ .setUpBackupManagerServiceBasics;
+import static com.android.server.backup.testing.BackupManagerServiceTestUtils
+ .setUpBinderCallerAndApplicationAsSystem;
import static com.android.server.backup.testing.PackageData.PM_PACKAGE;
import static com.android.server.backup.testing.PackageData.fullBackupPackage;
import static com.android.server.backup.testing.PackageData.keyValuePackage;
@@ -327,6 +330,22 @@
.isEqualTo("packageState".getBytes());
}
+ /**
+ * Do not update backup token if the backup queue was empty
+ */
+ @Test
+ public void testRunTask_whenQueueEmptyOnFirstBackup_doesNotUpdateCurrentToken()
+ throws Exception {
+ TransportMock transportMock = setUpInitializedTransport(mTransport);
+ KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true);
+ mBackupManagerService.setCurrentToken(0L);
+ when(transportMock.transport.getCurrentRestoreSet()).thenReturn(1234L);
+
+ runTask(task);
+
+ assertThat(mBackupManagerService.getCurrentToken()).isEqualTo(0L);
+ }
+
@Test
public void testRunTask_whenOnePackageAndTransportUnavailable() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport.unavailable());
@@ -2297,6 +2316,24 @@
expectThrows(IllegalArgumentException.class, () -> task.handleCancel(false));
}
+ /**
+ * Do not update backup token if no data was moved.
+ */
+ @Test
+ public void testRunTask_whenNoDataToBackupOnFirstBackup_doesNotUpdateCurrentToken()
+ throws Exception {
+ TransportMock transportMock = setUpInitializedTransport(mTransport);
+ mBackupManagerService.setCurrentToken(0L);
+ when(transportMock.transport.getCurrentRestoreSet()).thenReturn(1234L);
+ // Set up agent with no data.
+ setUpAgent(PACKAGE_1);
+ KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1);
+
+ runTask(task);
+
+ assertThat(mBackupManagerService.getCurrentToken()).isEqualTo(0L);
+ }
+
private void runTask(KeyValueBackupTask task) {
// Pretend we are not on the main-thread to prevent RemoteCall from complaining
mShadowMainLooper.setCurrentThread(false);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a188ef6..bd1a0fb 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -9361,4 +9361,55 @@
}
return false;
}
+
+ /**
+ * Set preferred opportunistic data subscription id.
+ *
+ * <p>Requires that the calling app has carrier privileges on both primary and
+ * secondary subscriptions (see
+ * {@link #hasCarrierPrivileges}), or has permission
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
+ *
+ * @param subId which opportunistic subscription
+ * {@link SubscriptionManager#getOpportunisticSubscriptions} is preferred for cellular data.
+ * Pass {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID} to unset the preference
+ * @return true if request is accepted, else false.
+ *
+ */
+ public boolean setPreferredOpportunisticDataSubscription(int subId) {
+ String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
+ try {
+ IAns iAlternativeNetworkService = getIAns();
+ if (iAlternativeNetworkService != null) {
+ return iAlternativeNetworkService.setPreferredData(subId, pkgForDebug);
+ }
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "setPreferredData RemoteException", ex);
+ }
+ return false;
+ }
+
+ /**
+ * Get preferred opportunistic data subscription Id
+ *
+ * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}),
+ * or has permission {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}.
+ * @return subId preferred opportunistic subscription id or
+ * {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID} if there are no preferred
+ * subscription id
+ *
+ */
+ public int getPreferredOpportunisticDataSubscription() {
+ String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
+ int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ try {
+ IAns iAlternativeNetworkService = getIAns();
+ if (iAlternativeNetworkService != null) {
+ subId = iAlternativeNetworkService.getPreferredData(pkgForDebug);
+ }
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "getPreferredData RemoteException", ex);
+ }
+ return subId;
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/IAns.aidl b/telephony/java/com/android/internal/telephony/IAns.aidl
index 6eb8d66..e9a4649 100755
--- a/telephony/java/com/android/internal/telephony/IAns.aidl
+++ b/telephony/java/com/android/internal/telephony/IAns.aidl
@@ -49,4 +49,33 @@
* @param callingPackage caller's package name
*/
boolean isEnabled(String callingPackage);
+
+ /**
+ * Set preferred opportunistic data subscription id.
+ *
+ * <p>Requires that the calling app has carrier privileges on both primary and
+ * secondary subscriptions (see
+ * {@link #hasCarrierPrivileges}), or has permission
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
+ *
+ * @param subId which opportunistic subscription
+ * {@link SubscriptionManager#getOpportunisticSubscriptions} is preferred for cellular data.
+ * Pass {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID} to unset the preference
+ * @param callingPackage caller's package name
+ * @return true if request is accepted, else false.
+ *
+ */
+ boolean setPreferredData(int subId, String callingPackage);
+
+ /**
+ * Get preferred opportunistic data subscription Id
+ *
+ * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}),
+ * or has permission {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}.
+ * @return subId preferred opportunistic subscription id or
+ * {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID} if there are no preferred
+ * subscription id
+ *
+ */
+ int getPreferredData(String callingPackage);
}