diff options
16 files changed, 661 insertions, 55 deletions
diff --git a/api/current.txt b/api/current.txt index 8458ed33ad5d..b321c2248fda 100644 --- a/api/current.txt +++ b/api/current.txt @@ -19446,9 +19446,9 @@ package android.icu.text { method public static android.icu.text.BreakIterator getSentenceInstance(java.util.Locale); method public static android.icu.text.BreakIterator getSentenceInstance(android.icu.util.ULocale); method public abstract java.text.CharacterIterator getText(); - method public static android.icu.text.BreakIterator getTitleInstance(); - method public static android.icu.text.BreakIterator getTitleInstance(java.util.Locale); - method public static android.icu.text.BreakIterator getTitleInstance(android.icu.util.ULocale); + method @Deprecated public static android.icu.text.BreakIterator getTitleInstance(); + method @Deprecated public static android.icu.text.BreakIterator getTitleInstance(java.util.Locale); + method @Deprecated public static android.icu.text.BreakIterator getTitleInstance(android.icu.util.ULocale); method public static android.icu.text.BreakIterator getWordInstance(); method public static android.icu.text.BreakIterator getWordInstance(java.util.Locale); method public static android.icu.text.BreakIterator getWordInstance(android.icu.util.ULocale); @@ -19465,7 +19465,7 @@ package android.icu.text { field public static final int KIND_CHARACTER = 0; // 0x0 field public static final int KIND_LINE = 2; // 0x2 field public static final int KIND_SENTENCE = 3; // 0x3 - field public static final int KIND_TITLE = 4; // 0x4 + field @Deprecated public static final int KIND_TITLE = 4; // 0x4 field public static final int KIND_WORD = 1; // 0x1 field public static final int WORD_IDEO = 400; // 0x190 field public static final int WORD_IDEO_LIMIT = 500; // 0x1f4 diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index aee4b1f63812..8a097883dd5c 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -339,7 +339,11 @@ public final class InputMethodManager { // For scheduling work on the main thread. This also serves as our // global lock. - @UnsupportedAppUsage + // Remark on @UnsupportedAppUsage: there were context leaks on old versions + // of android (b/37043700), so developers used this field to perform manual clean up. + // Leaks were fixed, hacks were backported to AppCompatActivity, + // so an access to the field is closed. + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) final H mH; // Our generic input connection if the current target does not have its own. @@ -375,13 +379,15 @@ public final class InputMethodManager { * This is the view that should currently be served by an input method, * regardless of the state of setting that up. */ - @UnsupportedAppUsage + // See comment to mH field in regard to @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) View mServedView; /** * This is then next view that will be served by the input method, when * we get around to updating things. */ - @UnsupportedAppUsage + // See comment to mH field in regard to @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) View mNextServedView; /** * This is set when we are in the process of connecting, to determine diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index a02103e94b75..542df4562908 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -442,8 +442,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te /** * Handles one frame of a fling + * + * To interrupt a fling early you should use smoothScrollBy(0,0) instead */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) private FlingRunnable mFlingRunnable; /** @@ -4679,7 +4681,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mScroller = new OverScroller(getContext()); } - @UnsupportedAppUsage + // Use AbsListView#fling(int) instead + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) void start(int initialVelocity) { int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0; mLastFlingY = initialY; @@ -4757,7 +4760,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te postOnAnimation(this); } - @UnsupportedAppUsage + // To interrupt a fling early you should use smoothScrollBy(0,0) instead + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) void endFling() { mTouchMode = TOUCH_MODE_REST; diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/ByteRange.java b/services/backup/java/com/android/server/backup/encryption/chunking/ByteRange.java new file mode 100644 index 000000000000..004d9e3b45f1 --- /dev/null +++ b/services/backup/java/com/android/server/backup/encryption/chunking/ByteRange.java @@ -0,0 +1,80 @@ +/* + * 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; + +import com.android.internal.util.Preconditions; + +/** Representation of a range of bytes to be downloaded. */ +final class ByteRange { + private final long mStart; + private final long mEnd; + + /** Creates a range of bytes which includes {@code mStart} and {@code mEnd}. */ + ByteRange(long start, long end) { + Preconditions.checkArgument(start >= 0); + Preconditions.checkArgument(end >= start); + mStart = start; + mEnd = end; + } + + /** Returns the start of the {@code ByteRange}. The start is included in the range. */ + long getStart() { + return mStart; + } + + /** Returns the end of the {@code ByteRange}. The end is included in the range. */ + long getEnd() { + return mEnd; + } + + /** Returns the number of bytes included in the {@code ByteRange}. */ + int getLength() { + return (int) (mEnd - mStart + 1); + } + + /** Creates a new {@link ByteRange} from {@code mStart} to {@code mEnd + length}. */ + ByteRange extend(long length) { + Preconditions.checkArgument(length > 0); + return new ByteRange(mStart, mEnd + length); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ByteRange)) { + return false; + } + + ByteRange byteRange = (ByteRange) o; + return (mEnd == byteRange.mEnd && mStart == byteRange.mStart); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (int) (mStart ^ (mStart >>> 32)); + result = 31 * result + (int) (mEnd ^ (mEnd >>> 32)); + return result; + } + + @Override + public String toString() { + return String.format("ByteRange{mStart=%d, mEnd=%d}", mStart, mEnd); + } +} diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java b/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java new file mode 100644 index 000000000000..69fb5cbf606d --- /dev/null +++ b/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java @@ -0,0 +1,75 @@ +/* + * 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; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.IOException; +import java.io.OutputStream; + +/** Writes backup data to a diff script, using a {@link SingleStreamDiffScriptWriter}. */ +public class DiffScriptBackupWriter implements BackupWriter { + /** + * The maximum size of a chunk in the diff script. The diff script writer {@code mWriter} will + * buffer this many bytes in memory. + */ + private static final int ENCRYPTION_DIFF_SCRIPT_MAX_CHUNK_SIZE_BYTES = 1024 * 1024; + + private final SingleStreamDiffScriptWriter mWriter; + private long mBytesWritten; + + /** + * Constructs a new writer which writes the diff script to the given output stream, using the + * maximum new chunk size {@code ENCRYPTION_DIFF_SCRIPT_MAX_CHUNK_SIZE_BYTES}. + */ + public static DiffScriptBackupWriter newInstance(OutputStream outputStream) { + SingleStreamDiffScriptWriter writer = + new SingleStreamDiffScriptWriter( + outputStream, ENCRYPTION_DIFF_SCRIPT_MAX_CHUNK_SIZE_BYTES); + return new DiffScriptBackupWriter(writer); + } + + @VisibleForTesting + DiffScriptBackupWriter(SingleStreamDiffScriptWriter writer) { + mWriter = writer; + } + + @Override + public void writeBytes(byte[] bytes) throws IOException { + for (byte b : bytes) { + mWriter.writeByte(b); + } + + mBytesWritten += bytes.length; + } + + @Override + public void writeChunk(long start, int length) throws IOException { + mWriter.writeChunk(start, length); + mBytesWritten += length; + } + + @Override + public long getBytesWritten() { + return mBytesWritten; + } + + @Override + public void flush() throws IOException { + mWriter.flush(); + } +} diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptWriter.java b/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptWriter.java new file mode 100644 index 000000000000..49d15712d4cc --- /dev/null +++ b/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptWriter.java @@ -0,0 +1,36 @@ +/* + * 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; + +import java.io.IOException; +import java.io.OutputStream; + +/** Writer that formats a Diff Script and writes it to an output source. */ +interface DiffScriptWriter { + /** Adds a new byte to the diff script. */ + void writeByte(byte b) throws IOException; + + /** Adds a known chunk to the diff script. */ + void writeChunk(long chunkStart, int chunkLength) throws IOException; + + /** Indicates that no more bytes or chunks will be added to the diff script. */ + void flush() throws IOException; + + interface Factory { + DiffScriptWriter create(OutputStream outputStream); + } +} diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java b/services/backup/java/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java new file mode 100644 index 000000000000..4aea60121810 --- /dev/null +++ b/services/backup/java/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java @@ -0,0 +1,25 @@ +/* + * 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; + +import java.io.OutputStream; + +/** An interface that wraps one {@link OutputStream} with another for filtration purposes. */ +public interface OutputStreamWrapper { + /** Wraps a given {@link OutputStream}. */ + OutputStream wrap(OutputStream outputStream); +} diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java b/services/backup/java/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java new file mode 100644 index 000000000000..0e4bd58345d5 --- /dev/null +++ b/services/backup/java/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java @@ -0,0 +1,130 @@ +/* + * 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; + +import android.annotation.Nullable; + +import com.android.internal.util.Preconditions; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.Locale; + +/** + * A {@link DiffScriptWriter} that writes an entire diff script to a single {@link OutputStream}. + */ +public class SingleStreamDiffScriptWriter implements DiffScriptWriter { + static final byte LINE_SEPARATOR = 0xA; + private static final Charset UTF_8 = Charset.forName("UTF-8"); + + private final int mMaxNewByteChunkSize; + private final OutputStream mOutputStream; + private final byte[] mByteBuffer; + private int mBufferSize = 0; + // Each chunk could be written immediately to the output stream. However, + // it is possible that chunks may overlap. We therefore cache the most recent + // reusable chunk and try to merge it with future chunks. + private ByteRange mReusableChunk; + + public SingleStreamDiffScriptWriter(OutputStream outputStream, int maxNewByteChunkSize) { + mOutputStream = outputStream; + mMaxNewByteChunkSize = maxNewByteChunkSize; + mByteBuffer = new byte[maxNewByteChunkSize]; + } + + @Override + public void writeByte(byte b) throws IOException { + if (mReusableChunk != null) { + writeReusableChunk(); + } + mByteBuffer[mBufferSize++] = b; + if (mBufferSize == mMaxNewByteChunkSize) { + writeByteBuffer(); + } + } + + @Override + public void writeChunk(long chunkStart, int chunkLength) throws IOException { + Preconditions.checkArgument(chunkStart >= 0); + Preconditions.checkArgument(chunkLength > 0); + if (mBufferSize != 0) { + writeByteBuffer(); + } + + if (mReusableChunk != null && mReusableChunk.getEnd() + 1 == chunkStart) { + // The new chunk overlaps the old, so combine them into a single byte range. + mReusableChunk = mReusableChunk.extend(chunkLength); + } else { + writeReusableChunk(); + mReusableChunk = new ByteRange(chunkStart, chunkStart + chunkLength - 1); + } + } + + @Override + public void flush() throws IOException { + Preconditions.checkState(!(mBufferSize != 0 && mReusableChunk != null)); + if (mBufferSize != 0) { + writeByteBuffer(); + } + if (mReusableChunk != null) { + writeReusableChunk(); + } + mOutputStream.flush(); + } + + private void writeByteBuffer() throws IOException { + mOutputStream.write(Integer.toString(mBufferSize).getBytes(UTF_8)); + mOutputStream.write(LINE_SEPARATOR); + mOutputStream.write(mByteBuffer, 0, mBufferSize); + mOutputStream.write(LINE_SEPARATOR); + mBufferSize = 0; + } + + private void writeReusableChunk() throws IOException { + if (mReusableChunk != null) { + mOutputStream.write( + String.format( + Locale.US, + "%d-%d", + mReusableChunk.getStart(), + mReusableChunk.getEnd()) + .getBytes(UTF_8)); + mOutputStream.write(LINE_SEPARATOR); + mReusableChunk = null; + } + } + + /** A factory that creates {@link SingleStreamDiffScriptWriter}s. */ + public static class Factory implements DiffScriptWriter.Factory { + private final int mMaxNewByteChunkSize; + private final OutputStreamWrapper mOutputStreamWrapper; + + public Factory(int maxNewByteChunkSize, @Nullable OutputStreamWrapper outputStreamWrapper) { + mMaxNewByteChunkSize = maxNewByteChunkSize; + mOutputStreamWrapper = outputStreamWrapper; + } + + @Override + public SingleStreamDiffScriptWriter create(OutputStream outputStream) { + if (mOutputStreamWrapper != null) { + outputStream = mOutputStreamWrapper.wrap(outputStream); + } + return new SingleStreamDiffScriptWriter(outputStream, mMaxNewByteChunkSize); + } + } +} diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index d06fc513c0e8..1b719048bc89 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -44,7 +44,6 @@ import static com.android.server.pm.PackageInstallerService.prepareStageDir; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; -import android.apex.IApexService; import android.app.admin.DevicePolicyManagerInternal; import android.content.Context; import android.content.IIntentReceiver; @@ -80,7 +79,6 @@ import android.os.ParcelableException; import android.os.Process; import android.os.RemoteException; import android.os.RevocableFileDescriptor; -import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserHandle; import android.os.storage.StorageManager; @@ -1084,6 +1082,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED, "Session staged", null); return; } + if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) { + throw new PackageManagerException( + PackageManager.INSTALL_FAILED_INTERNAL_ERROR, + "APEX packages can only be installed using staged sessions."); + } final PackageManagerService.ActiveInstallSession committingSession = makeSessionActiveLocked(); if (committingSession == null) { @@ -1101,12 +1104,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final PackageManagerService.ActiveInstallSession activeSession = session.makeSessionActiveLocked(); if (activeSession != null) { - if ((activeSession.getSessionParams().installFlags - & PackageManager.INSTALL_APEX) != 0) { - throw new PackageManagerException( - PackageManager.INSTALL_FAILED_INTERNAL_ERROR, - "Atomic install is not supported for APEX packages."); - } childSessions.add(activeSession); } } catch (PackageManagerException e) { @@ -1124,27 +1121,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } mPm.installStage(childSessions); } else { - if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) { - commitApexLocked(); - } else { - mPm.installStage(committingSession); - } - } - } - - @GuardedBy("mLock") - private void commitApexLocked() throws PackageManagerException { - try { - IApexService apex = IApexService.Stub.asInterface( - ServiceManager.getService("apexservice")); - apex.stagePackage(mResolvedBaseFile.toString()); - } catch (Throwable e) { - // Convert all exceptions into package manager exceptions as only those are handled - // in the code above - throw new PackageManagerException(e); - } finally { - destroyInternal(); - dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED, "APEX installed", null); + mPm.installStage(committingSession); } } diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 2213901b2049..ee6995b11430 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -228,8 +228,11 @@ public class DexManager { continue; } - mDexLogger.recordDex(loaderUserId, dexPath, searchResult.mOwningPackageName, - loadingAppInfo.packageName); + if (!primaryOrSplit) { + // Record loading of a DEX file from an app data directory. + mDexLogger.recordDex(loaderUserId, dexPath, searchResult.mOwningPackageName, + loadingAppInfo.packageName); + } if (classLoaderContexts != null) { diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 24d5bd14bc44..95c3f4c43313 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -340,12 +340,9 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // for apex? if (!info.isApex()) { String installerPackageName = pm.getInstallerPackageName(info.getPackageName()); - if (installerPackageName == null) { - sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE, - "Cannot find installer package"); - return; + if (installerPackageName != null) { + params.setInstallerPackageName(installerPackageName); } - params.setInstallerPackageName(installerPackageName); } params.setAllowDowngrade(true); if (data.isStaged()) { diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java new file mode 100644 index 000000000000..8df08262c9fa --- /dev/null +++ b/services/robotests/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java @@ -0,0 +1,57 @@ +/* + * 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; + +import static org.junit.Assert.assertEquals; +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 ByteRange}. */ +@RunWith(RobolectricTestRunner.class) +@Presubmit +public class ByteRangeTest { + @Test + public void getLength_includesEnd() throws Exception { + ByteRange byteRange = new ByteRange(5, 10); + + int length = byteRange.getLength(); + + assertEquals(6, length); + } + + @Test + public void constructor_rejectsNegativeStart() { + assertThrows(IllegalArgumentException.class, () -> new ByteRange(-1, 10)); + } + + @Test + public void constructor_rejectsEndBeforeStart() { + assertThrows(IllegalArgumentException.class, () -> new ByteRange(10, 9)); + } + + @Test + public void extend_withZeroLength_throwsException() { + ByteRange byteRange = new ByteRange(5, 10); + + assertThrows(IllegalArgumentException.class, () -> byteRange.extend(0)); + } +} diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java new file mode 100644 index 000000000000..2af6f2bee8ff --- /dev/null +++ b/services/robotests/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java @@ -0,0 +1,90 @@ +/* + * 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; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.platform.test.annotations.Presubmit; + +import com.google.common.primitives.Bytes; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.robolectric.RobolectricTestRunner; + +import java.io.IOException; + +/** Tests for {@link DiffScriptBackupWriter}. */ +@RunWith(RobolectricTestRunner.class) +@Presubmit +public class DiffScriptBackupWriterTest { + private static final byte[] TEST_BYTES = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + + @Captor private ArgumentCaptor<Byte> mBytesCaptor; + @Mock private SingleStreamDiffScriptWriter mDiffScriptWriter; + private BackupWriter mBackupWriter; + + @Before + public void setUp() { + mDiffScriptWriter = mock(SingleStreamDiffScriptWriter.class); + mBackupWriter = new DiffScriptBackupWriter(mDiffScriptWriter); + mBytesCaptor = ArgumentCaptor.forClass(Byte.class); + } + + @Test + public void writeBytes_writesBytesToWriter() throws Exception { + mBackupWriter.writeBytes(TEST_BYTES); + + verify(mDiffScriptWriter, atLeastOnce()).writeByte(mBytesCaptor.capture()); + assertThat(mBytesCaptor.getAllValues()) + .containsExactlyElementsIn(Bytes.asList(TEST_BYTES)) + .inOrder(); + } + + @Test + public void writeChunk_writesChunkToWriter() throws Exception { + mBackupWriter.writeChunk(0, 10); + + verify(mDiffScriptWriter).writeChunk(0, 10); + } + + @Test + public void getBytesWritten_returnsTotalSum() throws Exception { + mBackupWriter.writeBytes(TEST_BYTES); + mBackupWriter.writeBytes(TEST_BYTES); + mBackupWriter.writeChunk(/*start=*/ 0, /*length=*/ 10); + + long bytesWritten = mBackupWriter.getBytesWritten(); + + assertThat(bytesWritten).isEqualTo(2 * TEST_BYTES.length + 10); + } + + @Test + public void flush_flushesWriter() throws IOException { + mBackupWriter.flush(); + + verify(mDiffScriptWriter).flush(); + } +} diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java new file mode 100644 index 000000000000..73baf80a2c70 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java @@ -0,0 +1,128 @@ +/* + * 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; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.testng.Assert.assertThrows; + +import android.platform.test.annotations.Presubmit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Locale; + +/** Tests for {@link SingleStreamDiffScriptWriter}. */ +@RunWith(RobolectricTestRunner.class) +@Presubmit +public class SingleStreamDiffScriptWriterTest { + private static final int MAX_CHUNK_SIZE_IN_BYTES = 256; + /** By default this Locale does not use Arabic numbers for %d formatting. */ + private static final Locale HINDI = new Locale("hi", "IN"); + + private Locale mDefaultLocale; + private ByteArrayOutputStream mOutputStream; + private SingleStreamDiffScriptWriter mDiffScriptWriter; + + @Before + public void setUp() { + mDefaultLocale = Locale.getDefault(); + mOutputStream = new ByteArrayOutputStream(); + mDiffScriptWriter = + new SingleStreamDiffScriptWriter(mOutputStream, MAX_CHUNK_SIZE_IN_BYTES); + } + + @After + public void tearDown() { + Locale.setDefault(mDefaultLocale); + } + + @Test + public void writeChunk_withNegativeStart_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> mDiffScriptWriter.writeChunk(-1, 50)); + } + + @Test + public void writeChunk_withZeroLength_throwsException() { + assertThrows( + IllegalArgumentException.class, + () -> mDiffScriptWriter.writeChunk(0, 0)); + } + + @Test + public void writeChunk_withExistingBytesInBuffer_writesBufferFirst() + throws IOException { + String testString = "abcd"; + writeStringAsBytesToWriter(testString, mDiffScriptWriter); + + mDiffScriptWriter.writeChunk(0, 20); + mDiffScriptWriter.flush(); + + // Expected format: length of abcd, newline, abcd, newline, chunk start - chunk end + assertThat(mOutputStream.toString("UTF-8")).isEqualTo( + String.format("%d\n%s\n%d-%d\n", testString.length(), testString, 0, 19)); + } + + @Test + public void writeChunk_overlappingPreviousChunk_combinesChunks() throws IOException { + mDiffScriptWriter.writeChunk(3, 4); + + mDiffScriptWriter.writeChunk(7, 5); + mDiffScriptWriter.flush(); + + assertThat(mOutputStream.toString("UTF-8")).isEqualTo(String.format("3-11\n")); + } + + @Test + public void writeChunk_formatsByteIndexesUsingArabicNumbers() throws Exception { + Locale.setDefault(HINDI); + + mDiffScriptWriter.writeChunk(0, 12345); + mDiffScriptWriter.flush(); + + assertThat(mOutputStream.toString("UTF-8")).isEqualTo("0-12344\n"); + } + + @Test + public void flush_flushesOutputStream() throws IOException { + ByteArrayOutputStream mockOutputStream = mock(ByteArrayOutputStream.class); + SingleStreamDiffScriptWriter diffScriptWriter = + new SingleStreamDiffScriptWriter(mockOutputStream, MAX_CHUNK_SIZE_IN_BYTES); + + diffScriptWriter.flush(); + + verify(mockOutputStream).flush(); + } + + private void writeStringAsBytesToWriter(String string, SingleStreamDiffScriptWriter writer) + throws IOException { + byte[] bytes = string.getBytes("UTF-8"); + for (int i = 0; i < bytes.length; i++) { + writer.writeByte(bytes[i]); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index 2ddc71f570b8..48ab8d6698bd 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -141,7 +141,8 @@ public class DexManagerTests { assertIsUsedByOtherApps(mBarUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); - assertHasDclInfo(mBarUser0, mFooUser0, mBarUser0.getBaseAndSplitDexPaths()); + // A package loading another package's APK is not DCL (it's not app data). + assertNoDclInfo(mBarUser0); } @Test @@ -334,7 +335,9 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, newSplits, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); assertIsUsedByOtherApps(newSplits, pui, true); - assertHasDclInfo(mBarUser0, mFooUser0, newSplits); + + // Primary and split APKs are not recorded as DCL. + assertNoDclInfo(mBarUser0); } @Test diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index 4b277ae850c5..f07ae9f65b1b 100644 --- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -685,7 +685,7 @@ public class RollbackTest { ActivityManager am = context.getSystemService(ActivityManager.class); am.killBackgroundProcesses(TEST_APP_A); // Allow another package launch - crashQueue.offer(intent.getIntExtra("count", 0), 5, TimeUnit.SECONDS); + crashQueue.put(intent.getIntExtra("count", 0)); } catch (InterruptedException e) { fail("Failed to communicate with test app"); } @@ -694,14 +694,9 @@ public class RollbackTest { context.registerReceiver(crashCountReceiver, crashCountFilter); // Start apps PackageWatchdog#TRIGGER_FAILURE_COUNT times so TEST_APP_A crashes - Integer crashCount = null; do { RollbackTestUtils.launchPackage(TEST_APP_A); - crashCount = crashQueue.poll(5, TimeUnit.SECONDS); - if (crashCount == null) { - fail("Timed out waiting for crash signal from test app"); - } - } while(crashCount < 5); + } while(crashQueue.take() < 5); // TEST_APP_A is automatically rolled back by the RollbackPackageHealthObserver assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); |